Compare commits

..

54 Commits

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

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

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

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

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

View File

@@ -160,16 +160,16 @@
},
{
"name": "composer/xdebug-handler",
"version": "3.0.4",
"version": "3.0.5",
"source": {
"type": "git",
"url": "https://github.com/composer/xdebug-handler.git",
"reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255"
"reference": "6c1925561632e83d60a44492e0b344cf48ab85ef"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
"reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255",
"url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef",
"reference": "6c1925561632e83d60a44492e0b344cf48ab85ef",
"shasum": ""
},
"require": {
@@ -206,7 +206,7 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/xdebug-handler/issues",
"source": "https://github.com/composer/xdebug-handler/tree/3.0.4"
"source": "https://github.com/composer/xdebug-handler/tree/3.0.5"
},
"funding": [
{
@@ -222,20 +222,20 @@
"type": "tidelift"
}
],
"time": "2024-03-26T18:29:49+00:00"
"time": "2024-05-06T16:37:16+00:00"
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.54.0",
"version": "v3.56.1",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "2aecbc8640d7906c38777b3dcab6f4ca79004d08"
"reference": "69c6168ae8bc96dc656c7f6c7271120a68ae5903"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2aecbc8640d7906c38777b3dcab6f4ca79004d08",
"reference": "2aecbc8640d7906c38777b3dcab6f4ca79004d08",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/69c6168ae8bc96dc656c7f6c7271120a68ae5903",
"reference": "69c6168ae8bc96dc656c7f6c7271120a68ae5903",
"shasum": ""
},
"require": {
@@ -307,7 +307,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.54.0"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.56.1"
},
"funding": [
{
@@ -315,7 +315,7 @@
"type": "github"
}
],
"time": "2024-04-17T08:12:13+00:00"
"time": "2024-05-10T11:31:15+00:00"
},
{
"name": "psr/container",
@@ -539,16 +539,16 @@
},
{
"name": "symfony/console",
"version": "v7.0.6",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5"
"reference": "c981e0e9380ce9f146416bde3150c79197ce9986"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
"reference": "fde915cd8e7eb99b3d531d3d5c09531429c3f9e5",
"url": "https://api.github.com/repos/symfony/console/zipball/c981e0e9380ce9f146416bde3150c79197ce9986",
"reference": "c981e0e9380ce9f146416bde3150c79197ce9986",
"shasum": ""
},
"require": {
@@ -612,7 +612,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.0.6"
"source": "https://github.com/symfony/console/tree/v7.0.7"
},
"funding": [
{
@@ -628,20 +628,20 @@
"type": "tidelift"
}
],
"time": "2024-04-01T11:04:53+00:00"
"time": "2024-04-18T09:29:19+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.4.0",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf"
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf",
"reference": "7c3aff79d10325257a001fcf92d991f24fc967cf",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1",
"shasum": ""
},
"require": {
@@ -650,7 +650,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -679,7 +679,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0"
},
"funding": [
{
@@ -695,20 +695,20 @@
"type": "tidelift"
}
],
"time": "2023-05-23T14:45:45+00:00"
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v7.0.3",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "834c28d533dd0636f910909d01b9ff45cc094b5e"
"reference": "db2a7fab994d67d92356bb39c367db115d9d30f9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e",
"reference": "834c28d533dd0636f910909d01b9ff45cc094b5e",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/db2a7fab994d67d92356bb39c367db115d9d30f9",
"reference": "db2a7fab994d67d92356bb39c367db115d9d30f9",
"shasum": ""
},
"require": {
@@ -759,7 +759,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.0.3"
"source": "https://github.com/symfony/event-dispatcher/tree/v7.0.7"
},
"funding": [
{
@@ -775,20 +775,20 @@
"type": "tidelift"
}
],
"time": "2024-01-23T15:02:46+00:00"
"time": "2024-04-18T09:29:19+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v3.4.2",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "4e64b49bf370ade88e567de29465762e316e4224"
"reference": "8f93aec25d41b72493c6ddff14e916177c9efc50"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/4e64b49bf370ade88e567de29465762e316e4224",
"reference": "4e64b49bf370ade88e567de29465762e316e4224",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50",
"reference": "8f93aec25d41b72493c6ddff14e916177c9efc50",
"shasum": ""
},
"require": {
@@ -798,7 +798,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -835,7 +835,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.4.2"
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0"
},
"funding": [
{
@@ -851,26 +851,27 @@
"type": "tidelift"
}
],
"time": "2024-01-23T14:51:35+00:00"
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/filesystem",
"version": "v7.0.6",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "408105dff4c104454100730bdfd1a9cdd993f04d"
"reference": "cc168be6fbdcdf3401f50ae863ee3818ed4338f5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/408105dff4c104454100730bdfd1a9cdd993f04d",
"reference": "408105dff4c104454100730bdfd1a9cdd993f04d",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/cc168be6fbdcdf3401f50ae863ee3818ed4338f5",
"reference": "cc168be6fbdcdf3401f50ae863ee3818ed4338f5",
"shasum": ""
},
"require": {
"php": ">=8.2",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-mbstring": "~1.8"
"symfony/polyfill-mbstring": "~1.8",
"symfony/process": "^6.4|^7.0"
},
"type": "library",
"autoload": {
@@ -898,7 +899,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v7.0.6"
"source": "https://github.com/symfony/filesystem/tree/v7.0.7"
},
"funding": [
{
@@ -914,20 +915,20 @@
"type": "tidelift"
}
],
"time": "2024-03-21T19:37:36+00:00"
"time": "2024-04-18T09:29:19+00:00"
},
{
"name": "symfony/finder",
"version": "v7.0.0",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56"
"reference": "4d58f0f4fe95a30d7b538d71197135483560b97c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/6e5688d69f7cfc4ed4a511e96007e06c2d34ce56",
"reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56",
"url": "https://api.github.com/repos/symfony/finder/zipball/4d58f0f4fe95a30d7b538d71197135483560b97c",
"reference": "4d58f0f4fe95a30d7b538d71197135483560b97c",
"shasum": ""
},
"require": {
@@ -962,7 +963,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v7.0.0"
"source": "https://github.com/symfony/finder/tree/v7.0.7"
},
"funding": [
{
@@ -978,20 +979,20 @@
"type": "tidelift"
}
],
"time": "2023-10-31T17:59:56+00:00"
"time": "2024-04-28T11:44:19+00:00"
},
{
"name": "symfony/options-resolver",
"version": "v7.0.0",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
"reference": "700ff4096e346f54cb628ea650767c8130f1001f"
"reference": "23cc173858776ad451e31f053b1c9f47840b2cfa"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f",
"reference": "700ff4096e346f54cb628ea650767c8130f1001f",
"url": "https://api.github.com/repos/symfony/options-resolver/zipball/23cc173858776ad451e31f053b1c9f47840b2cfa",
"reference": "23cc173858776ad451e31f053b1c9f47840b2cfa",
"shasum": ""
},
"require": {
@@ -1029,7 +1030,7 @@
"options"
],
"support": {
"source": "https://github.com/symfony/options-resolver/tree/v7.0.0"
"source": "https://github.com/symfony/options-resolver/tree/v7.0.7"
},
"funding": [
{
@@ -1045,7 +1046,7 @@
"type": "tidelift"
}
],
"time": "2023-08-08T10:20:21+00:00"
"time": "2024-04-18T09:29:19+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -1523,16 +1524,16 @@
},
{
"name": "symfony/process",
"version": "v7.0.4",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9"
"reference": "3839e56b94dd1dbd13235d27504e66baf23faba0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/0e7727191c3b71ebec6d529fa0e50a01ca5679e9",
"reference": "0e7727191c3b71ebec6d529fa0e50a01ca5679e9",
"url": "https://api.github.com/repos/symfony/process/zipball/3839e56b94dd1dbd13235d27504e66baf23faba0",
"reference": "3839e56b94dd1dbd13235d27504e66baf23faba0",
"shasum": ""
},
"require": {
@@ -1564,7 +1565,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.0.4"
"source": "https://github.com/symfony/process/tree/v7.0.7"
},
"funding": [
{
@@ -1580,25 +1581,26 @@
"type": "tidelift"
}
],
"time": "2024-02-22T20:27:20+00:00"
"time": "2024-04-18T09:29:19+00:00"
},
{
"name": "symfony/service-contracts",
"version": "v3.4.2",
"version": "v3.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "11bbf19a0fb7b36345861e85c5768844c552906e"
"reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e",
"reference": "11bbf19a0fb7b36345861e85c5768844c552906e",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
"reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f",
"shasum": ""
},
"require": {
"php": ">=8.1",
"psr/container": "^1.1|^2.0"
"psr/container": "^1.1|^2.0",
"symfony/deprecation-contracts": "^2.5|^3"
},
"conflict": {
"ext-psr": "<1.1|>=2"
@@ -1606,7 +1608,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.4-dev"
"dev-main": "3.5-dev"
},
"thanks": {
"name": "symfony/contracts",
@@ -1646,7 +1648,7 @@
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.4.2"
"source": "https://github.com/symfony/service-contracts/tree/v3.5.0"
},
"funding": [
{
@@ -1662,20 +1664,20 @@
"type": "tidelift"
}
],
"time": "2023-12-19T21:51:00+00:00"
"time": "2024-04-18T09:32:20+00:00"
},
{
"name": "symfony/stopwatch",
"version": "v7.0.3",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
"reference": "983900d6fddf2b0cbaacacbbad07610854bd8112"
"reference": "41a7a24aa1dc82adf46a06bc292d1923acfe6b84"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/983900d6fddf2b0cbaacacbbad07610854bd8112",
"reference": "983900d6fddf2b0cbaacacbbad07610854bd8112",
"url": "https://api.github.com/repos/symfony/stopwatch/zipball/41a7a24aa1dc82adf46a06bc292d1923acfe6b84",
"reference": "41a7a24aa1dc82adf46a06bc292d1923acfe6b84",
"shasum": ""
},
"require": {
@@ -1708,7 +1710,7 @@
"description": "Provides a way to profile code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/stopwatch/tree/v7.0.3"
"source": "https://github.com/symfony/stopwatch/tree/v7.0.7"
},
"funding": [
{
@@ -1724,20 +1726,20 @@
"type": "tidelift"
}
],
"time": "2024-01-23T15:02:46+00:00"
"time": "2024-04-18T09:29:19+00:00"
},
{
"name": "symfony/string",
"version": "v7.0.4",
"version": "v7.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "f5832521b998b0bec40bee688ad5de98d4cf111b"
"reference": "e405b5424dc2528e02e31ba26b83a79fd4eb8f63"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b",
"reference": "f5832521b998b0bec40bee688ad5de98d4cf111b",
"url": "https://api.github.com/repos/symfony/string/zipball/e405b5424dc2528e02e31ba26b83a79fd4eb8f63",
"reference": "e405b5424dc2528e02e31ba26b83a79fd4eb8f63",
"shasum": ""
},
"require": {
@@ -1794,7 +1796,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v7.0.4"
"source": "https://github.com/symfony/string/tree/v7.0.7"
},
"funding": [
{
@@ -1810,7 +1812,7 @@
"type": "tidelift"
}
],
"time": "2024-02-01T13:17:36+00:00"
"time": "2024-04-18T09:29:19+00:00"
}
],
"packages-dev": [],

