mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-04 19:53:44 +00:00
Compare commits
139 Commits
develop-20
...
develop-20
Author | SHA1 | Date | |
---|---|---|---|
|
cd0b64bd24 | ||
|
91b26bce0e | ||
|
26a8bd921d | ||
|
ad77d55c16 | ||
|
445a383dd0 | ||
|
1666af939e | ||
|
35447f5eee | ||
|
7c6fcb5731 | ||
|
fabd9bf765 | ||
|
6352d26633 | ||
|
ebef145bd6 | ||
|
acc89eb5f9 | ||
|
6523596415 | ||
|
b6c2d23116 | ||
|
2a123354f9 | ||
|
1e7ea4b76c | ||
|
d959526eb3 | ||
|
8846ee9091 | ||
|
6eb8d0fc8c | ||
|
1b0e16b6a5 | ||
|
2e4df28288 | ||
|
f3b7a3015d | ||
|
5de5e08b1d | ||
|
0a116cd04c | ||
|
fd32a692c1 | ||
|
1ac762aba8 | ||
|
315dc532b6 | ||
|
e19ed1be15 | ||
|
cbb0621fd9 | ||
|
049cbab861 | ||
|
28b620fb5c | ||
|
c183f91ff6 | ||
|
172efae41c | ||
|
756e857ba0 | ||
|
1cde7aab0c | ||
|
2d67eece5d | ||
|
b1f79c4c0f | ||
|
33bd2ceae8 | ||
|
e68850f192 | ||
|
450ac7e6ee | ||
|
91f52b5dbc | ||
|
eed2405d76 | ||
|
c956df7790 | ||
|
0fdccec6a8 | ||
|
8ded54d7a8 | ||
|
bb1b4ca5ca | ||
|
e90d60113b | ||
|
d95dada0e0 | ||
|
8722456595 | ||
|
b5ad226451 | ||
|
ddb0e66651 | ||
|
e802899608 | ||
|
0894d3bf42 | ||
|
80bcfd3bcd | ||
|
8c410f42bd | ||
|
7a1021dffc | ||
|
63883c9a84 | ||
|
50d7f9d1ec | ||
|
ebc7ea0eb6 | ||
|
b905efd0aa | ||
|
93085599b7 | ||
|
8a8bbaf827 | ||
|
96a66b894a | ||
|
1ba641c279 | ||
|
6c5ddfcb8a | ||
|
65ddc246dc | ||
|
e4aff5ff4c | ||
|
dfece91541 | ||
|
6ddda13c3a | ||
|
46219c4678 | ||
|
a1595d0647 | ||
|
4ee9f9bb27 | ||
|
bcaa0bddea | ||
|
01cce49070 | ||
|
293be04d40 | ||
|
44a00ec8eb | ||
|
d84e772945 | ||
|
b475f6c51d | ||
|
2712662510 | ||
|
1f2eeba862 | ||
|
46a60134f4 | ||
|
f3f7820816 | ||
|
a57f8076b2 | ||
|
517afa2273 | ||
|
2142b23aec | ||
|
1bec43b111 | ||
|
d2978a5ee8 | ||
|
39b61c71e8 | ||
|
fa2c964790 | ||
|
134aeb3a46 | ||
|
6f6e1a4ff4 | ||
|
b743bf3d9e | ||
|
84ee6f16c9 | ||
|
9fe39e42b3 | ||
|
4013c7e316 | ||
|
0b76747531 | ||
|
3129756b37 | ||
|
b0df383004 | ||
|
9c5b1df86c | ||
|
5854e24775 | ||
|
3f873422f2 | ||
|
f898990773 | ||
|
a5759ab1c6 | ||
|
9bd6417e02 | ||
|
cd12a10214 | ||
|
ee8cb62e04 | ||
|
87ee95a852 | ||
|
10f8436885 | ||
|
6955846a1c | ||
|
11d2f8d471 | ||
|
99347ffff1 | ||
|
3ddc11a905 | ||
|
0a48c0c20f | ||
|
8bc764d6ef | ||
|
4b4f568558 | ||
|
d42117281a | ||
|
d68ed5a713 | ||
|
2f6f36c3f0 | ||
|
984aa02e35 | ||
|
cdadc7d533 | ||
|
4d59955cc5 | ||
|
fc547ba59a | ||
|
1b2ded3167 | ||
|
dcf20a472a | ||
|
7bd528defe | ||
|
9e6d123165 | ||
|
c592f51c83 | ||
|
fbb6f30366 | ||
|
4546c721fb | ||
|
3a659c9a81 | ||
|
e28a988eb3 | ||
|
a29742fe1f | ||
|
dd06838eec | ||
|
5eb828bff8 | ||
|
c7f3701053 | ||
|
ab6799442c | ||
|
1fe9bf7d76 | ||
|
bd14797da6 | ||
|
93b4e6a8d0 |
154
.ci/php-cs-fixer/composer.lock
generated
154
.ci/php-cs-fixer/composer.lock
generated
@@ -151,16 +151,16 @@
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.4.3",
|
||||
"version": "3.4.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12"
|
||||
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||
"reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
|
||||
"reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -212,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.3"
|
||||
"source": "https://github.com/composer/semver/tree/3.4.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -222,13 +222,9 @@
|
||||
{
|
||||
"url": "https://github.com/composer",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-19T14:15:21+00:00"
|
||||
"time": "2025-08-20T19:15:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
@@ -959,23 +955,23 @@
|
||||
},
|
||||
{
|
||||
"name": "react/promise",
|
||||
"version": "v3.2.0",
|
||||
"version": "v3.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/reactphp/promise.git",
|
||||
"reference": "8a164643313c71354582dc850b42b33fa12a4b63"
|
||||
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63",
|
||||
"reference": "8a164643313c71354582dc850b42b33fa12a4b63",
|
||||
"url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a",
|
||||
"reference": "23444f53a813a3296c1368bb104793ce8d88f04a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "1.10.39 || 1.4.10",
|
||||
"phpstan/phpstan": "1.12.28 || 1.4.10",
|
||||
"phpunit/phpunit": "^9.6 || ^7.5"
|
||||
},
|
||||
"type": "library",
|
||||
@@ -1020,7 +1016,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/reactphp/promise/issues",
|
||||
"source": "https://github.com/reactphp/promise/tree/v3.2.0"
|
||||
"source": "https://github.com/reactphp/promise/tree/v3.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1028,7 +1024,7 @@
|
||||
"type": "open_collective"
|
||||
}
|
||||
],
|
||||
"time": "2024-05-24T10:39:05+00:00"
|
||||
"time": "2025-08-19T18:57:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "react/socket",
|
||||
@@ -1257,16 +1253,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "5f360ebc65c55265a74d23d7fe27f957870158a1"
|
||||
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1",
|
||||
"reference": "5f360ebc65c55265a74d23d7fe27f957870158a1",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
|
||||
"reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1331,7 +1327,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1351,7 +1347,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-30T17:13:41+00:00"
|
||||
"time": "2025-08-25T06:35:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
@@ -1422,16 +1418,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v7.3.0",
|
||||
"version": "v7.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "497f73ac996a598c92409b44ac43b6690c4f666d"
|
||||
"reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d",
|
||||
"reference": "497f73ac996a598c92409b44ac43b6690c4f666d",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191",
|
||||
"reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1482,7 +1478,7 @@
|
||||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0"
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1493,12 +1489,16 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-22T09:11:45+00:00"
|
||||
"time": "2025-08-13T11:49:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
@@ -1716,16 +1716,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/options-resolver",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/options-resolver.git",
|
||||
"reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37"
|
||||
"reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/119bcf13e67dbd188e5dbc74228b1686f66acd37",
|
||||
"reference": "119bcf13e67dbd188e5dbc74228b1686f66acd37",
|
||||
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/0ff2f5c3df08a395232bbc3c2eb7e84912df911d",
|
||||
"reference": "0ff2f5c3df08a395232bbc3c2eb7e84912df911d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1763,7 +1763,7 @@
|
||||
"options"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/options-resolver/tree/v7.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1783,11 +1783,11 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-15T11:36:08+00:00"
|
||||
"time": "2025-08-05T10:16:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-ctype",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||
@@ -1846,7 +1846,7 @@
|
||||
"portable"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1857,6 +1857,10 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
@@ -1866,16 +1870,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
|
||||
"reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe"
|
||||
"reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
|
||||
"reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70",
|
||||
"reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1924,7 +1928,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1935,16 +1939,20 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
"time": "2025-06-27T09:58:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-normalizer",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||
@@ -2005,7 +2013,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2016,6 +2024,10 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
@@ -2025,7 +2037,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
@@ -2086,7 +2098,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2097,6 +2109,10 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
@@ -2106,7 +2122,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php80",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||
@@ -2166,7 +2182,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2177,6 +2193,10 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
@@ -2186,7 +2206,7 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-php81",
|
||||
"version": "v1.32.0",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-php81.git",
|
||||
@@ -2242,7 +2262,7 @@
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0"
|
||||
"source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2253,6 +2273,10 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
@@ -2262,16 +2286,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v7.3.0",
|
||||
"version": "v7.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/process.git",
|
||||
"reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af"
|
||||
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
|
||||
"reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af",
|
||||
"url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1",
|
||||
"reference": "32241012d521e2e8a9d713adb0812bb773b907f1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2303,7 +2327,7 @@
|
||||
"description": "Executes commands in sub-processes",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/process/tree/v7.3.0"
|
||||
"source": "https://github.com/symfony/process/tree/v7.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2314,12 +2338,16 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-17T09:11:12+00:00"
|
||||
"time": "2025-08-18T09:42:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
@@ -2468,16 +2496,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/string",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/string.git",
|
||||
"reference": "42f505aff654e62ac7ac2ce21033818297ca89ca"
|
||||
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca",
|
||||
"reference": "42f505aff654e62ac7ac2ce21033818297ca89ca",
|
||||
"url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
|
||||
"reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2535,7 +2563,7 @@
|
||||
"utf8"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/string/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/string/tree/v7.3.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2555,7 +2583,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-10T08:47:49+00:00"
|
||||
"time": "2025-08-25T06:35:40+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
6
.github/label-actions.yml
vendored
6
.github/label-actions.yml
vendored
@@ -25,7 +25,7 @@ feature:
|
||||
|
||||
This issue has been marked as a feature request.
|
||||
|
||||
If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates.
|
||||
If you come across this issue, please be aware there is NO need to reply with "+1" or "I need this too" or "any updates?" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates.
|
||||
|
||||
Thank you for your contributions.
|
||||
|
||||
@@ -39,7 +39,7 @@ epic:
|
||||
|
||||
This issue has been marked as an epic. In epics, large amounts of works are collected that will be part of a major new feature. If you have more ideas that could be a part of this epic, feel free to reply.
|
||||
|
||||
*However*, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted.
|
||||
*However*, please be aware there is NO need to reply with "+1" or "I need this too" or "any updates?" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted.
|
||||
|
||||
If you are merely interested in this epic's progress, you can subscribe to this issue to get updates.
|
||||
|
||||
@@ -56,7 +56,7 @@ enhancement:
|
||||
|
||||
This issue has been marked as an enhancement.
|
||||
|
||||
If you come across this issue, please be aware there is NO need to reply with "+1" or "me too" or "I need this too" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates.
|
||||
If you come across this issue, please be aware there is NO need to reply with "+1" or "I need this too" or "any updates?" or whatever. Such comments are not helpful, and do not influence [the roadmap](https://roadmap.firefly-iii.org/). Your comment may be :skull: deleted. You can subscribe to this issue to get updates.
|
||||
|
||||
Thank you for your contributions.
|
||||
|
||||
|
4
.github/release-notes/alpha.md
vendored
4
.github/release-notes/alpha.md
vendored
@@ -16,6 +16,10 @@ Alpha releases are created to test new features and fixes before they are includ
|
||||
|
||||
The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Develop with Firefly III
|
||||
|
||||
Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III.
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
||||
|
4
.github/release-notes/beta.md
vendored
4
.github/release-notes/beta.md
vendored
@@ -16,6 +16,10 @@ Alpha releases are created to test new features and fixes before they are includ
|
||||
|
||||
The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Develop with Firefly III
|
||||
|
||||
Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III.
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
||||
|
4
.github/release-notes/branch.md
vendored
4
.github/release-notes/branch.md
vendored
@@ -16,6 +16,10 @@ There is no changelog for this release, as it is not final. However, [changelog.
|
||||
|
||||
The release files are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Develop with Firefly III
|
||||
|
||||
Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III.
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
||||
|
4
.github/release-notes/develop.md
vendored
4
.github/release-notes/develop.md
vendored
@@ -16,6 +16,10 @@ The changelog for this release may not be up-to-date, so it is not included. How
|
||||
|
||||
The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Develop with Firefly III
|
||||
|
||||
Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III.
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
||||
|
4
.github/release-notes/release.md
vendored
4
.github/release-notes/release.md
vendored
@@ -11,6 +11,10 @@ Welcome to release %version of Firefly III. It contains the latest fixes, transl
|
||||
|
||||
The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/).
|
||||
|
||||
## Develop with Firefly III
|
||||
|
||||
Are you interested in (future) API changes to Firefly III, or other interesting dev-related updates? Sign up to the [Firefly III developer newsletter](https://firefly-iii.kit.com/dev) to receive low-frequency updates about the development of Firefly III.
|
||||
|
||||
## Support Firefly III
|
||||
|
||||
Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. Please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information. Thank you for your consideration.
|
||||
|
2
.github/workflows/depsreview.yml
vendored
2
.github/workflows/depsreview.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout repository'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: 'Dependency review'
|
||||
|
2
.github/workflows/lock.yml
vendored
2
.github/workflows/lock.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
discussions: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: JC5/lock-threads@v6.0.2
|
||||
- uses: JC5/lock-threads@v6.0.6
|
||||
with:
|
||||
issue-inactive-days: 21
|
||||
pr-inactive-days: 21
|
||||
|
16
.github/workflows/release.yml
vendored
16
.github/workflows/release.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
||||
git pull
|
||||
echo "Current branch is $(git branch --show-current)"
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }}
|
||||
- name: Configure Git
|
||||
run: |
|
||||
# do some configuration
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
GH_TOKEN: ""
|
||||
FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
FF_III_VERSION: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }}
|
||||
- name: Generate JSON v1
|
||||
id: json-v1
|
||||
uses: JC5/firefly-iii-dev@main
|
||||
@@ -221,7 +221,7 @@ jobs:
|
||||
echo "tarName=$tarName" >> "$GITHUB_ENV"
|
||||
echo "BRANCH_NAME=$BRANCH_NAME" >> "$GITHUB_ENV"
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }}
|
||||
- name: Commit all changes
|
||||
run: |
|
||||
# add all content, except output.txt (this contains the changelog and/or the download instructions)
|
||||
@@ -232,12 +232,12 @@ jobs:
|
||||
git commit -m "🤖 Auto commit for release '$version' on $(date +'%Y-%m-%d')" || true
|
||||
git push
|
||||
env:
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }}
|
||||
- name: Generate release description
|
||||
id: release-description
|
||||
uses: JC5/firefly-iii-dev@main
|
||||
with:
|
||||
action: "ff3:generate-release-notes firefly-iii ${{ github.event.inputs.version }}"
|
||||
action: "ff3:generate-release-notes firefly-iii ${{ inputs.version || 'develop' }}"
|
||||
output: 'output'
|
||||
env:
|
||||
FIREFLY_III_ROOT: /github/workspace
|
||||
@@ -291,7 +291,7 @@ jobs:
|
||||
echo "DONE!"
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }}
|
||||
- name: Create archives
|
||||
run: |
|
||||
echo "Create zip file $zipName"
|
||||
@@ -375,7 +375,7 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }}
|
||||
- name: Upload artifacts
|
||||
run: |
|
||||
# add zip file to release.
|
||||
@@ -411,4 +411,4 @@ jobs:
|
||||
rm -f $tarName.sha256
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || github.event.inputs.version }}
|
||||
version: ${{ github.event_name == 'schedule' && 'develop' || inputs.version }}
|
||||
|
@@ -114,6 +114,7 @@ class AccountController extends Controller
|
||||
'id' => (string) $account->id,
|
||||
'name' => $account->name,
|
||||
'name_with_balance' => $nameWithBalance,
|
||||
'active' => $account->active,
|
||||
'type' => $account->accountType->type,
|
||||
'currency_id' => (string) $useCurrency->id,
|
||||
'currency_name' => $useCurrency->name,
|
||||
|
@@ -67,8 +67,9 @@ class BudgetController extends Controller
|
||||
$result = $this->repository->searchBudget($data['query'], $this->parameters->get('limit'));
|
||||
$filtered = $result->map(
|
||||
static fn (Budget $item) => [
|
||||
'id' => (string) $item->id,
|
||||
'name' => $item->name,
|
||||
'id' => (string) $item->id,
|
||||
'name' => $item->name,
|
||||
'active' => $item->active,
|
||||
]
|
||||
);
|
||||
|
||||
|
@@ -69,6 +69,7 @@ class RecurrenceController extends Controller
|
||||
'id' => (string) $recurrence->id,
|
||||
'name' => $recurrence->title,
|
||||
'description' => $recurrence->description,
|
||||
'active' => $recurrence->active,
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -37,7 +37,7 @@ use Illuminate\Http\JsonResponse;
|
||||
class RuleController extends Controller
|
||||
{
|
||||
private RuleRepositoryInterface $repository;
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_RULES];
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_RULES];
|
||||
|
||||
/**
|
||||
* RuleController constructor.
|
||||
@@ -66,9 +66,10 @@ class RuleController extends Controller
|
||||
/** @var Rule $rule */
|
||||
foreach ($rules as $rule) {
|
||||
$response[] = [
|
||||
'id' => (string) $rule->id,
|
||||
'id' => (string)$rule->id,
|
||||
'name' => $rule->title,
|
||||
'description' => $rule->description,
|
||||
'active' => $rule->active,
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -69,6 +69,7 @@ class RuleGroupController extends Controller
|
||||
'id' => (string) $group->id,
|
||||
'name' => $group->title,
|
||||
'description' => $group->description,
|
||||
'active' => $group->active,
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -49,8 +49,6 @@ class TransactionTypeController extends Controller
|
||||
function ($request, $next) {
|
||||
$this->validateUserGroup($request);
|
||||
$this->repository = app(TransactionTypeRepositoryInterface::class);
|
||||
$this->repository->setUser($this->user);
|
||||
$this->repository->setUserGroup($this->userGroup);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
|
@@ -130,6 +130,7 @@ class AccountController extends Controller
|
||||
'yAxisID' => 0,
|
||||
'period' => '1D',
|
||||
'entries' => [],
|
||||
'pc_entries' => [],
|
||||
];
|
||||
if ($this->convertToPrimary) {
|
||||
$currentSet['pc_entries'] = [];
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* BalanceController.php
|
||||
* Copyright (c) 2025 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\V1\Controllers\Chart;
|
||||
|
@@ -97,22 +97,23 @@ class CategoryController extends Controller
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setRange($start, $end)->withAccountInformation();
|
||||
$collector->setXorAccounts($accounts)->withCategoryInformation();
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::RECONCILIATION->value]);
|
||||
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value]);
|
||||
$journals = $collector->getExtractedJournals();
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
// find journal:
|
||||
$journalCurrencyId = (int)$journal['currency_id'];
|
||||
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
|
||||
$currencies[$journalCurrencyId] = $currency;
|
||||
$currencyId = (int)$currency->id;
|
||||
$currencyName = (string)$currency->name;
|
||||
$currencyCode = (string)$currency->code;
|
||||
$currencySymbol = (string)$currency->symbol;
|
||||
$currencyDecimalPlaces = (int)$currency->decimal_places;
|
||||
$amount = Steam::positive($journal['amount']);
|
||||
$pcAmount = null;
|
||||
$journalCurrencyId = (int)$journal['currency_id'];
|
||||
$type = $journal['transaction_type_type'];
|
||||
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
|
||||
$currencies[$journalCurrencyId] = $currency;
|
||||
$currencyId = (int)$currency->id;
|
||||
$currencyName = (string)$currency->name;
|
||||
$currencyCode = (string)$currency->code;
|
||||
$currencySymbol = (string)$currency->symbol;
|
||||
$currencyDecimalPlaces = (int)$currency->decimal_places;
|
||||
$amount = Steam::positive((string)$journal['amount']);
|
||||
$pcAmount = null;
|
||||
|
||||
// overrule if necessary:
|
||||
if ($this->convertToPrimary && $journalCurrencyId === $this->primaryCurrency->id) {
|
||||
@@ -129,8 +130,8 @@ class CategoryController extends Controller
|
||||
}
|
||||
|
||||
|
||||
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
|
||||
$key = sprintf('%s-%s', $categoryName, $currencyCode);
|
||||
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
|
||||
$key = sprintf('%s-%s', $categoryName, $currencyCode);
|
||||
// create arrays
|
||||
$return[$key] ??= [
|
||||
'label' => $categoryName,
|
||||
@@ -150,23 +151,37 @@ class CategoryController extends Controller
|
||||
'yAxisID' => 0,
|
||||
'type' => 'bar',
|
||||
'entries' => [
|
||||
'spent' => '0',
|
||||
'spent' => '0',
|
||||
'earned' => '0',
|
||||
],
|
||||
'pc_entries' => [
|
||||
'spent' => '0',
|
||||
'spent' => '0',
|
||||
'earned' => '0',
|
||||
],
|
||||
];
|
||||
|
||||
// add monies
|
||||
$return[$key]['entries']['spent'] = bcadd($return[$key]['entries']['spent'], (string)$amount);
|
||||
if (null !== $pcAmount) {
|
||||
$return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], (string)$pcAmount);
|
||||
// expenses to spent
|
||||
if (TransactionTypeEnum::WITHDRAWAL->value === $type) {
|
||||
$return[$key]['entries']['spent'] = bcadd($return[$key]['entries']['spent'], $amount);
|
||||
if (null !== $pcAmount) {
|
||||
$return[$key]['pc_entries']['spent'] = bcadd($return[$key]['pc_entries']['spent'], $pcAmount);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
// positive amount = earned
|
||||
if (TransactionTypeEnum::DEPOSIT->value === $type) {
|
||||
$return[$key]['entries']['earned'] = bcadd($return[$key]['entries']['earned'], $amount);
|
||||
if (null !== $pcAmount) {
|
||||
$return[$key]['pc_entries']['earned'] = bcadd($return[$key]['pc_entries']['earned'], $pcAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
$return = array_values($return);
|
||||
|
||||
// order by amount
|
||||
usort($return, static fn (array $a, array $b) => (float)$a['entries']['spent'] < (float)$b['entries']['spent'] ? 1 : -1);
|
||||
usort($return, static fn (array $a, array $b) => ((float)$a['entries']['spent'] + (float)$a['entries']['earned']) < ((float)$b['entries']['spent'] + (float)$b['entries']['earned']) ? 1 : -1);
|
||||
|
||||
return response()->json($this->clean($return));
|
||||
}
|
||||
|
@@ -108,12 +108,7 @@ abstract class Controller extends BaseController
|
||||
{
|
||||
$bag = new ParameterBag();
|
||||
$page = (int)request()->get('page');
|
||||
if ($page < 1) {
|
||||
$page = 1;
|
||||
}
|
||||
if ($page > 2 ** 16) {
|
||||
$page = 2 ** 16;
|
||||
}
|
||||
$page = min(max(1, $page), 2 ** 16);
|
||||
$bag->set('page', $page);
|
||||
|
||||
// some date fields:
|
||||
@@ -131,19 +126,15 @@ abstract class Controller extends BaseController
|
||||
$obj = null;
|
||||
if (null !== $date) {
|
||||
try {
|
||||
$obj = Carbon::parse((string)$date);
|
||||
$obj = Carbon::parse((string)$date, config('app.timezone'));
|
||||
} catch (InvalidFormatException $e) {
|
||||
// don't care
|
||||
Log::warning(
|
||||
sprintf(
|
||||
'Ignored invalid date "%s" in API controller parameter check: %s',
|
||||
substr((string)$date, 0, 20),
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', substr((string)$date, 0, 20), $e->getMessage()));
|
||||
}
|
||||
}
|
||||
$bag->set($field, $obj);
|
||||
if ($obj instanceof Carbon) {
|
||||
$bag->set($field, $obj);
|
||||
}
|
||||
}
|
||||
|
||||
// integer fields:
|
||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\Models\Account;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Account\ShowRequest;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
@@ -33,7 +34,6 @@ use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use League\Fractal\Resource\Collection as FractalCollection;
|
||||
@@ -71,34 +71,36 @@ class ShowController extends Controller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
public function index(ShowRequest $request): JsonResponse
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
$type = $request->get('type') ?? 'all';
|
||||
$this->parameters->set('type', $type);
|
||||
$params = $request->getParameters();
|
||||
$this->parameters->set('type', $params['type']);
|
||||
|
||||
// types to get, page size:
|
||||
$types = $this->mapAccountTypes($this->parameters->get('type'));
|
||||
$pageSize = $this->parameters->get('limit');
|
||||
$types = $this->mapAccountTypes($params['type']);
|
||||
|
||||
// get list of accounts. Count it and split it.
|
||||
$this->repository->resetAccountOrder();
|
||||
$collection = $this->repository->getAccountsByType($types, $this->parameters->get('sort') ?? []);
|
||||
// TODO fix sort.
|
||||
$collection = $this->repository->getAccountsByType($types);
|
||||
$count = $collection->count();
|
||||
|
||||
// continue sort:
|
||||
$accounts = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$accounts = $collection->slice(($this->parameters->get('page') - 1) * $params['limit'], $params['limit']);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment->setDate($this->parameters->get('date'));
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$enrichment->setUser($admin);
|
||||
$accounts = $enrichment->enrich($accounts);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($accounts, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator = new LengthAwarePaginator($accounts, $count, $params['limit'], $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.accounts.index').$this->buildParams());
|
||||
|
||||
/** @var AccountTransformer $transformer */
|
||||
@@ -117,7 +119,7 @@ class ShowController extends Controller
|
||||
*
|
||||
* Show single instance.
|
||||
*/
|
||||
public function show(Account $account): JsonResponse
|
||||
public function show(ShowRequest $request, Account $account): JsonResponse
|
||||
{
|
||||
// get list of accounts. Count it and split it.
|
||||
$this->repository->resetAccountOrder();
|
||||
@@ -129,6 +131,8 @@ class ShowController extends Controller
|
||||
$admin = auth()->user();
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment->setDate($this->parameters->get('date'));
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$enrichment->setUser($admin);
|
||||
$account = $enrichment->enrichSingle($account);
|
||||
|
||||
|
@@ -96,7 +96,6 @@ class ShowController extends Controller
|
||||
$paginator = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.budgets.limits.index', [$budget->id]).$this->buildParams());
|
||||
|
||||
|
||||
// enrich
|
||||
$enrichment = new BudgetLimitEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
|
@@ -86,7 +86,7 @@ class UpdateController extends Controller
|
||||
$admin = auth()->user();
|
||||
$enrichment = new BudgetLimitEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$budgetLimit = $enrichment->enrich($budgetLimit);
|
||||
$budgetLimit = $enrichment->enrichSingle($budgetLimit);
|
||||
|
||||
/** @var BudgetLimitTransformer $transformer */
|
||||
$transformer = app(BudgetLimitTransformer::class);
|
||||
|
126
app/Api/V1/Controllers/Models/Recurrence/TriggerController.php
Normal file
126
app/Api/V1/Controllers/Models/Recurrence/TriggerController.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* TriggerController.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Models\Recurrence;
|
||||
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Generic\SingleDateRequest;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Jobs\CreateRecurringTransactions;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
use League\Fractal\Resource\Collection as FractalCollection;
|
||||
|
||||
class TriggerController extends Controller
|
||||
{
|
||||
private RecurringRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* RecurrenceController constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(RecurringRepositoryInterface::class);
|
||||
$this->repository->setUser(auth()->user());
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public function trigger(SingleDateRequest $request, Recurrence $recurrence): JsonResponse
|
||||
{
|
||||
// find recurrence occurrence for this date and trigger it.
|
||||
// grab the date from the last time the recurrence fired:
|
||||
$backupDate = $recurrence->latest_date;
|
||||
$date = $request->getDate();
|
||||
|
||||
// fire the recurring cron job on the given date, then post-date the created transaction.
|
||||
Log::info(sprintf('Trigger: will now fire recurring cron job task for date "%s".', $date->format('Y-m-d H:i:s')));
|
||||
|
||||
/** @var CreateRecurringTransactions $job */
|
||||
$job = app(CreateRecurringTransactions::class);
|
||||
$job->setRecurrences(new Collection()->push($recurrence));
|
||||
$job->setDate($date);
|
||||
$job->setForce(false);
|
||||
$job->handle();
|
||||
Log::debug('Done with recurrence.');
|
||||
|
||||
$groups = $job->getGroups();
|
||||
$this->repository->markGroupsAsNow($groups);
|
||||
$recurrence->latest_date = $backupDate;
|
||||
$recurrence->latest_date_tz = $backupDate?->format('e');
|
||||
$recurrence->save();
|
||||
Preferences::mark();
|
||||
|
||||
// enrich groups and return them:
|
||||
|
||||
if (0 === $groups->count()) {
|
||||
$paginator = new LengthAwarePaginator(new Collection(), 0, 1);
|
||||
}
|
||||
if ($groups->count() > 0) {
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
|
||||
// use new group collector:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector
|
||||
->setUser($admin)
|
||||
->setIds($groups->pluck('id')->toArray())
|
||||
->withAPIInformation()
|
||||
;
|
||||
$paginator = $collector->getPaginatedGroups();
|
||||
}
|
||||
|
||||
$manager = $this->getManager();
|
||||
$paginator->setPath(route('api.v1.recurrences.trigger', [$recurrence->id]).$this->buildParams());
|
||||
|
||||
// enrich
|
||||
$admin = auth()->user();
|
||||
$enrichment = new TransactionGroupEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$transactions = $enrichment->enrich($paginator->getCollection());
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
||||
$resource = new FractalCollection($transactions, $transformer, 'transactions');
|
||||
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
|
||||
|
||||
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
|
||||
}
|
||||
}
|
@@ -32,7 +32,9 @@ use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment;
|
||||
use FireflyIII\Transformers\WebhookTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -90,6 +92,13 @@ class ShowController extends Controller
|
||||
$paginator = new LengthAwarePaginator($webhooks, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.webhooks.index').$this->buildParams());
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new WebhookEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$webhooks = $enrichment->enrich($webhooks);
|
||||
|
||||
/** @var WebhookTransformer $transformer */
|
||||
$transformer = app(WebhookTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
@@ -117,6 +126,13 @@ class ShowController extends Controller
|
||||
Log::channel('audit')->info(sprintf('User views webhook #%d.', $webhook->id));
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new WebhookEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$webhook = $enrichment->enrichSingle($webhook);
|
||||
|
||||
/** @var WebhookTransformer $transformer */
|
||||
$transformer = app(WebhookTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
@@ -156,7 +172,7 @@ class ShowController extends Controller
|
||||
$engine->generateMessages();
|
||||
|
||||
// trigger event to send them:
|
||||
Log::debug('send event RequestedSendWebhookMessages');
|
||||
Log::debug('send event RequestedSendWebhookMessages from ShowController::triggerTransaction()');
|
||||
event(new RequestedSendWebhookMessages());
|
||||
|
||||
return response()->json([], 204);
|
||||
|
@@ -27,7 +27,9 @@ namespace FireflyIII\Api\V1\Controllers\Webhook;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Webhook\CreateRequest;
|
||||
use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment;
|
||||
use FireflyIII\Transformers\WebhookTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Fractal\Resource\Item;
|
||||
@@ -68,6 +70,15 @@ class StoreController extends Controller
|
||||
}
|
||||
|
||||
$webhook = $this->repository->store($data);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new WebhookEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$webhook = $enrichment->enrichSingle($webhook);
|
||||
|
||||
|
||||
$manager = $this->getManager();
|
||||
|
||||
Log::channel('audit')->info('User stores new webhook', $data);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Webhook\UpdateRequest;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\WebhookEnrichment;
|
||||
use FireflyIII\Transformers\WebhookTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Fractal\Resource\Item;
|
||||
@@ -70,6 +72,13 @@ class UpdateController extends Controller
|
||||
$webhook = $this->repository->update($webhook, $data);
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new WebhookEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$webhook = $enrichment->enrichSingle($webhook);
|
||||
|
||||
Log::channel('audit')->info(sprintf('User updates webhook #%d', $webhook->id), $data);
|
||||
|
||||
/** @var WebhookTransformer $transformer */
|
||||
|
@@ -64,6 +64,7 @@ class ChartRequest extends FormRequest
|
||||
'end' => 'required|date|after:1970-01-02|before:2038-01-17|after_or_equal:start',
|
||||
'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
'accounts' => 'nullable|array',
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
];
|
||||
|
||||
|
100
app/Api/V1/Requests/Models/Account/ShowRequest.php
Normal file
100
app/Api/V1/Requests/Models/Account/ShowRequest.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* ShowRequest.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace FireflyIII\Api\V1\Requests\Models\Account;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ShowRequest extends FormRequest
|
||||
{
|
||||
use AccountFilter;
|
||||
use ConvertsDataTypes;
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
$limit = $this->convertInteger('limit');
|
||||
if (0 === $limit) {
|
||||
// get default for user:
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
/** @var Preference $pageSize */
|
||||
$limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data;
|
||||
}
|
||||
|
||||
$page = $this->convertInteger('page');
|
||||
$page = min(max(1, $page), 2 ** 16);
|
||||
|
||||
return [
|
||||
'type' => $this->convertString('type', 'all'),
|
||||
'limit' => $limit,
|
||||
'sort' => $this->convertString('sort', 'order'),
|
||||
'page' => $page,
|
||||
];
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$keys = implode(',', array_keys($this->types));
|
||||
|
||||
return [
|
||||
'date' => 'date',
|
||||
'start' => 'date|present_with:end|before_or_equal:end|before:2038-01-17|after:1970-01-02',
|
||||
'end' => 'date|present_with:start|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
'sort' => 'nullable|in:active,iban,name,order,-active,-iban,-name,-order', // TODO improve me.
|
||||
'type' => sprintf('in:%s', $keys),
|
||||
'limit' => 'numeric|min:1|max:131337',
|
||||
'page' => 'numeric|min:1|max:131337',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator): void {
|
||||
if ($validator->failed()) {
|
||||
return;
|
||||
}
|
||||
$data = $validator->getData();
|
||||
|
||||
|
||||
if (array_key_exists('date', $data) && array_key_exists('start', $data) && array_key_exists('end', $data)) {
|
||||
// assume valid dates, before we got here.
|
||||
$start = Carbon::parse($data['start'], config('app.timezone'))->startOfDay();
|
||||
$end = Carbon::parse($data['end'], config('app.timezone'))->endOfDay();
|
||||
$date = Carbon::parse($data['date'], config('app.timezone'));
|
||||
if (!$date->between($start, $end)) {
|
||||
$validator->errors()->add('date', (string)trans('validation.between_date'));
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -78,7 +78,7 @@ class StoreRequest extends FormRequest
|
||||
'object_group_id' => 'numeric|belongsToUser:object_groups,id',
|
||||
'object_group_title' => ['min:1', 'max:255'],
|
||||
'target_amount' => ['required', new IsValidZeroOrMoreAmount()],
|
||||
'start_date' => 'date|nullable',
|
||||
'start_date' => 'required|date|after:1970-01-01|before:2038-01-17',
|
||||
'transaction_currency_id' => 'exists:transaction_currencies,id|required_without:transaction_currency_code',
|
||||
'transaction_currency_code' => 'exists:transaction_currencies,code|required_without:transaction_currency_id',
|
||||
'target_date' => 'date|nullable|after:start_date',
|
||||
|
@@ -24,15 +24,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Requests\Models\Webhook;
|
||||
|
||||
use FireflyIII\Enums\WebhookResponse;
|
||||
use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use FireflyIII\Support\Request\ValidatesWebhooks;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class CreateRequest
|
||||
@@ -41,27 +39,28 @@ class CreateRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use ValidatesWebhooks;
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
$triggers = Webhook::getTriggersForValidation();
|
||||
$responses = Webhook::getResponsesForValidation();
|
||||
$deliveries = Webhook::getDeliveriesForValidation();
|
||||
|
||||
$fields = [
|
||||
'title' => ['title', 'convertString'],
|
||||
'active' => ['active', 'boolean'],
|
||||
'trigger' => ['trigger', 'convertString'],
|
||||
'response' => ['response', 'convertString'],
|
||||
'delivery' => ['delivery', 'convertString'],
|
||||
'url' => ['url', 'convertString'],
|
||||
$fields = [
|
||||
'title' => ['title', 'convertString'],
|
||||
'active' => ['active', 'boolean'],
|
||||
'url' => ['url', 'convertString'],
|
||||
];
|
||||
$triggers = $this->get('triggers', []);
|
||||
$responses = $this->get('responses', []);
|
||||
$deliveries = $this->get('deliveries', []);
|
||||
|
||||
// this is the way.
|
||||
$return = $this->getAllData($fields);
|
||||
$return['trigger'] = $triggers[$return['trigger']] ?? (int)$return['trigger'];
|
||||
$return['response'] = $responses[$return['response']] ?? (int)$return['response'];
|
||||
$return['delivery'] = $deliveries[$return['delivery']] ?? (int)$return['delivery'];
|
||||
if (0 === count($triggers) || 0 === count($responses) || 0 === count($deliveries)) {
|
||||
throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.');
|
||||
}
|
||||
|
||||
|
||||
$return = $this->getAllData($fields);
|
||||
$return['triggers'] = $triggers;
|
||||
$return['responses'] = $responses;
|
||||
$return['deliveries'] = $deliveries;
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -71,59 +70,24 @@ class CreateRequest extends FormRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$triggers = implode(',', array_keys(Webhook::getTriggersForValidation()));
|
||||
$responses = implode(',', array_keys(Webhook::getResponsesForValidation()));
|
||||
$deliveries = implode(',', array_keys(Webhook::getDeliveriesForValidation()));
|
||||
$triggers = implode(',', array_values(Webhook::getTriggers()));
|
||||
$responses = implode(',', array_values(Webhook::getResponses()));
|
||||
$deliveries = implode(',', array_values(Webhook::getDeliveries()));
|
||||
$validProtocols = config('firefly.valid_url_protocols');
|
||||
|
||||
return [
|
||||
'title' => 'required|min:1|max:255|uniqueObjectForUser:webhooks,title',
|
||||
'active' => [new IsBoolean()],
|
||||
'trigger' => sprintf('required|in:%s', $triggers),
|
||||
'response' => sprintf('required|in:%s', $responses),
|
||||
'delivery' => sprintf('required|in:%s', $deliveries),
|
||||
'url' => ['required', sprintf('url:%s', $validProtocols), 'uniqueWebhook'],
|
||||
'title' => 'required|min:1|max:255|uniqueObjectForUser:webhooks,title',
|
||||
'active' => [new IsBoolean()],
|
||||
'trigger' => 'prohibited',
|
||||
'triggers' => 'required|array|min:1|max:10',
|
||||
'triggers.*' => sprintf('required|in:%s', $triggers),
|
||||
'response' => 'prohibited',
|
||||
'responses' => 'required|array|min:1|max:1',
|
||||
'responses.*' => sprintf('required|in:%s', $responses),
|
||||
'delivery' => 'prohibited',
|
||||
'deliveries' => 'required|array|min:1|max:1',
|
||||
'deliveries.*' => sprintf('required|in:%s', $deliveries),
|
||||
'url' => ['required', sprintf('url:%s', $validProtocols)],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator): void {
|
||||
Log::debug('Validating webhook');
|
||||
$data = $validator->getData();
|
||||
$trigger = $data['trigger'] ?? null;
|
||||
$response = $data['response'] ?? null;
|
||||
if (null === $trigger || null === $response) {
|
||||
Log::debug('No trigger or response, return.');
|
||||
|
||||
return;
|
||||
}
|
||||
$triggers = array_keys(Webhook::getTriggersForValidation());
|
||||
$responses = array_keys(Webhook::getResponsesForValidation());
|
||||
if (!in_array($trigger, $triggers, true) || !in_array($response, $responses, true)) {
|
||||
return;
|
||||
}
|
||||
// cannot deliver budget info.
|
||||
if (is_int($trigger)) {
|
||||
Log::debug(sprintf('Trigger was integer (%d).', $trigger));
|
||||
$trigger = WebhookTrigger::from($trigger)->name;
|
||||
}
|
||||
if (is_int($response)) {
|
||||
Log::debug(sprintf('Response was integer (%d).', $response));
|
||||
$response = WebhookResponse::from($response)->name;
|
||||
}
|
||||
Log::debug(sprintf('Trigger is %s, response is %s', $trigger, $response));
|
||||
if (str_contains($trigger, 'TRANSACTION') && str_contains($response, 'BUDGET')) {
|
||||
$validator->errors()->add('response', trans('validation.webhook_budget_info'));
|
||||
}
|
||||
if (str_contains($trigger, 'BUDGET') && str_contains($response, 'ACCOUNT')) {
|
||||
$validator->errors()->add('response', trans('validation.webhook_account_info'));
|
||||
}
|
||||
if (str_contains($trigger, 'BUDGET') && str_contains($response, 'TRANSACTION')) {
|
||||
$validator->errors()->add('response', trans('validation.webhook_transaction_info'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -24,15 +24,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Requests\Models\Webhook;
|
||||
|
||||
use FireflyIII\Enums\WebhookResponse;
|
||||
use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Rules\IsBoolean;
|
||||
use FireflyIII\Support\Request\ChecksLogin;
|
||||
use FireflyIII\Support\Request\ConvertsDataTypes;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use FireflyIII\Support\Request\ValidatesWebhooks;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class UpdateRequest
|
||||
@@ -41,38 +39,29 @@ class UpdateRequest extends FormRequest
|
||||
{
|
||||
use ChecksLogin;
|
||||
use ConvertsDataTypes;
|
||||
use ValidatesWebhooks;
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
$triggers = Webhook::getTriggersForValidation();
|
||||
$responses = Webhook::getResponsesForValidation();
|
||||
$deliveries = Webhook::getDeliveriesForValidation();
|
||||
|
||||
$fields = [
|
||||
$fields = [
|
||||
'title' => ['title', 'convertString'],
|
||||
'active' => ['active', 'boolean'],
|
||||
'trigger' => ['trigger', 'convertString'],
|
||||
'response' => ['response', 'convertString'],
|
||||
'delivery' => ['delivery', 'convertString'],
|
||||
'url' => ['url', 'convertString'],
|
||||
];
|
||||
|
||||
// this is the way.
|
||||
$return = $this->getAllData($fields);
|
||||
if (array_key_exists('trigger', $return)) {
|
||||
$return['trigger'] = $triggers[$return['trigger']] ?? 0;
|
||||
}
|
||||
if (array_key_exists('response', $return)) {
|
||||
$return['response'] = $responses[$return['response']] ?? 0;
|
||||
}
|
||||
if (array_key_exists('delivery', $return)) {
|
||||
$return['delivery'] = $deliveries[$return['delivery']] ?? 0;
|
||||
}
|
||||
$return['secret'] = null !== $this->get('secret');
|
||||
if (null !== $this->get('title')) {
|
||||
$return['title'] = $this->convertString('title');
|
||||
$triggers = $this->get('triggers', []);
|
||||
$responses = $this->get('responses', []);
|
||||
$deliveries = $this->get('deliveries', []);
|
||||
|
||||
if (0 === count($triggers) || 0 === count($responses) || 0 === count($deliveries)) {
|
||||
throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.');
|
||||
}
|
||||
|
||||
$return = $this->getAllData($fields);
|
||||
$return['triggers'] = $triggers;
|
||||
$return['responses'] = $responses;
|
||||
$return['deliveries'] = $deliveries;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -81,62 +70,29 @@ class UpdateRequest extends FormRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$triggers = implode(',', array_keys(Webhook::getTriggersForValidation()));
|
||||
$responses = implode(',', array_keys(Webhook::getResponsesForValidation()));
|
||||
$deliveries = implode(',', array_keys(Webhook::getDeliveriesForValidation()));
|
||||
$triggers = implode(',', array_values(Webhook::getTriggers()));
|
||||
$responses = implode(',', array_values(Webhook::getResponses()));
|
||||
$deliveries = implode(',', array_values(Webhook::getDeliveries()));
|
||||
$validProtocols = config('firefly.valid_url_protocols');
|
||||
|
||||
/** @var Webhook $webhook */
|
||||
$webhook = $this->route()->parameter('webhook');
|
||||
|
||||
return [
|
||||
'title' => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id),
|
||||
'active' => [new IsBoolean()],
|
||||
'trigger' => sprintf('in:%s', $triggers),
|
||||
'response' => sprintf('in:%s', $responses),
|
||||
'delivery' => sprintf('in:%s', $deliveries),
|
||||
'url' => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)],
|
||||
'title' => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id),
|
||||
'active' => [new IsBoolean()],
|
||||
|
||||
'trigger' => 'prohibited',
|
||||
'triggers' => 'required|array|min:1|max:10',
|
||||
'triggers.*' => sprintf('required|in:%s', $triggers),
|
||||
'response' => 'prohibited',
|
||||
'responses' => 'required|array|min:1|max:1',
|
||||
'responses.*' => sprintf('required|in:%s', $responses),
|
||||
'delivery' => 'prohibited',
|
||||
'deliveries' => 'required|array|min:1|max:1',
|
||||
'deliveries.*' => sprintf('required|in:%s', $deliveries),
|
||||
|
||||
'url' => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)],
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator): void {
|
||||
Log::debug('Validating webhook');
|
||||
$data = $validator->getData();
|
||||
$trigger = $data['trigger'] ?? null;
|
||||
$response = $data['response'] ?? null;
|
||||
if (null === $trigger || null === $response) {
|
||||
Log::debug('No trigger or response, return.');
|
||||
|
||||
return;
|
||||
}
|
||||
$triggers = array_keys(Webhook::getTriggersForValidation());
|
||||
$responses = array_keys(Webhook::getResponsesForValidation());
|
||||
if (!in_array($trigger, $triggers, true) || !in_array($response, $responses, true)) {
|
||||
return;
|
||||
}
|
||||
// cannot deliver budget info.
|
||||
if (is_int($trigger)) {
|
||||
Log::debug(sprintf('Trigger was integer (%d).', $trigger));
|
||||
$trigger = WebhookTrigger::from($trigger)->name;
|
||||
}
|
||||
if (is_int($response)) {
|
||||
Log::debug(sprintf('Response was integer (%d).', $response));
|
||||
$response = WebhookResponse::from($response)->name;
|
||||
}
|
||||
Log::debug(sprintf('Trigger is %s, response is %s', $trigger, $response));
|
||||
if (str_contains($trigger, 'TRANSACTION') && str_contains($response, 'BUDGET')) {
|
||||
$validator->errors()->add('response', trans('validation.webhook_budget_info'));
|
||||
}
|
||||
if (str_contains($trigger, 'BUDGET') && str_contains($response, 'ACCOUNT')) {
|
||||
$validator->errors()->add('response', trans('validation.webhook_account_info'));
|
||||
}
|
||||
if (str_contains($trigger, 'BUDGET') && str_contains($response, 'TRANSACTION')) {
|
||||
$validator->errors()->add('response', trans('validation.webhook_transaction_info'));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ namespace FireflyIII\Casts;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class SeparateTimezoneCaster
|
||||
@@ -51,6 +52,7 @@ class SeparateTimezoneCaster implements CastsAttributes
|
||||
$timeZone = $attributes[sprintf('%s_tz', $key)] ?? config('app.timezone');
|
||||
|
||||
return Carbon::parse($value, $timeZone)->setTimezone(config('app.timezone'));
|
||||
// Log::debug(sprintf('SeparateTimezoneCaster: %s.%s = %s', str_replace('FireflyIII\\Models\\','',get_class($model)), $key, $result->toAtomString()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* ValidatesEnvironmentVariables.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org.
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -18,9 +18,11 @@ declare(strict_types=1);
|
||||
* 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/.
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Integrity;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
|
@@ -133,6 +133,9 @@ class OutputsInstructions extends Command
|
||||
if ('03-31' === $today) {
|
||||
$colors = ['bright-blue', 'bright-red', 'white', 'white', 'bright-red', 'bright-blue', 'default', 'default'];
|
||||
}
|
||||
if ('ru_RU' === config('firefly.default_language')) {
|
||||
$colors = ['blue', 'blue', 'blue', 'yellow', 'yellow', 'yellow', 'default', 'default'];
|
||||
}
|
||||
|
||||
$this->line(sprintf('<fg=%s> ______ _ __ _ _____ _____ _____ </>', $colors[0]));
|
||||
$this->line(sprintf('<fg=%s> | ____(_) / _| | |_ _|_ _|_ _| </>', $colors[1]));
|
||||
@@ -238,14 +241,38 @@ class OutputsInstructions extends Command
|
||||
|
||||
private function someQuote(): void
|
||||
{
|
||||
$lines = [
|
||||
$lines = [
|
||||
'Forgive yourself for not being at peace.',
|
||||
'Doesn\'t look like anything to me.',
|
||||
'Be proud of what you make.',
|
||||
'Be there or forever wonder.',
|
||||
'A year from now you will wish you had started today.',
|
||||
];
|
||||
$random = random_int(0, count($lines) - 1);
|
||||
$this->line(sprintf(' "%s"', $lines[$random]));
|
||||
$addQuotes = true;
|
||||
|
||||
// fuck the Russian aggression in Ukraine.
|
||||
|
||||
// There is no point even trying to be neutral, because you can’t. When I say you can’t be neutral on
|
||||
// a moving train, it means the world is already moving in certain directions. Children are going
|
||||
// hungry, wars are taking place. In a situation like that, to be neutral or to try to be neutral,
|
||||
// to stand aside, not to take a stand, not to participate, is to collaborate with whatever is
|
||||
// going on, to allow that to happen.
|
||||
|
||||
if ('ru_RU' === config('firefly.default_language')) {
|
||||
$addQuotes = false;
|
||||
$lines = [
|
||||
'🇺🇦 Слава Україні!',
|
||||
'🇺🇦 Slava Ukraini!',
|
||||
];
|
||||
}
|
||||
|
||||
$random = random_int(0, count($lines) - 1);
|
||||
if ($addQuotes) {
|
||||
$this->line(sprintf(' "%s"', $lines[$random]));
|
||||
|
||||
return;
|
||||
}
|
||||
$this->line(sprintf(' %s', $lines[$random]));
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* RecalculatesRunningBalance.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org.
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -18,9 +18,11 @@ declare(strict_types=1);
|
||||
* 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/.
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* ResetsErrorMailLimit.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\System;
|
||||
|
@@ -75,6 +75,7 @@ class UpgradesDatabase extends Command
|
||||
'upgrade:610-currency-preferences',
|
||||
'upgrade:620-piggy-banks',
|
||||
'upgrade:620-pc-amounts',
|
||||
'upgrade:640-upgrade-webhooks',
|
||||
'firefly-iii:correct-database',
|
||||
];
|
||||
$args = [];
|
||||
|
116
app/Console/Commands/Upgrade/UpgradesWebhooks.php
Normal file
116
app/Console/Commands/Upgrade/UpgradesWebhooks.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* UpgradesWebhooks.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Console\Commands\Upgrade;
|
||||
|
||||
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
|
||||
use FireflyIII\Enums\WebhookDelivery;
|
||||
use FireflyIII\Enums\WebhookResponse;
|
||||
use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Models\WebhookDelivery as WebhookDeliveryModel;
|
||||
use FireflyIII\Models\WebhookResponse as WebhookResponseModel;
|
||||
use FireflyIII\Models\WebhookTrigger as WebhookTriggerModel;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpgradesWebhooks extends Command
|
||||
{
|
||||
use ShowsFriendlyMessages;
|
||||
|
||||
public const string CONFIG_NAME = '640_upgrade_webhooks';
|
||||
protected $description = 'Upgrade webhooks so they can handle multiple triggers.';
|
||||
protected $signature = 'upgrade:640-upgrade-webhooks {--F|force : Force the execution of this command.}';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle(): int
|
||||
{
|
||||
if ($this->isExecuted() && true !== $this->option('force')) {
|
||||
$this->friendlyInfo('This command has already been executed.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->upgradeWebhooks();
|
||||
$this->markAsExecuted();
|
||||
$this->friendlyPositive('Upgraded webhooks.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function isExecuted(): bool
|
||||
{
|
||||
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
|
||||
if (null !== $configVar) {
|
||||
return (bool)$configVar->data;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function upgradeWebhooks(): void
|
||||
{
|
||||
$set = Webhook::where('delivery', '>', 1)->orWhere('trigger', '>', 1)->orWhere('response', '>', 1)->get();
|
||||
|
||||
/** @var Webhook $webhook */
|
||||
foreach ($set as $webhook) {
|
||||
$this->upgradeWebhook($webhook);
|
||||
}
|
||||
}
|
||||
|
||||
private function upgradeWebhook(Webhook $webhook): void
|
||||
{
|
||||
$delivery = WebhookDelivery::tryFrom((int)$webhook->delivery);
|
||||
$response = WebhookResponse::tryFrom((int)$webhook->response);
|
||||
$trigger = WebhookTrigger::tryFrom((int)$webhook->trigger);
|
||||
if (null === $delivery || null === $response || null === $trigger) {
|
||||
$this->friendlyError(sprintf('[a] Webhook #%d has an invalid delivery, response or trigger value. Will not upgrade.', $webhook->id));
|
||||
|
||||
return;
|
||||
}
|
||||
$deliveryModel = WebhookDeliveryModel::where('key', $delivery->value)->first();
|
||||
$responseModel = WebhookResponseModel::where('key', $response->value)->first();
|
||||
$triggerModel = WebhookTriggerModel::where('key', $trigger->value)->first();
|
||||
if (null === $deliveryModel || null === $responseModel || null === $triggerModel) {
|
||||
$this->friendlyError(sprintf('[b] Webhook #%d has an invalid delivery, response or trigger model. Will not upgrade.', $webhook->id));
|
||||
|
||||
return;
|
||||
}
|
||||
$webhook->webhookDeliveries()->attach([$deliveryModel->id]);
|
||||
$webhook->webhookResponses()->attach([$responseModel->id]);
|
||||
$webhook->webhookTriggers()->attach([$triggerModel->id]);
|
||||
$webhook->delivery = 1;
|
||||
$webhook->response = 1;
|
||||
$webhook->trigger = 1;
|
||||
$webhook->save();
|
||||
$this->friendlyPositive(sprintf('Webhook #%d upgraded.', $webhook->id));
|
||||
}
|
||||
|
||||
private function markAsExecuted(): void
|
||||
{
|
||||
app('fireflyconfig')->set(self::CONFIG_NAME, true);
|
||||
}
|
||||
}
|
@@ -32,5 +32,6 @@ enum WebhookResponse: int
|
||||
case TRANSACTIONS = 200;
|
||||
case ACCOUNTS = 210;
|
||||
case BUDGET = 230;
|
||||
case RELEVANT = 240;
|
||||
case NONE = 220;
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ namespace FireflyIII\Enums;
|
||||
*/
|
||||
enum WebhookTrigger: int
|
||||
{
|
||||
case ANY = 50;
|
||||
case STORE_TRANSACTION = 100;
|
||||
case UPDATE_TRANSACTION = 110;
|
||||
case DESTROY_TRANSACTION = 120;
|
||||
|
@@ -1,6 +1,27 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* WarnUserAboutBill.php
|
||||
* Copyright (c) 2025 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\Events\Model\Bill;
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* WarnUserAboutOverdueSubscriptions.php
|
||||
* Copyright (c) 2025 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\Events\Model\Bill;
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* ChangedName.php
|
||||
* Copyright (c) 2025 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\Events\Model\PiggyBank;
|
||||
|
@@ -30,6 +30,7 @@ use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Services\Internal\Support\RecurringTransactionTrait;
|
||||
use FireflyIII\Services\Internal\Support\TransactionTypeTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\MessageBag;
|
||||
|
||||
/**
|
||||
@@ -62,8 +63,8 @@ class RecurrenceFactory
|
||||
$type = $this->findTransactionType(ucfirst((string) $data['recurrence']['type']));
|
||||
} catch (FireflyException $e) {
|
||||
$message = sprintf('Cannot make a recurring transaction of type "%s"', $data['recurrence']['type']);
|
||||
app('log')->error($message);
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error($message);
|
||||
Log::error($e->getTraceAsString());
|
||||
|
||||
throw new FireflyException($message, 0, $e);
|
||||
}
|
||||
@@ -101,17 +102,18 @@ class RecurrenceFactory
|
||||
|
||||
$recurrence = new Recurrence(
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->user->user_group_id,
|
||||
'transaction_type_id' => $type->id,
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'first_date' => $firstDate?->format('Y-m-d'),
|
||||
'repeat_until' => $repetitions > 0 ? null : $repeatUntilString,
|
||||
'latest_date' => null,
|
||||
'repetitions' => $repetitions,
|
||||
'apply_rules' => $applyRules,
|
||||
'active' => $active,
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->user->user_group_id,
|
||||
'transaction_type_id' => $type->id,
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'first_date' => $firstDate?->format('Y-m-d'),
|
||||
'first_date_tz' => $firstDate?->format('e'),
|
||||
'repeat_until' => $repetitions > 0 ? null : $repeatUntilString,
|
||||
'latest_date' => null,
|
||||
'repetitions' => $repetitions,
|
||||
'apply_rules' => $applyRules,
|
||||
'active' => $active,
|
||||
]
|
||||
);
|
||||
$recurrence->save();
|
||||
@@ -125,8 +127,8 @@ class RecurrenceFactory
|
||||
try {
|
||||
$this->createTransactions($recurrence, $data['transactions'] ?? []);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error($e->getMessage());
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error($e->getMessage());
|
||||
Log::error($e->getTraceAsString());
|
||||
$recurrence->forceDelete();
|
||||
$message = sprintf('Could not create recurring transaction: %s', $e->getMessage());
|
||||
$this->errors->add('store', $message);
|
||||
|
@@ -27,13 +27,21 @@ namespace FireflyIII\Generator\Webhook;
|
||||
use FireflyIII\Enums\WebhookResponse;
|
||||
use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
use FireflyIII\Models\WebhookResponse as WebhookResponseModel;
|
||||
use FireflyIII\Models\WebhookTrigger as WebhookTriggerModel;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\BudgetEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\BudgetLimitEnrichment;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use FireflyIII\Transformers\BudgetLimitTransformer;
|
||||
use FireflyIII\Transformers\BudgetTransformer;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -74,7 +82,13 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
|
||||
private function getWebhooks(): Collection
|
||||
{
|
||||
return $this->user->webhooks()->where('active', true)->where('trigger', $this->trigger)->get(['webhooks.*']);
|
||||
return $this->user->webhooks()
|
||||
->leftJoin('webhook_webhook_trigger', 'webhook_webhook_trigger.webhook_id', 'webhooks.id')
|
||||
->leftJoin('webhook_triggers', 'webhook_webhook_trigger.webhook_trigger_id', 'webhook_triggers.id')
|
||||
->where('active', true)
|
||||
->whereIn('webhook_triggers.title', [$this->trigger->name, WebhookTrigger::ANY->name])
|
||||
->get(['webhooks.*'])
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,23 +123,25 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
*/
|
||||
private function generateMessage(Webhook $webhook, Model $model): void
|
||||
{
|
||||
$class = $model::class;
|
||||
$class = $model::class;
|
||||
// Line is ignored because all of Firefly III's Models have an id property.
|
||||
Log::debug(sprintf('Now in generateMessage(#%d, %s#%d)', $webhook->id, $class, $model->id));
|
||||
$uuid = Uuid::uuid4();
|
||||
|
||||
$uuid = Uuid::uuid4();
|
||||
$basicMessage = [
|
||||
/** @var WebhookResponseModel $response */
|
||||
$response = $webhook->webhookResponses()->first();
|
||||
$triggers = $this->getTriggerTitles($webhook->webhookTriggers()->get());
|
||||
$basicMessage = [
|
||||
'uuid' => $uuid->toString(),
|
||||
'user_id' => 0,
|
||||
'user_group_id' => 0,
|
||||
'trigger' => $webhook->trigger->name,
|
||||
'response' => WebhookResponse::from($webhook->response)->name,
|
||||
'trigger' => $this->trigger->name,
|
||||
'response' => $response->title, // guess that the database is correct.
|
||||
'url' => $webhook->url,
|
||||
'version' => sprintf('v%d', $this->getVersion()),
|
||||
'content' => [],
|
||||
];
|
||||
|
||||
// depends on the model how user_id is set:
|
||||
switch ($class) {
|
||||
default:
|
||||
// Line is ignored because all of Firefly III's Models have an id property.
|
||||
@@ -133,6 +149,21 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
|
||||
return;
|
||||
|
||||
case Budget::class:
|
||||
/** @var Budget $model */
|
||||
$basicMessage['user_id'] = $model->user_id;
|
||||
$basicMessage['user_group_id'] = $model->user_group_id;
|
||||
$relevantResponse = WebhookResponse::BUDGET->name;
|
||||
|
||||
break;
|
||||
|
||||
case BudgetLimit::class:
|
||||
$basicMessage['user_id'] = $model->budget->user_id;
|
||||
$basicMessage['user_group_id'] = $model->budget->user_group_id;
|
||||
$relevantResponse = WebhookResponse::BUDGET->name;
|
||||
|
||||
break;
|
||||
|
||||
case TransactionGroup::class:
|
||||
/** @var TransactionGroup $model */
|
||||
$basicMessage['user_id'] = $model->user_id;
|
||||
@@ -140,20 +171,46 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
|
||||
break;
|
||||
}
|
||||
$responseTitle = $this->getRelevantResponse($triggers, $response, $class);
|
||||
|
||||
// then depends on the response what to put in the message:
|
||||
switch ($webhook->response) {
|
||||
switch ($responseTitle) {
|
||||
default:
|
||||
Log::error(sprintf('The response code for webhook #%d is "%d" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response));
|
||||
Log::error(sprintf('The response code for webhook #%d is "%s" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response));
|
||||
|
||||
return;
|
||||
|
||||
case WebhookResponse::NONE->value:
|
||||
case WebhookResponse::BUDGET->name:
|
||||
$basicMessage['content'] = [];
|
||||
if ($model instanceof Budget) {
|
||||
$enrichment = new BudgetEnrichment();
|
||||
$enrichment->setUser($model->user);
|
||||
$model = $enrichment->enrichSingle($model);
|
||||
$transformer = new BudgetTransformer();
|
||||
$basicMessage['content'] = $transformer->transform($model);
|
||||
}
|
||||
if ($model instanceof BudgetLimit) {
|
||||
$user = $model->budget->user;
|
||||
$enrichment = new BudgetLimitEnrichment();
|
||||
$enrichment->setUser($user);
|
||||
|
||||
$parameters = new ParameterBag();
|
||||
$parameters->set('start', $model->start_date);
|
||||
$parameters->set('end', $model->end_date);
|
||||
|
||||
$model = $enrichment->enrichSingle($model);
|
||||
$transformer = new BudgetLimitTransformer();
|
||||
$transformer->setParameters($parameters);
|
||||
$basicMessage['content'] = $transformer->transform($model);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case WebhookResponse::NONE->name:
|
||||
$basicMessage['content'] = [];
|
||||
|
||||
break;
|
||||
|
||||
case WebhookResponse::TRANSACTIONS->value:
|
||||
case WebhookResponse::TRANSACTIONS->name:
|
||||
/** @var TransactionGroup $model */
|
||||
$transformer = new TransactionGroupTransformer();
|
||||
|
||||
@@ -170,7 +227,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
|
||||
break;
|
||||
|
||||
case WebhookResponse::ACCOUNTS->value:
|
||||
case WebhookResponse::ACCOUNTS->name:
|
||||
/** @var TransactionGroup $model */
|
||||
$accounts = $this->collectAccounts($model);
|
||||
$enrichment = new AccountEnrichment();
|
||||
@@ -237,4 +294,50 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
{
|
||||
$this->webhooks = $webhooks;
|
||||
}
|
||||
|
||||
private function getRelevantResponse(array $triggers, WebhookResponseModel $response, $class): string
|
||||
{
|
||||
// return none if none.
|
||||
if (WebhookResponse::NONE->name === $response->title) {
|
||||
Log::debug(sprintf('Return "%s" because requested nothing.', WebhookResponse::NONE->name));
|
||||
|
||||
return WebhookResponse::NONE->name;
|
||||
}
|
||||
|
||||
if (WebhookResponse::RELEVANT->name === $response->title) {
|
||||
Log::debug('Expected response is any relevant data.');
|
||||
|
||||
// depends on the $class
|
||||
switch ($class) {
|
||||
case TransactionGroup::class:
|
||||
Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::TRANSACTIONS->name, $class));
|
||||
|
||||
return WebhookResponse::TRANSACTIONS->name;
|
||||
|
||||
case Budget::class:
|
||||
case BudgetLimit::class:
|
||||
Log::debug(sprintf('Return "%s" because class is %s', WebhookResponse::BUDGET->name, $class));
|
||||
|
||||
return WebhookResponse::BUDGET->name;
|
||||
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot deal with "relevant" if the given object is a "%s"', $class));
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Return response again: %s', $response->title));
|
||||
|
||||
return $response->title;
|
||||
}
|
||||
|
||||
private function getTriggerTitles(Collection $collection): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/** @var WebhookTriggerModel $item */
|
||||
foreach ($collection as $item) {
|
||||
$return[] = $item->title;
|
||||
}
|
||||
|
||||
return array_unique($return);
|
||||
}
|
||||
}
|
||||
|
@@ -108,10 +108,10 @@ class BillEventHandler
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
|
||||
$bill = $event->bill;
|
||||
$bill = $event->bill;
|
||||
|
||||
/** @var bool $preference */
|
||||
Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data;
|
||||
$preference = Preferences::getForUser($bill->user, 'notification_bill_reminder', true)->data;
|
||||
|
||||
if (true === $preference) {
|
||||
Log::debug('Bill reminder is true!');
|
||||
|
@@ -55,7 +55,7 @@ class DestroyedGroupEventHandler
|
||||
$engine->setObjects(new Collection([$group]));
|
||||
$engine->setTrigger(WebhookTrigger::DESTROY_TRANSACTION);
|
||||
$engine->generateMessages();
|
||||
|
||||
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
|
||||
|
@@ -121,6 +121,7 @@ class StoredGroupEventHandler
|
||||
$engine->generateMessages();
|
||||
|
||||
// trigger event to send them:
|
||||
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
}
|
||||
|
@@ -167,6 +167,7 @@ class UpdatedGroupEventHandler
|
||||
$engine->setTrigger(WebhookTrigger::UPDATE_TRANSACTION);
|
||||
$engine->generateMessages();
|
||||
|
||||
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
|
||||
|
@@ -40,7 +40,7 @@ class WebhookEventHandler
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
if (false === config('firefly.feature_flags.webhooks') || false === config('firefly.allow_webhooks')) {
|
||||
Log::info('Webhook event handler is disabled, do not run sendWebhookMessages().');
|
||||
Log::debug('Webhook event handler is disabled, do not run sendWebhookMessages().');
|
||||
|
||||
return;
|
||||
}
|
||||
|
@@ -24,17 +24,37 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Observer;
|
||||
|
||||
use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Observers\RecalculatesAvailableBudgetsTrait;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BudgetLimitObserver
|
||||
{
|
||||
use RecalculatesAvailableBudgetsTrait;
|
||||
|
||||
public function created(BudgetLimit $budgetLimit): void
|
||||
{
|
||||
Log::debug('Observe "created" of a budget limit.');
|
||||
$this->updatePrimaryCurrencyAmount($budgetLimit);
|
||||
$this->updateAvailableBudget($budgetLimit);
|
||||
|
||||
$user = $budgetLimit->budget->user;
|
||||
|
||||
/** @var MessageGeneratorInterface $engine */
|
||||
$engine = app(MessageGeneratorInterface::class);
|
||||
$engine->setUser($user);
|
||||
$engine->setObjects(new Collection()->push($budgetLimit));
|
||||
$engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT);
|
||||
$engine->generateMessages();
|
||||
|
||||
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
|
||||
private function updatePrimaryCurrencyAmount(BudgetLimit $budgetLimit): void
|
||||
@@ -60,5 +80,18 @@ class BudgetLimitObserver
|
||||
{
|
||||
Log::debug('Observe "updated" of a budget limit.');
|
||||
$this->updatePrimaryCurrencyAmount($budgetLimit);
|
||||
$this->updateAvailableBudget($budgetLimit);
|
||||
|
||||
$user = $budgetLimit->budget->user;
|
||||
|
||||
/** @var MessageGeneratorInterface $engine */
|
||||
$engine = app(MessageGeneratorInterface::class);
|
||||
$engine->setUser($user);
|
||||
$engine->setObjects(new Collection()->push($budgetLimit));
|
||||
$engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT);
|
||||
$engine->generateMessages();
|
||||
|
||||
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
}
|
||||
|
@@ -23,19 +23,70 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Observer;
|
||||
|
||||
use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Events\RequestedSendWebhookMessages;
|
||||
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||
use FireflyIII\Support\Observers\RecalculatesAvailableBudgetsTrait;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BudgetObserver
|
||||
*/
|
||||
class BudgetObserver
|
||||
{
|
||||
use RecalculatesAvailableBudgetsTrait;
|
||||
|
||||
public function created(Budget $budget): void
|
||||
{
|
||||
Log::debug(sprintf('Observe "created" of budget #%d ("%s").', $budget->id, $budget->name));
|
||||
|
||||
// fire event.
|
||||
$user = $budget->user;
|
||||
|
||||
/** @var MessageGeneratorInterface $engine */
|
||||
$engine = app(MessageGeneratorInterface::class);
|
||||
$engine->setUser($user);
|
||||
$engine->setObjects(new Collection()->push($budget));
|
||||
$engine->setTrigger(WebhookTrigger::STORE_BUDGET);
|
||||
$engine->generateMessages();
|
||||
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
|
||||
public function updated(Budget $budget): void
|
||||
{
|
||||
Log::debug(sprintf('Observe "updated" of budget #%d ("%s").', $budget->id, $budget->name));
|
||||
$user = $budget->user;
|
||||
|
||||
/** @var MessageGeneratorInterface $engine */
|
||||
$engine = app(MessageGeneratorInterface::class);
|
||||
$engine->setUser($user);
|
||||
$engine->setObjects(new Collection()->push($budget));
|
||||
$engine->setTrigger(WebhookTrigger::UPDATE_BUDGET);
|
||||
$engine->generateMessages();
|
||||
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
|
||||
event(new RequestedSendWebhookMessages());
|
||||
}
|
||||
|
||||
public function deleting(Budget $budget): void
|
||||
{
|
||||
app('log')->debug('Observe "deleting" of a budget.');
|
||||
Log::debug('Observe "deleting" of a budget.');
|
||||
|
||||
$user = $budget->user;
|
||||
|
||||
/** @var MessageGeneratorInterface $engine */
|
||||
$engine = app(MessageGeneratorInterface::class);
|
||||
$engine->setUser($user);
|
||||
$engine->setObjects(new Collection()->push($budget));
|
||||
$engine->setTrigger(WebhookTrigger::DESTROY_BUDGET);
|
||||
$engine->generateMessages();
|
||||
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
|
||||
event(new RequestedSendWebhookMessages());
|
||||
|
||||
$repository = app(AttachmentRepositoryInterface::class);
|
||||
$repository->setUser($budget->user);
|
||||
@@ -49,7 +100,10 @@ class BudgetObserver
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
foreach ($budgetLimits as $budgetLimit) {
|
||||
// this loop exists so several events are fired.
|
||||
$budgetLimit->delete();
|
||||
$copy = clone $budgetLimit;
|
||||
$copy->id = 0;
|
||||
$this->updateAvailableBudget($copy);
|
||||
$budgetLimit->deleteQuietly(); // delete is quietly when in a loop.
|
||||
}
|
||||
|
||||
$budget->notes()->delete();
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Handlers\Observer;
|
||||
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class TransactionGroup
|
||||
@@ -32,7 +33,7 @@ class TransactionGroupObserver
|
||||
{
|
||||
public function deleting(TransactionGroup $transactionGroup): void
|
||||
{
|
||||
app('log')->debug('Observe "deleting" of a transaction group.');
|
||||
Log::debug('Observe "deleting" of a transaction group.');
|
||||
foreach ($transactionGroup->transactionJournals()->get() as $journal) {
|
||||
$journal->delete();
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@ namespace FireflyIII\Handlers\Observer;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class TransactionJournalObserver
|
||||
@@ -34,7 +35,7 @@ class TransactionJournalObserver
|
||||
{
|
||||
public function deleting(TransactionJournal $transactionJournal): void
|
||||
{
|
||||
app('log')->debug('Observe "deleting" of a transaction journal.');
|
||||
Log::debug('Observe "deleting" of a transaction journal.');
|
||||
|
||||
$repository = app(AttachmentRepositoryInterface::class);
|
||||
$repository->setUser($transactionJournal->user);
|
||||
|
@@ -72,7 +72,7 @@ class TransactionObserver
|
||||
}
|
||||
|
||||
$transaction->saveQuietly();
|
||||
Log::debug('Transaction primary currency amounts are updated.');
|
||||
Log::debug(sprintf('Transaction #%d primary currency amounts are updated.', $transaction->id));
|
||||
}
|
||||
|
||||
public function deleting(?Transaction $transaction): void
|
||||
|
@@ -851,7 +851,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
*/
|
||||
public function getPaginatedGroups(): LengthAwarePaginator
|
||||
{
|
||||
$set = $this->getGroups();
|
||||
$set = $this->getGroups();
|
||||
if (0 === $this->limit) {
|
||||
$this->setLimit(50);
|
||||
}
|
||||
@@ -861,8 +861,9 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
return new LengthAwarePaginator($set, $this->total, $total, 1);
|
||||
}
|
||||
$limit = $this->limit ?? 1;
|
||||
|
||||
return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page);
|
||||
return new LengthAwarePaginator($set, $this->total, $limit, $this->page);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -158,18 +158,8 @@ class ShowController extends Controller
|
||||
|
||||
Log::debug('End collect transactions');
|
||||
$timer->stop('collection');
|
||||
|
||||
// enrich data in arrays.
|
||||
|
||||
// enrich
|
||||
// $enrichment = new TransactionGroupEnrichment();
|
||||
// $enrichment->setUser(auth()->user());
|
||||
// $groups->setCollection($enrichment->enrich($groups->getCollection()));
|
||||
|
||||
|
||||
$groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
|
||||
$showAll = false;
|
||||
// correct
|
||||
$now = today()->endOfDay();
|
||||
if ($now->gt($end) || $now->lt($start)) {
|
||||
$now = $end;
|
||||
|
@@ -129,7 +129,6 @@ class IndexController extends Controller
|
||||
$spent = $spentArr[$this->primaryCurrency->id]['sum'] ?? '0';
|
||||
unset($spentArr);
|
||||
}
|
||||
|
||||
// number of days for consistent budgeting.
|
||||
$activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description.
|
||||
$activeDaysLeft = $this->activeDaysLeft($start, $end); // see method description.
|
||||
|
@@ -37,6 +37,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Http\Controllers\AugumentData;
|
||||
use FireflyIII\Support\Http\Controllers\ChartGeneration;
|
||||
use FireflyIII\Support\Http\Controllers\DateCalculation;
|
||||
@@ -504,6 +505,7 @@ class AccountController extends Controller
|
||||
Log::debug(sprintf('Step is %s', $step));
|
||||
$locale = Steam::getLocale();
|
||||
$return = [];
|
||||
$converter = new ExchangeRateConverter();
|
||||
|
||||
// fix for issue https://github.com/firefly-iii/firefly-iii/issues/8041
|
||||
// have to make sure this chart is always based on the balance at the END of the period.
|
||||
@@ -512,10 +514,10 @@ class AccountController extends Controller
|
||||
$current = app('navigation')->endOfX($current, $step, null);
|
||||
$format = (string)trans('config.month_and_day_js', [], $locale);
|
||||
$accountCurrency = $this->accountRepository->getAccountCurrency($account);
|
||||
|
||||
Log::debug('Get and filter balance for entire range start');
|
||||
$range = Steam::finalAccountBalanceInRange($account, $start, $end, $this->convertToPrimary);
|
||||
$range = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency);
|
||||
|
||||
Log::debug('Get and filter balance for entire range end');
|
||||
// temp, get end balance.
|
||||
Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
|
||||
Steam::finalAccountBalance($account, $end);
|
||||
@@ -552,7 +554,15 @@ class AccountController extends Controller
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $newRange[$expectedIndex]['date'])->endOfDay();
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('momentBalance is now %s', json_encode($momentBalance)));
|
||||
Log::debug(sprintf('momentBalance[%s] is now %s', $current->format('Y-m-d H:i:s'), json_encode($momentBalance)));
|
||||
|
||||
// check, perhaps recalculate the amount in currency X if the
|
||||
if ($accountCurrency->id !== $this->primaryCurrency->id && $this->convertToPrimary && array_key_exists($accountCurrency->code, $momentBalance)) {
|
||||
$converted = $converter->convert($accountCurrency, $this->primaryCurrency, $current, $momentBalance[$accountCurrency->code]);
|
||||
$momentBalance['pc_balance'] = $converted;
|
||||
}
|
||||
|
||||
|
||||
$return = $this->updateChartKeys($return, $momentBalance);
|
||||
$previous = $momentBalance;
|
||||
|
||||
|
@@ -105,7 +105,6 @@ class EditController extends Controller
|
||||
/** @var RecurrenceTransformer $transformer */
|
||||
$transformer = app(RecurrenceTransformer::class);
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
|
||||
$array = $transformer->transform($recurrence);
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgetRepos->getActiveBudgets());
|
||||
$bills = ExpandedForm::makeSelectListWithEmpty($this->billRepository->getActiveBills());
|
||||
|
@@ -28,8 +28,7 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\TriggerRecurrenceRequest;
|
||||
use FireflyIII\Jobs\CreateRecurringTransactions;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
@@ -58,20 +57,11 @@ class TriggerController extends Controller
|
||||
app('log')->debug('Done with recurrence.');
|
||||
|
||||
$groups = $job->getGroups();
|
||||
|
||||
/** @var TransactionGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($group->transactionJournals as $journal) {
|
||||
app('log')->debug(sprintf('Set date of journal #%d to today!', $journal->id));
|
||||
$journal->date = today(config('app.timezone'));
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
$this->repository->markGroupsAsNow($groups);
|
||||
$recurrence->latest_date = $backupDate;
|
||||
$recurrence->latest_date_tz = $backupDate?->format('e');
|
||||
$recurrence->save();
|
||||
app('preferences')->mark();
|
||||
Preferences::mark();
|
||||
|
||||
if (0 === $groups->count()) {
|
||||
$request->session()->flash('info', (string) trans('firefly.no_new_transaction_in_recurrence'));
|
||||
|
@@ -25,7 +25,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Rule;
|
||||
|
||||
use Throwable;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\SelectTransactionsRequest;
|
||||
@@ -74,20 +73,16 @@ class SelectController extends Controller
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$accounts = implode(',', $request->get('accounts'));
|
||||
$startDate = new Carbon($request->get('start'));
|
||||
$endDate = new Carbon($request->get('end'));
|
||||
|
||||
// create new rule engine:
|
||||
$newRuleEngine = app(RuleEngineInterface::class);
|
||||
$newRuleEngine->setUser($user);
|
||||
|
||||
// add extra operators:
|
||||
$newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]);
|
||||
$newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]);
|
||||
$newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]);
|
||||
|
||||
// set rules:
|
||||
$newRuleEngine->setRules(new Collection([$rule]));
|
||||
$newRuleEngine->setRules(new Collection()->push($rule));
|
||||
$newRuleEngine->fire();
|
||||
$resultCount = $newRuleEngine->getResults();
|
||||
|
||||
@@ -107,11 +102,9 @@ class SelectController extends Controller
|
||||
return redirect(route('rules.index'));
|
||||
}
|
||||
// does the user have shared accounts?
|
||||
$first = session('first', today(config('app.timezone'))->subYear())->format('Y-m-d');
|
||||
$today = today(config('app.timezone'))->format('Y-m-d');
|
||||
$subTitle = (string) trans('firefly.apply_rule_selection', ['title' => $rule->title]);
|
||||
|
||||
return view('rules.rule.select-transactions', compact('first', 'today', 'rule', 'subTitle'));
|
||||
return view('rules.rule.select-transactions', compact('rule', 'subTitle'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -25,11 +25,9 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\RuleGroup;
|
||||
|
||||
use Exception;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\SelectTransactionsRequest;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
@@ -42,8 +40,6 @@ use Illuminate\View\View;
|
||||
*/
|
||||
class ExecutionController extends Controller
|
||||
{
|
||||
private RuleGroupRepositoryInterface $ruleGroupRepository;
|
||||
|
||||
/**
|
||||
* ExecutionController constructor.
|
||||
*/
|
||||
@@ -56,7 +52,6 @@ class ExecutionController extends Controller
|
||||
app('view')->share('title', (string) trans('firefly.rules'));
|
||||
app('view')->share('mainTitleIcon', 'fa-random');
|
||||
|
||||
$this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@@ -74,15 +69,11 @@ class ExecutionController extends Controller
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
$accounts = implode(',', $request->get('accounts'));
|
||||
$startDate = new Carbon($request->get('start'));
|
||||
$endDate = new Carbon($request->get('end'));
|
||||
// create new rule engine:
|
||||
$newRuleEngine = app(RuleEngineInterface::class);
|
||||
$newRuleEngine->setUser($user);
|
||||
|
||||
// add extra operators:
|
||||
$newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]);
|
||||
$newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]);
|
||||
$newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]);
|
||||
|
||||
// set rules:
|
||||
@@ -104,10 +95,8 @@ class ExecutionController extends Controller
|
||||
*/
|
||||
public function selectTransactions(RuleGroup $ruleGroup)
|
||||
{
|
||||
$first = session('first')->format('Y-m-d');
|
||||
$today = today(config('app.timezone'))->format('Y-m-d');
|
||||
$subTitle = (string) trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]);
|
||||
|
||||
return view('rules.rule-group.select-transactions', compact('first', 'today', 'ruleGroup', 'subTitle'));
|
||||
return view('rules.rule-group.select-transactions', compact('ruleGroup', 'subTitle'));
|
||||
}
|
||||
}
|
||||
|
@@ -303,22 +303,22 @@ class ConvertController extends Controller
|
||||
private function convertJournal(TransactionJournal $journal, TransactionType $transactionType, array $data): TransactionJournal
|
||||
{
|
||||
/** @var AccountValidator $validator */
|
||||
$validator = app(AccountValidator::class);
|
||||
$validator = app(AccountValidator::class);
|
||||
$validator->setUser(auth()->user());
|
||||
$validator->setTransactionType($transactionType->type);
|
||||
|
||||
$sourceId = $data['source_id'][$journal->id] ?? null;
|
||||
$sourceName = $data['source_name'][$journal->id] ?? null;
|
||||
$destinationId = $data['destination_id'][$journal->id] ?? null;
|
||||
$destinationName = $data['destination_name'][$journal->id] ?? null;
|
||||
$sourceId = $data['source_id'][$journal->id] ?? null;
|
||||
$sourceName = $data['source_name'][$journal->id] ?? null;
|
||||
$destinationId = $data['destination_id'][$journal->id] ?? null;
|
||||
$destinationName = $data['destination_name'][$journal->id] ?? null;
|
||||
|
||||
// double check it's not an empty string.
|
||||
$sourceId = '' === $sourceId || null === $sourceId ? null : (int) $sourceId;
|
||||
$sourceName = '' === $sourceName ? null : (string) $sourceName;
|
||||
$destinationId = '' === $destinationId || null === $destinationId ? null : (int) $destinationId;
|
||||
$destinationName = '' === $destinationName ? null : (string) $destinationName;
|
||||
$validSource = $validator->validateSource(['id' => $sourceId, 'name' => $sourceName]);
|
||||
$validDestination = $validator->validateDestination(['id' => $destinationId, 'name' => $destinationName]);
|
||||
$sourceId = '' === $sourceId || null === $sourceId ? null : (int) $sourceId;
|
||||
$sourceName = '' === $sourceName ? null : (string) $sourceName;
|
||||
$destinationId = '' === $destinationId || null === $destinationId ? null : (int) $destinationId;
|
||||
$destinationName = '' === $destinationName ? null : (string) $destinationName;
|
||||
$validSource = $validator->validateSource(['id' => $sourceId, 'name' => $sourceName]);
|
||||
$validDestination = $validator->validateDestination(['id' => $destinationId, 'name' => $destinationName]);
|
||||
|
||||
if (false === $validSource) {
|
||||
throw new FireflyException(sprintf(trans('firefly.convert_invalid_source'), $journal->id));
|
||||
@@ -329,7 +329,7 @@ class ConvertController extends Controller
|
||||
|
||||
// TODO typeOverrule: the account validator may have another opinion on the transaction type.
|
||||
|
||||
$update = [
|
||||
$update = [
|
||||
'source_id' => $sourceId,
|
||||
'source_name' => $sourceName,
|
||||
'destination_id' => $destinationId,
|
||||
@@ -337,6 +337,9 @@ class ConvertController extends Controller
|
||||
'type' => $transactionType->type,
|
||||
];
|
||||
|
||||
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
$amount = $sourceTransaction?->amount ?? '0';
|
||||
|
||||
// also set the currency to the currency of the source account, in case you're converting a deposit into a transfer.
|
||||
if (TransactionTypeEnum::TRANSFER->value === $transactionType->type && TransactionTypeEnum::DEPOSIT->value === $journal->transactionType->type) {
|
||||
$source = $this->accountRepository->find((int) $sourceId);
|
||||
@@ -346,12 +349,25 @@ class ConvertController extends Controller
|
||||
if ($sourceCurrency instanceof TransactionCurrency && $destCurrency instanceof TransactionCurrency && $sourceCurrency->code !== $destCurrency->code) {
|
||||
$update['currency_id'] = $sourceCurrency->id;
|
||||
$update['foreign_currency_id'] = $destCurrency->id;
|
||||
$update['foreign_amount'] = '1'; // not the best solution but at this point the amount is hard to get.
|
||||
$update['foreign_amount'] = Steam::positive($amount); // not the best solution but at this point the amount is hard to get.
|
||||
}
|
||||
}
|
||||
|
||||
// same thing for converting a withdrawal into a transfer, but with the currency of the destination account.
|
||||
if (TransactionTypeEnum::TRANSFER->value === $transactionType->type && TransactionTypeEnum::WITHDRAWAL->value === $journal->transactionType->type) {
|
||||
$source = $this->accountRepository->find((int) $sourceId);
|
||||
$sourceCurrency = $this->accountRepository->getAccountCurrency($source);
|
||||
$dest = $this->accountRepository->find((int) $destinationId);
|
||||
$destCurrency = $this->accountRepository->getAccountCurrency($dest);
|
||||
if ($sourceCurrency instanceof TransactionCurrency && $destCurrency instanceof TransactionCurrency && $sourceCurrency->code !== $destCurrency->code) {
|
||||
$update['currency_id'] = $sourceCurrency->id;
|
||||
$update['foreign_currency_id'] = $destCurrency->id;
|
||||
$update['foreign_amount'] = Steam::positive($amount); // not the best solution but at this point the amount is hard to get.
|
||||
}
|
||||
}
|
||||
|
||||
/** @var JournalUpdateService $service */
|
||||
$service = app(JournalUpdateService::class);
|
||||
$service = app(JournalUpdateService::class);
|
||||
$service->setTransactionJournal($journal);
|
||||
$service->setData($update);
|
||||
$service->update();
|
||||
|
@@ -41,8 +41,6 @@ class SelectTransactionsRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17|before:end|required_with:end',
|
||||
'end' => 'required|date|after:1970-01-02|before:2038-01-17|after:start|required_with:start',
|
||||
'accounts' => 'required',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -37,6 +37,7 @@ use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class CreateAutoBudgetLimits
|
||||
@@ -59,7 +60,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
$newDate = clone $date;
|
||||
$newDate->startOfDay();
|
||||
$this->date = $newDate;
|
||||
app('log')->debug(sprintf('Created new CreateAutoBudgetLimits("%s")', $this->date->format('Y-m-d')));
|
||||
Log::debug(sprintf('Created new CreateAutoBudgetLimits("%s")', $this->date->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +71,9 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now at start of CreateAutoBudgetLimits() job for %s.', $this->date->format('D d M Y')));
|
||||
Log::debug(sprintf('Now at start of CreateAutoBudgetLimits() job for %s.', $this->date->format('D d M Y')));
|
||||
$autoBudgets = AutoBudget::get();
|
||||
app('log')->debug(sprintf('Found %d auto budgets.', $autoBudgets->count()));
|
||||
Log::debug(sprintf('Found %d auto budgets.', $autoBudgets->count()));
|
||||
foreach ($autoBudgets as $autoBudget) {
|
||||
$this->handleAutoBudget($autoBudget);
|
||||
}
|
||||
@@ -84,18 +85,18 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
private function handleAutoBudget(AutoBudget $autoBudget): void
|
||||
{
|
||||
if (null === $autoBudget->budget) {
|
||||
app('log')->info(sprintf('Auto budget #%d is associated with a deleted budget.', $autoBudget->id));
|
||||
Log::info(sprintf('Auto budget #%d is associated with a deleted budget.', $autoBudget->id));
|
||||
$autoBudget->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
if (false === $autoBudget->budget->active) {
|
||||
app('log')->info(sprintf('Auto budget #%d is associated with an inactive budget.', $autoBudget->id));
|
||||
Log::info(sprintf('Auto budget #%d is associated with an inactive budget.', $autoBudget->id));
|
||||
|
||||
return;
|
||||
}
|
||||
if (!$this->isMagicDay($autoBudget)) {
|
||||
app('log')->info(
|
||||
Log::info(
|
||||
sprintf(
|
||||
'Today (%s) is not a magic day for %s auto-budget #%d (part of budget #%d "%s")',
|
||||
$this->date->format('Y-m-d'),
|
||||
@@ -105,11 +106,11 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
$autoBudget->budget->name
|
||||
)
|
||||
);
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->info(
|
||||
Log::info(
|
||||
sprintf(
|
||||
'Today (%s) is a magic day for %s auto-budget #%d (part of budget #%d "%s")',
|
||||
$this->date->format('Y-m-d'),
|
||||
@@ -131,7 +132,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
// that's easy: create one.
|
||||
// do nothing else.
|
||||
$this->createBudgetLimit($autoBudget, $start, $end);
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -139,18 +140,18 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
if (!$budgetLimit instanceof BudgetLimit && AutoBudgetType::AUTO_BUDGET_ROLLOVER->value === (int) $autoBudget->auto_budget_type) {
|
||||
// budget limit exists already,
|
||||
$this->createRollover($autoBudget);
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
|
||||
return;
|
||||
}
|
||||
if (!$budgetLimit instanceof BudgetLimit && AutoBudgetType::AUTO_BUDGET_ADJUSTED->value === (int) $autoBudget->auto_budget_type) {
|
||||
// budget limit exists already,
|
||||
$this->createAdjustedLimit($autoBudget);
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,7 +194,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
|
||||
private function findBudgetLimit(Budget $budget, Carbon $start, Carbon $end): ?BudgetLimit
|
||||
{
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Going to find a budget limit for budget #%d ("%s") between %s and %s',
|
||||
$budget->id,
|
||||
@@ -212,21 +213,21 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
|
||||
private function createBudgetLimit(AutoBudget $autoBudget, Carbon $start, Carbon $end, ?string $amount = null): void
|
||||
{
|
||||
app('log')->debug(sprintf('No budget limit exist. Must create one for auto-budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('No budget limit exist. Must create one for auto-budget #%d', $autoBudget->id));
|
||||
if (null !== $amount) {
|
||||
app('log')->debug(sprintf('Amount is overruled and will be set to %s', $amount));
|
||||
Log::debug(sprintf('Amount is overruled and will be set to %s', $amount));
|
||||
}
|
||||
$budgetLimit = new BudgetLimit();
|
||||
$budgetLimit->budget()->associate($autoBudget->budget);
|
||||
$budgetLimit->transactionCurrency()->associate($autoBudget->transactionCurrency);
|
||||
$budgetLimit->start_date = $start;
|
||||
$budgetLimit->end_date = $end;
|
||||
$budgetLimit->start_date = clone $start;
|
||||
$budgetLimit->end_date = clone $end;
|
||||
$budgetLimit->amount = $amount ?? $autoBudget->amount;
|
||||
$budgetLimit->period = $autoBudget->period;
|
||||
$budgetLimit->generated = 1;
|
||||
$budgetLimit->save();
|
||||
|
||||
app('log')->debug(sprintf('Created budget limit #%d.', $budgetLimit->id));
|
||||
Log::debug(sprintf('Created budget limit #%d.', $budgetLimit->id));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,7 +235,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
*/
|
||||
private function createRollover(AutoBudget $autoBudget): void
|
||||
{
|
||||
app('log')->debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id));
|
||||
// current period:
|
||||
$start = app('navigation')->startOfPeriod($this->date, $autoBudget->period);
|
||||
$end = app('navigation')->endOfPeriod($start, $autoBudget->period);
|
||||
@@ -243,7 +244,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
$previousStart = app('navigation')->subtractPeriod($start, $autoBudget->period);
|
||||
$previousEnd = app('navigation')->endOfPeriod($previousStart, $autoBudget->period);
|
||||
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Current period is %s-%s, so previous period is %s-%s',
|
||||
$start->format('Y-m-d'),
|
||||
@@ -257,44 +258,44 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
$budgetLimit = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd);
|
||||
|
||||
if (!$budgetLimit instanceof BudgetLimit) {
|
||||
app('log')->debug('No budget limit exists in previous period, so create one.');
|
||||
Log::debug('No budget limit exists in previous period, so create one.');
|
||||
// if not, create it and we're done.
|
||||
$this->createBudgetLimit($autoBudget, $start, $end);
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->debug('Budget limit exists for previous period.');
|
||||
Log::debug('Budget limit exists for previous period.');
|
||||
// if has one, calculate expenses and use that as a base.
|
||||
$repository = app(OperationsRepositoryInterface::class);
|
||||
$repository->setUser($autoBudget->budget->user);
|
||||
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency);
|
||||
$currencyId = $autoBudget->transaction_currency_id;
|
||||
$spentAmount = $spent[$currencyId]['sum'] ?? '0';
|
||||
app('log')->debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
|
||||
Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
|
||||
|
||||
// if you spent more in previous budget period, than whatever you had previous budget period, the amount resets
|
||||
// previous budget limit + spent
|
||||
$budgetLeft = bcadd($budgetLimit->amount, $spentAmount);
|
||||
$totalAmount = $autoBudget->amount;
|
||||
app('log')->debug(sprintf('Total amount left for previous budget period is %s', $budgetLeft));
|
||||
Log::debug(sprintf('Total amount left for previous budget period is %s', $budgetLeft));
|
||||
|
||||
if (-1 !== bccomp('0', $budgetLeft)) {
|
||||
app('log')->info(sprintf('The amount left is negative, so it will be reset to %s.', $totalAmount));
|
||||
Log::info(sprintf('The amount left is negative, so it will be reset to %s.', $totalAmount));
|
||||
}
|
||||
if (1 !== bccomp('0', $budgetLeft)) {
|
||||
$totalAmount = bcadd($budgetLeft, $totalAmount);
|
||||
app('log')->info(sprintf('The amount left is positive, so the new amount will be %s.', $totalAmount));
|
||||
Log::info(sprintf('The amount left is positive, so the new amount will be %s.', $totalAmount));
|
||||
}
|
||||
|
||||
// create budget limit:
|
||||
$this->createBudgetLimit($autoBudget, $start, $end, $totalAmount);
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
}
|
||||
|
||||
private function createAdjustedLimit(AutoBudget $autoBudget): void
|
||||
{
|
||||
app('log')->debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Will now manage rollover for auto budget #%d', $autoBudget->id));
|
||||
// current period:
|
||||
$start = app('navigation')->startOfPeriod($this->date, $autoBudget->period);
|
||||
$end = app('navigation')->endOfPeriod($start, $autoBudget->period);
|
||||
@@ -303,7 +304,7 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
$previousStart = app('navigation')->subtractPeriod($start, $autoBudget->period);
|
||||
$previousEnd = app('navigation')->endOfPeriod($previousStart, $autoBudget->period);
|
||||
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Current period is %s-%s, so previous period is %s-%s',
|
||||
$start->format('Y-m-d'),
|
||||
@@ -317,13 +318,13 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
$budgetLimit = $this->findBudgetLimit($autoBudget->budget, $previousStart, $previousEnd);
|
||||
|
||||
if (!$budgetLimit instanceof BudgetLimit) {
|
||||
app('log')->debug('No budget limit exists in previous period, so create one.');
|
||||
Log::debug('No budget limit exists in previous period, so create one.');
|
||||
// if not, create standard amount, and we're done.
|
||||
$this->createBudgetLimit($autoBudget, $start, $end);
|
||||
|
||||
return;
|
||||
}
|
||||
app('log')->debug('Budget limit exists for previous period.');
|
||||
Log::debug('Budget limit exists for previous period.');
|
||||
|
||||
// if has one, calculate expenses and use that as a base.
|
||||
$repository = app(OperationsRepositoryInterface::class);
|
||||
@@ -331,31 +332,31 @@ class CreateAutoBudgetLimits implements ShouldQueue
|
||||
$spent = $repository->sumExpenses($previousStart, $previousEnd, null, new Collection([$autoBudget->budget]), $autoBudget->transactionCurrency);
|
||||
$currencyId = $autoBudget->transaction_currency_id;
|
||||
$spentAmount = $spent[$currencyId]['sum'] ?? '0';
|
||||
app('log')->debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
|
||||
Log::debug(sprintf('Spent in previous budget period (%s-%s) is %s', $previousStart->format('Y-m-d'), $previousEnd->format('Y-m-d'), $spentAmount));
|
||||
|
||||
// what you spent in previous period PLUS the amount for the current period,
|
||||
// if that is more than zero, that's the amount that will be set.
|
||||
|
||||
$budgetAvailable = bcadd(bcadd($budgetLimit->amount, $autoBudget->amount), $spentAmount);
|
||||
$totalAmount = $autoBudget->amount;
|
||||
app('log')->debug(sprintf('Total amount available for current budget period is %s', $budgetAvailable));
|
||||
Log::debug(sprintf('Total amount available for current budget period is %s', $budgetAvailable));
|
||||
|
||||
if (-1 !== bccomp($budgetAvailable, $totalAmount)) {
|
||||
app('log')->info(sprintf('There is no overspending, no need to adjust. Budget limit amount will be %s.', $budgetAvailable));
|
||||
Log::info(sprintf('There is no overspending, no need to adjust. Budget limit amount will be %s.', $budgetAvailable));
|
||||
// create budget limit:
|
||||
$this->createBudgetLimit($autoBudget, $start, $end, $budgetAvailable);
|
||||
}
|
||||
if (1 !== bccomp($budgetAvailable, $totalAmount) && 1 === bccomp($budgetAvailable, '0')) {
|
||||
app('log')->info(sprintf('There was overspending, so the new amount will be %s.', $budgetAvailable));
|
||||
Log::info(sprintf('There was overspending, so the new amount will be %s.', $budgetAvailable));
|
||||
// create budget limit:
|
||||
$this->createBudgetLimit($autoBudget, $start, $end, $budgetAvailable);
|
||||
}
|
||||
if (1 !== bccomp($budgetAvailable, $totalAmount) && -1 === bccomp($budgetAvailable, '0')) {
|
||||
app('log')->info('There was overspending, but so much even this period cant fix that. Reset it to 1.');
|
||||
Log::info('There was overspending, but so much even this period cant fix that. Reset it to 1.');
|
||||
// create budget limit:
|
||||
$this->createBudgetLimit($autoBudget, $start, $end, '1');
|
||||
}
|
||||
app('log')->debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
Log::debug(sprintf('Done with auto budget #%d', $autoBudget->id));
|
||||
}
|
||||
|
||||
public function setDate(Carbon $date): void
|
||||
|
@@ -24,9 +24,6 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use FireflyIII\Casts\SeparateTimezoneCaster;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -37,12 +34,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
class BudgetLimit extends Model
|
||||
{
|
||||
use ReturnsIntegerIdTrait;
|
||||
protected $dispatchesEvents
|
||||
= [
|
||||
'created' => Created::class,
|
||||
'updated' => Updated::class,
|
||||
'deleted' => Deleted::class,
|
||||
];
|
||||
|
||||
protected $fillable = ['budget_id', 'start_date', 'end_date', 'start_date_tz', 'end_date_tz', 'amount', 'transaction_currency_id', 'native_amount'];
|
||||
|
||||
|
@@ -24,14 +24,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use FireflyIII\Enums\WebhookDelivery;
|
||||
use FireflyIII\Enums\WebhookResponse;
|
||||
use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Enums\WebhookDelivery as WebhookDeliveryEnum;
|
||||
use FireflyIII\Enums\WebhookResponse as WebhookResponseEnum;
|
||||
use FireflyIII\Enums\WebhookTrigger as WebhookTriggerEnum;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerUserIdTrait;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
@@ -56,7 +57,7 @@ class Webhook extends Model
|
||||
public static function getDeliveries(): array
|
||||
{
|
||||
$array = [];
|
||||
$set = WebhookDelivery::cases();
|
||||
$set = WebhookDeliveryEnum::cases();
|
||||
foreach ($set as $item) {
|
||||
$array[$item->value] = $item->name;
|
||||
}
|
||||
@@ -67,7 +68,7 @@ class Webhook extends Model
|
||||
public static function getDeliveriesForValidation(): array
|
||||
{
|
||||
$array = [];
|
||||
$set = WebhookDelivery::cases();
|
||||
$set = WebhookDeliveryEnum::cases();
|
||||
foreach ($set as $item) {
|
||||
$array[$item->name] = $item->value;
|
||||
$array[$item->value] = $item->value;
|
||||
@@ -79,7 +80,7 @@ class Webhook extends Model
|
||||
public static function getResponses(): array
|
||||
{
|
||||
$array = [];
|
||||
$set = WebhookResponse::cases();
|
||||
$set = WebhookResponseEnum::cases();
|
||||
foreach ($set as $item) {
|
||||
$array[$item->value] = $item->name;
|
||||
}
|
||||
@@ -90,7 +91,7 @@ class Webhook extends Model
|
||||
public static function getResponsesForValidation(): array
|
||||
{
|
||||
$array = [];
|
||||
$set = WebhookResponse::cases();
|
||||
$set = WebhookResponseEnum::cases();
|
||||
foreach ($set as $item) {
|
||||
$array[$item->name] = $item->value;
|
||||
$array[$item->value] = $item->value;
|
||||
@@ -102,7 +103,7 @@ class Webhook extends Model
|
||||
public static function getTriggers(): array
|
||||
{
|
||||
$array = [];
|
||||
$set = WebhookTrigger::cases();
|
||||
$set = WebhookTriggerEnum::cases();
|
||||
foreach ($set as $item) {
|
||||
$array[$item->value] = $item->name;
|
||||
}
|
||||
@@ -113,7 +114,7 @@ class Webhook extends Model
|
||||
public static function getTriggersForValidation(): array
|
||||
{
|
||||
$array = [];
|
||||
$set = WebhookTrigger::cases();
|
||||
$set = WebhookTriggerEnum::cases();
|
||||
foreach ($set as $item) {
|
||||
$array[$item->name] = $item->value;
|
||||
$array[$item->value] = $item->value;
|
||||
@@ -130,7 +131,7 @@ class Webhook extends Model
|
||||
public static function routeBinder(string $value): self
|
||||
{
|
||||
if (auth()->check()) {
|
||||
$webhookId = (int) $value;
|
||||
$webhookId = (int)$value;
|
||||
|
||||
/** @var User $user */
|
||||
$user = auth()->user();
|
||||
@@ -155,6 +156,21 @@ class Webhook extends Model
|
||||
return $this->hasMany(WebhookMessage::class);
|
||||
}
|
||||
|
||||
public function webhookDeliveries(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(WebhookDelivery::class);
|
||||
}
|
||||
|
||||
public function webhookResponses(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(WebhookResponse::class);
|
||||
}
|
||||
|
||||
public function webhookTriggers(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(WebhookTrigger::class);
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
|
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* Deleted.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
* WebhookDelivery.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,18 +23,25 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Class Deleted
|
||||
*/
|
||||
class Deleted extends Event
|
||||
class WebhookDelivery extends Model
|
||||
{
|
||||
use SerializesModels;
|
||||
use ReturnsIntegerIdTrait;
|
||||
|
||||
public function __construct(public BudgetLimit $budgetLimit) {}
|
||||
/**
|
||||
* Get the ID
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ShortMethodName")
|
||||
*/
|
||||
protected function key(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => (int) $value,
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* Updated.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
* WebhookResponse.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,18 +23,25 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Class Updated
|
||||
*/
|
||||
class Updated extends Event
|
||||
class WebhookResponse extends Model
|
||||
{
|
||||
use SerializesModels;
|
||||
use ReturnsIntegerIdTrait;
|
||||
|
||||
public function __construct(public BudgetLimit $budgetLimit) {}
|
||||
/**
|
||||
* Get the ID
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ShortMethodName")
|
||||
*/
|
||||
protected function key(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => (int) $value,
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* Created.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
* WebhookTrigger.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,18 +23,25 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Events\Model\BudgetLimit;
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use FireflyIII\Events\Event;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* Class Created
|
||||
*/
|
||||
class Created extends Event
|
||||
class WebhookTrigger extends Model
|
||||
{
|
||||
use SerializesModels;
|
||||
use ReturnsIntegerIdTrait;
|
||||
|
||||
public function __construct(public BudgetLimit $budgetLimit) {}
|
||||
/**
|
||||
* Get the ID
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ShortMethodName")
|
||||
*/
|
||||
protected function key(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: static fn ($value) => (int) $value,
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* SubscriptionsOverdueReminder.php
|
||||
* Copyright (c) 2025 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\Notifications\User;
|
||||
|
@@ -29,9 +29,6 @@ use FireflyIII\Events\DestroyedTransactionGroup;
|
||||
use FireflyIII\Events\DetectedNewIPAddress;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutBill;
|
||||
use FireflyIII\Events\Model\Bill\WarnUserAboutOverdueSubscriptions;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
|
||||
use FireflyIII\Events\Model\PiggyBank\ChangedName;
|
||||
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
|
||||
@@ -219,17 +216,6 @@ class EventServiceProvider extends ServiceProvider
|
||||
'FireflyIII\Handlers\Events\Model\PiggyBankEventHandler@changedPiggyBankName',
|
||||
],
|
||||
|
||||
// budget related events: CRUD budget limit
|
||||
Created::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@created',
|
||||
],
|
||||
Updated::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@updated',
|
||||
],
|
||||
Deleted::class => [
|
||||
'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@deleted',
|
||||
],
|
||||
|
||||
// rule actions
|
||||
RuleActionFailedOnArray::class => [
|
||||
'FireflyIII\Handlers\Events\Model\RuleHandler@ruleActionFailedOnArray',
|
||||
|
@@ -268,7 +268,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
|
||||
*/
|
||||
foreach ($budgets as $index => $budget) {
|
||||
$budget->order = $index + 1;
|
||||
$budget->save();
|
||||
$budget->saveQuietly();
|
||||
}
|
||||
// other budgets, set to 0.
|
||||
$this->user->budgets()->where('active', 0)->update(['order' => 0]);
|
||||
|
@@ -35,6 +35,7 @@ use FireflyIII\Models\RecurrenceMeta;
|
||||
use FireflyIII\Models\RecurrenceRepetition;
|
||||
use FireflyIII\Models\RecurrenceTransaction;
|
||||
use FireflyIII\Models\RecurrenceTransactionMeta;
|
||||
use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalMeta;
|
||||
use FireflyIII\Services\Internal\Destroy\RecurrenceDestroyService;
|
||||
@@ -582,4 +583,17 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
|
||||
|
||||
return $service->update($recurrence, $data);
|
||||
}
|
||||
|
||||
public function markGroupsAsNow(Collection $groups): void
|
||||
{
|
||||
/** @var TransactionGroup $group */
|
||||
foreach ($groups as $group) {
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($group->transactionJournals as $journal) {
|
||||
Log::debug(sprintf('Set date of journal #%d to today!', $journal->id));
|
||||
$journal->date = now(config('app.timezone'));
|
||||
$journal->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -139,6 +139,8 @@ interface RecurringRepositoryInterface
|
||||
*/
|
||||
public function getXOccurrencesSince(RecurrenceRepetition $repetition, Carbon $date, Carbon $afterDate, int $count): array;
|
||||
|
||||
public function markGroupsAsNow(Collection $groups): void;
|
||||
|
||||
/**
|
||||
* Parse the repetition in a string that is user readable.
|
||||
*/
|
||||
|
@@ -24,9 +24,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Webhook;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Models\WebhookAttempt;
|
||||
use FireflyIII\Models\WebhookDelivery;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
use FireflyIII\Models\WebhookResponse;
|
||||
use FireflyIII\Models\WebhookTrigger;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -41,11 +45,20 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac
|
||||
|
||||
public function all(): Collection
|
||||
{
|
||||
return $this->user->webhooks()->get();
|
||||
return $this->user->webhooks()
|
||||
// only get upgraded webhooks
|
||||
->where('delivery', 1)
|
||||
->where('response', 1)
|
||||
->where('trigger', 1)
|
||||
->get()
|
||||
;
|
||||
}
|
||||
|
||||
public function destroy(Webhook $webhook): void
|
||||
{
|
||||
// force delete all messages and attempts:
|
||||
$webhook->webhookMessages()->delete();
|
||||
|
||||
$webhook->delete();
|
||||
}
|
||||
|
||||
@@ -87,38 +100,108 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac
|
||||
|
||||
public function store(array $data): Webhook
|
||||
{
|
||||
$secret = Str::random(24);
|
||||
$fullData = [
|
||||
$secret = Str::random(24);
|
||||
$fullData = [
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->user->user_group_id,
|
||||
'active' => $data['active'] ?? false,
|
||||
'title' => $data['title'] ?? null,
|
||||
'trigger' => $data['trigger'],
|
||||
'response' => $data['response'],
|
||||
'delivery' => $data['delivery'],
|
||||
// 'trigger' => $data['trigger'],
|
||||
// 'response' => $data['response'],
|
||||
// 'delivery' => $data['delivery'],
|
||||
'trigger' => 1,
|
||||
'response' => 1,
|
||||
'delivery' => 1,
|
||||
'secret' => $secret,
|
||||
'url' => $data['url'],
|
||||
];
|
||||
|
||||
return Webhook::create($fullData);
|
||||
/** @var Webhook $webhook */
|
||||
$webhook = Webhook::create($fullData);
|
||||
$triggers = new Collection();
|
||||
$responses = new Collection();
|
||||
$deliveries = new Collection();
|
||||
|
||||
foreach ($data['triggers'] as $trigger) {
|
||||
// get the relevant ID:
|
||||
$object = WebhookTrigger::where('title', $trigger)->first();
|
||||
if (null === $object) {
|
||||
throw new FireflyException(sprintf('Could not find webhook trigger with title "%s".', $trigger));
|
||||
}
|
||||
$triggers->push($object);
|
||||
}
|
||||
$webhook->webhookTriggers()->saveMany($triggers);
|
||||
|
||||
foreach ($data['responses'] as $response) {
|
||||
// get the relevant ID:
|
||||
$object = WebhookResponse::where('title', $response)->first();
|
||||
if (null === $object) {
|
||||
throw new FireflyException(sprintf('Could not find webhook response with title "%s".', $response));
|
||||
}
|
||||
$responses->push($object);
|
||||
}
|
||||
$webhook->webhookResponses()->saveMany($responses);
|
||||
|
||||
foreach ($data['deliveries'] as $delivery) {
|
||||
// get the relevant ID:
|
||||
$object = WebhookDelivery::where('title', $delivery)->first();
|
||||
if (null === $object) {
|
||||
throw new FireflyException(sprintf('Could not find webhook delivery with title "%s".', $delivery));
|
||||
}
|
||||
$deliveries->push($object);
|
||||
}
|
||||
$webhook->webhookDeliveries()->saveMany($deliveries);
|
||||
|
||||
return $webhook;
|
||||
}
|
||||
|
||||
public function update(Webhook $webhook, array $data): Webhook
|
||||
{
|
||||
$webhook->active = $data['active'] ?? $webhook->active;
|
||||
$webhook->trigger = $data['trigger'] ?? $webhook->trigger;
|
||||
$webhook->response = $data['response'] ?? $webhook->response;
|
||||
$webhook->delivery = $data['delivery'] ?? $webhook->delivery;
|
||||
$webhook->title = $data['title'] ?? $webhook->title;
|
||||
$webhook->url = $data['url'] ?? $webhook->url;
|
||||
$webhook->active = $data['active'] ?? $webhook->active;
|
||||
$webhook->title = $data['title'] ?? $webhook->title;
|
||||
$webhook->url = $data['url'] ?? $webhook->url;
|
||||
|
||||
if (true === $data['secret']) {
|
||||
if (array_key_exists('secret', $data) && true === $data['secret']) {
|
||||
$secret = Str::random(24);
|
||||
$webhook->secret = $secret;
|
||||
}
|
||||
|
||||
$webhook->save();
|
||||
|
||||
$triggers = new Collection();
|
||||
$responses = new Collection();
|
||||
$deliveries = new Collection();
|
||||
|
||||
foreach ($data['triggers'] as $trigger) {
|
||||
// get the relevant ID:
|
||||
$object = WebhookTrigger::where('title', $trigger)->first();
|
||||
if (null === $object) {
|
||||
throw new FireflyException(sprintf('Could not find webhook trigger with title "%s".', $trigger));
|
||||
}
|
||||
$triggers->push($object);
|
||||
}
|
||||
$webhook->webhookTriggers()->sync($triggers);
|
||||
|
||||
foreach ($data['responses'] as $response) {
|
||||
// get the relevant ID:
|
||||
$object = WebhookResponse::where('title', $response)->first();
|
||||
if (null === $object) {
|
||||
throw new FireflyException(sprintf('Could not find webhook response with title "%s".', $response));
|
||||
}
|
||||
$responses->push($object);
|
||||
}
|
||||
$webhook->webhookResponses()->sync($responses);
|
||||
|
||||
foreach ($data['deliveries'] as $delivery) {
|
||||
// get the relevant ID:
|
||||
$object = WebhookDelivery::where('title', $delivery)->first();
|
||||
if (null === $object) {
|
||||
throw new FireflyException(sprintf('Could not find webhook delivery with title "%s".', $delivery));
|
||||
}
|
||||
$deliveries->push($object);
|
||||
}
|
||||
$webhook->webhookDeliveries()->sync($deliveries);
|
||||
|
||||
return $webhook;
|
||||
}
|
||||
}
|
||||
|
@@ -52,6 +52,7 @@ class EitherConfigKey
|
||||
'firefly.languages',
|
||||
'app.timezone',
|
||||
'firefly.valid_view_ranges',
|
||||
'firefly.preselected_accounts',
|
||||
|
||||
// triggers and actions:
|
||||
'firefly.rule-actions',
|
||||
|
@@ -103,7 +103,7 @@ class ExchangeRateConverter
|
||||
|
||||
// find in cache
|
||||
if (null !== $res) {
|
||||
Log::debug(sprintf('ExchangeRateConverter: Return cached rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d')));
|
||||
Log::debug(sprintf('ExchangeRateConverter: Return cached rate (%s) from %s to %s on %s.', $res, $from->code, $to->code, $date->format('Y-m-d')));
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
@@ -67,8 +67,12 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
private UserGroup $userGroup;
|
||||
private array $lastActivities = [];
|
||||
private ?Carbon $date = null;
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private bool $convertToPrimary;
|
||||
private array $balances = [];
|
||||
private array $startBalances = [];
|
||||
private array $endBalances = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
|
||||
@@ -234,9 +238,9 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
private function appendCollectedData(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (Account $item) {
|
||||
$id = (int)$item->id;
|
||||
$item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null;
|
||||
$meta = [
|
||||
$id = (int)$item->id;
|
||||
$item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null;
|
||||
$meta = [
|
||||
'currency' => null,
|
||||
'location' => [
|
||||
'latitude' => null,
|
||||
@@ -249,14 +253,14 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
'opening_balance_date' => null,
|
||||
'opening_balance_amount' => null,
|
||||
'account_number' => null,
|
||||
'notes' => $notes[$id] ?? null,
|
||||
'notes' => $this->notes[$id] ?? null,
|
||||
'last_activity' => $this->lastActivities[$id] ?? null,
|
||||
];
|
||||
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_id'] = (string)$this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
@@ -283,43 +287,47 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
|
||||
// add balances
|
||||
// get currencies:
|
||||
$currency = $this->primaryCurrency; // assume primary currency
|
||||
$currency = $this->primaryCurrency; // assume primary currency
|
||||
if (null !== $meta['currency']) {
|
||||
$currency = $meta['currency'];
|
||||
}
|
||||
|
||||
// get the current balance:
|
||||
$date = $this->getDate();
|
||||
$date = $this->getDate();
|
||||
// $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$finalBalance = $this->balances[$id];
|
||||
$finalBalance = $this->balances[$id];
|
||||
$balanceDifference = $this->getBalanceDifference($id, $currency);
|
||||
Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance);
|
||||
|
||||
// collect current balances:
|
||||
$currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
|
||||
$openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places);
|
||||
$virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places);
|
||||
$debtAmount = $meta['current_debt'] ?? null;
|
||||
$currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
|
||||
$openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places);
|
||||
$virtualBalance = Steam::bcround($account->virtual_balance ?? '0', $currency->decimal_places);
|
||||
$debtAmount = $meta['current_debt'] ?? null;
|
||||
|
||||
// set some pc_ default values to NULL:
|
||||
$pcCurrentBalance = null;
|
||||
$pcOpeningBalance = null;
|
||||
$pcVirtualBalance = null;
|
||||
$pcDebtAmount = null;
|
||||
$pcCurrentBalance = null;
|
||||
$pcOpeningBalance = null;
|
||||
$pcVirtualBalance = null;
|
||||
$pcDebtAmount = null;
|
||||
$pcBalanceDifference = null;
|
||||
|
||||
// convert to primary currency if needed:
|
||||
if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) {
|
||||
Log::debug(sprintf('Convert to primary, from %s to %s', $currency->code, $this->primaryCurrency->code));
|
||||
$converter = new ExchangeRateConverter();
|
||||
$pcCurrentBalance = $converter->convert($currency, $this->primaryCurrency, $date, $currentBalance);
|
||||
$pcOpeningBalance = $converter->convert($currency, $this->primaryCurrency, $date, $openingBalance);
|
||||
$pcVirtualBalance = $converter->convert($currency, $this->primaryCurrency, $date, $virtualBalance);
|
||||
$pcDebtAmount = null === $debtAmount ? null : $converter->convert($currency, $this->primaryCurrency, $date, $debtAmount);
|
||||
$converter = new ExchangeRateConverter();
|
||||
$pcCurrentBalance = $converter->convert($currency, $this->primaryCurrency, $date, $currentBalance);
|
||||
$pcOpeningBalance = $converter->convert($currency, $this->primaryCurrency, $date, $openingBalance);
|
||||
$pcVirtualBalance = $converter->convert($currency, $this->primaryCurrency, $date, $virtualBalance);
|
||||
$pcBalanceDifference = null === $balanceDifference ? null : $converter->convert($currency, $this->primaryCurrency, $date, $balanceDifference);
|
||||
$pcDebtAmount = null === $debtAmount ? null : $converter->convert($currency, $this->primaryCurrency, $date, $debtAmount);
|
||||
}
|
||||
if ($this->convertToPrimary && $currency->id === $this->primaryCurrency->id) {
|
||||
$pcCurrentBalance = $currentBalance;
|
||||
$pcOpeningBalance = $openingBalance;
|
||||
$pcVirtualBalance = $virtualBalance;
|
||||
$pcDebtAmount = $debtAmount;
|
||||
$pcCurrentBalance = $currentBalance;
|
||||
$pcOpeningBalance = $openingBalance;
|
||||
$pcVirtualBalance = $virtualBalance;
|
||||
$pcBalanceDifference = $balanceDifference;
|
||||
$pcDebtAmount = $debtAmount;
|
||||
}
|
||||
|
||||
// set opening balance(s) to NULL if the date is null
|
||||
@@ -327,18 +335,21 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
$openingBalance = null;
|
||||
$pcOpeningBalance = null;
|
||||
}
|
||||
$meta['balances'] = [
|
||||
'current_balance' => $currentBalance,
|
||||
'pc_current_balance' => $pcCurrentBalance,
|
||||
'opening_balance' => $openingBalance,
|
||||
'pc_opening_balance' => $pcOpeningBalance,
|
||||
'virtual_balance' => $virtualBalance,
|
||||
'pc_virtual_balance' => $pcVirtualBalance,
|
||||
'debt_amount' => $debtAmount,
|
||||
'pc_debt_amount' => $pcDebtAmount,
|
||||
$meta['current_balance_date'] = $this->getDate();
|
||||
$meta['balances'] = [
|
||||
'current_balance' => $currentBalance,
|
||||
'pc_current_balance' => $pcCurrentBalance,
|
||||
'opening_balance' => $openingBalance,
|
||||
'pc_opening_balance' => $pcOpeningBalance,
|
||||
'virtual_balance' => $virtualBalance,
|
||||
'pc_virtual_balance' => $pcVirtualBalance,
|
||||
'debt_amount' => $debtAmount,
|
||||
'pc_debt_amount' => $pcDebtAmount,
|
||||
'balance_difference' => $balanceDifference,
|
||||
'pc_balance_difference' => $pcBalanceDifference,
|
||||
];
|
||||
// end add balances
|
||||
$item->meta = $meta;
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
@@ -352,6 +363,10 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
private function collectBalances(): void
|
||||
{
|
||||
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
|
||||
if (null !== $this->start && null !== $this->end) {
|
||||
$this->startBalances = Steam::accountsBalancesOptimized($this->collection, $this->start, $this->primaryCurrency, $this->convertToPrimary);
|
||||
$this->endBalances = Steam::accountsBalancesOptimized($this->collection, $this->end, $this->primaryCurrency, $this->convertToPrimary);
|
||||
}
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
@@ -378,15 +393,45 @@ class AccountEnrichment implements EnrichmentInterface
|
||||
|
||||
public function setDate(?Carbon $date): void
|
||||
{
|
||||
if (null !== $date) {
|
||||
$date->endOfDay();
|
||||
Log::debug(sprintf('Date is now %s', $date->toW3cString()));
|
||||
}
|
||||
$this->date = $date;
|
||||
}
|
||||
|
||||
public function getDate(): Carbon
|
||||
{
|
||||
if (null === $this->date) {
|
||||
return today();
|
||||
return now();
|
||||
}
|
||||
|
||||
return $this->date;
|
||||
}
|
||||
|
||||
public function setStart(?Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setEnd(?Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
private function getBalanceDifference(int $id, TransactionCurrency $currency): ?string
|
||||
{
|
||||
if (null === $this->start || null === $this->end) {
|
||||
return null;
|
||||
}
|
||||
$startBalance = $this->startBalances[$id] ?? [];
|
||||
$endBalance = $this->endBalances[$id] ?? [];
|
||||
if (0 === count($startBalance) || 0 === count($endBalance)) {
|
||||
return null;
|
||||
}
|
||||
$start = $startBalance[$currency->code] ?? '0';
|
||||
$end = $endBalance[$currency->code] ?? '0';
|
||||
|
||||
return bcsub($end, $start);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* BudgetEnrichment.php
|
||||
* Copyright (c) 2025 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\Enrichments;
|
||||
@@ -117,7 +138,7 @@ class BudgetEnrichment implements EnrichmentInterface
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_id'] = (string) $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* BudgetLimitEnrichment.php
|
||||
* Copyright (c) 2025 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\Enrichments;
|
||||
@@ -47,6 +68,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
$this->collectCurrencies();
|
||||
$this->collectNotes();
|
||||
$this->collectBudgets();
|
||||
$this->stringifyIds();
|
||||
$this->appendCollectedData();
|
||||
|
||||
return $this->collection;
|
||||
@@ -134,6 +156,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
foreach ($this->collection as $budgetLimit) {
|
||||
$id = (int)$budgetLimit->id;
|
||||
$filteredExpenses = $this->filterToBudget($expenses, $budgetLimit->budget_id);
|
||||
$filteredExpenses = $repository->sumCollectedExpenses($expenses, $budgetLimit->start_date, $budgetLimit->end_date, $budgetLimit->transactionCurrency, false);
|
||||
$this->expenses[$id] = array_values($filteredExpenses);
|
||||
|
||||
@@ -155,4 +178,30 @@ class BudgetLimitEnrichment implements EnrichmentInterface
|
||||
$this->currencies[(int)$currency->id] = $currency;
|
||||
}
|
||||
}
|
||||
|
||||
private function stringifyIds(): void
|
||||
{
|
||||
$this->expenses = array_map(function ($first) {
|
||||
return array_map(function ($second) {
|
||||
$second['currency_id'] = (string)($second['currency_id'] ?? 0);
|
||||
|
||||
return $second;
|
||||
}, $first);
|
||||
}, $this->expenses);
|
||||
|
||||
$this->pcExpenses = array_map(function ($first) {
|
||||
return array_map(function ($second) {
|
||||
$second['currency_id'] = (string)($second['currency_id'] ?? 0);
|
||||
|
||||
return $second;
|
||||
}, $first);
|
||||
}, $this->expenses);
|
||||
}
|
||||
|
||||
private function filterToBudget(array $expenses, int $budget): array
|
||||
{
|
||||
return array_filter($expenses, function (array $item) use ($budget) {
|
||||
return (int)$item['budget_id'] === $budget;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* CategoryEnrichment.php
|
||||
* Copyright (c) 2025 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\Enrichments;
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* PiggyBankEnrichment.php
|
||||
* Copyright (c) 2025 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\Enrichments;
|
||||
@@ -105,7 +126,7 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
'pc_current_amount' => '0',
|
||||
];
|
||||
}
|
||||
$this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], $item->current_amount);
|
||||
$this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], (string) $item->current_amount);
|
||||
if (null !== $this->amounts[$id][$accountId]['pc_current_amount'] && null !== $item->native_current_amount) {
|
||||
$this->amounts[$id][$accountId]['pc_current_amount'] = bcadd($this->amounts[$id][$accountId]['pc_current_amount'], $item->native_current_amount);
|
||||
}
|
||||
@@ -170,7 +191,7 @@ class PiggyBankEnrichment implements EnrichmentInterface
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_id'] = (string) $this->objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
|
||||
}
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* PiggyBankEventEnrichment.php
|
||||
* Copyright (c) 2025 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\Enrichments;
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* RecurringEnrichment.php
|
||||
* Copyright (c) 2025 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\Enrichments;
|
||||
@@ -38,7 +59,7 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
private Collection $collection;
|
||||
private array $ids = [];
|
||||
private array $transactionTypeIds = [];
|
||||
private array $transactionTypes = [];
|
||||
// private array $transactionTypes = [];
|
||||
private array $notes = [];
|
||||
private array $repetitions = [];
|
||||
private array $transactions = [];
|
||||
@@ -107,14 +128,14 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
$this->ids[] = $id;
|
||||
$this->transactionTypeIds[$id] = $typeId;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
$this->ids = array_unique($this->ids);
|
||||
|
||||
// collect transaction types.
|
||||
$transactionTypes = TransactionType::whereIn('id', array_unique($this->transactionTypeIds))->get();
|
||||
foreach ($transactionTypes as $transactionType) {
|
||||
$id = (int)$transactionType->id;
|
||||
$this->transactionTypes[$id] = TransactionTypeEnum::from($transactionType->type);
|
||||
}
|
||||
// $transactionTypes = TransactionType::whereIn('id', array_unique($this->transactionTypeIds))->get();
|
||||
// foreach ($transactionTypes as $transactionType) {
|
||||
// $id = (int)$transactionType->id;
|
||||
// $this->transactionTypes[$id] = TransactionTypeEnum::from($transactionType->type);
|
||||
// }
|
||||
}
|
||||
|
||||
private function collectRepetitions(): void
|
||||
@@ -129,7 +150,7 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
$recurrence = $this->collection->filter(function (Recurrence $item) use ($repetition) {
|
||||
return (int)$item->id === (int)$repetition->recurrence_id;
|
||||
})->first();
|
||||
$fromDate = $recurrence->latest_date ?? $recurrence->first_date;
|
||||
$fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date);
|
||||
$id = (int)$repetition->recurrence_id;
|
||||
$repId = (int)$repetition->id;
|
||||
$this->repetitions[$id] ??= [];
|
||||
@@ -137,6 +158,7 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
// get the (future) occurrences for this specific type of repetition:
|
||||
$amount = 'daily' === $repetition->repetition_type ? 9 : 5;
|
||||
$set = $repository->getXOccurrencesSince($repetition, $fromDate, now(config('app.timezone')), $amount);
|
||||
$occurrences = [];
|
||||
|
||||
/** @var Carbon $carbon */
|
||||
foreach ($set as $carbon) {
|
||||
@@ -378,7 +400,7 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
private function collectTransactionMetaData(): void
|
||||
{
|
||||
$ids = array_keys($this->transactions);
|
||||
$meta = RecurrenceTransactionMeta::whereIn('rt_id', $ids)->get();
|
||||
$meta = RecurrenceTransactionMeta::whereNull('deleted_at')->whereIn('rt_id', $ids)->get();
|
||||
// other meta-data to be collected:
|
||||
$billIds = [];
|
||||
$piggyBankIds = [];
|
||||
@@ -388,8 +410,15 @@ class RecurringEnrichment implements EnrichmentInterface
|
||||
foreach ($meta as $entry) {
|
||||
$id = (int)$entry->id;
|
||||
$transactionId = (int)$entry->rt_id;
|
||||
$recurrenceId = $this->recurrenceIds[$transactionId];
|
||||
$name = (string)$entry->name;
|
||||
|
||||
// this should refer to another array, were rtIds can be used to find the recurrence.
|
||||
$recurrenceId = $this->recurrenceIds[$transactionId] ?? 0;
|
||||
$name = (string)$entry->name ?? '';
|
||||
if (0 === $recurrenceId) {
|
||||
Log::error(sprintf('Could not find recurrence ID for recurrence transaction ID %d', $transactionId));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($name) {
|
||||
default:
|
||||
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* SubscriptionEnrichment.php
|
||||
* Copyright (c) 2025 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\Enrichments;
|
||||
@@ -101,7 +122,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $objectGroups[$key]['id'];
|
||||
$meta['object_group_id'] = (string) $objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $objectGroups[$key]['order'];
|
||||
}
|
||||
@@ -362,7 +383,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
|
||||
Log::debug(sprintf('[b] Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('[c] Last paid date is: "%s"', $return?->format('Y-m-d')));
|
||||
// Log::debug(sprintf('[c] Last paid date is: "%s"', $return?->format('Y-m-d')));
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
@@ -143,9 +143,9 @@ class TransactionGroupEnrichment implements EnrichmentInterface
|
||||
continue;
|
||||
}
|
||||
if (in_array($name, $this->dateFields, true)) {
|
||||
Log::debug(sprintf('Meta data for "%s" is a date : "%s"', $name, $data));
|
||||
// Log::debug(sprintf('Meta data for "%s" is a date : "%s"', $name, $data));
|
||||
$this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data, config('app.timezone'));
|
||||
Log::debug(sprintf('Meta data for "%s" converts to: "%s"', $name, $this->metaData[$entry['transaction_journal_id']][$name]->toW3CString()));
|
||||
// Log::debug(sprintf('Meta data for "%s" converts to: "%s"', $name, $this->metaData[$entry['transaction_journal_id']][$name]->toW3CString()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
164
app/Support/JsonApi/Enrichments/WebhookEnrichment.php
Normal file
164
app/Support/JsonApi/Enrichments/WebhookEnrichment.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* WebhookEnrichment.php
|
||||
* Copyright (c) 2025 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\Enrichments;
|
||||
|
||||
use FireflyIII\Enums\WebhookDelivery as WebhookDeliveryEnum;
|
||||
use FireflyIII\Enums\WebhookResponse as WebhookResponseEnum;
|
||||
use FireflyIII\Enums\WebhookTrigger as WebhookTriggerEnum;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Models\WebhookDelivery;
|
||||
use FireflyIII\Models\WebhookResponse;
|
||||
use FireflyIII\Models\WebhookTrigger;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use stdClass;
|
||||
|
||||
class WebhookEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private Collection $collection;
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private array $ids = [];
|
||||
private array $deliveries = [];
|
||||
private array $responses = [];
|
||||
private array $triggers = [];
|
||||
|
||||
private array $webhookDeliveries = [];
|
||||
private array $webhookResponses = [];
|
||||
private array $webhookTriggers = [];
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
$this->collection = $collection;
|
||||
if ($this->collection->count() > 0) {
|
||||
$this->collectIds();
|
||||
$this->collectInfo();
|
||||
$this->collectWebhookInfo();
|
||||
$this->appendCollectedInfo();
|
||||
}
|
||||
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
public function enrichSingle(array|Model $model): array|Model
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection([$model]);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
private function collectIds(): void
|
||||
{
|
||||
/** @var Webhook $webhook */
|
||||
foreach ($this->collection as $webhook) {
|
||||
$this->ids[] = $webhook->id;
|
||||
}
|
||||
$this->ids = array_unique($this->ids);
|
||||
}
|
||||
|
||||
private function collectInfo(): void
|
||||
{
|
||||
$all = WebhookDelivery::get();
|
||||
|
||||
/** @var WebhookDelivery $item */
|
||||
foreach ($all as $item) {
|
||||
$this->deliveries[$item->id] = $item->key;
|
||||
}
|
||||
$all = WebhookResponse::get();
|
||||
|
||||
/** @var WebhookResponse $item */
|
||||
foreach ($all as $item) {
|
||||
$this->responses[$item->id] = $item->key;
|
||||
}
|
||||
$all = WebhookTrigger::get();
|
||||
|
||||
/** @var WebhookTrigger $item */
|
||||
foreach ($all as $item) {
|
||||
$this->triggers[$item->id] = $item->key;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function collectWebhookInfo(): void
|
||||
{
|
||||
$set = DB::table('webhook_webhook_delivery')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_delivery_id']);
|
||||
|
||||
/** @var stdClass $item */
|
||||
foreach ($set as $item) {
|
||||
$id = $item->webhook_id;
|
||||
$deliveryId = $item->webhook_delivery_id;
|
||||
$this->webhookDeliveries[$id][] = WebhookDeliveryEnum::from($this->deliveries[$deliveryId])->name;
|
||||
}
|
||||
|
||||
$set = DB::table('webhook_webhook_response')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_response_id']);
|
||||
|
||||
/** @var stdClass $item */
|
||||
foreach ($set as $item) {
|
||||
$id = $item->webhook_id;
|
||||
$responseId = $item->webhook_response_id;
|
||||
$this->webhookResponses[$id][] = WebhookResponseEnum::from($this->responses[$responseId])->name;
|
||||
}
|
||||
|
||||
$set = DB::table('webhook_webhook_trigger')->whereIn('webhook_id', $this->ids)->get(['webhook_id', 'webhook_trigger_id']);
|
||||
|
||||
/** @var stdClass $item */
|
||||
foreach ($set as $item) {
|
||||
$id = $item->webhook_id;
|
||||
$triggerId = $item->webhook_trigger_id;
|
||||
$this->webhookTriggers[$id][] = WebhookTriggerEnum::from($this->triggers[$triggerId])->name;
|
||||
}
|
||||
}
|
||||
|
||||
private function appendCollectedInfo(): void
|
||||
{
|
||||
$this->collection = $this->collection->map(function (Webhook $item) {
|
||||
$meta = [
|
||||
'deliveries' => $this->webhookDeliveries[$item->id] ?? [],
|
||||
'responses' => $this->webhookResponses[$item->id] ?? [],
|
||||
'triggers' => $this->webhookTriggers[$item->id] ?? [],
|
||||
];
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
}
|
||||
}
|
@@ -1,8 +1,9 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* BudgetLimitHandler.php
|
||||
* Copyright (c) 2023 james@firefly-iii.org
|
||||
* RecalculatesAvailableBudgetsTrait.php
|
||||
* Copyright (c) 2025 james@firefly-iii.org
|
||||
*
|
||||
* This file is part of Firefly III (https://github.com/firefly-iii).
|
||||
*
|
||||
@@ -22,11 +23,8 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events\Model;
|
||||
namespace FireflyIII\Support\Observers;
|
||||
|
||||
use FireflyIII\Events\Model\BudgetLimit\Created;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Deleted;
|
||||
use FireflyIII\Events\Model\BudgetLimit\Updated;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
@@ -39,17 +37,8 @@ use Spatie\Period\Boundaries;
|
||||
use Spatie\Period\Period;
|
||||
use Spatie\Period\Precision;
|
||||
|
||||
/**
|
||||
* Class BudgetLimitHandler
|
||||
*/
|
||||
class BudgetLimitHandler
|
||||
trait RecalculatesAvailableBudgetsTrait
|
||||
{
|
||||
public function created(Created $event): void
|
||||
{
|
||||
Log::debug(sprintf('BudgetLimitHandler::created(#%s)', $event->budgetLimit->id));
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
private function updateAvailableBudget(BudgetLimit $budgetLimit): void
|
||||
{
|
||||
Log::debug(sprintf('Now in updateAvailableBudget(limit #%d)', $budgetLimit->id));
|
||||
@@ -241,18 +230,4 @@ class BudgetLimitHandler
|
||||
|
||||
return $amount;
|
||||
}
|
||||
|
||||
public function deleted(Deleted $event): void
|
||||
{
|
||||
Log::debug(sprintf('BudgetLimitHandler::deleted(#%s)', $event->budgetLimit->id));
|
||||
$budgetLimit = $event->budgetLimit;
|
||||
$budgetLimit->id = 0;
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
|
||||
public function updated(Updated $event): void
|
||||
{
|
||||
Log::debug(sprintf('BudgetLimitHandler::updated(#%s)', $event->budgetLimit->id));
|
||||
$this->updateAvailableBudget($event->budgetLimit);
|
||||
}
|
||||
}
|
@@ -255,7 +255,7 @@ trait ConvertsDataTypes
|
||||
if (10 === strlen((string) $value)) {
|
||||
// probably a date format.
|
||||
try {
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $value);
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $value, config('app.timezone'));
|
||||
} catch (InvalidDateException $e) { // @phpstan-ignore-line
|
||||
Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage()));
|
||||
|
||||
@@ -276,7 +276,7 @@ trait ConvertsDataTypes
|
||||
|
||||
// is an atom string, I hope?
|
||||
try {
|
||||
$carbon = Carbon::parse($value);
|
||||
$carbon = Carbon::parse($value, $value, config('app.timezone'));
|
||||
} catch (InvalidDateException $e) { // @phpstan-ignore-line
|
||||
Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage()));
|
||||
|
||||
|
95
app/Support/Request/ValidatesWebhooks.php
Normal file
95
app/Support/Request/ValidatesWebhooks.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* ValidatesWebhooks.php
|
||||
* Copyright (c) 2025 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\Request;
|
||||
|
||||
use FireflyIII\Enums\WebhookTrigger;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use Illuminate\Contracts\Validation\Validator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
trait ValidatesWebhooks
|
||||
{
|
||||
public function withValidator(Validator $validator): void
|
||||
{
|
||||
$validator->after(
|
||||
function (Validator $validator): void {
|
||||
Log::debug('Validating webhook');
|
||||
if ($validator->failed()) {
|
||||
return;
|
||||
}
|
||||
$data = $validator->getData();
|
||||
$triggers = $data['triggers'] ?? [];
|
||||
$responses = $data['responses'] ?? [];
|
||||
|
||||
if (0 === count($triggers) || 0 === count($responses)) {
|
||||
Log::debug('No trigger or response, return.');
|
||||
|
||||
return;
|
||||
}
|
||||
$validTriggers = array_values(Webhook::getTriggers());
|
||||
$validResponses = array_values(Webhook::getResponses());
|
||||
$containsAny = false;
|
||||
$count = 0;
|
||||
foreach ($triggers as $trigger) {
|
||||
if (!in_array($trigger, $validTriggers, true)) {
|
||||
return;
|
||||
}
|
||||
++$count;
|
||||
if ($trigger === WebhookTrigger::ANY->name) {
|
||||
$containsAny = true;
|
||||
}
|
||||
}
|
||||
if ($containsAny && $count > 1) {
|
||||
$validator->errors()->add('triggers.0', trans('validation.only_any_trigger'));
|
||||
|
||||
return;
|
||||
}
|
||||
foreach ($responses as $response) {
|
||||
if (!in_array($response, $validResponses, true)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// some combinations are illegal.
|
||||
foreach ($triggers as $i => $trigger) {
|
||||
$forbidden = config(sprintf('webhooks.forbidden_responses.%s', $trigger));
|
||||
if (null === $forbidden) {
|
||||
$validator->errors()->add(sprintf('triggers.%d', $i), trans('validation.unknown_webhook_trigger', ['trigger' => $trigger]));
|
||||
|
||||
continue;
|
||||
}
|
||||
foreach ($responses as $ii => $response) {
|
||||
if (in_array($response, $forbidden, true)) {
|
||||
Log::debug(sprintf('Trigger %s and response %s are forbidden.', $trigger, $response));
|
||||
$validator->errors()->add(sprintf('responses.%d', $ii), trans('validation.bad_webhook_combination', ['trigger' => $trigger, 'response' => $response]));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,5 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
/*
|
||||
* PreferencesSingleton.php
|
||||
* Copyright (c) 2025 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\Singleton;
|
||||
|
@@ -224,6 +224,7 @@ class Steam
|
||||
*/
|
||||
$request = clone $start;
|
||||
$request->subDay()->endOfDay();
|
||||
Log::debug('Get first balance to start.');
|
||||
Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
|
||||
$startBalance = $this->finalAccountBalance($account, $request);
|
||||
$primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
|
||||
@@ -315,7 +316,7 @@ class Steam
|
||||
Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance);
|
||||
}
|
||||
$cache->store($balances);
|
||||
Log::debug('End of method');
|
||||
Log::debug('End of method finalAccountBalanceInRange');
|
||||
|
||||
return $balances;
|
||||
}
|
||||
@@ -356,8 +357,10 @@ class Steam
|
||||
continue;
|
||||
}
|
||||
$accountSum = array_values($accountSum)[0];
|
||||
$sumOfAmount = (string)$accountSum['sum_of_amount'];
|
||||
$sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
|
||||
$sumsByCode = [
|
||||
$accountSum['code'] => $accountSum['sum_of_amount'],
|
||||
$accountSum['code'] => $sumOfAmount,
|
||||
];
|
||||
|
||||
// Log::debug('All balances are (joined)', $others);
|
||||
|
@@ -66,22 +66,18 @@ class AccountTransformer extends AbstractTransformer
|
||||
}
|
||||
|
||||
// get account type:
|
||||
$accountType = (string) config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = (string) config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
|
||||
$accountType = (string)config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = (string)config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
|
||||
$liabilityDirection = $account->meta['liability_direction'] ?? null;
|
||||
$accountRole = $this->getAccountRole($account, $accountType);
|
||||
$hasCurrencySettings = null !== $account->meta['currency'];
|
||||
$includeNetWorth = 1 === (int) ($account->meta['include_net_worth'] ?? 0);
|
||||
$includeNetWorth = 1 === (int)($account->meta['include_net_worth'] ?? 0);
|
||||
$longitude = $account->meta['location']['longitude'] ?? null;
|
||||
$latitude = $account->meta['location']['latitude'] ?? null;
|
||||
$zoomLevel = $account->meta['location']['zoom_level'] ?? null;
|
||||
$order = $account->order;
|
||||
|
||||
// date (for balance etc.)
|
||||
$date = $this->getDate();
|
||||
$date->endOfDay();
|
||||
|
||||
// get primary currency as fallback:
|
||||
$currency = $this->primary; // assume primary currency
|
||||
if ($hasCurrencySettings) {
|
||||
@@ -99,7 +95,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
[$interest, $interestPeriod] = $this->getInterest($account, $accountType);
|
||||
|
||||
return [
|
||||
'id' => (string) $account->id,
|
||||
'id' => (string)$account->id,
|
||||
'created_at' => $account->created_at->toAtomString(),
|
||||
'updated_at' => $account->updated_at->toAtomString(),
|
||||
'active' => $account->active,
|
||||
@@ -116,13 +112,13 @@ class AccountTransformer extends AbstractTransformer
|
||||
'object_has_currency_setting' => $hasCurrencySettings,
|
||||
|
||||
// currency is object specific or primary, already determined above.
|
||||
'currency_id' => (string) $currency['id'],
|
||||
'currency_id' => (string)$currency['id'],
|
||||
'currency_name' => $currency['name'],
|
||||
'currency_code' => $currency['code'],
|
||||
'currency_symbol' => $currency['symbol'],
|
||||
'currency_decimal_places' => $currency['decimal_places'],
|
||||
|
||||
'primary_currency_id' => (string) $this->primary->id,
|
||||
'primary_currency_id' => (string)$this->primary->id,
|
||||
'primary_currency_name' => $this->primary->name,
|
||||
'primary_currency_code' => $this->primary->code,
|
||||
'primary_currency_symbol' => $this->primary->symbol,
|
||||
@@ -141,7 +137,10 @@ class AccountTransformer extends AbstractTransformer
|
||||
'debt_amount' => $account->meta['balances']['debt_amount'],
|
||||
'pc_debt_amount' => $account->meta['balances']['pc_debt_amount'],
|
||||
|
||||
'current_balance_date' => $date->toAtomString(),
|
||||
'balance_difference' => $account->meta['balances']['balance_difference'],
|
||||
'pc_balance_difference' => $account->meta['balances']['pc_balance_difference'],
|
||||
|
||||
'current_balance_date' => $account->meta['current_balance_date']->toAtomString(),
|
||||
'notes' => $account->meta['notes'] ?? null,
|
||||
'monthly_payment_date' => $monthlyPaymentDate,
|
||||
'credit_card_type' => $creditCardType,
|
||||
@@ -170,7 +169,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
private function getAccountRole(Account $account, string $accountType): ?string
|
||||
{
|
||||
$accountRole = $account->meta['account_role'] ?? null;
|
||||
if ('asset' !== $accountType || '' === (string) $accountRole) {
|
||||
if ('asset' !== $accountType || '' === (string)$accountRole) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -206,7 +205,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
}
|
||||
$monthlyPaymentDate = $object->toAtomString();
|
||||
}
|
||||
if (10 !== strlen((string) $monthlyPaymentDate)) {
|
||||
if (10 !== strlen((string)$monthlyPaymentDate)) {
|
||||
$monthlyPaymentDate = Carbon::parse($monthlyPaymentDate, config('app.timezone'))->toAtomString();
|
||||
}
|
||||
}
|
||||
|
@@ -28,6 +28,7 @@ use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\BudgetEnrichment;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
/**
|
||||
@@ -55,7 +56,15 @@ class BudgetLimitTransformer extends AbstractTransformer
|
||||
*/
|
||||
public function includeBudget(BudgetLimit $limit)
|
||||
{
|
||||
return $this->item($limit->budget, new BudgetTransformer(), 'budgets');
|
||||
// enrich budget
|
||||
$budget = $limit->budget;
|
||||
$enrichment = new BudgetEnrichment();
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$enrichment->setUser($budget->user);
|
||||
$budget = $enrichment->enrichSingle($budget);
|
||||
|
||||
return $this->item($budget, new BudgetTransformer(), 'budgets');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,7 +100,7 @@ class BudgetLimitTransformer extends AbstractTransformer
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
|
||||
'primary_currency_id' => (int)$this->primaryCurrency->id,
|
||||
'primary_currency_id' => (string) $this->primaryCurrency->id,
|
||||
'primary_currency_name' => $this->primaryCurrency->name,
|
||||
'primary_currency_code' => $this->primaryCurrency->code,
|
||||
'primary_currency_symbol' => $this->primaryCurrency->symbol,
|
||||
|
@@ -51,9 +51,12 @@ class WebhookTransformer extends AbstractTransformer
|
||||
'active' => $webhook->active,
|
||||
'title' => $webhook->title,
|
||||
'secret' => $webhook->secret,
|
||||
'trigger' => $this->getEnum('trigger', $webhook->trigger),
|
||||
'response' => $this->getEnum('response', $webhook->response),
|
||||
'delivery' => $this->getEnum('delivery', $webhook->delivery),
|
||||
'triggers' => $webhook->meta['triggers'],
|
||||
'deliveries' => $webhook->meta['deliveries'],
|
||||
'responses' => $webhook->meta['responses'],
|
||||
// 'trigger' => $this->getEnum('trigger', $webhook->trigger),
|
||||
// 'response' => $this->getEnum('response', $webhook->response),
|
||||
// 'delivery' => $this->getEnum('delivery', $webhook->delivery),
|
||||
'url' => $webhook->url,
|
||||
'links' => [
|
||||
[
|
||||
|
57
changelog.md
57
changelog.md
@@ -3,7 +3,62 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 6.3.0 - 2025-08-xx
|
||||
## 6.4.0 - 2025-09-01
|
||||
|
||||
### Added
|
||||
|
||||
- [Issue 5532](https://github.com/firefly-iii/firefly-iii/issues/5532) (Asset prices and exchange rates) reported by @svozniuk
|
||||
- [Discussion 10725](https://github.com/orgs/firefly-iii/discussions/10725) (New webhook triggers) started by @Billos. See the [documentation](https://docs.firefly-iii.org/how-to/firefly-iii/features/webhooks/).
|
||||
|
||||
### Changed
|
||||
|
||||
- Initial release.
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Issue 10790](https://github.com/firefly-iii/firefly-iii/issues/10790) (Undefined variable $occurrences) reported by @senna1992
|
||||
- [Issue 10791](https://github.com/firefly-iii/firefly-iii/issues/10791) (Clone and edit a transaction with a different currency doesn't clear the foreign transaction amount) reported by @jxtxzzw
|
||||
- [Issue 10794](https://github.com/firefly-iii/firefly-iii/issues/10794) (Error with recurring transaction) reported by @MaximSN
|
||||
- [Issue 10799](https://github.com/firefly-iii/firefly-iii/issues/10799) (Budget - "Left (per day)" not showing the correct value) reported by @GensHaze
|
||||
- [Issue 10802](https://github.com/firefly-iii/firefly-iii/issues/10802) (Crash when trying to update a budget limit) reported by @Billos
|
||||
- [Issue 10803](https://github.com/firefly-iii/firefly-iii/issues/10803) (Issue in /v1/budget-limits spent attribute) reported by @Billos
|
||||
- [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro
|
||||
- [Issue 10808](https://github.com/firefly-iii/firefly-iii/issues/10808) (cron job Error: Undefined variable $preference) reported by @MexerSam
|
||||
- [Issue 10813](https://github.com/firefly-iii/firefly-iii/issues/10813) (Error "Argument #2 ($symbol) must be of type string" while try open subscriptions section) reported by @mrResident
|
||||
- [Issue 10819](https://github.com/firefly-iii/firefly-iii/issues/10819) (Internal Server Error when trying to open piggy banks) reported by @noantiq
|
||||
- [Issue 10820](https://github.com/firefly-iii/firefly-iii/issues/10820) (Unable to search date 1970-01-01 to apply rule.) reported by @Kage1
|
||||
- [Issue 10824](https://github.com/firefly-iii/firefly-iii/issues/10824) (Converting withdrawal to transfer to account in different currency doesn't allow setting correct currencies) reported by @avee87
|
||||
|
||||
### API
|
||||
|
||||
- [Issue 8345](https://github.com/firefly-iii/firefly-iii/issues/8345) (API: Distinguish spent & earned at `/v2/chart/category/dashboard` (or future `v2/categories`)) reported by @dreautall
|
||||
- [Issue 10804](https://github.com/firefly-iii/firefly-iii/issues/10804) (No notes information included in the "List all accounts" API call) reported by @gpampuro
|
||||
- [Issue 10806](https://github.com/firefly-iii/firefly-iii/issues/10806) (API: `/v1/chart/balance/balance` has undocumented `period` parameter) reported by @dreautall
|
||||
- [Issue 10807](https://github.com/firefly-iii/firefly-iii/issues/10807) (API: `/v1/bills` field `object_group_id` returns int, should be string) reported by @dreautall
|
||||
- [Issue 10815](https://github.com/firefly-iii/firefly-iii/issues/10815) (API: `/v1/accounts` balance is off by a day) reported by @dreautall
|
||||
- [Issue 10827](https://github.com/firefly-iii/firefly-iii/issues/10827) (Trigger Recurrence by API) reported by @MexerSam
|
||||
|
||||
## 6.3.2 - 2025-08-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Discussion 10768](https://github.com/orgs/firefly-iii/discussions/10768) (Argument #1 ($start) must be of type Carbon\Carbon, null given) started by @tangodance
|
||||
- [Issue 10771](https://github.com/firefly-iii/firefly-iii/issues/10771) (/v1/budgets/{id}/limits seems broken) reported by @Sceptorrh
|
||||
- [Issue 10773](https://github.com/firefly-iii/firefly-iii/issues/10773) (API: Wrong Return types) reported by @dreautall
|
||||
- [Issue 10775](https://github.com/firefly-iii/firefly-iii/issues/10775) (API: /v1/chart/account/overview broken) reported by @dreautall
|
||||
- [Issue 10782](https://github.com/firefly-iii/firefly-iii/issues/10782) ([error'] /accounts/[asset,expense,revenue]) reported by @vkanev
|
||||
|
||||
## 6.3.1 - 2025-08-19
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Discussion 10768](https://github.com/orgs/firefly-iii/discussions/10768) (Argument #1 ($start) must be of type Carbon\Carbon, null given) started by @tangodance
|
||||
- [Issue 10771](https://github.com/firefly-iii/firefly-iii/issues/10771) (/v1/budgets/{id}/limits seems broken) reported by @Sceptorrh
|
||||
- [Issue 10773](https://github.com/firefly-iii/firefly-iii/issues/10773) (API: Wrong Return types) reported by @dreautall
|
||||
- [Issue 10775](https://github.com/firefly-iii/firefly-iii/issues/10775) (API: /v1/chart/account/overview broken) reported by @dreautall
|
||||
- [Issue 10782](https://github.com/firefly-iii/firefly-iii/issues/10782) ([error'] /accounts/[asset,expense,revenue]) reported by @vkanev
|
||||
|
||||
## 6.3.0 - 2025-08-17
|
||||
|
||||
> [!WARNING]
|
||||
> Firefly III v6.3.0 introduces a lot of API changes that deal with multi-currency support. Make sure your beloved apps are updated to support this.
|
||||
|
492
composer.lock
generated
492
composer.lock
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user