193
THANKS.md Executable file
View File

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

View File

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

View File

@@ -28,10 +28,11 @@ use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Autocomplete\AutocompleteRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountBalance;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Http\JsonResponse;
/**
@@ -39,11 +40,13 @@ use Illuminate\Http\JsonResponse;
*/
class AccountController extends Controller
{
use AccountFilter;
// use AccountFilter;
private AdminAccountRepositoryInterface $adminRepository;
private array $balanceTypes;
private AccountRepositoryInterface $repository;
private TransactionCurrency $default;
private ExchangeRateConverter $converter;
// private array $balanceTypes;
// private AccountRepositoryInterface $repository;
/**
* AccountController constructor.
@@ -53,18 +56,20 @@ class AccountController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$this->adminRepository = app(AdminAccountRepositoryInterface::class);
// new way of user group validation
$userGroup = $this->validateUserGroup($request);
if (null !== $userGroup) {
$this->adminRepository->setUserGroup($userGroup);
}
$this->adminRepository = app(AdminAccountRepositoryInterface::class);
$this->adminRepository->setUserGroup($userGroup);
$this->default = app('amount')->getDefaultCurrency();
$this->converter = app(ExchangeRateConverter::class);
// $this->repository = app(AccountRepositoryInterface::class);
// $this->adminRepository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}
);
$this->balanceTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
// $this->balanceTypes = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE];
}
/**
@@ -77,59 +82,68 @@ class AccountController extends Controller
* 5. Collector uses user_group_id
*
* @throws FireflyException
* @throws FireflyException
*/
public function accounts(AutocompleteRequest $request): JsonResponse
{
$data = $request->getData();
$types = $data['types'];
$query = $data['query'];
$date = $this->parameters->get('date') ?? today(config('app.timezone'));
$result = $this->adminRepository->searchAccount((string)$query, $types, $data['limit']);
$defaultCurrency = app('amount')->getDefaultCurrency();
$groupedResult = [];
$allItems = [];
$queryParameters = $request->getParameters();
$result = $this->adminRepository->searchAccount((string) $queryParameters['query'], $queryParameters['account_types'], $queryParameters['size']);
$return = [];
/** @var Account $account */
foreach ($result as $account) {
$nameWithBalance = $account->name;
$currency = $this->repository->getAccountCurrency($account) ?? $defaultCurrency;
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
$balance = app('steam')->balance($account, $date);
$nameWithBalance = sprintf('%s (%s)', $account->name, app('amount')->formatAnything($currency, $balance, false));
}
$type = (string)trans(sprintf('firefly.%s', $account->accountType->type));
$groupedResult[$type] ??= [
'group ' => $type,
'items' => [],
];
$allItems[] = [
'id' => (string)$account->id,
'value' => (string)$account->id,
'name' => $account->name,
'name_with_balance' => $nameWithBalance,
'label' => $nameWithBalance,
'type' => $account->accountType->type,
'currency_id' => (string)$currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
];
$return[] = $this->parseAccount($account);
}
usort(
$allItems,
static function (array $left, array $right): int {
$order = [AccountType::ASSET, AccountType::REVENUE, AccountType::EXPENSE];
$posLeft = (int)array_search($left['type'], $order, true);
$posRight = (int)array_search($right['type'], $order, true);
return response()->json($return);
}
return $posLeft - $posRight;
}
);
private function parseAccount(Account $account): array
{
$currency = $this->adminRepository->getAccountCurrency($account);
return response()->json($allItems);
return [
'id' => (string) $account->id,
'title' => $account->name,
'meta' => [
'type' => $account->accountType->type,
'currency_id' => null === $currency ? null : (string) $currency->id,
'currency_code' => $currency?->code,
'currency_symbol' => $currency?->symbol,
'currency_decimal' => $currency?->decimal_places,
'account_balances' => $this->getAccountBalances($account),
],
];
}
private function getAccountBalances(Account $account): array
{
$return = [];
$balances = $this->adminRepository->getAccountBalances($account);
/** @var AccountBalance $balance */
foreach ($balances as $balance) {
$return[] = $this->parseAccountBalance($balance);
}
return $return;
}
private function parseAccountBalance(AccountBalance $balance): array
{
$currency = $balance->transactionCurrency;
return [
'title' => $balance->title,
'native_amount' => $this->converter->convert($currency, $this->default, today(), $balance->balance),
'amount' => app('steam')->bcround($balance->balance, $currency->decimal_places),
'currency_id' => (string) $currency->id,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string) $this->default->id,
'native_currency_code' => $this->default->code,
'native_currency_symbol' => $this->default->symbol,
'native_currency_decimal' => $this->default->decimal_places,
];
}
}

View File

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

View File

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

View File

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

View File

@@ -55,8 +55,7 @@ class AccountController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$userGroup = $this->validateUserGroup($request);
$this->repository->setUserGroup($userGroup);
$this->repository->setUserGroup($this->validateUserGroup($request));
return $next($request);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,24 +23,59 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Request\Autocomplete;
use FireflyIII\Enums\UserRoleEnum;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\JsonApi\Rules\IsValidFilter;
use FireflyIII\JsonApi\Rules\IsValidPage;
use FireflyIII\Models\AccountType;
use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
use LaravelJsonApi\Core\Query\QueryParameters;
use LaravelJsonApi\Validation\Rule as JsonApiRule;
use Illuminate\Support\Facades\Log;
/**
* Class AutocompleteRequest
*/
class AutocompleteRequest extends FormRequest
{
use AccountFilter;
use ChecksLogin;
use ConvertsDataTypes;
protected array $acceptedRoles = [UserRoleEnum::MANAGE_TRANSACTIONS];
/**
* Loops over all possible query parameters (these are shared over ALL auto complete requests)
* and returns a validated array of parameters.
*
* The advantage is a single class. But you may also submit "account types" to an endpoint that doesn't use these.
*/
public function getParameters(): array
{
$queryParameters = QueryParameters::cast($this->all());
try {
$date = Carbon::createFromFormat('Y-m-d', $queryParameters->filter()?->value('date', date('Y-m-d')), config('app.timezone'));
} catch (InvalidFormatException $e) {
Log::debug(sprintf('Invalid date format in autocomplete request. Using today: %s', $e->getMessage()));
$date = today();
}
$query = $queryParameters->filter()?->value('query', '') ?? '';
$size = (int) ($queryParameters->page()['size'] ?? 50);
$accountTypes = $this->getAccountTypeParameter($queryParameters->filter()?->value('account_types', '') ?? '');
return [
'date' => $date,
'query' => $query,
'size' => $size,
'account_types' => $accountTypes,
];
}
public function getData(): array
{
return [];
$types = $this->convertString('types');
$array = [];
if ('' !== $types) {
@@ -63,7 +98,27 @@ class AutocompleteRequest extends FormRequest
public function rules(): array
{
return [
'limit' => 'min:0|max:1337',
'fields' => JsonApiRule::notSupported(),
'filter' => ['nullable', 'array', new IsValidFilter(['query', 'date', 'account_types'])],
'include' => JsonApiRule::notSupported(),
'page' => ['nullable', 'array', new IsValidPage(['size'])],
'sort' => JsonApiRule::notSupported(),
];
}
private function getAccountTypeParameter(mixed $types): array
{
if (is_string($types) && str_contains($types, ',')) {
$types = explode(',', $types);
}
if (!is_iterable($types)) {
$types = [$types];
}
$return = [];
foreach ($types as $type) {
$return = array_merge($return, $this->mapAccountTypes($type));
}
return array_unique($return);
}
}

View File

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

View File

@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
/**
* Class CorrectionSkeleton
*/
class CorrectAccountBalance extends Command
{
use ShowsFriendlyMessages;
protected $description = 'Recalculate all account balance amounts';
protected $signature = 'firefly-iii:correct-account-balance';
public function handle(): int
{
$this->correctBalanceAmounts();
return 0;
}
private function correctBalanceAmounts(): void
{
AccountBalanceCalculator::recalculate(null, null);
foreach (TransactionJournal::all() as $journal) {
Log::debug(sprintf('Recalculating account balances for journal #%d', $journal->id));
foreach ($journal->transactions as $transaction) {
AccountBalanceCalculator::recalculate($transaction->account, $journal);
}
}
}
}

View File

@@ -74,6 +74,7 @@ class CorrectDatabase extends Command
'firefly-iii:unify-group-accounts',
'firefly-iii:trigger-credit-recalculation',
'firefly-iii:migrate-preferences',
'firefly-iii:correct-account-balance',
];
foreach ($commands as $command) {
$this->friendlyLine(sprintf('Now executing command "%s"', $command));

View File

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

View File

@@ -0,0 +1,49 @@
<?php
/*
* AccountBalance.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Entities;
use FireflyIII\Models\Account;
class AccountBalance
{
public string $id;
public string $amount;
public string $currencyId;
public static function fromArray(): self
{
$balance = new self();
$balance->id = (string) random_int(1, 1000);
$balance->name = (string) random_int(1, 1000);
$balance->amount = (string) random_int(1, 1000);
$balance->currencyId = '1';
return $balance;
}
public function getAccount(): Account
{
return Account::inRandomOrder()->first();
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -151,8 +151,9 @@ class HomeController extends Controller
}
/** @var Carbon $start */
/** @var Carbon $end */
$start = session('start', today(config('app.timezone'))->startOfMonth());
/** @var Carbon $end */
$end = session('end', today(config('app.timezone'))->endOfMonth());
$accounts = $repository->getAccountsById($frontpageArray);
$today = today(config('app.timezone'));

View File

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

View File

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

View File

@@ -0,0 +1,52 @@
<?php
/*
* IsValidFilter.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\JsonApi\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
class IsValidFilter implements ValidationRule
{
private array $allowed;
public function __construct(array $keys)
{
$this->allowed = $keys;
}
#[\Override]
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
if ('filter' !== $attribute) {
$fail('validation.bad_api_filter')->translate();
}
if (!is_array($value)) {
$value = explode(',', $value);
}
foreach ($value as $key => $val) {
if (!in_array($key, $this->allowed, true)) {
$fail('validation.bad_api_filter')->translate();
}
}
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* IsValidFilter.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\JsonApi\Rules;
use Illuminate\Contracts\Validation\ValidationRule;
class IsValidPage implements ValidationRule
{
private array $allowed;
public function __construct(array $keys)
{
$this->allowed = $keys;
}
#[\Override]
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
if ('page' !== $attribute) {
$fail('validation.bad_api_filter')->translate();
}
if (!is_array($value)) {
$value = explode(',', $value);
}
foreach ($value as $key => $val) {
if (!in_array($key, $this->allowed, true)) {
$fail('validation.bad_api_page')->translate();
}
}
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* AccountBalanceRepository.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\AccountBalances;
use FireflyIII\Entities\AccountBalance;
use LaravelJsonApi\Contracts\Store\QueriesAll;
use LaravelJsonApi\NonEloquent\AbstractRepository;
class AccountBalanceRepository extends AbstractRepository implements QueriesAll
{
#[\Override]
public function find(string $resourceId): ?object
{
return AccountBalance::fromArray();
}
public function queryAll(): Capabilities\AccountBalanceQuery
{
return Capabilities\AccountBalanceQuery::make()
->withServer($this->server)
->withSchema($this->schema)
;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,58 @@
<?php
/*
* AccountBalanceQuery.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\AccountBalances\Capabilities;
use FireflyIII\Entities\AccountBalance;
use FireflyIII\Models\Account;
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
class AccountBalanceQuery extends QueryAll
{
private Account $account;
/**
* QuerySites constructor.
*/
public function __construct()
{
parent::__construct();
}
public function get(): iterable
{
return [
AccountBalance::fromArray(),
AccountBalance::fromArray(),
AccountBalance::fromArray(),
AccountBalance::fromArray(),
];
}
public function withAccount(Account $account): self
{
$this->account = $account;
return $this;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* AccountRepository.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\Accounts;
use FireflyIII\Models\Account;
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
use LaravelJsonApi\Contracts\Store\QueriesAll;
use LaravelJsonApi\NonEloquent\AbstractRepository;
class AccountRepository extends AbstractRepository implements QueriesAll
{
use UsergroupAware;
/**
* SiteRepository constructor.
*/
public function __construct() {}
public function find(string $resourceId): ?object
{
return Account::find((int) $resourceId);
}
public function queryAll(): Capabilities\AccountQuery
{
return Capabilities\AccountQuery::make()
->withUserGroup($this->userGroup)
->withServer($this->server)
->withSchema($this->schema)
;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,73 @@
<?php
/*
* AccountQuery.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\JsonApi\V3\Accounts\Capabilities;
use FireflyIII\Support\JsonApi\Concerns\UsergroupAware;
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
use FireflyIII\Support\JsonApi\ExpandsQuery;
use FireflyIII\Support\JsonApi\FiltersPagination;
use FireflyIII\Support\JsonApi\SortsCollection;
use FireflyIII\Support\JsonApi\ValidateSortParameters;
use LaravelJsonApi\Contracts\Store\HasPagination;
use LaravelJsonApi\NonEloquent\Capabilities\QueryAll;
use LaravelJsonApi\NonEloquent\Concerns\PaginatesEnumerables;
class AccountQuery extends QueryAll implements HasPagination
{
use ExpandsQuery;
use FiltersPagination;
use PaginatesEnumerables;
use SortsCollection;
use UsergroupAware;
use ValidateSortParameters;
#[\Override]
public function get(): iterable
{
$filters = $this->queryParameters->filter();
$sort = $this->queryParameters->sortFields();
$pagination = $this->filtersPagination($this->queryParameters->page());
$needsAll = $this->validateParams('account', $sort);
$query = $this->userGroup->accounts();
if (!$needsAll) {
$query = $this->addPagination($query, $pagination);
}
$query = $this->addSortParams($query, $sort);
$query = $this->addFilterParams('account', $query, $filters);
$collection = $query->get(['accounts.*']);
// enrich data
$enrichment = new AccountEnrichment();
$collection = $enrichment->enrich($collection);
// add filters after the query
// add sort after the query
return $this->sortCollection($collection, $sort);
// var_dump($filters->value('name'));
// exit;
}
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
<?php
/*
* AccountPolicy.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Policies;
use FireflyIII\Entities\AccountBalance;
use FireflyIII\User;
class AccountBalancePolicy
{
/**
* TODO needs better authentication.
*/
public function view(User $user, AccountBalance $accountBalance): bool
{
return true;
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewAny(): bool
{
return true;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* AccountPolicy.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Policies;
use FireflyIII\Models\Account;
use FireflyIII\User;
class AccountPolicy
{
/**
* TODO needs better authentication.
*/
public function view(User $user, Account $account): bool
{
return true;
return auth()->check() && $user->id === $account->user_id;
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewAny(): bool
{
return true;
return auth()->check();
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewUser(User $user, Account $account): bool
{
return $this->view($user, $account);
}
public function viewAccountBalances(User $user, Account $account): bool
{
return $this->view($user, $account);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* BalancePolicy.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Policies;
use FireflyIII\Models\Account;
use FireflyIII\User;
class BalancePolicy
{
/**
* TODO needs better authentication.
*/
public function view(User $user, Account $account): bool
{
return auth()->check() && $user->id === $account->user_id;
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewAny(): bool
{
return auth()->check();
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* UserPolicy.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Policies;
use FireflyIII\User;
class UserPolicy
{
/**
* TODO needs better authentication.
*/
public function view(User $user, User $user1): bool
{
return true;
return auth()->check() && $user->id === $account->user_id;
}
/**
* Everybody can do this, but selection should limit to user.
*
* @return true
*/
public function viewAny(): bool
{
return true;
return auth()->check();
}
public function viewAccounts(User $user): bool
{
return true;
return auth()->check();
}
}

View File

@@ -27,6 +27,7 @@ namespace FireflyIII\Repositories\UserGroups\Account;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Services\Internal\Update\AccountUpdateService;
@@ -240,7 +241,7 @@ class AccountRepository implements AccountRepositoryInterface
}
}
public function getAccountsByType(array $types, ?array $sort = []): Collection
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection
{
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
$res = array_intersect([AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT], $types);
@@ -249,6 +250,22 @@ class AccountRepository implements AccountRepositoryInterface
$query->accountTypeIn($types);
}
// process filters
// TODO this should be repeatable, it feels like a hack when you do it here.
// TODO some fields cannot be filtered using the query, and a second filter must be applied on the collection.
foreach ($filters as $column => $value) {
// filter on NULL values
if (null === $value) {
continue;
}
if ('active' === $column) {
$query->where('accounts.active', $value);
}
if ('name' === $column) {
$query->where('accounts.name', 'LIKE', sprintf('%%%s%%', $value));
}
}
// add sort parameters. At this point they're filtered to allowed fields to sort by:
$hasActiveColumn = array_key_exists('active', $sort);
if (count($sort) > 0) {
@@ -285,6 +302,7 @@ class AccountRepository implements AccountRepositoryInterface
;
if ('' !== $query) {
// split query on spaces just in case:
// TODO this will always fail because it searches for AND.
$parts = explode(' ', $query);
foreach ($parts as $part) {
$search = sprintf('%%%s%%', $part);
@@ -337,4 +355,40 @@ class AccountRepository implements AccountRepositoryInterface
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line
;
}
#[\Override]
public function getObjectGroups(Collection $accounts): array
{
$groupIds = [];
$return = [];
$set = DB::table('object_groupables')->where('object_groupable_type', Account::class)
->whereIn('object_groupable_id', $accounts->pluck('id')->toArray())->get()
;
/** @var \stdClass $row */
foreach ($set as $row) {
$groupIds[] = $row->object_group_id;
}
$groupIds = array_unique($groupIds);
$groups = ObjectGroup::whereIn('id', $groupIds)->get();
/** @var \stdClass $row */
foreach ($set as $row) {
if (!array_key_exists($row->object_groupable_id, $return)) {
/** @var null|ObjectGroup $group */
$group = $groups->firstWhere('id', '=', $row->object_group_id);
if (null !== $group) {
$return[$row->object_groupable_id] = ['title' => $group->title, 'order' => $group->order, 'id' => $group->id];
}
}
}
return $return;
}
#[\Override]
public function getAccountBalances(Account $account): Collection
{
return $account->accountBalances;
}
}

View File

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

View File

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

View File

@@ -81,7 +81,7 @@ trait ValidatesUserGroupTrait
throw new AuthorizationException((string) trans('validation.belongs_user_or_user_group'));
}
Log::debug(sprintf('validateUserGroup: validate access of user to group #%d ("%s").', $groupId, $group->title));
$roles = property_exists($this, 'acceptedRoles') ? $this->acceptedRoles : [];
$roles = property_exists($this, 'acceptedRoles') ? $this->acceptedRoles : []; // @phpstan-ignore-line
if (0 === count($roles)) {
Log::debug('validateUserGroup: no roles defined, so no access.');

View File

@@ -0,0 +1,48 @@
<?php
/*
* UsergroupAware.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Concerns;
use FireflyIII\Models\UserGroup;
trait UsergroupAware
{
protected UserGroup $userGroup;
public function getUserGroup(): UserGroup
{
return $this->userGroup;
}
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
public function withUserGroup(UserGroup $userGroup): self
{
$this->userGroup = $userGroup;
return $this;
}
}

View File

@@ -0,0 +1,149 @@
<?php
/*
* AccountEnricher.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class AccountEnrichment implements EnrichmentInterface
{
private Collection $collection;
private array $currencies;
#[\Override]
public function enrich(Collection $collection): Collection
{
$this->collection = $collection;
$this->currencies = [];
// do everything here:
$this->getLastActivity();
// $this->getMetaBalances();
$this->collectAccountTypes();
$this->collectMetaData();
$this->collection->transform(function (Account $account) {
$account->user_array = ['id' => 1, 'bla bla' => 'bla'];
$account->balances = collect([
['balance_id' => 1, 'balance' => 5],
['balance_id' => 2, 'balance' => 5],
['balance_id' => 3, 'balance' => 5],
]);
return $account;
});
return $this->collection;
}
/**
* TODO this method refers to a single-use method inside Steam that could be moved here.
*/
private function getLastActivity(): void
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$lastActivity = $accountRepository->getLastActivity($this->collection);
foreach ($lastActivity as $row) {
$this->collection->where('id', $row['account_id'])->first()->last_activity = Carbon::parse($row['date_max'], config('app.timezone'));
}
}
/**
* TODO this method refers to a single-use method inside Steam that could be moved here.
*/
private function getMetaBalances(): void
{
try {
$array = app('steam')->balancesByAccountsConverted($this->collection, today());
} catch (FireflyException $e) {
Log::error(sprintf('Could not load balances: %s', $e->getMessage()));
return;
}
foreach ($array as $accountId => $row) {
$this->collection->where('id', $accountId)->first()->balance = $row['balance'];
$this->collection->where('id', $accountId)->first()->native_balance = $row['native_balance'];
}
}
/**
* TODO this method refers to a single-use method inside Steam that could be moved here.
*/
private function collectAccountTypes(): void
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accountTypes = $accountRepository->getAccountTypes($this->collection);
$types = [];
/** @var AccountType $row */
foreach ($accountTypes as $row) {
$types[$row->id] = $row->type;
}
$this->collection->transform(function (Account $account) use ($types) {
$account->type = $types[$account->id];
return $account;
});
}
private function collectMetaData(): void
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$metaFields = $accountRepository->getMetaValues($this->collection, ['currency_id', 'account_role', 'account_number', 'liability_direction', 'interest', 'interest_period', 'current_debt']);
$currencyIds = $metaFields->where('name', 'currency_id')->pluck('data')->toArray();
$currencies = [];
foreach ($repository->getByIds($currencyIds) as $currency) {
$id = $currency->id;
$currencies[$id] = $currency;
}
$this->collection->transform(function (Account $account) use ($metaFields, $currencies) {
$set = $metaFields->where('account_id', $account->id);
foreach ($set as $entry) {
$account->{$entry->name} = $entry->data;
if ('currency_id' === $entry->name) {
$id = (int) $entry->data;
$account->currency_code = $currencies[$id]?->code;
$account->currency_symbol = $currencies[$id]?->symbol;
$account->currency_decimal_places = $currencies[$id]?->decimal_places;
}
}
return $account;
});
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* EnricherInterface.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Illuminate\Support\Collection;
interface EnrichmentInterface
{
public function enrich(Collection $collection): Collection;
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* ExpandsQuery.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi;
use FireflyIII\Support\Http\Api\AccountFilter;
use Illuminate\Contracts\Database\Eloquent\Builder;
use LaravelJsonApi\Core\Query\FilterParameters;
use LaravelJsonApi\Core\Query\SortFields;
trait ExpandsQuery
{
use AccountFilter;
final protected function addPagination(Builder $query, array $pagination): Builder
{
$skip = ($pagination['number'] - 1) * $pagination['size'];
return $query->skip($skip)->take($pagination['size']);
}
final protected function addSortParams(Builder $query, ?SortFields $sort): Builder
{
if (null === $sort) {
return $query;
}
foreach ($sort->all() as $sortField) {
$query->orderBy($sortField->name(), $sortField->isAscending() ? 'ASC' : 'DESC');
}
return $query;
}
final protected function addFilterParams(string $class, Builder $query, ?FilterParameters $filters): Builder
{
if (null === $filters) {
return $query;
}
$config = config(sprintf('firefly.valid_query_filters.%s', $class)) ?? [];
if (0 === count($filters->all())) {
return $query;
}
$query->where(function (Builder $q) use ($config, $filters): void {
foreach ($filters->all() as $filter) {
if (in_array($filter->key(), $config, true)) {
foreach ($filter->value() as $value) {
$q->where($filter->key(), 'LIKE', sprintf('%%%s%%', $value));
}
}
}
});
// some filters are special, i.e. the account type filter.
$typeFilters = $filters->value('type', false);
if (false !== $typeFilters && count($typeFilters) > 0) {
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
foreach ($typeFilters as $typeFilter) {
$types = $this->mapAccountTypes($typeFilter);
$query->whereIn('account_types.type', $types);
}
}
return $query;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* FiltersPagination.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi;
trait FiltersPagination
{
protected function filtersPagination(?array $pagination): array
{
if (null === $pagination) {
return [
'number' => 1,
'size' => $this->getPageSize(),
];
}
// cleanup page number
$pagination['number'] = (int) ($pagination['number'] ?? 1);
$pagination['number'] = min(65536, max($pagination['number'], 1));
// clean up page size
$pagination['size'] = (int) ($pagination['size'] ?? $this->getPageSize());
$pagination['size'] = min(1337, max($pagination['size'], 1));
return $pagination;
}
private function getPageSize(): int
{
if (auth()->check()) {
return (int) app('preferences')->get('listPageSize', 50)->data;
}
return 50;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* SortsCollection.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi;
use Illuminate\Support\Collection;
use LaravelJsonApi\Core\Query\SortFields;
trait SortsCollection
{
protected function sortCollection(Collection $collection, ?SortFields $sortFields): Collection
{
if (null === $sortFields) {
return $collection;
}
foreach ($sortFields->all() as $sortField) {
$collection = $sortField->isAscending() ? $collection->sortBy($sortField->name()) : $collection->sortByDesc($sortField->name());
}
return $collection;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* ValidateSortParameters.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\JsonApi;
use LaravelJsonApi\Core\Query\SortFields;
trait ValidateSortParameters
{
public function validateParams(string $class, ?SortFields $params): bool
{
if (null === $params) {
return false;
}
$config = config(sprintf('firefly.full_data_set.%s', $class)) ?? [];
foreach ($params->all() as $field) {
if (in_array($field->name(), $config, true)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,133 @@
<?php
/*
* AccountBalanceCalculator.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Models;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountBalance;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Support\Facades\Log;
class AccountBalanceCalculator
{
public static function recalculate(?Account $account, ?TransactionJournal $transactionJournal): void
{
// first collect normal amounts (in whatever currency), and set them.
// select account_id, transaction_currency_id, foreign_currency_id, sum(amount), sum(foreign_amount) from transactions group by account_id, transaction_currency_id, foreign_currency_id
$query = Transaction::groupBy(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id']);
$title = 'balance';
if (null !== $account) {
$query->where('transactions.account_id', $account->id);
}
if (null !== $transactionJournal) {
$query->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id');
$query->where('transaction_journals.date', '<=', $transactionJournal->date);
$title = 'balance_after_journal';
}
$result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.foreign_currency_id', \DB::raw('SUM(transactions.amount) as sum_amount'), \DB::raw('SUM(transactions.foreign_amount) as sum_foreign_amount')]);
// reset account balances:
self::resetAccountBalances($title, $account, $transactionJournal);
/** @var \stdClass $row */
foreach ($result as $row) {
$account = (int) $row->account_id;
$transactionCurrency = (int) $row->transaction_currency_id;
$foreignCurrency = (int) $row->foreign_currency_id;
$sumAmount = $row->sum_amount;
$sumForeignAmount = $row->sum_foreign_amount;
// first create for normal currency:
$entry = self::getBalance($title, $account, $transactionCurrency, $transactionJournal?->id);
$entry->balance = bcadd($entry->balance, $sumAmount);
$entry->transaction_journal_id = $transactionJournal?->id;
$entry->save();
Log::debug(sprintf('Set balance entry "%s" #%d to amount %s', $title, $entry->id, $entry->balance));
// then do foreign amount, if present:
if ($foreignCurrency > 0) {
$entry = self::getBalance($title, $account, $foreignCurrency, $transactionJournal?->id);
$entry->balance = bcadd($entry->balance, $sumForeignAmount);
$entry->transaction_journal_id = $transactionJournal?->id;
$entry->save();
Log::debug(sprintf('Set balance entry "%s" #%d to amount %s', $title, $entry->id, $entry->balance));
}
}
}
private static function getBalance(string $title, int $account, int $currency, ?int $journal): AccountBalance
{
$query = AccountBalance::where('title', $title)->where('account_id', $account)->where('transaction_currency_id', $currency);
if (null !== $journal) {
$query->where('transaction_journal_id', $journal);
}
$entry = $query->first();
if (null !== $entry) {
Log::debug(sprintf('Found account balance "%s" for account #%d and currency #%d: %s', $title, $account, $currency, $entry->balance));
return $entry;
}
$entry = new AccountBalance();
$entry->title = $title;
$entry->account_id = $account;
$entry->transaction_currency_id = $currency;
$entry->transaction_journal_id = $journal;
$entry->balance = '0';
$entry->save();
Log::debug(sprintf('Created new account balance for account #%d and currency #%d: %s', $account, $currency, $entry->balance));
return $entry;
}
private static function resetAccountBalances(string $title, ?Account $account, ?TransactionJournal $transactionJournal): void
{
if (null === $account && null === $transactionJournal) {
AccountBalance::whereNotNull('updated_at')->where('title', $title)->update(['balance' => '0']);
Log::debug('Set ALL balances to zero.');
return;
}
if (null !== $account && null === $transactionJournal) {
AccountBalance::where('account_id', $account->id)->where('title', $title)->update(['balance' => '0']);
Log::debug(sprintf('Set balances of account #%d to zero.', $account->id));
return;
}
AccountBalance::where('account_id', $account->id)->where('transaction_journal_id', $transactionJournal->id)->where('title', $title)->update(['balance' => '0']);
Log::debug(sprintf('Set balances of account #%d + journal #%d to zero.', $account->id, $transactionJournal->id));
}
public static function recalculateByJournal(TransactionJournal $transactionJournal): void
{
Log::debug(sprintf('Recalculate balance after journal #%d', $transactionJournal->id));
// update both account balances, but limit to this transaction or earlier.
foreach ($transactionJournal->transactions as $transaction) {
self::recalculate($transaction->account, $transactionJournal);
}
}
}

View File

@@ -159,9 +159,7 @@ trait ConvertsDataTypes
if (method_exists($this, 'validateUserGroup')) { // @phpstan-ignore-line
$userGroup = $this->validateUserGroup($this);
if (null !== $userGroup) {
$repository->setUserGroup($userGroup);
}
$repository->setUserGroup($userGroup);
}
// set administration ID

View File

@@ -0,0 +1,77 @@
<?php
/*
* GetFilterInstructions.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Support\Request;
trait GetFilterInstructions
{
private const string INVALID_FILTER = '%INVALID_JAMES_%';
final public function getFilterInstructions(string $key): array
{
$config = config(sprintf('firefly.filters.allowed.%s', $key));
$allowed = array_keys($config);
$set = $this->get('filters', []);
$result = [];
if (0 === count($set)) {
return [];
}
foreach ($set as $info) {
$column = $info['column'] ?? 'NOPE';
$filterValue = (string) ($info['filter'] ?? self::INVALID_FILTER);
if (false === in_array($column, $allowed, true)) {
// skip invalid column
continue;
}
$filterType = $config[$column] ?? false;
switch ($filterType) {
default:
exit(sprintf('Do not support filter type "%s"', $filterType));
case 'boolean':
$filterValue = $this->booleanInstruction($filterValue);
break;
case 'string':
break;
}
$result[$column] = $filterValue;
}
return $result;
}
public function booleanInstruction(string $filterValue): ?bool
{
if ('true' === $filterValue) {
return true;
}
if ('false' === $filterValue) {
return false;
}
return null;
}
}

View File

@@ -41,11 +41,13 @@ class AccountTransformer extends AbstractTransformer
{
private array $accountMeta;
private array $accountTypes;
private array $fullTypes;
private array $balanceDifferences;
private array $convertedBalances;
private array $currencies;
private TransactionCurrency $default;
private array $lastActivity;
private array $objectGroups;
/**
* This method collects meta-data for one or all accounts in the transformer's collection.
@@ -55,10 +57,18 @@ class AccountTransformer extends AbstractTransformer
$this->currencies = [];
$this->accountMeta = [];
$this->accountTypes = [];
$this->fullTypes = [];
$this->lastActivity = [];
$this->objectGroups = [];
$this->convertedBalances = [];
$this->balanceDifferences = [];
Log::debug(sprintf('collectMetaData on %d object(s)', $objects->count()));
// first collect all the "heavy" stuff that relies on ALL data to be present.
// get last activity:
$this->getLastActivity($objects);
// get balances of all accounts
$this->getMetaBalances($objects);
@@ -71,15 +81,25 @@ class AccountTransformer extends AbstractTransformer
// get account types:
$this->collectAccountTypes($objects);
// get last activity:
$this->getLastActivity($objects);
// TODO add balance difference
// add balance difference
if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) {
$this->getBalanceDifference($objects, $this->parameters->get('start'), $this->parameters->get('end'));
}
return $this->sortAccounts($objects);
// get object groups
$this->getObjectGroups($objects);
// sort:
$objects = $this->sortAccounts($objects);
// if pagination is disabled, do it now:
if (true === $this->parameters->get('disablePagination')) {
$page = (int) $this->parameters->get('page');
$size = (int) $this->parameters->get('pageSize');
$objects = $objects->slice(($page - 1) * $size, $size);
}
return $objects;
}
private function getDate(): Carbon
@@ -97,32 +117,44 @@ class AccountTransformer extends AbstractTransformer
*/
public function transform(Account $account): array
{
$id = $account->id;
$id = $account->id;
// various meta
$accountRole = $this->accountMeta[$id]['account_role'] ?? null;
$accountType = $this->accountTypes[$id];
$order = $account->order;
$accountRole = $this->accountMeta[$id]['account_role'] ?? null;
$accountType = $this->accountTypes[$id];
$order = $account->order;
// liability type
$liabilityType = 'liabilities' === $accountType ? $this->fullTypes[$id] : null;
$liabilityDirection = $this->accountMeta[$id]['liability_direction'] ?? null;
$interest = $this->accountMeta[$id]['interest'] ?? null;
$interestPeriod = $this->accountMeta[$id]['interest_period'] ?? null;
$currentDebt = $this->accountMeta[$id]['current_debt'] ?? null;
// no currency? use default
$currency = $this->default;
$currency = $this->default;
if (array_key_exists($id, $this->accountMeta) && 0 !== (int) ($this->accountMeta[$id]['currency_id'] ?? 0)) {
$currency = $this->currencies[(int) $this->accountMeta[$id]['currency_id']];
}
// amounts and calculation.
$balance = $this->balances[$id]['balance'] ?? null;
$nativeBalance = $this->convertedBalances[$id]['native_balance'] ?? null;
$balance = $this->balances[$id]['balance'] ?? null;
$nativeBalance = $this->convertedBalances[$id]['native_balance'] ?? null;
// no order for some accounts:
if (!in_array(strtolower($accountType), ['liability', 'liabilities', 'asset'], true)) {
$order = null;
}
// object group
$objectGroupId = $this->objectGroups[$id]['id'] ?? null;
$objectGroupOrder = $this->objectGroups[$id]['order'] ?? null;
$objectGroupTitle = $this->objectGroups[$id]['title'] ?? null;
// balance difference
$diffStart = null;
$diffEnd = null;
$balanceDiff = null;
$nativeBalanceDiff = null;
$diffStart = null;
$diffEnd = null;
$balanceDiff = null;
$nativeBalanceDiff = null;
if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) {
$diffStart = $this->parameters->get('start')->toAtomString();
$diffEnd = $this->parameters->get('end')->toAtomString();
@@ -165,19 +197,25 @@ class AccountTransformer extends AbstractTransformer
// more meta
'last_activity' => array_key_exists($id, $this->lastActivity) ? $this->lastActivity[$id]->toAtomString() : null,
// liability stuff
'liability_type' => $liabilityType,
'liability_direction' => $liabilityDirection,
'interest' => $interest,
'interest_period' => $interestPeriod,
'current_debt' => $currentDebt,
// object group
'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null,
'object_group_order' => $objectGroupOrder,
'object_group_title' => $objectGroupTitle,
// 'notes' => $this->repository->getNoteText($account),
// 'monthly_payment_date' => $monthlyPaymentDate,
// 'credit_card_type' => $creditCardType,
// 'account_number' => $this->repository->getMetaValue($account, 'account_number'),
// 'bic' => $this->repository->getMetaValue($account, 'BIC'),
// 'virtual_balance' => number_format((float) $account->virtual_balance, $decimalPlaces, '.', ''),
// 'opening_balance' => $openingBalance,
// 'opening_balance_date' => $openingBalanceDate,
// 'liability_type' => $liabilityType,
// 'liability_direction' => $liabilityDirection,
// 'interest' => $interest,
// 'interest_period' => $interestPeriod,
// 'current_debt' => $this->repository->getMetaValue($account, 'current_debt'),
// 'include_net_worth' => $includeNetWorth,
// 'longitude' => $longitude,
// 'latitude' => $latitude,
@@ -212,7 +250,7 @@ class AccountTransformer extends AbstractTransformer
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$metaFields = $accountRepository->getMetaValues($accounts, ['currency_id', 'account_role', 'account_number']);
$metaFields = $accountRepository->getMetaValues($accounts, ['currency_id', 'account_role', 'account_number', 'liability_direction', 'interest', 'interest_period', 'current_debt']);
$currencyIds = $metaFields->where('name', 'currency_id')->pluck('data')->toArray();
$currencies = $repository->getByIds($currencyIds);
@@ -235,6 +273,7 @@ class AccountTransformer extends AbstractTransformer
/** @var AccountType $row */
foreach ($accountTypes as $row) {
$this->accountTypes[$row->id] = (string) config(sprintf('firefly.shortNamesByFullName.%s', $row->type));
$this->fullTypes[$row->id] = $row->type;
}
}
@@ -275,6 +314,9 @@ class AccountTransformer extends AbstractTransformer
if ('balance_difference' === $column) {
$accounts = $this->sortByBalanceDifference($accounts, $direction);
}
if ('current_debt' === $column) {
$accounts = $this->sortByCurrentDebt($accounts, $direction);
}
}
return $accounts;
@@ -365,4 +407,26 @@ class AccountTransformer extends AbstractTransformer
return $rightBalance <=> $leftBalance;
});
}
private function sortByCurrentDebt(Collection $accounts, string $direction): Collection
{
$amounts = $this->accountMeta;
return $accounts->sort(function (Account $left, Account $right) use ($amounts, $direction) {
$leftCurrent = (float) ($amounts[$left->id]['current_debt'] ?? 0);
$rightCurrent = (float) ($amounts[$right->id]['current_debt'] ?? 0);
if ('asc' === $direction) {
return $leftCurrent <=> $rightCurrent;
}
return $rightCurrent <=> $leftCurrent;
});
}
private function getObjectGroups(Collection $accounts): void
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$this->objectGroups = $accountRepository->getObjectGroups($accounts);
}
}

View File

@@ -3,11 +3,33 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 6.1.15 - 2024-04-24
## 6.1.14 - 2024-xx-xx
### Fixed
- [Issue 8812](https://github.com/firefly-iii/firefly-iii/issues/8812) (Login with `AUTHENTICATION_GUARD=remote_user_guard` fails due to missing UserGroup) reported by @nebulade
## 6.1.14 - 2024-04-24
### Changed
- You will have to define again which asset accounts you want to see on the dashboard. Sorry about that.
- You may have to define again which asset accounts you want to see on the dashboard. Sorry about that.
- Expanded some database models.
- Limit the number of error messages Firefly III will send (so Mailgun keeps liking me).
- [PR 8746](https://github.com/firefly-iii/firefly-iii/pull/8746) (Set date to now when cloning journal) reported by @imlonghao
### Fixed
- [Issue 8748](https://github.com/firefly-iii/firefly-iii/issues/8748) (Release tarballs mistakenly include the `.zip` artifact) reported by @sudoBash418
- [Discussion 8750](https://github.com/orgs/firefly-iii/discussions/8750) (API To change transaction fails to find destination_id) started by @soloam
- [Issue 8779](https://github.com/firefly-iii/firefly-iii/issues/8779) (Change Password Form not working ≥ 6.1.11) reported by @jemtz-deleon
- [Issue 8781](https://github.com/firefly-iii/firefly-iii/issues/8781) (Bill information missing in /api/v1/search/transactions responses) reported by @daanvanberkel
- [Issue 8752](https://github.com/firefly-iii/firefly-iii/issues/8752) (Transactions reorder not work (error 404)) reported by @BoGnY
- [Issue 8613](https://github.com/firefly-iii/firefly-iii/issues/8613) (Some minor color issues) reported by @rumpff
- [Issue 8776](https://github.com/firefly-iii/firefly-iii/issues/8776) (report-data/category/expenses has wrong sums with specific date range) reported by @bouil
### API
- [Issue 8804](https://github.com/firefly-iii/firefly-iii/issues/8804) (Unable to create rules with negation via API) reported by @tailg8nj
## 6.1.13 - 2024-04-01

View File

@@ -87,6 +87,8 @@
"guzzlehttp/guzzle": "^7.8",
"jc5/google2fa-laravel": "^2.0",
"jc5/recovery": "^2",
"laravel-json-api/laravel": "^4.0",
"laravel-json-api/non-eloquent": "^4.0",
"laravel/framework": "^11",
"laravel/passport": "^12",
"laravel/sanctum": "^4",
@@ -101,13 +103,13 @@
"psr/log": "<4",
"ramsey/uuid": "^4.7",
"rcrowe/twigbridge": "^0.14",
"twig/twig": "3.8.0",
"spatie/laravel-html": "^3.2",
"spatie/laravel-ignition": "^2",
"spatie/period": "^2.4",
"symfony/expression-language": "^7.0",
"symfony/http-client": "^7.0",
"symfony/mailgun-mailer": "^7.0"
"symfony/mailgun-mailer": "^7.0",
"twig/twig": "3.8.0"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.9",
@@ -116,6 +118,7 @@
"fakerphp/faker": "1.*",
"filp/whoops": "2.*",
"larastan/larastan": "^2",
"laravel-json-api/testing": "^3.0",
"mockery/mockery": "1.*",
"phpstan/extension-installer": "^1.3",
"phpstan/phpstan": "^1.10",

1319
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -62,7 +62,7 @@ return [
|
*/
'editor' => env('DEBUGBAR_EDITOR') ?: env('IGNITION_EDITOR', 'phpstorm'),
'editor' => env('DEBUGBAR_EDITOR') ?? env('IGNITION_EDITOR', 'phpstorm'),
/*
|--------------------------------------------------------------------------

View File

@@ -117,7 +117,7 @@ return [
'expression_engine' => false,
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2024-04-22',
'version' => 'develop/2024-05-13',
'api_version' => '2.0.14',
'db_version' => 24,
@@ -920,11 +920,31 @@ return [
// preselected account lists possibilities:
'preselected_accounts' => ['all', 'assets', 'liabilities'],
// allowed sort columns for API's
// allowed filters (search) for APIs
'filters' => [
'allowed' => [
'accounts' => [
'name' => 'string',
'active' => 'boolean',
'iban' => 'iban',
'balance' => 'numeric',
'last_activity' => 'date',
'balance_difference' => 'numeric',
],
],
],
// allowed sort columns for APIs
'sorting' => [
'allowed' => [
'transactions' => ['description', 'amount'],
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference'],
'accounts' => ['name', 'active', 'iban', 'balance', 'last_activity', 'balance_difference', 'current_debt'],
],
],
'full_data_set' => [
'account' => ['last_activity', 'balance_difference', 'current_balance', 'current_debt'],
],
'valid_query_filters' => [
'account' => ['name', 'iban', 'active'],
],
];

35
config/jsonapi.php Normal file
View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
use FireflyIII\JsonApi\V3\Server;
return [
/*
|--------------------------------------------------------------------------
| Root Namespace
|--------------------------------------------------------------------------
|
| The root JSON:API namespace, within your application's namespace.
| This is used when generating any class that does not sit *within*
| a server's namespace. For example, new servers and filters.
|
| By default this is set to `JsonApi` which means the root namespace
| will be `\App\JsonApi`, if your application's namespace is `App`.
*/
'namespace' => 'JsonApi',
/*
|--------------------------------------------------------------------------
| Servers
|--------------------------------------------------------------------------
|
| A list of the JSON:API compliant APIs in your application, referred to
| as "servers". They must be listed below, with the array key being the
| unique name for each server, and the value being the fully-qualified
| class name of the server class.
*/
'servers' => [
'v3' => Server::class,
],
];

View File

@@ -34,11 +34,33 @@ return [
'form' => [
'title',
],
'list' => [
'drag_and_drop',
'active',
'name',
'type',
'number',
'liability_type',
'current_balance',
'last_activity',
'amount_due',
'balance_difference',
'menu',
],
'validation' => [
'bad_type_source',
'bad_type_destination',
],
'firefly' => [
'liability_direction_debit_short',
'liability_direction_credit_short',
'interest_calc_yearly',
'interest_calc_',
'interest_calc_daily',
'interest_calc_monthly',
'interest_calc_weekly',
'interest_calc_half-year',
'interest_calc_quarterly',
'spent',
'administration_owner',
'administration_you',
@@ -94,6 +116,19 @@ return [
'account_role_savingAsset',
'account_role_ccAsset',
'account_role_cashWalletAsset',
// 'account_column_opt_drag_and_drop',
// 'account_column_opt_active',
// 'account_column_opt_name',
// 'account_column_opt_type',
// 'account_column_opt_liability_type',
// 'account_column_opt_liability_direction',
// 'account_column_opt_liability_interest',
// 'account_column_opt_number',
// 'account_column_opt_current_balance',
// 'account_column_opt_amount_due',
// 'account_column_opt_last_activity',
// 'account_column_opt_balance_difference',
// 'account_column_opt_menu',
],
],
'v1' => [

View File

@@ -284,7 +284,7 @@ class CreateMainTables extends Migration
$table->integer('budget_id', false, true);
$table->date('startdate');
$table->decimal('amount', 32, 12);
$table->string('repeat_freq', 30);
$table->string('repeat_freq', 30)->nullable();
$table->boolean('repeats')->default(0);
$table->foreign('budget_id')->references('id')->on('budgets')->onDelete('cascade');
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
/**
* Run the migrations.
*/
public function up(): void
{
if (!Schema::hasTable('account_balances')) {
Schema::create('account_balances', function (Blueprint $table): void {
$table->id();
$table->timestamps();
$table->string('title', 100)->nullable();
$table->integer('account_id', false, true);
$table->integer('transaction_currency_id', false, true);
$table->date('date')->nullable();
$table->integer('transaction_journal_id', false, true)->nullable();
$table->decimal('balance', 32, 12);
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('transaction_journal_id')->references('id')->on('transaction_journals')->onDelete('cascade');
$table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
$table->unique(['account_id', 'transaction_currency_id', 'transaction_journal_id', 'date', 'title'], 'unique_account_currency');
});
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('account_balances');
}
};

659
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,11 +9,33 @@
"form": {
"title": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "\u0410\u043a\u0442\u0438\u0432\u0435\u043d \u043b\u0438 \u0435?",
"name": "\u0418\u043c\u0435",
"type": "\u0412\u0438\u0434",
"number": "Account number",
"liability_type": "\u0412\u0438\u0434 \u043d\u0430 \u0437\u0430\u0434\u044a\u043b\u0436\u0435\u043d\u0438\u0435\u0442\u043e",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "\u0414\u044a\u043b\u0436\u0430 \u0434\u044a\u043b\u0433",
"liability_direction_credit_short": "\u0414\u044a\u043b\u0436\u044a\u0442 \u043c\u0438 \u0434\u044a\u043b\u0433",
"interest_calc_yearly": "\u0413\u043e\u0434\u0438\u0448\u043d\u043e",
"interest_calc_": "\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430",
"interest_calc_daily": "\u041d\u0430 \u0434\u0435\u043d",
"interest_calc_monthly": "\u041d\u0430 \u043c\u0435\u0441\u0435\u0446",
"interest_calc_weekly": "\u0421\u0435\u0434\u043c\u0438\u0447\u043d\u043e",
"interest_calc_half-year": "\u0417\u0430 \u043f\u043e\u043b\u043e\u0432\u0438\u043d \u0433\u043e\u0434\u0438\u043d\u0430",
"interest_calc_quarterly": "\u0417\u0430 \u0442\u0440\u0438\u043c\u0435\u0441\u0435\u0447\u0438\u0435",
"spent": "\u041f\u043e\u0445\u0430\u0440\u0447\u0435\u043d\u0438",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "\u0417\u0430\u0433\u043b\u0430\u0432\u0438\u0435"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "\u0410\u043a\u0442\u0438\u0432\u0435\u043d \u043b\u0438 \u0435?",
"name": "\u0418\u043c\u0435",
"type": "\u0412\u0438\u0434",
"number": "Account number",
"liability_type": "\u0412\u0438\u0434 \u043d\u0430 \u0437\u0430\u0434\u044a\u043b\u0436\u0435\u043d\u0438\u0435\u0442\u043e",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "\u0414\u044a\u043b\u0436\u0430 \u0434\u044a\u043b\u0433",
"liability_direction_credit_short": "\u0414\u044a\u043b\u0436\u044a\u0442 \u043c\u0438 \u0434\u044a\u043b\u0433",
"interest_calc_yearly": "\u0413\u043e\u0434\u0438\u0448\u043d\u043e",
"interest_calc_": "\u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430",
"interest_calc_daily": "\u041d\u0430 \u0434\u0435\u043d",
"interest_calc_monthly": "\u041d\u0430 \u043c\u0435\u0441\u0435\u0446",
"interest_calc_weekly": "\u0421\u0435\u0434\u043c\u0438\u0447\u043d\u043e",
"interest_calc_half-year": "\u0417\u0430 \u043f\u043e\u043b\u043e\u0432\u0438\u043d \u0433\u043e\u0434\u0438\u043d\u0430",
"interest_calc_quarterly": "\u0417\u0430 \u0442\u0440\u0438\u043c\u0435\u0441\u0435\u0447\u0438\u0435",
"spent": "\u041f\u043e\u0445\u0430\u0440\u0447\u0435\u043d\u0438",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "T\u00edtol"
},
"list": {
"drag_and_drop": "Arrossega i deixa anar",
"active": "Est\u00e0 actiu?",
"name": "Nom",
"type": "Tipus",
"number": "N\u00famero de compte",
"liability_type": "Tipus de passiu",
"current_balance": "Balan\u00e7 actual",
"last_activity": "Darrera activitat",
"amount_due": "Import pendent",
"balance_difference": "Difer\u00e8ncia de saldo",
"menu": "Men\u00fa"
},
"validation": {
"bad_type_source": "Firefly III no pot determinar el tipus de transacci\u00f3 a partir d'aquest compte font.",
"bad_type_destination": "Firefly III no pot determinar el tipus de transacci\u00f3 a partir d'aquest compte de dest\u00ed."
},
"firefly": {
"liability_direction_debit_short": "Ho dec",
"liability_direction_credit_short": "Se'm deu",
"interest_calc_yearly": "Per any",
"interest_calc_": "desconegut",
"interest_calc_daily": "Per dia",
"interest_calc_monthly": "Per mes",
"interest_calc_weekly": "Per setmana",
"interest_calc_half-year": "Cada mig any",
"interest_calc_quarterly": "Per quadrimestre",
"spent": "Gastat",
"administration_owner": "Propietari de l'administraci\u00f3: {{email}}",
"administration_you": "El teu rol: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "T\u00edtol"
},
"list": {
"drag_and_drop": "Arrossega i deixa anar",
"active": "Est\u00e0 actiu?",
"name": "Nom",
"type": "Tipus",
"number": "N\u00famero de compte",
"liability_type": "Tipus de passiu",
"current_balance": "Balan\u00e7 actual",
"last_activity": "Darrera activitat",
"amount_due": "Import pendent",
"balance_difference": "Difer\u00e8ncia de saldo",
"menu": "Men\u00fa"
},
"validation": {
"bad_type_source": "Firefly III no pot determinar el tipus de transacci\u00f3 a partir d'aquest compte font.",
"bad_type_destination": "Firefly III no pot determinar el tipus de transacci\u00f3 a partir d'aquest compte de dest\u00ed."
},
"firefly": {
"liability_direction_debit_short": "Ho dec",
"liability_direction_credit_short": "Se'm deu",
"interest_calc_yearly": "Per any",
"interest_calc_": "desconegut",
"interest_calc_daily": "Per dia",
"interest_calc_monthly": "Per mes",
"interest_calc_weekly": "Per setmana",
"interest_calc_half-year": "Cada mig any",
"interest_calc_quarterly": "Per quadrimestre",
"spent": "Gastat",
"administration_owner": "Propietari de l'administraci\u00f3: {{email}}",
"administration_you": "El teu rol: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "N\u00e1zev"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Aktivn\u00ed?",
"name": "Jm\u00e9no",
"type": "Typ",
"number": "Account number",
"liability_type": "Typ z\u00e1vazku",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "Owe this debt",
"liability_direction_credit_short": "Owed this debt",
"interest_calc_yearly": "Za rok",
"interest_calc_": "nezn\u00e1m\u00e9",
"interest_calc_daily": "Za den",
"interest_calc_monthly": "Za m\u011bs\u00edc",
"interest_calc_weekly": "Per week",
"interest_calc_half-year": "Per half year",
"interest_calc_quarterly": "Per quarter",
"spent": "Utraceno",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "N\u00e1zev"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Aktivn\u00ed?",
"name": "Jm\u00e9no",
"type": "Typ",
"number": "Account number",
"liability_type": "Typ z\u00e1vazku",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "Owe this debt",
"liability_direction_credit_short": "Owed this debt",
"interest_calc_yearly": "Za rok",
"interest_calc_": "nezn\u00e1m\u00e9",
"interest_calc_daily": "Za den",
"interest_calc_monthly": "Za m\u011bs\u00edc",
"interest_calc_weekly": "Per week",
"interest_calc_half-year": "Per half year",
"interest_calc_quarterly": "Per quarter",
"spent": "Utraceno",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Titel"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Aktiv?",
"name": "Navn",
"type": "Type",
"number": "Account number",
"liability_type": "G\u00e6ldstype",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III kan ikke bestemme transaktionstypen baseret p\u00e5 denne kildekonto.",
"bad_type_destination": "Firefly III kan ikke bestemme transaktionstypen baseret p\u00e5 denne destinationskonto."
},
"firefly": {
"liability_direction_debit_short": "Ejer denne g\u00e6ld",
"liability_direction_credit_short": "Ejer denne g\u00e6ld",
"interest_calc_yearly": "Pr. \u00e5r",
"interest_calc_": "ukendt",
"interest_calc_daily": "Pr. dag",
"interest_calc_monthly": "Pr. m\u00e5ned",
"interest_calc_weekly": "Pr. uge",
"interest_calc_half-year": "Hvert halve \u00e5r",
"interest_calc_quarterly": "Pr. kvartal",
"spent": "Spent",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Titel"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Aktiv?",
"name": "Navn",
"type": "Type",
"number": "Account number",
"liability_type": "G\u00e6ldstype",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III kan ikke bestemme transaktionstypen baseret p\u00e5 denne kildekonto.",
"bad_type_destination": "Firefly III kan ikke bestemme transaktionstypen baseret p\u00e5 denne destinationskonto."
},
"firefly": {
"liability_direction_debit_short": "Ejer denne g\u00e6ld",
"liability_direction_credit_short": "Ejer denne g\u00e6ld",
"interest_calc_yearly": "Pr. \u00e5r",
"interest_calc_": "ukendt",
"interest_calc_daily": "Pr. dag",
"interest_calc_monthly": "Pr. m\u00e5ned",
"interest_calc_weekly": "Pr. uge",
"interest_calc_half-year": "Hvert halve \u00e5r",
"interest_calc_quarterly": "Pr. kvartal",
"spent": "Spent",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,19 +9,41 @@
"form": {
"title": "Titel"
},
"list": {
"drag_and_drop": "Ziehen und Ablegen",
"active": "Aktiv?",
"name": "Name",
"type": "Typ",
"number": "Kontonummer",
"liability_type": "Verbindlichkeitsart",
"current_balance": "Aktueller Kontostand",
"last_activity": "Letzte Aktivit\u00e4t",
"amount_due": "F\u00e4lliger Betrag",
"balance_difference": "Saldendifferenz",
"menu": "Men\u00fc"
},
"validation": {
"bad_type_source": "Firefly III kann die Buchungsart anhand dieses Quellkontos nicht ermitteln.",
"bad_type_destination": "Firefly III kann die Buchungsart anhand dieses Zielkontos nicht ermitteln."
},
"firefly": {
"liability_direction_debit_short": "Schuldiger Betrag",
"liability_direction_credit_short": "Geschuldeter Betrag",
"interest_calc_yearly": "J\u00e4hrlich",
"interest_calc_": "Unbekannt",
"interest_calc_daily": "T\u00e4glich",
"interest_calc_monthly": "Monatlich",
"interest_calc_weekly": "Pro Woche",
"interest_calc_half-year": "Halbj\u00e4hrlich",
"interest_calc_quarterly": "Viertelj\u00e4hrlich",
"spent": "Ausgegeben",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Ihre Funktion: {{role}}",
"administration_role_owner": "Owner",
"administration_role_owner": "Inhaber",
"administration_role_ro": "Schreibgesch\u00fctzt",
"administration_role_mng_trx": "Buchungen verwalten",
"administration_role_mng_meta": "Klassifizierungs- und Metadaten verwalten",
"administration_role_mng_budgets": "Manage budgets",
"administration_role_mng_budgets": "Budgets verwalten",
"administration_role_mng_piggies": "Sparschweine verwalten",
"administration_role_mng_subscriptions": "Abonnements verwalten",
"administration_role_mng_rules": "Regeln verwalten",

View File

@@ -9,19 +9,41 @@
"form": {
"title": "Titel"
},
"list": {
"drag_and_drop": "Ziehen und Ablegen",
"active": "Aktiv?",
"name": "Name",
"type": "Typ",
"number": "Kontonummer",
"liability_type": "Verbindlichkeitsart",
"current_balance": "Aktueller Kontostand",
"last_activity": "Letzte Aktivit\u00e4t",
"amount_due": "F\u00e4lliger Betrag",
"balance_difference": "Saldendifferenz",
"menu": "Men\u00fc"
},
"validation": {
"bad_type_source": "Firefly III kann die Buchungsart anhand dieses Quellkontos nicht ermitteln.",
"bad_type_destination": "Firefly III kann die Buchungsart anhand dieses Zielkontos nicht ermitteln."
},
"firefly": {
"liability_direction_debit_short": "Schuldiger Betrag",
"liability_direction_credit_short": "Geschuldeter Betrag",
"interest_calc_yearly": "J\u00e4hrlich",
"interest_calc_": "Unbekannt",
"interest_calc_daily": "T\u00e4glich",
"interest_calc_monthly": "Monatlich",
"interest_calc_weekly": "Pro Woche",
"interest_calc_half-year": "Halbj\u00e4hrlich",
"interest_calc_quarterly": "Viertelj\u00e4hrlich",
"spent": "Ausgegeben",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Ihre Funktion: {{role}}",
"administration_role_owner": "Owner",
"administration_role_owner": "Inhaber",
"administration_role_ro": "Schreibgesch\u00fctzt",
"administration_role_mng_trx": "Buchungen verwalten",
"administration_role_mng_meta": "Klassifizierungs- und Metadaten verwalten",
"administration_role_mng_budgets": "Manage budgets",
"administration_role_mng_budgets": "Budgets verwalten",
"administration_role_mng_piggies": "Sparschweine verwalten",
"administration_role_mng_subscriptions": "Abonnements verwalten",
"administration_role_mng_rules": "Regeln verwalten",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "\u03a4\u03af\u03c4\u03bb\u03bf\u03c2"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "\u0395\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03cc;",
"name": "\u038c\u03bd\u03bf\u03bc\u03b1",
"type": "\u03a4\u03cd\u03c0\u03bf\u03c2",
"number": "Account number",
"liability_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c5\u03c0\u03bf\u03c7\u03c1\u03ad\u03c9\u03c3\u03b7\u03c2",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "\u03a4\u03bf Firefly III \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03b5\u03b9 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u03bc\u03b5 \u03b2\u03ac\u03c3\u03b7 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2.",
"bad_type_destination": "\u03a4\u03bf Firefly III \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03b5\u03b9 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u03bc\u03b5 \u03b2\u03ac\u03c3\u03b7 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd."
},
"firefly": {
"liability_direction_debit_short": "\u039f\u03c6\u03b5\u03af\u03bb\u03c9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c7\u03c1\u03ad\u03bf\u03c2",
"liability_direction_credit_short": "\u039c\u03bf\u03c5 \u03bf\u03c6\u03b5\u03af\u03bb\u03bf\u03c5\u03bd \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c7\u03c1\u03ad\u03bf\u03c2",
"interest_calc_yearly": "\u0391\u03bd\u03ac \u03ad\u03c4\u03bf\u03c2",
"interest_calc_": "\u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf",
"interest_calc_daily": "\u0391\u03bd\u03ac \u03b7\u03bc\u03ad\u03c1\u03b1",
"interest_calc_monthly": "\u0391\u03bd\u03ac \u03bc\u03ae\u03bd\u03b1",
"interest_calc_weekly": "\u0391\u03bd\u03ac \u03b5\u03b2\u03b4\u03bf\u03bc\u03ac\u03b4\u03b1",
"interest_calc_half-year": "\u0391\u03bd\u03ac \u03b5\u03be\u03ac\u03bc\u03b7\u03bd\u03bf",
"interest_calc_quarterly": "\u0391\u03bd\u03ac \u03c4\u03c1\u03af\u03bc\u03b7\u03bd\u03bf",
"spent": "\u0394\u03b1\u03c0\u03b1\u03bd\u03ae\u03b8\u03b7\u03ba\u03b1\u03bd",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "\u03a4\u03af\u03c4\u03bb\u03bf\u03c2"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "\u0395\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03cc;",
"name": "\u038c\u03bd\u03bf\u03bc\u03b1",
"type": "\u03a4\u03cd\u03c0\u03bf\u03c2",
"number": "Account number",
"liability_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c5\u03c0\u03bf\u03c7\u03c1\u03ad\u03c9\u03c3\u03b7\u03c2",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "\u03a4\u03bf Firefly III \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03b5\u03b9 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u03bc\u03b5 \u03b2\u03ac\u03c3\u03b7 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c0\u03c1\u03bf\u03ad\u03bb\u03b5\u03c5\u03c3\u03b7\u03c2.",
"bad_type_destination": "\u03a4\u03bf Firefly III \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03ba\u03b1\u03b8\u03bf\u03c1\u03af\u03c3\u03b5\u03b9 \u03c4\u03bf\u03bd \u03c4\u03cd\u03c0\u03bf \u03c3\u03c5\u03bd\u03b1\u03bb\u03bb\u03b1\u03b3\u03ae\u03c2 \u03bc\u03b5 \u03b2\u03ac\u03c3\u03b7 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc \u03c0\u03c1\u03bf\u03bf\u03c1\u03b9\u03c3\u03bc\u03bf\u03cd."
},
"firefly": {
"liability_direction_debit_short": "\u039f\u03c6\u03b5\u03af\u03bb\u03c9 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c7\u03c1\u03ad\u03bf\u03c2",
"liability_direction_credit_short": "\u039c\u03bf\u03c5 \u03bf\u03c6\u03b5\u03af\u03bb\u03bf\u03c5\u03bd \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c7\u03c1\u03ad\u03bf\u03c2",
"interest_calc_yearly": "\u0391\u03bd\u03ac \u03ad\u03c4\u03bf\u03c2",
"interest_calc_": "\u03ac\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf",
"interest_calc_daily": "\u0391\u03bd\u03ac \u03b7\u03bc\u03ad\u03c1\u03b1",
"interest_calc_monthly": "\u0391\u03bd\u03ac \u03bc\u03ae\u03bd\u03b1",
"interest_calc_weekly": "\u0391\u03bd\u03ac \u03b5\u03b2\u03b4\u03bf\u03bc\u03ac\u03b4\u03b1",
"interest_calc_half-year": "\u0391\u03bd\u03ac \u03b5\u03be\u03ac\u03bc\u03b7\u03bd\u03bf",
"interest_calc_quarterly": "\u0391\u03bd\u03ac \u03c4\u03c1\u03af\u03bc\u03b7\u03bd\u03bf",
"spent": "\u0394\u03b1\u03c0\u03b1\u03bd\u03ae\u03b8\u03b7\u03ba\u03b1\u03bd",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Title"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Is active?",
"name": "Name",
"type": "Type",
"number": "Account number",
"liability_type": "Type of liability",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "Owe this debt",
"liability_direction_credit_short": "Owed this debt",
"interest_calc_yearly": "Per year",
"interest_calc_": "unknown",
"interest_calc_daily": "Per day",
"interest_calc_monthly": "Per month",
"interest_calc_weekly": "Per week",
"interest_calc_half-year": "Per half year",
"interest_calc_quarterly": "Per quarter",
"spent": "Spent",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Title"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Is active?",
"name": "Name",
"type": "Type",
"number": "Account number",
"liability_type": "Type of liability",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "Owe this debt",
"liability_direction_credit_short": "Owed this debt",
"interest_calc_yearly": "Per year",
"interest_calc_": "unknown",
"interest_calc_daily": "Per day",
"interest_calc_monthly": "Per month",
"interest_calc_weekly": "Per week",
"interest_calc_half-year": "Per half year",
"interest_calc_quarterly": "Per quarter",
"spent": "Spent",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Title"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Is active?",
"name": "Name",
"type": "Type",
"number": "Account number",
"liability_type": "Type of liability",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "Owe this debt",
"liability_direction_credit_short": "Owed this debt",
"interest_calc_yearly": "Per year",
"interest_calc_": "unknown",
"interest_calc_daily": "Per day",
"interest_calc_monthly": "Per month",
"interest_calc_weekly": "Per week",
"interest_calc_half-year": "Per half year",
"interest_calc_quarterly": "Per quarter",
"spent": "Spent",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Title"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Is active?",
"name": "Name",
"type": "Type",
"number": "Account number",
"liability_type": "Type of liability",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "Owe this debt",
"liability_direction_credit_short": "Owed this debt",
"interest_calc_yearly": "Per year",
"interest_calc_": "unknown",
"interest_calc_daily": "Per day",
"interest_calc_monthly": "Per month",
"interest_calc_weekly": "Per week",
"interest_calc_half-year": "Per half year",
"interest_calc_quarterly": "Per quarter",
"spent": "Spent",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "T\u00edtulo"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "\u00bfEst\u00e1 Activo?",
"name": "Nombre",
"type": "Tipo",
"number": "Account number",
"liability_type": "Tipo de pasivo",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III no puede determinar el tipo de transacci\u00f3n basado en esta cuenta de origen.",
"bad_type_destination": "Firefly III no puede determinar el tipo de transacci\u00f3n basado en esta cuenta de destino."
},
"firefly": {
"liability_direction_debit_short": "Tiene esta deuda",
"liability_direction_credit_short": "Ten\u00eda esta deuda",
"interest_calc_yearly": "Por a\u00f1o",
"interest_calc_": "desconocido",
"interest_calc_daily": "Por dia",
"interest_calc_monthly": "Por mes",
"interest_calc_weekly": "Por semana",
"interest_calc_half-year": "Por semestre",
"interest_calc_quarterly": "Por trimestre",
"spent": "Gastado",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "T\u00edtulo"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "\u00bfEst\u00e1 Activo?",
"name": "Nombre",
"type": "Tipo",
"number": "Account number",
"liability_type": "Tipo de pasivo",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III no puede determinar el tipo de transacci\u00f3n basado en esta cuenta de origen.",
"bad_type_destination": "Firefly III no puede determinar el tipo de transacci\u00f3n basado en esta cuenta de destino."
},
"firefly": {
"liability_direction_debit_short": "Tiene esta deuda",
"liability_direction_credit_short": "Ten\u00eda esta deuda",
"interest_calc_yearly": "Por a\u00f1o",
"interest_calc_": "desconocido",
"interest_calc_daily": "Por dia",
"interest_calc_monthly": "Por mes",
"interest_calc_weekly": "Por semana",
"interest_calc_half-year": "Por semestre",
"interest_calc_quarterly": "Por trimestre",
"spent": "Gastado",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Otsikko"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Aktiivinen?",
"name": "Nimi",
"type": "Tyyppi",
"number": "Account number",
"liability_type": "Lainatyyppi",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "Olen velkaa",
"liability_direction_credit_short": "Minulle ollaan velkaa",
"interest_calc_yearly": "Vuodessa",
"interest_calc_": "tuntematon",
"interest_calc_daily": "P\u00e4iv\u00e4ss\u00e4",
"interest_calc_monthly": "Kuukaudessa",
"interest_calc_weekly": "Viikossa",
"interest_calc_half-year": "Puolessa vuodessa",
"interest_calc_quarterly": "Nelj\u00e4nnest\u00e4 kohden",
"spent": "K\u00e4ytetty",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Otsikko"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Aktiivinen?",
"name": "Nimi",
"type": "Tyyppi",
"number": "Account number",
"liability_type": "Lainatyyppi",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III can't determine the transaction type based on this source account.",
"bad_type_destination": "Firefly III can't determine the transaction type based on this destination account."
},
"firefly": {
"liability_direction_debit_short": "Olen velkaa",
"liability_direction_credit_short": "Minulle ollaan velkaa",
"interest_calc_yearly": "Vuodessa",
"interest_calc_": "tuntematon",
"interest_calc_daily": "P\u00e4iv\u00e4ss\u00e4",
"interest_calc_monthly": "Kuukaudessa",
"interest_calc_weekly": "Viikossa",
"interest_calc_half-year": "Puolessa vuodessa",
"interest_calc_quarterly": "Nelj\u00e4nnest\u00e4 kohden",
"spent": "K\u00e4ytetty",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Titre"
},
"list": {
"drag_and_drop": "Glisser-d\u00e9poser",
"active": "Actif ?",
"name": "Nom",
"type": "Type",
"number": "N\u00b0 de compte",
"liability_type": "Type de passif",
"current_balance": "Solde actuel",
"last_activity": "Activit\u00e9 r\u00e9cente",
"amount_due": "Montant d\u00fb",
"balance_difference": "Diff\u00e9rence de solde",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III ne peut pas d\u00e9terminer le type de transaction bas\u00e9 sur ce compte source.",
"bad_type_destination": "Firefly III ne peut pas d\u00e9terminer le type de transaction bas\u00e9 sur ce compte de destination."
},
"firefly": {
"liability_direction_debit_short": "Pr\u00eateur",
"liability_direction_credit_short": "Emprunteur",
"interest_calc_yearly": "Par an",
"interest_calc_": "inconnu",
"interest_calc_daily": "Par jour",
"interest_calc_monthly": "Par mois",
"interest_calc_weekly": "Par semaine",
"interest_calc_half-year": "Par semestre",
"interest_calc_quarterly": "Par trimestre",
"spent": "D\u00e9pens\u00e9",
"administration_owner": "Propri\u00e9taire de l'administration : {{email}}",
"administration_you": "Votre r\u00f4le : {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "Titre"
},
"list": {
"drag_and_drop": "Glisser-d\u00e9poser",
"active": "Actif ?",
"name": "Nom",
"type": "Type",
"number": "N\u00b0 de compte",
"liability_type": "Type de passif",
"current_balance": "Solde actuel",
"last_activity": "Activit\u00e9 r\u00e9cente",
"amount_due": "Montant d\u00fb",
"balance_difference": "Diff\u00e9rence de solde",
"menu": "Menu"
},
"validation": {
"bad_type_source": "Firefly III ne peut pas d\u00e9terminer le type de transaction bas\u00e9 sur ce compte source.",
"bad_type_destination": "Firefly III ne peut pas d\u00e9terminer le type de transaction bas\u00e9 sur ce compte de destination."
},
"firefly": {
"liability_direction_debit_short": "Pr\u00eateur",
"liability_direction_credit_short": "Emprunteur",
"interest_calc_yearly": "Par an",
"interest_calc_": "inconnu",
"interest_calc_daily": "Par jour",
"interest_calc_monthly": "Par mois",
"interest_calc_weekly": "Par semaine",
"interest_calc_half-year": "Par semestre",
"interest_calc_quarterly": "Par trimestre",
"spent": "D\u00e9pens\u00e9",
"administration_owner": "Propri\u00e9taire de l'administration : {{email}}",
"administration_you": "Votre r\u00f4le : {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "C\u00edm"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Akt\u00edv?",
"name": "N\u00e9v",
"type": "T\u00edpus",
"number": "Account number",
"liability_type": "A k\u00f6telezetts\u00e9g t\u00edpusa",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "A Firefly III nem tudja eld\u00f6nteni a tranzakci\u00f3 t\u00edpus\u00e1t a forr\u00e1ssz\u00e1mla alapj\u00e1n.",
"bad_type_destination": "A Firefly III nem tudja eld\u00f6nteni a tranzakci\u00f3 t\u00edpus\u00e1t a c\u00e9lsz\u00e1mla alapj\u00e1n."
},
"firefly": {
"liability_direction_debit_short": "Owe this debt",
"liability_direction_credit_short": "Owed this debt",
"interest_calc_yearly": "\u00c9vente",
"interest_calc_": "ismeretlen",
"interest_calc_daily": "Naponta",
"interest_calc_monthly": "Havonta",
"interest_calc_weekly": "Per week",
"interest_calc_half-year": "Per half year",
"interest_calc_quarterly": "Per quarter",
"spent": "Elk\u00f6lt\u00f6tt",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

View File

@@ -9,11 +9,33 @@
"form": {
"title": "C\u00edm"
},
"list": {
"drag_and_drop": "Drag and drop",
"active": "Akt\u00edv?",
"name": "N\u00e9v",
"type": "T\u00edpus",
"number": "Account number",
"liability_type": "A k\u00f6telezetts\u00e9g t\u00edpusa",
"current_balance": "Current balance",
"last_activity": "Last activity",
"amount_due": "Amount due",
"balance_difference": "Balance difference",
"menu": "Menu"
},
"validation": {
"bad_type_source": "A Firefly III nem tudja eld\u00f6nteni a tranzakci\u00f3 t\u00edpus\u00e1t a forr\u00e1ssz\u00e1mla alapj\u00e1n.",
"bad_type_destination": "A Firefly III nem tudja eld\u00f6nteni a tranzakci\u00f3 t\u00edpus\u00e1t a c\u00e9lsz\u00e1mla alapj\u00e1n."
},
"firefly": {
"liability_direction_debit_short": "Owe this debt",
"liability_direction_credit_short": "Owed this debt",
"interest_calc_yearly": "\u00c9vente",
"interest_calc_": "ismeretlen",
"interest_calc_daily": "Naponta",
"interest_calc_monthly": "Havonta",
"interest_calc_weekly": "Per week",
"interest_calc_half-year": "Per half year",
"interest_calc_quarterly": "Per quarter",
"spent": "Elk\u00f6lt\u00f6tt",
"administration_owner": "Administration owner: {{email}}",
"administration_you": "Your role: {{role}}",

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