mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-22 20:16:22 +00:00
Compare commits
158 Commits
develop-20
...
develop-20
Author | SHA1 | Date | |
---|---|---|---|
|
a476b4259c | ||
|
0c03ec5ddd | ||
|
2d95b7f8ef | ||
|
34cc79ce4f | ||
|
6ff9943fe0 | ||
|
a0166b45e4 | ||
|
d27b035b20 | ||
|
f3b387fc22 | ||
|
03904ffcde | ||
|
a7973190c2 | ||
|
671ff95f22 | ||
|
01181ceea9 | ||
|
97643639d1 | ||
|
424783c47b | ||
|
ea0ced70b2 | ||
|
1a633e64ef | ||
|
30da3f4399 | ||
|
6bdff95d87 | ||
|
895ae279d5 | ||
|
8b57f45963 | ||
|
a2d2b7edd3 | ||
|
3d65f00c6e | ||
|
333004c4d9 | ||
|
7451659824 | ||
|
ec89c23ace | ||
|
92d07d346f | ||
|
a7ac894af2 | ||
|
20f89e3a7c | ||
|
5d2f11c3c7 | ||
|
1eea79e431 | ||
|
1aaaac67ca | ||
|
1eb86639c9 | ||
|
28b911c4ee | ||
|
c525c70ec0 | ||
|
1f7d6e218b | ||
|
a69b6d9ce2 | ||
|
28b2ddde18 | ||
|
a16cc73c77 | ||
|
46395e350a | ||
|
f62e49090c | ||
|
af3b40a314 | ||
|
c3cea0fa9e | ||
|
6cbdb2ce70 | ||
|
b78460100d | ||
|
bf6e1cb0e1 | ||
|
6a53f5031c | ||
|
ae15ec01e8 | ||
|
fe3c7c47c4 | ||
|
68b934010c | ||
|
22852bd238 | ||
|
5b3b1804f3 | ||
|
f2588eb343 | ||
|
64a643ceec | ||
|
1add505644 | ||
|
9663eb6a19 | ||
|
f30a24a02f | ||
|
68655d60a6 | ||
|
63b0efcd81 | ||
|
93284682c8 | ||
|
3bafcb6ad2 | ||
|
942d027556 | ||
|
a60882d5f5 | ||
|
680f554981 | ||
|
20e4dc07ce | ||
|
184d8eb027 | ||
|
59725b088a | ||
|
32fca4a9f5 | ||
|
7dccf6ec48 | ||
|
917665feac | ||
|
06c50b68c2 | ||
|
7035c399d8 | ||
|
7c0ac5805c | ||
|
3424741583 | ||
|
baf0297994 | ||
|
31d06752fa | ||
|
8a27154798 | ||
|
6d87e38ec0 | ||
|
ccdc30a6fb | ||
|
90005538d3 | ||
|
f4e0428ebc | ||
|
bd1326eca9 | ||
|
bdfa834251 | ||
|
4a9aeb4e44 | ||
|
3886c0fbde | ||
|
d998eff56e | ||
|
d73df9bf0a | ||
|
754f2f3a34 | ||
|
43fd7c928a | ||
|
05768c2e73 | ||
|
3feb2c9955 | ||
|
7d9f3ac473 | ||
|
8a5755c8f1 | ||
|
a75a760019 | ||
|
78d1a130d2 | ||
|
79fd43f32b | ||
|
7a7bd65a27 | ||
|
01a9ecccac | ||
|
c1b3c71090 | ||
|
323d04fe6c | ||
|
80c2f1ea3f | ||
|
784494871d | ||
|
8e93af5cc7 | ||
|
ebfdeeedaa | ||
|
1d02ed6a56 | ||
|
cca53cb0e8 | ||
|
ab33aee4b1 | ||
|
41d4fab071 | ||
|
f0a1913dc6 | ||
|
b956b463c2 | ||
|
43603c4990 | ||
|
196e738f60 | ||
|
59e2ea357a | ||
|
5e9d942069 | ||
|
53d5bedd85 | ||
|
49c68af07b | ||
|
c84c8e1aef | ||
|
a8d43d7174 | ||
|
1087278890 | ||
|
ae5912ab52 | ||
|
035bd96ae5 | ||
|
7283c616a0 | ||
|
5706666bb6 | ||
|
4607466fb6 | ||
|
34bcfcfe9b | ||
|
05986cb6a6 | ||
|
0c4ee9f043 | ||
|
ff222795cf | ||
|
e0c76695ee | ||
|
ae126e8322 | ||
|
8f9c35fbe8 | ||
|
84efd6e2ee | ||
|
b1fbe4e909 | ||
|
8576877072 | ||
|
c298aced01 | ||
|
ac61a78d8d | ||
|
fce90a94c4 | ||
|
023a4f178d | ||
|
ef254995ad | ||
|
faeb74634b | ||
|
b5baae373f | ||
|
63de711cda | ||
|
bd28d116cb | ||
|
7efc2861bc | ||
|
5c689a2ca2 | ||
|
d5d65df76f | ||
|
df7d4f700c | ||
|
efebe3cb41 | ||
|
2ba5b6ae49 | ||
|
31d93efab2 | ||
|
657b95485c | ||
|
3bfc12f93b | ||
|
ccfd2f2ac3 | ||
|
fb3fe0d87b | ||
|
c80c6d52fe | ||
|
0fb3c0c7bf | ||
|
43c625bee2 | ||
|
967a5dd256 | ||
|
3c9d1bcaa1 |
@@ -19,26 +19,25 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
|
||||
|
||||
$current = __DIR__;
|
||||
|
||||
$paths = [
|
||||
$current . '/../../app',
|
||||
$current . '/../../config',
|
||||
$current . '/../../database',
|
||||
$current . '/../../routes',
|
||||
$current . '/../../tests',
|
||||
$current . '/../../resources/lang/en_US',
|
||||
];
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in($paths);
|
||||
|
||||
|
||||
$config = new PhpCsFixer\Config();
|
||||
$config->setParallelConfig(ParallelConfigFactory::detect());
|
||||
$config = (new PhpCsFixer\Config())
|
||||
// ->setUnsupportedPhpVersionAllowed(true) // use this when PHP 8.5 comes out.
|
||||
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
|
||||
;
|
||||
return $config->setRules(
|
||||
|
||||
[
|
||||
// rule sets
|
||||
'@PHP83Migration' => true,
|
||||
@@ -53,9 +52,6 @@ return $config->setRules(
|
||||
'statement_indentation' => true,
|
||||
'void_return' => true,
|
||||
|
||||
// about importing statements
|
||||
'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true],
|
||||
|
||||
// disabled rules
|
||||
'native_function_invocation' => false, // annoying
|
||||
'php_unit_data_provider_name' => false, // bloody annoying long test names
|
||||
@@ -64,9 +60,15 @@ return $config->setRules(
|
||||
'comment_to_phpdoc' => false, // breaks phpstan lines in combination with PHPStorm.
|
||||
'type_declaration_spaces' => false,
|
||||
'cast_spaces' => false,
|
||||
'phpdoc_to_comment' => false, // do not overrule single line comment style, breaks phpstan.
|
||||
|
||||
// enabled rules
|
||||
'global_namespace_import' => true, // matches with rector.
|
||||
|
||||
// complex rules
|
||||
'phpdoc_to_comment' => ['ignored_tags' => ['var']],
|
||||
'php_unit_test_case_static_method_calls' => [
|
||||
'call_type' => 'this',
|
||||
],
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'binary_operator_spaces' => [
|
||||
'default' => 'at_least_single_space',
|
||||
@@ -76,5 +78,7 @@ return $config->setRules(
|
||||
'??=' => 'align_single_space_minimal_by_scope',
|
||||
],
|
||||
],
|
||||
])
|
||||
]
|
||||
|
||||
)
|
||||
->setFinder($finder);
|
||||
|
73
.ci/php-cs-fixer/composer.lock
generated
73
.ci/php-cs-fixer/composer.lock
generated
@@ -406,58 +406,59 @@
|
||||
},
|
||||
{
|
||||
"name": "friendsofphp/php-cs-fixer",
|
||||
"version": "v3.75.0",
|
||||
"version": "v3.85.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
|
||||
"reference": "399a128ff2fdaf4281e4e79b755693286cdf325c"
|
||||
"reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/399a128ff2fdaf4281e4e79b755693286cdf325c",
|
||||
"reference": "399a128ff2fdaf4281e4e79b755693286cdf325c",
|
||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/2fb6d7f6c3398dca5786a1635b27405d73a417ba",
|
||||
"reference": "2fb6d7f6c3398dca5786a1635b27405d73a417ba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"clue/ndjson-react": "^1.0",
|
||||
"clue/ndjson-react": "^1.3",
|
||||
"composer/semver": "^3.4",
|
||||
"composer/xdebug-handler": "^3.0.3",
|
||||
"composer/xdebug-handler": "^3.0.5",
|
||||
"ext-filter": "*",
|
||||
"ext-hash": "*",
|
||||
"ext-json": "*",
|
||||
"ext-tokenizer": "*",
|
||||
"fidry/cpu-core-counter": "^1.2",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"react/child-process": "^0.6.5",
|
||||
"react/event-loop": "^1.0",
|
||||
"react/promise": "^2.0 || ^3.0",
|
||||
"react/socket": "^1.0",
|
||||
"react/stream": "^1.0",
|
||||
"sebastian/diff": "^4.0 || ^5.1 || ^6.0 || ^7.0",
|
||||
"symfony/console": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/filesystem": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/finder": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/options-resolver": "^5.4 || ^6.4 || ^7.0",
|
||||
"symfony/polyfill-mbstring": "^1.31",
|
||||
"symfony/polyfill-php80": "^1.31",
|
||||
"symfony/polyfill-php81": "^1.31",
|
||||
"symfony/process": "^5.4 || ^6.4 || ^7.2",
|
||||
"symfony/stopwatch": "^5.4 || ^6.4 || ^7.0"
|
||||
"react/child-process": "^0.6.6",
|
||||
"react/event-loop": "^1.5",
|
||||
"react/promise": "^3.2",
|
||||
"react/socket": "^1.16",
|
||||
"react/stream": "^1.4",
|
||||
"sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0",
|
||||
"symfony/console": "^5.4.47 || ^6.4.13 || ^7.0",
|
||||
"symfony/event-dispatcher": "^5.4.45 || ^6.4.13 || ^7.0",
|
||||
"symfony/filesystem": "^5.4.45 || ^6.4.13 || ^7.0",
|
||||
"symfony/finder": "^5.4.45 || ^6.4.17 || ^7.0",
|
||||
"symfony/options-resolver": "^5.4.45 || ^6.4.16 || ^7.0",
|
||||
"symfony/polyfill-mbstring": "^1.32",
|
||||
"symfony/polyfill-php80": "^1.32",
|
||||
"symfony/polyfill-php81": "^1.32",
|
||||
"symfony/process": "^5.4.47 || ^6.4.20 || ^7.2",
|
||||
"symfony/stopwatch": "^5.4.45 || ^6.4.19 || ^7.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"facile-it/paraunit": "^1.3.1 || ^2.6",
|
||||
"infection/infection": "^0.29.14",
|
||||
"justinrainbow/json-schema": "^5.3 || ^6.2",
|
||||
"keradus/cli-executor": "^2.1",
|
||||
"justinrainbow/json-schema": "^5.3 || ^6.4",
|
||||
"keradus/cli-executor": "^2.2",
|
||||
"mikey179/vfsstream": "^1.6.12",
|
||||
"php-coveralls/php-coveralls": "^2.7",
|
||||
"php-coveralls/php-coveralls": "^2.8",
|
||||
"php-cs-fixer/accessible-object": "^1.1",
|
||||
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
|
||||
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
|
||||
"phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.12",
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.3",
|
||||
"symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.3"
|
||||
"phpunit/phpunit": "^9.6.23 || ^10.5.47 || ^11.5.25",
|
||||
"symfony/polyfill-php84": "^1.32",
|
||||
"symfony/var-dumper": "^5.4.48 || ^6.4.23 || ^7.3.1",
|
||||
"symfony/yaml": "^5.4.45 || ^6.4.23 || ^7.3.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "For handling output formats in XML",
|
||||
@@ -498,7 +499,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.75.0"
|
||||
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.85.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -506,7 +507,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-03-31T18:40:42+00:00"
|
||||
"time": "2025-07-29T22:22:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
@@ -1256,16 +1257,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.3.0",
|
||||
"version": "v7.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44"
|
||||
"reference": "9e27aecde8f506ba0fd1d9989620c04a87697101"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/66c1440edf6f339fd82ed6c7caa76cb006211b44",
|
||||
"reference": "66c1440edf6f339fd82ed6c7caa76cb006211b44",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/9e27aecde8f506ba0fd1d9989620c04a87697101",
|
||||
"reference": "9e27aecde8f506ba0fd1d9989620c04a87697101",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1330,7 +1331,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.0"
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1346,7 +1347,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-05-24T10:34:04+00:00"
|
||||
"time": "2025-06-27T19:55:54+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
|
@@ -26,9 +26,10 @@ SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
cd $SCRIPT_DIR/php-cs-fixer
|
||||
composer update --quiet
|
||||
rm -f .php-cs-fixer.cache
|
||||
PHP_CS_FIXER_IGNORE_ENV=true ./vendor/bin/php-cs-fixer fix \
|
||||
./vendor/bin/php-cs-fixer fix \
|
||||
--config $SCRIPT_DIR/php-cs-fixer/.php-cs-fixer.php \
|
||||
--format=txt -v \
|
||||
--format=txt \
|
||||
-v \
|
||||
--allow-risky=yes
|
||||
|
||||
EXIT_CODE=$?
|
||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\EarlyReturn\Rector\If_\ChangeOrIfContinueToMultiContinueRector;
|
||||
use Rector\Php80\Rector\ClassMethod\AddParamBasedOnParentClassMethodRector;
|
||||
use Rector\Transform\Rector\String_\StringToClassConstantRector;
|
||||
use RectorLaravel\Set\LaravelLevelSetList;
|
||||
|
||||
@@ -32,6 +33,7 @@ use RectorLaravel\Set\LaravelLevelSetList;
|
||||
return RectorConfig::configure()
|
||||
->withSkip([
|
||||
ChangeOrIfContinueToMultiContinueRector::class,
|
||||
AddParamBasedOnParentClassMethodRector::class,
|
||||
StringToClassConstantRector::class => [
|
||||
__DIR__ . '/../app/Http/Controllers/Auth/LoginController.php',
|
||||
],
|
||||
|
@@ -329,7 +329,7 @@ FIREFLY_III_LAYOUT=v1
|
||||
# Which Query Parser implementation to use for the search engine and rules
|
||||
# 'new' is experimental, 'legacy' is the classic one
|
||||
#
|
||||
QUERY_PARSER_IMPLEMENTATION=legacy
|
||||
QUERY_PARSER_IMPLEMENTATION=new
|
||||
|
||||
#
|
||||
# Please make sure this URL matches the external URL of your Firefly III installation.
|
||||
|
13
.github/ISSUE_TEMPLATE/bug.yml
vendored
13
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Bug Report
|
||||
name: Bug report
|
||||
description: Report a bug in Firefly III (or associated tools)
|
||||
body:
|
||||
- type: checkboxes
|
||||
@@ -12,7 +12,7 @@ body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: I've found a bug and checked that ...
|
||||
description: Make sure that your request fulfills all of the following requirements. If one requirement cannot be satisfied, explain in detail why.
|
||||
description: Make sure that your request fulfills all of the following requirements. If one requirement cannot be satisfied, please explain why.
|
||||
options:
|
||||
- label: ... [the documentation](https://docs.firefly-iii.org/) does not mention anything about my problem
|
||||
- label: ... there are no open or closed issues that are related to my problem
|
||||
@@ -33,13 +33,6 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: Please describe precisely what you'd expect to happen. Be specific.
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
@@ -54,4 +47,4 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional info
|
||||
description: Please provide any additional information that seem useful.
|
||||
description: Please provide any additional information that seems useful.
|
||||
|
3
.github/ISSUE_TEMPLATE/config.yml
vendored
3
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -3,3 +3,6 @@ contact_links:
|
||||
- name: Ask a question
|
||||
url: https://github.com/firefly-iii/firefly-iii/discussions
|
||||
about: Please ask and answer questions here.
|
||||
- name: I need support!
|
||||
url: https://github.com/firefly-iii/firefly-iii/discussions
|
||||
about: I think I broke something...
|
||||
|
7
.github/ISSUE_TEMPLATE/fr.yml
vendored
7
.github/ISSUE_TEMPLATE/fr.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Feature Request
|
||||
name: Feature request
|
||||
description: Request a feature or enhancement in Firefly III (or associated tools)
|
||||
body:
|
||||
- type: checkboxes
|
||||
@@ -31,11 +31,6 @@ body:
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: What are alternatives?
|
||||
description: Please describe what alternatives currently exist.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context
|
||||
|
1
.github/workflows/cleanup.yml
vendored
1
.github/workflows/cleanup.yml
vendored
@@ -66,7 +66,6 @@ jobs:
|
||||
'label-actions.yml',
|
||||
'lock.yml',
|
||||
'release.yml',
|
||||
'sonarcloud.yml',
|
||||
'stale.yml'
|
||||
]
|
||||
|
||||
|
21
.github/workflows/release.yml
vendored
21
.github/workflows/release.yml
vendored
@@ -259,6 +259,12 @@ jobs:
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
echo "" >> output.txt
|
||||
echo ":warning: Please be careful with this pre-release, as it may not work as expected." >> output.txt
|
||||
|
||||
# donations!
|
||||
echo '' >> output.txt
|
||||
echo '### Support Firefly III' >> output.txt
|
||||
echo 'Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. For more information, please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information.' >> output.txt
|
||||
echo '' >> output.txt
|
||||
fi
|
||||
# describe a branch release
|
||||
if [[ "$version" == branch* ]]; then
|
||||
@@ -279,16 +285,31 @@ jobs:
|
||||
if [[ "develop" != "$version" ]] && [[ "$version" != branch* ]] && [[ "$version" != *alpha* ]] && [[ "$version" != *beta* ]]; then
|
||||
echo 'Describe the latest release'
|
||||
sudo chown -R runner:docker output.txt
|
||||
|
||||
# the changelog is in output.txt
|
||||
mv output.txt output2.txt
|
||||
|
||||
touch output.txt
|
||||
echo '' >> output.txt
|
||||
echo "Welcome to release $version of Firefly III. It contains the the latest fixes, translations and features. Docker users can find this release under the \`latest\` tag." >> output.txt
|
||||
echo '' >> output.txt
|
||||
|
||||
# add changelog to file.
|
||||
cat output2.txt >> output.txt
|
||||
echo '' >> output.txt
|
||||
rm -f output2.txt
|
||||
|
||||
echo '### Instructions' >> output.txt
|
||||
echo '' >> output.txt
|
||||
echo "* Installation instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/installation/docker/), [Portainer](https://docs.firefly-iii.org/how-to/firefly-iii/installation/portainer/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/installation/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/installation/self-managed/)" >> output.txt
|
||||
echo "* Or read the upgrade instructions for [Docker](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/docker/), [Kubernetes](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/kubernetes/) or [self-managed servers](https://docs.firefly-iii.org/how-to/firefly-iii/upgrade/self-managed/)" >> output.txt
|
||||
echo "* The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
|
||||
|
||||
# donations!
|
||||
echo '' >> output.txt
|
||||
echo '### Support Firefly III' >> output.txt
|
||||
echo 'Did you know you can support the development of Firefly III? You can donate in many ways, like GitHub Sponsors or Patreon. For more information, please [follow this link](https://bit.ly/donate-to-Firefly-III) for more information.' >> output.txt
|
||||
echo '' >> output.txt
|
||||
fi
|
||||
|
||||
# describe alpha release
|
||||
|
71
.github/workflows/sonarcloud.yml
vendored
71
.github/workflows/sonarcloud.yml
vendored
@@ -1,71 +0,0 @@
|
||||
name: 'Code - Run Sonarcloud'
|
||||
on:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
env:
|
||||
DB_CONNECTION: sqlite
|
||||
APP_KEY: TestTestTestTestTestTestTestTest
|
||||
jobs:
|
||||
sonarcloud:
|
||||
name: SonarCloud
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup PHP with Xdebug
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: '8.4'
|
||||
coverage: xdebug
|
||||
extensions: >-
|
||||
bcmath
|
||||
curl
|
||||
fileinfo
|
||||
iconv
|
||||
intl
|
||||
json
|
||||
sqlite3
|
||||
mbstring
|
||||
openssl
|
||||
pdo
|
||||
session
|
||||
simplexml
|
||||
sodium
|
||||
tokenizer
|
||||
xml
|
||||
xmlwriter
|
||||
|
||||
- name: Copy standard configuration
|
||||
run: cp .env.testing .env
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: composer install --prefer-dist --no-interaction --no-progress --no-scripts
|
||||
|
||||
- name: "Create database file"
|
||||
run: |
|
||||
touch storage/database/database.sqlite
|
||||
wget -q https://github.com/firefly-iii/test-fixtures/raw/refs/heads/main/test-database.sqlite -O storage/database/database.sqlite
|
||||
|
||||
- name: "Upgrades the database to the latest version"
|
||||
run: |
|
||||
php artisan firefly-iii:upgrade-database
|
||||
chmod 600 storage/oauth-public.key
|
||||
chmod 600 storage/oauth-private.key
|
||||
|
||||
- name: "Integrity Database Report"
|
||||
run: php artisan firefly-iii:report-integrity
|
||||
|
||||
- name: "Run tests with coverage"
|
||||
run: composer coverage
|
||||
|
||||
- name: Fix code coverage paths
|
||||
run: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' coverage.xml
|
||||
|
||||
- name: SonarCloud Scan
|
||||
uses: SonarSource/sonarqube-scan-action@v5.2.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_PERSONAL_ACCESS_TOKEN }}
|
||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
|
||||
|
||||
use Deprecated;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
@@ -83,9 +84,8 @@ class CurrencyController extends Controller
|
||||
/**
|
||||
* Documentation for this endpoint is at:
|
||||
* https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/autocomplete/getCurrenciesCodeAC
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function currenciesWithCode(AutocompleteRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->getData();
|
||||
|
@@ -24,16 +24,16 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
|
||||
use FireflyIII\Api\V1\Requests\Data\DateRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Exceptions\ValidationException;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Chart\ChartData;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
@@ -42,6 +42,7 @@ use FireflyIII\Support\Http\Api\ApiSupport;
|
||||
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class AccountController
|
||||
@@ -86,10 +87,12 @@ class AccountController extends Controller
|
||||
// move date to end of day
|
||||
$queryParameters['start']->startOfDay();
|
||||
$queryParameters['end']->endOfDay();
|
||||
Log::debug(sprintf('dashboard(), convert to native: %s', var_export($this->convertToNative, true)));
|
||||
|
||||
// loop each account, and collect info:
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Account #%d ("%s")', $account->id, $account->name));
|
||||
$this->renderAccountData($queryParameters, $account);
|
||||
}
|
||||
|
||||
@@ -101,15 +104,22 @@ class AccountController extends Controller
|
||||
*/
|
||||
private function renderAccountData(array $params, Account $account): void
|
||||
{
|
||||
$currency = $this->repository->getAccountCurrency($account);
|
||||
Log::debug(sprintf('Now in %s(array, #%d)', __METHOD__, $account->id));
|
||||
$currency = $this->repository->getAccountCurrency($account);
|
||||
$currentStart = clone $params['start'];
|
||||
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
|
||||
|
||||
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
$nativePrevious = null;
|
||||
if (!$currency instanceof TransactionCurrency) {
|
||||
$currency = $this->default;
|
||||
}
|
||||
$currentSet = [
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
|
||||
// the currency that belongs to the account.
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
@@ -121,18 +131,33 @@ class AccountController extends Controller
|
||||
'period' => '1D',
|
||||
'entries' => [],
|
||||
];
|
||||
$currentStart = clone $params['start'];
|
||||
$range = Steam::finalAccountBalanceInRange($account, $params['start'], clone $params['end'], $this->convertToNative);
|
||||
if ($this->convertToNative) {
|
||||
$currentSet['native_entries'] = [];
|
||||
$currentSet['native_currency_id'] = (string)$this->nativeCurrency->id;
|
||||
$currentSet['native_currency_code'] = $this->nativeCurrency->code;
|
||||
$currentSet['native_currency_symbol'] = $this->nativeCurrency->symbol;
|
||||
$currentSet['native_currency_decimal_places'] = $this->nativeCurrency->decimal_places;
|
||||
$nativePrevious = array_values($range)[0]['native_balance'];
|
||||
}
|
||||
|
||||
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
while ($currentStart <= $params['end']) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
|
||||
$previous = $balance;
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
|
||||
|
||||
// do the same for the native balance, if relevant:
|
||||
$nativeBalance = null;
|
||||
if ($this->convertToNative) {
|
||||
$nativeBalance = array_key_exists($format, $range) ? $range[$format]['native_balance'] : $nativePrevious;
|
||||
$nativePrevious = $nativeBalance;
|
||||
$currentSet['native_entries'][$label] = $nativeBalance;
|
||||
}
|
||||
|
||||
$currentStart->addDay();
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
}
|
||||
$this->chartData->add($currentSet);
|
||||
}
|
||||
@@ -146,40 +171,34 @@ class AccountController extends Controller
|
||||
public function overview(DateRequest $request): JsonResponse
|
||||
{
|
||||
// parameters for chart:
|
||||
$dates = $request->getAll();
|
||||
$dates = $request->getAll();
|
||||
|
||||
|
||||
/** @var Carbon $start */
|
||||
$start = $dates['start'];
|
||||
$start = $dates['start'];
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $dates['end'];
|
||||
$end = $dates['end'];
|
||||
|
||||
// set dates to end of day + start of day:
|
||||
$start->startOfDay();
|
||||
$end->endOfDay();
|
||||
|
||||
// user's preferences
|
||||
$defaultSet = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
|
||||
|
||||
/** @var Preference $frontpage */
|
||||
Preferences::get('frontpageAccounts', $defaultSet);
|
||||
|
||||
if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
|
||||
$frontpage->data = $defaultSet;
|
||||
$frontpage->save();
|
||||
}
|
||||
|
||||
// get accounts:
|
||||
$accounts = $this->repository->getAccountsById($frontpage->data);
|
||||
$chartData = [];
|
||||
$frontPageIds = $this->getFrontPageAccountIds();
|
||||
$accounts = $this->repository->getAccountsById($frontPageIds);
|
||||
$chartData = [];
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$currency = $this->repository->getAccountCurrency($account) ?? $this->nativeCurrency;
|
||||
$field = $this->convertToNative && $currency->id !== $this->nativeCurrency->id ? 'native_balance' : 'balance';
|
||||
$currentSet = [
|
||||
Log::debug(sprintf('Rendering chart data for account %s (%d)', $account->name, $account->id));
|
||||
$currency = $this->repository->getAccountCurrency($account) ?? $this->nativeCurrency;
|
||||
$currentStart = clone $start;
|
||||
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
|
||||
$previous = array_values($range)[0]['balance'];
|
||||
$nativePrevious = null;
|
||||
$currentSet = [
|
||||
'label' => $account->name,
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_id' => (string)$currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
@@ -189,21 +208,57 @@ class AccountController extends Controller
|
||||
'yAxisID' => 0, // 0, 1, 2
|
||||
'entries' => [],
|
||||
];
|
||||
// TODO this code is also present in the V2 chart account controller so this method is due to be deprecated.
|
||||
$currentStart = clone $start;
|
||||
$range = Steam::finalAccountBalanceInRange($account, $start, clone $end, $this->convertToNative);
|
||||
$previous = array_values($range)[0][$field];
|
||||
|
||||
// add "native_entries" if convertToNative is true:
|
||||
if ($this->convertToNative) {
|
||||
$currentSet['native_entries'] = [];
|
||||
$currentSet['native_currency_id'] = (string)$this->nativeCurrency->id;
|
||||
$currentSet['native_currency_code'] = $this->nativeCurrency->code;
|
||||
$currentSet['native_currency_symbol'] = $this->nativeCurrency->symbol;
|
||||
$currentSet['native_currency_decimal_places'] = $this->nativeCurrency->decimal_places;
|
||||
$nativePrevious = array_values($range)[0]['native_balance'];
|
||||
|
||||
}
|
||||
|
||||
// also get the native balance if convertToNative is true:
|
||||
while ($currentStart <= $end) {
|
||||
$format = $currentStart->format('Y-m-d');
|
||||
$label = $currentStart->toAtomString();
|
||||
$balance = array_key_exists($format, $range) ? $range[$format][$field] : $previous;
|
||||
|
||||
// balance is based on "balance" from the $range variable.
|
||||
$balance = array_key_exists($format, $range) ? $range[$format]['balance'] : $previous;
|
||||
$previous = $balance;
|
||||
$currentStart->addDay();
|
||||
$currentSet['entries'][$label] = $balance;
|
||||
|
||||
// do the same for the native balance, if relevant:
|
||||
$nativeBalance = null;
|
||||
if ($this->convertToNative) {
|
||||
$nativeBalance = array_key_exists($format, $range) ? $range[$format]['native_balance'] : $nativePrevious;
|
||||
$nativePrevious = $nativeBalance;
|
||||
$currentSet['native_entries'][$label] = $nativeBalance;
|
||||
}
|
||||
|
||||
$currentStart->addDay();
|
||||
|
||||
}
|
||||
$chartData[] = $currentSet;
|
||||
$chartData[] = $currentSet;
|
||||
}
|
||||
|
||||
return response()->json($chartData);
|
||||
}
|
||||
|
||||
private function getFrontPageAccountIds(): array
|
||||
{
|
||||
$defaultSet = $this->repository->getAccountsByType([AccountTypeEnum::ASSET->value])->pluck('id')->toArray();
|
||||
|
||||
/** @var Preference $frontpage */
|
||||
$frontpage = Preferences::get('frontpageAccounts', $defaultSet);
|
||||
|
||||
if (!(is_array($frontpage->data) && count($frontpage->data) > 0)) {
|
||||
$frontpage->data = $defaultSet;
|
||||
$frontpage->save();
|
||||
}
|
||||
|
||||
return $frontpage->data ?? $defaultSet;
|
||||
}
|
||||
}
|
||||
|
@@ -36,6 +36,7 @@ use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -77,6 +78,8 @@ class BudgetController extends Controller
|
||||
|
||||
/**
|
||||
* TODO see autocomplete/accountcontroller
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function dashboard(DateRequest $request): JsonResponse
|
||||
{
|
||||
@@ -107,23 +110,41 @@ class BudgetController extends Controller
|
||||
private function processBudget(Budget $budget, Carbon $start, Carbon $end): array
|
||||
{
|
||||
// get all limits:
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$rows = [];
|
||||
$limits = $this->blRepository->getBudgetLimits($budget, $start, $end);
|
||||
$rows = [];
|
||||
$spent = $this->opsRepository->listExpenses($start, $end, null, new Collection([$budget]));
|
||||
$expenses = $this->processExpenses($budget->id, $spent, $start, $end);
|
||||
|
||||
/**
|
||||
* @var int $currencyId
|
||||
* @var array $row
|
||||
*/
|
||||
foreach ($expenses as $currencyId => $row) {
|
||||
// budgeted, left and overspent are now 0.
|
||||
$limit = $this->filterLimit($currencyId, $limits);
|
||||
if (null !== $limit) {
|
||||
$row['budgeted'] = $limit->amount;
|
||||
$row['left'] = bcsub($row['budgeted'], bcmul($row['spent'], '-1'));
|
||||
$row['overspent'] = bcmul($row['left'], '-1');
|
||||
$row['left'] = 1 === bccomp($row['left'], '0') ? $row['left'] : '0';
|
||||
$row['overspent'] = 1 === bccomp($row['overspent'], '0') ? $row['overspent'] : '0';
|
||||
}
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
|
||||
// if no limits
|
||||
if (0 === $limits->count()) {
|
||||
// return as a single item in an array
|
||||
$rows = $this->noBudgetLimits($budget, $start, $end);
|
||||
}
|
||||
if ($limits->count() > 0) {
|
||||
$rows = $this->budgetLimits($budget, $limits);
|
||||
}
|
||||
// if (0 === $limits->count()) {
|
||||
// return as a single item in an array
|
||||
// $rows = $this->noBudgetLimits($budget, $start, $end);
|
||||
// }
|
||||
|
||||
// is always an array
|
||||
$return = [];
|
||||
$return = [];
|
||||
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'],
|
||||
@@ -131,6 +152,7 @@ class BudgetController extends Controller
|
||||
'start' => $row['start'],
|
||||
'end' => $row['end'],
|
||||
'entries' => [
|
||||
'budgeted' => $row['budgeted'],
|
||||
'spent' => $row['spent'],
|
||||
'left' => $row['left'],
|
||||
'overspent' => $row['overspent'],
|
||||
@@ -159,11 +181,9 @@ class BudgetController extends Controller
|
||||
* Shared between the "noBudgetLimits" function and "processLimit". Will take a single set of expenses and return
|
||||
* its info.
|
||||
*
|
||||
* @param array<int, array<int, string>> $array
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
private function processExpenses(int $budgetId, array $array, Carbon $start, Carbon $end): array
|
||||
private function processExpenses(int $budgetId, array $spent, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
@@ -174,16 +194,17 @@ class BudgetController extends Controller
|
||||
* @var int $currencyId
|
||||
* @var array $block
|
||||
*/
|
||||
foreach ($array as $currencyId => $block) {
|
||||
foreach ($spent 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'],
|
||||
'currency_decimal_places' => (int)$block['currency_decimal_places'],
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
'budgeted' => '0',
|
||||
'spent' => '0',
|
||||
'left' => '0',
|
||||
'overspent' => '0',
|
||||
@@ -193,7 +214,7 @@ class BudgetController extends Controller
|
||||
// var_dump($return);
|
||||
/** @var array $journal */
|
||||
foreach ($currentBudgetArray['transaction_journals'] as $journal) {
|
||||
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], (string) $journal['amount']);
|
||||
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], (string)$journal['amount']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,21 +258,55 @@ class BudgetController extends Controller
|
||||
/** @var array $entry */
|
||||
// only spent the entry where the entry's currency matches the budget limit's currency
|
||||
// so $filtered will only have 1 or 0 entries
|
||||
$filtered = array_filter($spent, function ($entry) use ($limitCurrencyId) {
|
||||
return $entry['currency_id'] === $limitCurrencyId;
|
||||
});
|
||||
$filtered = array_filter($spent, fn ($entry) => $entry['currency_id'] === $limitCurrencyId);
|
||||
$result = $this->processExpenses($budget->id, $filtered, $limit->start_date, $end);
|
||||
if (1 === count($result)) {
|
||||
$compare = bccomp($limit->amount, (string) app('steam')->positive($result[$limitCurrencyId]['spent']));
|
||||
$compare = bccomp($limit->amount, (string)app('steam')->positive($result[$limitCurrencyId]['spent']));
|
||||
$result[$limitCurrencyId]['budgeted'] = $limit->amount;
|
||||
if (1 === $compare) {
|
||||
// convert this amount into the native currency:
|
||||
$result[$limitCurrencyId]['left'] = bcadd($limit->amount, (string) $result[$limitCurrencyId]['spent']);
|
||||
$result[$limitCurrencyId]['left'] = bcadd($limit->amount, (string)$result[$limitCurrencyId]['spent']);
|
||||
}
|
||||
if ($compare <= 0) {
|
||||
$result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, (string) $result[$limitCurrencyId]['spent']));
|
||||
$result[$limitCurrencyId]['overspent'] = app('steam')->positive(bcadd($limit->amount, (string)$result[$limitCurrencyId]['spent']));
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function filterLimit(int $currencyId, Collection $limits): ?BudgetLimit
|
||||
{
|
||||
$amount = '0';
|
||||
$limit = null;
|
||||
$converter = new ExchangeRateConverter();
|
||||
|
||||
/** @var BudgetLimit $current */
|
||||
foreach ($limits as $current) {
|
||||
if (true === $this->convertToNative) {
|
||||
if ($current->transaction_currency_id === $this->nativeCurrency->id) {
|
||||
// simply add it.
|
||||
$amount = bcadd($amount, (string)$current->amount);
|
||||
Log::debug(sprintf('Set amount in limit to %s', $amount));
|
||||
}
|
||||
if ($current->transaction_currency_id !== $this->nativeCurrency->id) {
|
||||
// convert and then add it.
|
||||
$converted = $converter->convert($current->transactionCurrency, $this->nativeCurrency, $limit->start_date, $limit->amount);
|
||||
$amount = bcadd($amount, $converted);
|
||||
Log::debug(sprintf('Budgeted in limit #%d: %s %s, converted to %s %s', $current->id, $current->transactionCurrency->code, $current->amount, $this->nativeCurrency->code, $converted));
|
||||
Log::debug(sprintf('Set amount in limit to %s', $amount));
|
||||
}
|
||||
}
|
||||
if ($current->transaction_currency_id === $currencyId) {
|
||||
$limit = $current;
|
||||
}
|
||||
}
|
||||
if (null !== $limit && true === $this->convertToNative) {
|
||||
// convert and add all amounts.
|
||||
$limit->amount = app('steam')->positive($amount);
|
||||
Log::debug(sprintf('Final amount in limit with converted amount %s', $limit->amount));
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
}
|
||||
|
@@ -25,17 +25,20 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Api\V1\Controllers\Chart;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Api\V2\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V2\Request\Generic\DateRequest;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\CleansChartData;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class BudgetController
|
||||
@@ -45,6 +48,8 @@ class CategoryController extends Controller
|
||||
use CleansChartData;
|
||||
use ValidatesUserGroupTrait;
|
||||
|
||||
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
|
||||
|
||||
private AccountRepositoryInterface $accountRepos;
|
||||
private CurrencyRepositoryInterface $currencyRepos;
|
||||
|
||||
@@ -79,9 +84,10 @@ class CategoryController extends Controller
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = $this->parameters->get('end');
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
|
||||
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value]);
|
||||
$currencies = [];
|
||||
$return = [];
|
||||
$converter = new ExchangeRateConverter();
|
||||
|
||||
// get journals for entire period:
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
@@ -93,20 +99,40 @@ class CategoryController extends Controller
|
||||
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currency = $currencies[$currencyId] ?? $this->currencyRepos->find($currencyId);
|
||||
$currencies[$currencyId] = $currency;
|
||||
$categoryName = $journal['category_name'] ?? (string) trans('firefly.no_category');
|
||||
$amount = app('steam')->positive($journal['amount']);
|
||||
$key = sprintf('%s-%s', $categoryName, $currency->code);
|
||||
// find journal:
|
||||
$journalCurrencyId = (int)$journal['currency_id'];
|
||||
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
|
||||
$currencies[$journalCurrencyId] = $currency;
|
||||
$currencyId = (int)$currency->id;
|
||||
$currencyName = (string)$currency->name;
|
||||
$currencyCode = (string)$currency->code;
|
||||
$currencySymbol = (string)$currency->symbol;
|
||||
$currencyDecimalPlaces = (int)$currency->decimal_places;
|
||||
$amount = app('steam')->positive($journal['amount']);
|
||||
|
||||
// overrule if necessary:
|
||||
if ($this->convertToNative && $journalCurrencyId !== $this->nativeCurrency->id) {
|
||||
$currencyId = (int)$this->nativeCurrency->id;
|
||||
$currencyName = (string)$this->nativeCurrency->name;
|
||||
$currencyCode = (string)$this->nativeCurrency->code;
|
||||
$currencySymbol = (string)$this->nativeCurrency->symbol;
|
||||
$currencyDecimalPlaces = (int)$this->nativeCurrency->decimal_places;
|
||||
$convertedAmount = $converter->convert($currency, $this->nativeCurrency, $journal['date'], $amount);
|
||||
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->nativeCurrency->code, $convertedAmount));
|
||||
$amount = $convertedAmount;
|
||||
}
|
||||
|
||||
|
||||
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
|
||||
$key = sprintf('%s-%s', $categoryName, $currencyCode);
|
||||
// create arrays
|
||||
$return[$key] ??= [
|
||||
'label' => $categoryName,
|
||||
'currency_id' => (string) $currency->id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_name' => $currency->name,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'currency_id' => (string)$currencyId,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
'period' => null,
|
||||
'start' => $start->toAtomString(),
|
||||
'end' => $end->toAtomString(),
|
||||
@@ -114,12 +140,12 @@ class CategoryController extends Controller
|
||||
];
|
||||
|
||||
// add monies
|
||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string) $amount);
|
||||
$return[$key]['amount'] = bcadd($return[$key]['amount'], (string)$amount);
|
||||
}
|
||||
$return = array_values($return);
|
||||
|
||||
// order by amount
|
||||
usort($return, static fn (array $a, array $b) => (float) $a['amount'] < (float) $b['amount'] ? 1 : -1);
|
||||
usort($return, static fn (array $a, array $b) => (float)$a['amount'] < (float)$b['amount'] ? 1 : -1);
|
||||
|
||||
return response()->json($this->clean($return));
|
||||
}
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
|
||||
@@ -76,6 +78,17 @@ class ShowController extends Controller
|
||||
$bills = $bills->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
$paginator = new LengthAwarePaginator($bills, $count, $pageSize, $this->parameters->get('page'));
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bills = $enrichment->enrich($bills);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
@@ -96,6 +109,17 @@ class ShowController extends Controller
|
||||
{
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bill = $enrichment->enrichSingle($bill);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -29,7 +29,9 @@ use FireflyIII\Api\V1\Requests\Models\Bill\StoreRequest;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -72,6 +74,17 @@ class StoreController extends Controller
|
||||
$bill = $this->repository->store($data);
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bill = $enrichment->enrichSingle($bill);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -28,7 +28,9 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Api\V1\Requests\Models\Bill\UpdateRequest;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use League\Fractal\Resource\Item;
|
||||
|
||||
@@ -67,6 +69,17 @@ class UpdateController extends Controller
|
||||
$bill = $this->repository->update($bill, $data);
|
||||
$manager = $this->getManager();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bill = $enrichment->enrichSingle($bill);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\Transformers\PiggyBankTransformer;
|
||||
use FireflyIII\User;
|
||||
@@ -79,6 +80,17 @@ class ListController extends Controller
|
||||
$count = $collection->count();
|
||||
$bills = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bills = $enrichment->enrich($bills);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($bills, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.currencies.bills', [$objectGroup->id]).$this->buildParams());
|
||||
|
@@ -147,6 +147,7 @@ class ShowController extends Controller
|
||||
$enrichment->setUser($admin);
|
||||
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
|
||||
|
||||
|
||||
/** @var TransactionGroupTransformer $transformer */
|
||||
$transformer = app(TransactionGroupTransformer::class);
|
||||
$transformer->setParameters($this->parameters);
|
||||
|
@@ -43,6 +43,7 @@ use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
|
||||
use FireflyIII\Support\Http\Api\AccountFilter;
|
||||
use FireflyIII\Support\Http\Api\TransactionFilter;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use FireflyIII\Transformers\AvailableBudgetTransformer;
|
||||
@@ -182,6 +183,17 @@ class ListController extends Controller
|
||||
$count = $collection->count();
|
||||
$bills = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->nativeCurrency);
|
||||
$enrichment->setStart($this->parameters->get('start'));
|
||||
$enrichment->setEnd($this->parameters->get('end'));
|
||||
$bills = $enrichment->enrichSingle($bills);
|
||||
|
||||
// make paginator:
|
||||
$paginator = new LengthAwarePaginator($bills, $count, $pageSize, $this->parameters->get('page'));
|
||||
$paginator->setPath(route('api.v1.currencies.bills', [$currency->code]).$this->buildParams());
|
||||
|
@@ -58,7 +58,7 @@ class AutocompleteRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -60,8 +60,8 @@ class ChartRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before_or_equal:end',
|
||||
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after_or_equal:start',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17|before_or_equal:end',
|
||||
'end' => 'required|date|after:1970-01-02|before:2038-01-17|after_or_equal:start',
|
||||
'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
|
@@ -65,9 +65,9 @@ class DateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31|before:end|required_with:end',
|
||||
'end' => 'date|after:1900-01-01|before:2099-12-31|after:start|required_with:start',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end',
|
||||
'end' => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -55,8 +55,8 @@ class DateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ class SingleDateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -66,8 +66,8 @@ class Request extends FormRequest
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -80,9 +80,9 @@ class StoreRequest extends FormRequest
|
||||
'amount_max' => ['required', new IsValidPositiveAmount()],
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'date' => 'date|required|after:1900-01-01|before:2099-12-31',
|
||||
'end_date' => 'nullable|date|after:date|after:1900-01-01|before:2099-12-31',
|
||||
'extension_date' => 'nullable|date|after:date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|required|after:1970-01-02|before:2038-01-17',
|
||||
'end_date' => 'nullable|date|after:date|after:1970-01-02|before:2038-01-17',
|
||||
'extension_date' => 'nullable|date|after:date|after:1970-01-02|before:2038-01-17',
|
||||
'repeat_freq' => 'in:weekly,monthly,quarterly,half-year,yearly|required',
|
||||
'skip' => 'min:0|max:31|numeric',
|
||||
'active' => [new IsBoolean()],
|
||||
|
@@ -81,9 +81,9 @@ class UpdateRequest extends FormRequest
|
||||
'amount_max' => ['nullable', new IsValidPositiveAmount()],
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end_date' => 'date|after:date|after:1900-01-01|before:2099-12-31',
|
||||
'extension_date' => 'date|after:date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end_date' => 'date|after:date|after:1970-01-02|before:2038-01-17',
|
||||
'extension_date' => 'date|after:date|after:1970-01-02|before:2038-01-17',
|
||||
'repeat_freq' => 'in:weekly,monthly,quarterly,half-year,yearly',
|
||||
'skip' => 'min:0|max:31|numeric',
|
||||
'active' => [new IsBoolean()],
|
||||
|
@@ -67,8 +67,8 @@ class UpdateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'amount' => ['nullable', new IsValidPositiveAmount()],
|
||||
'currency_id' => 'numeric|exists:transaction_currencies,id',
|
||||
'currency_code' => 'min:3|max:51|exists:transaction_currencies,code',
|
||||
|
@@ -45,7 +45,7 @@ class DestroyRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -61,7 +61,7 @@ class StoreRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'rate' => 'required|numeric|gt:0',
|
||||
'from' => 'required|exists:transaction_currencies,code',
|
||||
'to' => 'required|exists:transaction_currencies,code',
|
||||
|
@@ -50,7 +50,7 @@ class UpdateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'rate' => 'required|numeric|gt:0',
|
||||
];
|
||||
}
|
||||
|
@@ -154,7 +154,7 @@ class UpdateRequest extends FormRequest
|
||||
return [
|
||||
'title' => sprintf('min:1|max:255|uniqueObjectForUser:recurrences,title,%d', $recurrence->id),
|
||||
'description' => 'min:1|max:32768',
|
||||
'first_date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'first_date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'apply_rules' => [new IsBoolean()],
|
||||
'active' => [new IsBoolean()],
|
||||
'repeat_until' => 'nullable|date',
|
||||
|
@@ -71,8 +71,8 @@ class TestRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -65,8 +65,8 @@ class TriggerRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -65,8 +65,8 @@ class TestRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
'accounts' => '',
|
||||
'accounts.*' => 'exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -69,8 +69,8 @@ class TriggerRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after_or_equal:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after_or_equal:start|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ class StoreRequest extends FormRequest
|
||||
$rules = [
|
||||
'tag' => 'required|min:1|uniqueObjectForUser:tags,tag|max:1024',
|
||||
'description' => 'min:1|nullable|max:32768',
|
||||
'date' => 'date|nullable|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
|
@@ -66,7 +66,7 @@ class UpdateRequest extends FormRequest
|
||||
$rules = [
|
||||
'tag' => 'min:1|max:1024|uniqueObjectForUser:tags,tag,'.$tag->id,
|
||||
'description' => 'min:1|nullable|max:32768',
|
||||
'date' => 'date|nullable|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
|
@@ -73,7 +73,7 @@ class CronRequest extends FormRequest
|
||||
{
|
||||
return [
|
||||
'force' => 'in:true,false',
|
||||
'date' => 'nullable|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'nullable|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -116,7 +116,7 @@ class Controller extends BaseController
|
||||
app('log')->warning(sprintf('Ignored invalid date "%s" in API v2 controller parameter check: %s', substr((string) $date, 0, 20), $e->getMessage()));
|
||||
}
|
||||
// out of range? set to null.
|
||||
if ($obj instanceof Carbon && ($obj->year <= 1900 || $obj->year > 2099)) {
|
||||
if ($obj instanceof Carbon && ($obj->year <= 1970 || $obj->year > 2038)) {
|
||||
app('log')->warning(sprintf('Refuse to use date "%s" in API v2 controller parameter check: %s', $field, $obj->toAtomString()));
|
||||
$obj = null;
|
||||
}
|
||||
|
@@ -86,7 +86,7 @@ class AutocompleteRequest extends FormRequest
|
||||
$valid = array_keys($this->types);
|
||||
|
||||
return [
|
||||
'date' => 'nullable|date|after:1900-01-01|before:2100-01-01',
|
||||
'date' => 'nullable|date|after:1970-01-02|before:2038-01-17',
|
||||
'query' => 'nullable|string',
|
||||
'size' => 'nullable|integer|min:1|max:100',
|
||||
'page' => 'nullable|integer|min:1',
|
||||
|
@@ -60,8 +60,8 @@ class BalanceChartRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
'accounts.*' => 'required|exists:accounts,id',
|
||||
'period' => sprintf('required|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
];
|
||||
|
@@ -61,8 +61,8 @@ class ChartRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before_or_equal:end',
|
||||
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after_or_equal:start',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17|before_or_equal:end',
|
||||
'end' => 'required|date|after:1970-01-02|before:2038-01-17|after_or_equal:start',
|
||||
'preselected' => sprintf('nullable|in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'period' => sprintf('nullable|in:%s', implode(',', config('firefly.valid_view_ranges'))),
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
|
@@ -60,8 +60,8 @@ class DashboardChartRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
'preselected' => sprintf('in:%s', implode(',', config('firefly.preselected_accounts'))),
|
||||
'accounts.*' => 'exists:accounts,id',
|
||||
];
|
||||
|
@@ -55,8 +55,8 @@ class DateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'required|date|after_or_equal:start|before:2099-12-31|after:1900-01-01',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'required|date|after_or_equal:start|before:2038-01-17|after:1970-01-02',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ class SingleDateRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'required|date|after:1900-01-01|before:2099-12-31',
|
||||
'date' => 'required|date|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -63,9 +63,9 @@ class IndexRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'date' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31|before:end|required_with:end',
|
||||
'end' => 'date|after:1900-01-01|before:2099-12-31|after:start|required_with:start',
|
||||
'date' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end',
|
||||
'end' => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -109,8 +109,8 @@ class InfiniteListRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after:start|after:1970-01-02|before:2038-01-17',
|
||||
'start_row' => 'integer|min:0|max:4294967296',
|
||||
'end_row' => 'integer|min:0|max:4294967296|gt:start_row',
|
||||
];
|
||||
|
@@ -84,8 +84,8 @@ class ListRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'date|after:1900-01-01|before:2099-12-31',
|
||||
'end' => 'date|after:start|after:1900-01-01|before:2099-12-31',
|
||||
'start' => 'date|after:1970-01-02|before:2038-01-17',
|
||||
'end' => 'date|after:start|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@@ -58,11 +58,13 @@ use function Safe\parse_url;
|
||||
*/
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
public static ?Throwable $lastError = null;
|
||||
|
||||
/**
|
||||
* @var array<int, class-string<Throwable>>
|
||||
*/
|
||||
protected $dontReport
|
||||
= [
|
||||
= [
|
||||
AuthenticationException::class,
|
||||
LaravelValidationException::class,
|
||||
NotFoundHttpException::class,
|
||||
@@ -123,7 +125,7 @@ class Handler extends ExceptionHandler
|
||||
// somehow Laravel handler does not catch this:
|
||||
app('log')->debug('Return JSON unauthenticated error.');
|
||||
|
||||
return response()->json(['message' => 'Unauthenticated', 'exception' => 'AuthenticationException'], 401);
|
||||
return response()->json(['message' => $e->getMessage(), 'exception' => 'AuthenticationException'], 401);
|
||||
}
|
||||
|
||||
if ($e instanceof OAuthServerException && $expectsJson) {
|
||||
@@ -215,13 +217,14 @@ class Handler extends ExceptionHandler
|
||||
#[Override]
|
||||
public function report(Throwable $e): void
|
||||
{
|
||||
$doMailError = (bool) config('firefly.send_error_message');
|
||||
self::$lastError = $e;
|
||||
$doMailError = (bool) config('firefly.send_error_message');
|
||||
if ($this->shouldntReportLocal($e) || !$doMailError) {
|
||||
parent::report($e);
|
||||
|
||||
return;
|
||||
}
|
||||
$userData = [
|
||||
$userData = [
|
||||
'id' => 0,
|
||||
'email' => 'unknown@example.com',
|
||||
];
|
||||
@@ -230,9 +233,9 @@ class Handler extends ExceptionHandler
|
||||
$userData['email'] = auth()->user()->email;
|
||||
}
|
||||
|
||||
$headers = request()->headers->all();
|
||||
$headers = request()->headers->all();
|
||||
|
||||
$data = [
|
||||
$data = [
|
||||
'class' => $e::class,
|
||||
'errorMessage' => $e->getMessage(),
|
||||
'time' => Carbon::now()->format('r'),
|
||||
@@ -250,8 +253,8 @@ class Handler extends ExceptionHandler
|
||||
];
|
||||
|
||||
// create job that will mail.
|
||||
$ipAddress = request()->ip() ?? '0.0.0.0';
|
||||
$job = new MailError($userData, (string) config('firefly.site_owner'), $ipAddress, $data);
|
||||
$ipAddress = request()->ip() ?? '0.0.0.0';
|
||||
$job = new MailError($userData, (string) config('firefly.site_owner'), $ipAddress, $data);
|
||||
dispatch($job);
|
||||
|
||||
parent::report($e);
|
||||
|
@@ -75,15 +75,15 @@ class TagFactory
|
||||
$latitude = 0.0 === (float) $data['latitude'] ? null : (float) $data['latitude']; // intentional float
|
||||
$longitude = 0.0 === (float) $data['longitude'] ? null : (float) $data['longitude']; // intentional float
|
||||
$array = [
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->userGroup->id,
|
||||
'tag' => trim((string) $data['tag']),
|
||||
'tagMode' => 'nothing',
|
||||
'date' => $data['date'],
|
||||
'description' => $data['description'],
|
||||
'latitude' => null,
|
||||
'longitude' => null,
|
||||
'zoomLevel' => null,
|
||||
'user_id' => $this->user->id,
|
||||
'user_group_id' => $this->userGroup->id,
|
||||
'tag' => trim((string) $data['tag']),
|
||||
'tag_mode' => 'nothing',
|
||||
'date' => $data['date'],
|
||||
'description' => $data['description'],
|
||||
'latitude' => null,
|
||||
'longitude' => null,
|
||||
'zoomLevel' => null,
|
||||
];
|
||||
|
||||
/** @var null|Tag $tag */
|
||||
|
@@ -32,11 +32,14 @@ use FireflyIII\Models\TransactionGroup;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\Webhook;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment;
|
||||
use FireflyIII\Transformers\AccountTransformer;
|
||||
use FireflyIII\Transformers\TransactionGroupTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||
|
||||
@@ -59,14 +62,14 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
|
||||
public function generateMessages(): void
|
||||
{
|
||||
app('log')->debug(__METHOD__);
|
||||
Log::debug(__METHOD__);
|
||||
// get the webhooks:
|
||||
if (0 === $this->webhooks->count()) {
|
||||
$this->webhooks = $this->getWebhooks();
|
||||
}
|
||||
|
||||
// do some debugging
|
||||
app('log')->debug(
|
||||
Log::debug(
|
||||
sprintf('StandardMessageGenerator will generate messages for %d object(s) and %d webhook(s).', $this->objects->count(), $this->webhooks->count())
|
||||
);
|
||||
$this->run();
|
||||
@@ -79,13 +82,13 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
|
||||
private function run(): void
|
||||
{
|
||||
app('log')->debug('Now in StandardMessageGenerator::run');
|
||||
Log::debug('Now in StandardMessageGenerator::run');
|
||||
|
||||
/** @var Webhook $webhook */
|
||||
foreach ($this->webhooks as $webhook) {
|
||||
$this->runWebhook($webhook);
|
||||
}
|
||||
app('log')->debug('Done with StandardMessageGenerator::run');
|
||||
Log::debug('Done with StandardMessageGenerator::run');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -93,7 +96,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
*/
|
||||
private function runWebhook(Webhook $webhook): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in runWebhook(#%d)', $webhook->id));
|
||||
Log::debug(sprintf('Now in runWebhook(#%d)', $webhook->id));
|
||||
|
||||
/** @var Model $object */
|
||||
foreach ($this->objects as $object) {
|
||||
@@ -108,7 +111,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
{
|
||||
$class = $model::class;
|
||||
// Line is ignored because all of Firefly III's Models have an id property.
|
||||
app('log')->debug(sprintf('Now in generateMessage(#%d, %s#%d)', $webhook->id, $class, $model->id));
|
||||
Log::debug(sprintf('Now in generateMessage(#%d, %s#%d)', $webhook->id, $class, $model->id));
|
||||
|
||||
$uuid = Uuid::uuid4();
|
||||
$basicMessage = [
|
||||
@@ -125,7 +128,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
switch ($class) {
|
||||
default:
|
||||
// Line is ignored because all of Firefly III's Models have an id property.
|
||||
app('log')->error(
|
||||
Log::error(
|
||||
sprintf('Webhook #%d was given %s#%d to deal with but can\'t extract user ID from it.', $webhook->id, $class, $model->id)
|
||||
);
|
||||
|
||||
@@ -141,7 +144,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
// then depends on the response what to put in the message:
|
||||
switch ($webhook->response) {
|
||||
default:
|
||||
app('log')->error(
|
||||
Log::error(
|
||||
sprintf('The response code for webhook #%d is "%d" and the message generator cant handle it. Soft fail.', $webhook->id, $webhook->response)
|
||||
);
|
||||
|
||||
@@ -159,10 +162,10 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
try {
|
||||
$basicMessage['content'] = $transformer->transformObject($model);
|
||||
} catch (FireflyException $e) {
|
||||
app('log')->error(
|
||||
Log::error(
|
||||
sprintf('The transformer could not include the requested transaction group for webhook #%d: %s', $webhook->id, $e->getMessage())
|
||||
);
|
||||
app('log')->error($e->getTraceAsString());
|
||||
Log::error($e->getTraceAsString());
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -172,6 +175,10 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
case WebhookResponse::ACCOUNTS->value:
|
||||
/** @var TransactionGroup $model */
|
||||
$accounts = $this->collectAccounts($model);
|
||||
$enrichment = new AccountEnrichment();
|
||||
$enrichment->setUser($model->user);
|
||||
$enrichment->setNative(Amount::getNativeCurrencyByUserGroup($model->userGroup));
|
||||
$accounts = $enrichment->enrich($accounts);
|
||||
foreach ($accounts as $account) {
|
||||
$transformer = new AccountTransformer();
|
||||
$transformer->setParameters(new ParameterBag());
|
||||
@@ -210,7 +217,7 @@ class StandardMessageGenerator implements MessageGeneratorInterface
|
||||
$webhookMessage->uuid = $message['uuid'];
|
||||
$webhookMessage->message = $message;
|
||||
$webhookMessage->save();
|
||||
app('log')->debug(sprintf('Stored new webhook message #%d', $webhookMessage->id));
|
||||
Log::debug(sprintf('Stored new webhook message #%d', $webhookMessage->id));
|
||||
}
|
||||
|
||||
public function setObjects(Collection $objects): void
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Events\RequestedVersionCheckStatus;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
@@ -42,9 +43,8 @@ class VersionCheckEventHandler
|
||||
* Checks with GitHub to see if there is a new version.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @deprecated ?
|
||||
*/
|
||||
#[Deprecated(message: '?')]
|
||||
public function checkForUpdates(RequestedVersionCheckStatus $event): void
|
||||
{
|
||||
Log::debug('Now in checkForUpdates()');
|
||||
|
@@ -26,6 +26,7 @@ namespace FireflyIII\Handlers\Events;
|
||||
|
||||
use FireflyIII\Jobs\SendWebhookMessage;
|
||||
use FireflyIII\Models\WebhookMessage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class WebhookEventHandler
|
||||
@@ -37,7 +38,13 @@ class WebhookEventHandler
|
||||
*/
|
||||
public function sendWebhookMessages(): void
|
||||
{
|
||||
app('log')->debug(sprintf('Now in %s', __METHOD__));
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
if (false === config('firefly.feature_flags.webhooks') || false === config('firefly.allow_webhooks')) {
|
||||
Log::info('Webhook event handler is disabled, do not run sendWebhookMessages().');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// kick off the job!
|
||||
$messages = WebhookMessage::where('webhook_messages.sent', false)
|
||||
->get(['webhook_messages.*'])
|
||||
@@ -45,14 +52,14 @@ class WebhookEventHandler
|
||||
static fn (WebhookMessage $message) => $message->webhookAttempts()->count() <= 2
|
||||
)->splice(0, 5)
|
||||
;
|
||||
app('log')->debug(sprintf('Found %d webhook message(s) ready to be send.', $messages->count()));
|
||||
Log::debug(sprintf('Found %d webhook message(s) ready to be send.', $messages->count()));
|
||||
foreach ($messages as $message) {
|
||||
if (false === $message->sent) {
|
||||
app('log')->debug(sprintf('Send message #%d', $message->id));
|
||||
Log::debug(sprintf('Send message #%d', $message->id));
|
||||
SendWebhookMessage::dispatch($message)->afterResponse();
|
||||
}
|
||||
if (false !== $message->sent) {
|
||||
app('log')->debug(sprintf('Skip message #%d', $message->id));
|
||||
Log::debug(sprintf('Skip message #%d', $message->id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -434,6 +434,7 @@ class GroupCollector implements GroupCollectorInterface
|
||||
|
||||
public function findNothing(): GroupCollectorInterface
|
||||
{
|
||||
Log::warning('The search engine was instructed to FIND NOTHING. This may be a bug.');
|
||||
$this->query->where('transaction_groups.id', -1);
|
||||
|
||||
return $this;
|
||||
@@ -557,7 +558,6 @@ class GroupCollector implements GroupCollectorInterface
|
||||
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
|
||||
}
|
||||
}
|
||||
|
||||
$groups = $this->parseSums($groups);
|
||||
|
||||
return new Collection($groups);
|
||||
@@ -1094,6 +1094,10 @@ class GroupCollector implements GroupCollectorInterface
|
||||
->whereNull('transaction_groups.deleted_at')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereNull('source.deleted_at')
|
||||
|
||||
// #10507 ignore opening balance.
|
||||
->where('transaction_types.type', '!=', TransactionTypeEnum::OPENING_BALANCE->value)
|
||||
|
||||
->whereNotNull('transaction_groups.id')
|
||||
->whereNull('destination.deleted_at')
|
||||
->orderBy('transaction_journals.date', 'DESC')
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Report;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
@@ -81,7 +82,7 @@ class NetWorth implements NetWorthInterface
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
Log::debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name));
|
||||
// Log::debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name));
|
||||
$currency = $this->accountRepository->getAccountCurrency($account) ?? $default;
|
||||
$useNative = $convertToNative && $default->id !== $currency->id;
|
||||
$currency = $useNative ? $default : $currency;
|
||||
@@ -92,12 +93,12 @@ class NetWorth implements NetWorthInterface
|
||||
$balance = $balances[$account->id]['balance'] ?? '0';
|
||||
$nativeBalance = $balances[$account->id]['native_balance'] ?? '0';
|
||||
}
|
||||
Log::debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance));
|
||||
// Log::debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance));
|
||||
// always subtract virtual balance again.
|
||||
$balance = '' !== (string) $account->virtual_balance ? bcsub($balance, (string) $account->virtual_balance) : $balance;
|
||||
$nativeBalance = '' !== (string) $account->native_virtual_balance ? bcsub($nativeBalance, (string) $account->native_virtual_balance) : $nativeBalance;
|
||||
$amountToUse = $useNative ? $nativeBalance : $balance;
|
||||
Log::debug(sprintf('Will use %s %s', $currencyCode, $amountToUse));
|
||||
// Log::debug(sprintf('Will use %s %s', $currencyCode, $amountToUse));
|
||||
|
||||
$netWorth[$currencyCode] ??= [
|
||||
'balance' => '0',
|
||||
@@ -134,9 +135,7 @@ class NetWorth implements NetWorthInterface
|
||||
$this->currencyRepos->setUserGroup($this->userGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function sumNetWorthByCurrency(Carbon $date): array
|
||||
{
|
||||
/**
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Helpers\Report;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\User;
|
||||
@@ -53,8 +54,7 @@ interface NetWorthInterface
|
||||
* TODO move to repository
|
||||
*
|
||||
* Same as above but cleaner function with less dependencies.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function sumNetWorthByCurrency(Carbon $date): array;
|
||||
}
|
||||
|
@@ -88,7 +88,9 @@ class IndexController extends Controller
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
$start->subDay();
|
||||
|
||||
// #10618 go to the end of the previous day.
|
||||
$start->subSecond();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
Log::debug(sprintf('inactive start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||
@@ -164,8 +166,8 @@ class IndexController extends Controller
|
||||
/** @var Carbon $end */
|
||||
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
|
||||
|
||||
// 2025-05-11 removed this so start is exactly the start of the month.
|
||||
// $start->subDay();
|
||||
// #10618 go to the end of the previous day.
|
||||
$start->subSecond();
|
||||
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
Log::debug(sprintf('index start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));
|
||||
|
@@ -29,7 +29,9 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\ObjectGroup\OrganisesObjectGroups;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Application;
|
||||
@@ -76,11 +78,26 @@ class IndexController extends Controller
|
||||
$collection = $this->repository->getBills();
|
||||
$total = $collection->count();
|
||||
|
||||
|
||||
|
||||
$parameters = new ParameterBag();
|
||||
// sub one day from temp start so the last paid date is one day before it should be.
|
||||
$tempStart = clone $start;
|
||||
// 2023-06-23 do not sub one day from temp start, fix is in BillTransformer::payDates instead
|
||||
// $tempStart->subDay();
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->defaultCurrency);
|
||||
$enrichment->setStart($tempStart);
|
||||
$enrichment->setEnd($end);
|
||||
$collection = $enrichment->enrich($collection);
|
||||
|
||||
|
||||
$parameters->set('start', $tempStart);
|
||||
$parameters->set('end', $end);
|
||||
$parameters->set('convertToNative', $this->convertToNative);
|
||||
|
@@ -30,9 +30,11 @@ use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Attachment;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
|
||||
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
|
||||
use FireflyIII\Transformers\AttachmentTransformer;
|
||||
use FireflyIII\Transformers\BillTransformer;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -142,6 +144,17 @@ class ShowController extends Controller
|
||||
$parameters->set('start', $start);
|
||||
$parameters->set('end', $end);
|
||||
|
||||
// enrich
|
||||
/** @var User $admin */
|
||||
$admin = auth()->user();
|
||||
$enrichment = new SubscriptionEnrichment();
|
||||
$enrichment->setUser($admin);
|
||||
$enrichment->setConvertToNative($this->convertToNative);
|
||||
$enrichment->setNative($this->defaultCurrency);
|
||||
$enrichment->setStart($start);
|
||||
$enrichment->setEnd($end);
|
||||
$bill = $enrichment->enrichSingle($bill);
|
||||
|
||||
/** @var BillTransformer $transformer */
|
||||
$transformer = app(BillTransformer::class);
|
||||
$transformer->setParameters($parameters);
|
||||
|
@@ -91,7 +91,7 @@ class IndexController extends Controller
|
||||
public function index(?Carbon $start = null, ?Carbon $end = null)
|
||||
{
|
||||
$this->abRepository->cleanup();
|
||||
app('log')->debug(sprintf('Start of IndexController::index("%s", "%s")', $start?->format('Y-m-d'), $end?->format('Y-m-d')));
|
||||
Log::debug(sprintf('Start of IndexController::index("%s", "%s")', $start?->format('Y-m-d'), $end?->format('Y-m-d')));
|
||||
|
||||
// collect some basic vars:
|
||||
$range = app('navigation')->getViewRange(true);
|
||||
@@ -199,12 +199,12 @@ class IndexController extends Controller
|
||||
// get all budgets, and paginate them into $budgets.
|
||||
$collection = $this->repository->getActiveBudgets();
|
||||
$budgets = [];
|
||||
app('log')->debug(sprintf('7) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('(getAllBudgets) Start is "%s", end is "%s"', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
|
||||
// complement budget with budget limits in range, and expenses in currency X in range.
|
||||
/** @var Budget $current */
|
||||
foreach ($collection as $current) {
|
||||
app('log')->debug(sprintf('Working on budget #%d ("%s")', $current->id, $current->name));
|
||||
Log::debug(sprintf('Working on budget #%d ("%s")', $current->id, $current->name));
|
||||
$array = $current->toArray();
|
||||
$array['spent'] = [];
|
||||
$array['spent_total'] = [];
|
||||
@@ -215,7 +215,7 @@ class IndexController extends Controller
|
||||
|
||||
/** @var BudgetLimit $limit */
|
||||
foreach ($budgetLimits as $limit) {
|
||||
app('log')->debug(sprintf('Working on budget limit #%d', $limit->id));
|
||||
Log::debug(sprintf('Working on budget limit #%d', $limit->id));
|
||||
$currency = $limit->transactionCurrency ?? $defaultCurrency;
|
||||
$amount = app('steam')->bcround($limit->amount, $currency->decimal_places);
|
||||
$array['budgeted'][] = [
|
||||
@@ -231,9 +231,11 @@ class IndexController extends Controller
|
||||
'currency_name' => $currency->name,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
];
|
||||
app('log')->debug(sprintf('The amount budgeted for budget limit #%d is %s %s', $limit->id, $currency->code, $amount));
|
||||
Log::debug(sprintf('The amount budgeted for budget limit #%d is %s %s', $limit->id, $currency->code, $amount));
|
||||
}
|
||||
|
||||
// #10463
|
||||
|
||||
/** @var TransactionCurrency $currency */
|
||||
foreach ($currencies as $currency) {
|
||||
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$current]), $currency, false);
|
||||
@@ -260,6 +262,8 @@ class IndexController extends Controller
|
||||
|
||||
/** @var array $budget */
|
||||
foreach ($budgets as $budget) {
|
||||
Log::debug(sprintf('Now working on budget #%d ("%s")', $budget['id'], $budget['name']));
|
||||
|
||||
/** @var array $spent */
|
||||
foreach ($budget['spent'] as $spent) {
|
||||
$currencyId = $spent['currency_id'];
|
||||
|
@@ -23,8 +23,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use function Safe\realpath;
|
||||
use function Safe\ini_get;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
@@ -38,6 +36,9 @@ use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
use function Safe\realpath;
|
||||
use function Safe\ini_get;
|
||||
|
||||
/**
|
||||
* Class Controller.
|
||||
*
|
||||
@@ -78,12 +79,12 @@ abstract class Controller extends BaseController
|
||||
View::share('featuringWebhooks', true === config('firefly.feature_flags.webhooks') && true === config('firefly.allow_webhooks'));
|
||||
|
||||
// share custom auth guard info.
|
||||
$authGuard = config('firefly.authentication_guard');
|
||||
$logoutUrl = config('firefly.custom_logout_url');
|
||||
$authGuard = config('firefly.authentication_guard');
|
||||
$logoutUrl = config('firefly.custom_logout_url');
|
||||
|
||||
// overrule v2 layout back to v1.
|
||||
if ('true' === request()->get('force_default_layout') && 'v2' === config('view.layout')) {
|
||||
//config('view.layout','v1');
|
||||
// config('view.layout','v1');
|
||||
Config::set('view.layout', 'v1');
|
||||
View::getFinder()->setPaths([realpath(base_path('resources/views'))]); // @phpstan-ignore-line
|
||||
}
|
||||
@@ -92,15 +93,15 @@ abstract class Controller extends BaseController
|
||||
View::share('logoutUrl', $logoutUrl);
|
||||
|
||||
// upload size
|
||||
$maxFileSize = Steam::phpBytes((string) ini_get('upload_max_filesize'));
|
||||
$maxPostSize = Steam::phpBytes((string) ini_get('post_max_size'));
|
||||
$uploadSize = min($maxFileSize, $maxPostSize);
|
||||
$maxFileSize = Steam::phpBytes((string) ini_get('upload_max_filesize'));
|
||||
$maxPostSize = Steam::phpBytes((string) ini_get('post_max_size'));
|
||||
$uploadSize = min($maxFileSize, $maxPostSize);
|
||||
View::share('uploadSize', $uploadSize);
|
||||
|
||||
// share is alpha, is beta
|
||||
$isAlpha = false;
|
||||
$isBeta = false;
|
||||
$isDevelop = false;
|
||||
$isAlpha = false;
|
||||
$isBeta = false;
|
||||
$isDevelop = false;
|
||||
if (str_contains((string) config('firefly.version'), 'alpha')) {
|
||||
$isAlpha = true;
|
||||
}
|
||||
@@ -118,7 +119,7 @@ abstract class Controller extends BaseController
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next): mixed {
|
||||
$locale = Steam::getLocale();
|
||||
$locale = Steam::getLocale();
|
||||
// translations for specific strings:
|
||||
$this->monthFormat = (string) trans('config.month_js', [], $locale);
|
||||
$this->monthAndDayFormat = (string) trans('config.month_and_day_js', [], $locale);
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Json;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
@@ -46,9 +47,8 @@ class BoxController extends Controller
|
||||
|
||||
/**
|
||||
* Deprecated method, no longer in use.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function available(): JsonResponse
|
||||
{
|
||||
return response()->json([]);
|
||||
|
@@ -107,7 +107,7 @@ class RecurrenceController extends Controller
|
||||
$repetitionMoment = str_ireplace('ndom,', '', $request->get('type'));
|
||||
}
|
||||
if ('yearly' === $repetitionType) {
|
||||
$repetitionMoment = explode(',', (string) $request->get('type'))[1] ?? '2018-01-01';
|
||||
$repetitionMoment = explode(',', (string) $request->get('type'))[1] ?? '2025-01-01';
|
||||
}
|
||||
$actualStart->startOfDay();
|
||||
$repetition = new RecurrenceRepetition();
|
||||
|
@@ -154,12 +154,18 @@ class TagController extends Controller
|
||||
*/
|
||||
public function index(TagRepositoryInterface $repository)
|
||||
{
|
||||
// start with oldest tag
|
||||
// start with the oldest tag
|
||||
$first = session('first', today()) ?? today();
|
||||
$oldestTagDate = $repository->oldestTag() instanceof Tag ? $repository->oldestTag()->date : clone $first;
|
||||
$newestTagDate = $repository->newestTag() instanceof Tag ? $repository->newestTag()->date : today();
|
||||
$oldestTagDate->startOfYear();
|
||||
$newestTagDate->endOfYear();
|
||||
|
||||
if ($oldestTagDate->year < 1970) {
|
||||
$oldestTagDate = Carbon::create(1970, 1, 1, 0, 0, 0, config('app.timezone'));
|
||||
request()->session()->flash('error', trans('firefly.bad_date_transaction'));
|
||||
}
|
||||
|
||||
$tags = [];
|
||||
$tags['no-date'] = $repository->getTagsInYear(null);
|
||||
|
||||
|
@@ -26,10 +26,12 @@ namespace FireflyIII\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Exceptions\Handler;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Contracts\Auth\Factory as Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
|
||||
/**
|
||||
* Class Authenticate
|
||||
@@ -84,6 +86,7 @@ class Authenticate
|
||||
if ($this->auth->check()) {
|
||||
// do an extra check on user object.
|
||||
/** @noinspection PhpUndefinedMethodInspection */
|
||||
|
||||
/** @var User $user */
|
||||
$user = $this->auth->authenticate();
|
||||
$this->validateBlockedUser($user, $guards);
|
||||
@@ -107,7 +110,14 @@ class Authenticate
|
||||
}
|
||||
}
|
||||
|
||||
throw new AuthenticationException('Unauthenticated.', $guards);
|
||||
// this is a massive hack, but if the handler has the oauth exception
|
||||
// at this point we can report its error instead of a generic one.
|
||||
$message = 'Unauthenticated.';
|
||||
if (Handler::$lastError instanceof OAuthServerException) {
|
||||
$message = Handler::$lastError->getHint();
|
||||
}
|
||||
|
||||
throw new AuthenticationException($message, $guards);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -41,8 +41,8 @@ class SelectTransactionsRequest extends FormRequest
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'start' => 'required|date|after:1900-01-01|before:2099-12-31|before:end|required_with:end',
|
||||
'end' => 'required|date|after:1900-01-01|before:2099-12-31|after:start|required_with:start',
|
||||
'start' => 'required|date|after:1970-01-02|before:2038-01-17|before:end|required_with:end',
|
||||
'end' => 'required|date|after:1970-01-02|before:2038-01-17|after:start|required_with:start',
|
||||
'accounts' => 'required',
|
||||
'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts',
|
||||
];
|
||||
|
@@ -74,7 +74,7 @@ class TagFormRequest extends FormRequest
|
||||
'tag' => $tagRule,
|
||||
'id' => $idRule,
|
||||
'description' => 'max:32768|min:1|nullable',
|
||||
'date' => 'date|nullable|after:1984-09-17',
|
||||
'date' => 'date|nullable|after:1970-01-02|before:2038-01-17',
|
||||
];
|
||||
|
||||
return Location::requestRules($rules);
|
||||
|
@@ -40,7 +40,7 @@ class Tag extends Model
|
||||
use ReturnsIntegerUserIdTrait;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'date_tz', 'description', 'tagMode'];
|
||||
protected $fillable = ['user_id', 'user_group_id', 'tag', 'date', 'date_tz', 'description', 'tag_mode'];
|
||||
|
||||
protected $hidden = ['zoomLevel', 'latitude', 'longitude'];
|
||||
|
||||
|
@@ -312,11 +312,23 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
|
||||
Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString()));
|
||||
|
||||
return $bill->transactionJournals()
|
||||
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
|
||||
->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
|
||||
->where('transactions.amount', '>', 0)
|
||||
->before($end)->after($start)->get(
|
||||
[
|
||||
'transaction_journals.id',
|
||||
'transaction_journals.date',
|
||||
'transaction_journals.transaction_group_id',
|
||||
'transactions.transaction_currency_id',
|
||||
'currency.code AS transaction_currency_code',
|
||||
'currency.decimal_places AS transaction_currency_decimal_places',
|
||||
'transactions.foreign_currency_id',
|
||||
'foreign_currency.code AS foreign_currency_code',
|
||||
'foreign_currency.decimal_places AS foreign_currency_decimal_places',
|
||||
'transactions.amount',
|
||||
'transactions.foreign_amount',
|
||||
]
|
||||
)
|
||||
;
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Budget;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
@@ -205,9 +206,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget
|
||||
{
|
||||
/** @var null|AvailableBudget */
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Budget;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\AvailableBudget;
|
||||
@@ -66,9 +67,7 @@ interface AvailableBudgetRepositoryInterface
|
||||
*/
|
||||
public function get(?Carbon $start = null, ?Carbon $end = null): Collection;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string;
|
||||
|
||||
public function getAvailableBudgetWithCurrency(Carbon $start, Carbon $end): array;
|
||||
@@ -90,9 +89,7 @@ interface AvailableBudgetRepositoryInterface
|
||||
*/
|
||||
public function getByCurrencyDate(Carbon $start, Carbon $end, TransactionCurrency $currency): ?AvailableBudget;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget;
|
||||
|
||||
public function store(array $data): ?AvailableBudget;
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Budget;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
@@ -44,9 +45,7 @@ use Illuminate\Support\Collection;
|
||||
*/
|
||||
interface NoBudgetRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array;
|
||||
|
||||
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array;
|
||||
|
@@ -25,12 +25,15 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Repositories\Budget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Deprecated;
|
||||
use FireflyIII\Enums\TransactionTypeEnum;
|
||||
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Report\Summarizer\TransactionSummarizer;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
|
||||
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
|
||||
@@ -54,17 +57,17 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$total = '0';
|
||||
$count = 0;
|
||||
foreach ($budget->budgetlimits as $limit) {
|
||||
$diff = (int) $limit->start_date->diffInDays($limit->end_date, true);
|
||||
$diff = (int)$limit->start_date->diffInDays($limit->end_date, true);
|
||||
$diff = 0 === $diff ? 1 : $diff;
|
||||
$amount = $limit->amount;
|
||||
$perDay = bcdiv((string) $amount, (string) $diff);
|
||||
$perDay = bcdiv((string)$amount, (string)$diff);
|
||||
$total = bcadd($total, $perDay);
|
||||
++$count;
|
||||
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
|
||||
}
|
||||
$avg = $total;
|
||||
if ($count > 0) {
|
||||
$avg = bcdiv($total, (string) $count);
|
||||
$avg = bcdiv($total, (string)$count);
|
||||
}
|
||||
app('log')->debug(sprintf('%s / %d = %s = average.', $total, $count, $avg));
|
||||
|
||||
@@ -74,9 +77,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
/**
|
||||
* This method is being used to generate the budget overview in the year/multi-year report. Its used
|
||||
* in both the year/multi-year budget overview AND in the accompanying chart.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$carbonFormat = app('navigation')->preferredCarbonFormat($start, $end);
|
||||
@@ -93,9 +95,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
/** @var array $journal */
|
||||
foreach ($journals as $journal) {
|
||||
// prep data array for currency:
|
||||
$budgetId = (int) $journal['budget_id'];
|
||||
$budgetId = (int)$journal['budget_id'];
|
||||
$budgetName = $journal['budget_name'];
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$currencyId = (int)$journal['currency_id'];
|
||||
$key = sprintf('%d-%d', $budgetId, $currencyId);
|
||||
|
||||
$data[$key] ??= [
|
||||
@@ -110,7 +112,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
'entries' => [],
|
||||
];
|
||||
$date = $journal['date']->format($carbonFormat);
|
||||
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string) $journal['amount']);
|
||||
$data[$key]['entries'][$date] = bcadd($data[$key]['entries'][$date] ?? '0', (string)$journal['amount']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
@@ -124,7 +126,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null): array
|
||||
{
|
||||
/** @var GroupCollectorInterface $collector */
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
|
||||
if ($accounts instanceof Collection && $accounts->count() > 0) {
|
||||
$collector->setAccounts($accounts);
|
||||
@@ -136,15 +138,41 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$collector->setBudgets($this->getBudgets());
|
||||
}
|
||||
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
$journals = $collector->getExtractedJournals();
|
||||
$array = [];
|
||||
|
||||
// if needs conversion to native.
|
||||
$convertToNative = Amount::convertToNative($this->user);
|
||||
$nativeCurrency = Amount::getNativeCurrencyByUserGroup($this->userGroup);
|
||||
$currencyId = (int) $nativeCurrency->id;
|
||||
$currencyCode = $nativeCurrency->code;
|
||||
$currencyName = $nativeCurrency->name;
|
||||
$currencySymbol = $nativeCurrency->symbol;
|
||||
$currencyDecimalPlaces = $nativeCurrency->decimal_places;
|
||||
$converter = new ExchangeRateConverter();
|
||||
$currencies = [
|
||||
$currencyId => $nativeCurrency,
|
||||
];
|
||||
|
||||
foreach ($journals as $journal) {
|
||||
$currencyId = (int) $journal['currency_id'];
|
||||
$budgetId = (int) $journal['budget_id'];
|
||||
$budgetName = (string) $journal['budget_name'];
|
||||
$amount = app('steam')->negative($journal['amount']);
|
||||
$journalCurrencyId = (int)$journal['currency_id'];
|
||||
if (false === $convertToNative) {
|
||||
$currencyId = $journalCurrencyId;
|
||||
$currencyName = $journal['currency_name'];
|
||||
$currencySymbol = $journal['currency_symbol'];
|
||||
$currencyCode = $journal['currency_code'];
|
||||
$currencyDecimalPlaces = $journal['currency_decimal_places'];
|
||||
}
|
||||
if (true === $convertToNative && $journalCurrencyId !== $currencyId) {
|
||||
$currencies[$journalCurrencyId] ??= TransactionCurrency::find($journalCurrencyId);
|
||||
$amount = $converter->convert($currencies[$journalCurrencyId], $nativeCurrency, $journal['date'], $amount);
|
||||
}
|
||||
|
||||
// catch "no category" entries.
|
||||
$budgetId = (int)$journal['budget_id'];
|
||||
$budgetName = (string)$journal['budget_name'];
|
||||
|
||||
// catch "no budget" entries.
|
||||
if (0 === $budgetId) {
|
||||
continue;
|
||||
}
|
||||
@@ -153,10 +181,10 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
$array[$currencyId] ??= [
|
||||
'budgets' => [],
|
||||
'currency_id' => $currencyId,
|
||||
'currency_name' => $journal['currency_name'],
|
||||
'currency_symbol' => $journal['currency_symbol'],
|
||||
'currency_code' => $journal['currency_code'],
|
||||
'currency_decimal_places' => $journal['currency_decimal_places'],
|
||||
'currency_name' => $currencyName,
|
||||
'currency_symbol' => $currencySymbol,
|
||||
'currency_code' => $currencyCode,
|
||||
'currency_decimal_places' => $currencyDecimalPlaces,
|
||||
];
|
||||
|
||||
// info about the categories:
|
||||
@@ -168,9 +196,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
|
||||
// add journal to array:
|
||||
// only a subset of the fields.
|
||||
$journalId = (int) $journal['transaction_journal_id'];
|
||||
$journalId = (int)$journal['transaction_journal_id'];
|
||||
$array[$currencyId]['budgets'][$budgetId]['transaction_journals'][$journalId] = [
|
||||
'amount' => app('steam')->negative($journal['amount']),
|
||||
'amount' => $amount,
|
||||
'destination_account_id' => $journal['destination_account_id'],
|
||||
'destination_account_name' => $journal['destination_account_name'],
|
||||
'source_account_id' => $journal['source_account_id'],
|
||||
@@ -204,7 +232,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
|
||||
?TransactionCurrency $currency = null,
|
||||
bool $convertToNative = false
|
||||
): array {
|
||||
Log::debug(sprintf('Start of %s(date, date, array, array, "%s", "%s").', __METHOD__, $currency?->code, var_export($convertToNative, true)));
|
||||
Log::debug(sprintf('Start of %s(date, date, array, array, "%s", %s).', __METHOD__, $currency?->code, var_export($convertToNative, true)));
|
||||
// this collector excludes all transfers TO liabilities (which are also withdrawals)
|
||||
// because those expenses only become expenses once they move from the liability to the friend.
|
||||
// 2024-12-24 disable the exclusion for now.
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\Budget;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\UserRoleEnum;
|
||||
use FireflyIII\Models\Budget;
|
||||
@@ -51,9 +52,7 @@ interface OperationsRepositoryInterface
|
||||
*/
|
||||
public function budgetedPerDay(Budget $budget): string;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array;
|
||||
|
||||
/**
|
||||
@@ -71,6 +70,7 @@ interface OperationsRepositoryInterface
|
||||
Carbon $end,
|
||||
?Collection $accounts = null,
|
||||
?Collection $budgets = null,
|
||||
?TransactionCurrency $currency = null
|
||||
?TransactionCurrency $currency = null,
|
||||
bool $convertToNative = false
|
||||
): array;
|
||||
}
|
||||
|
@@ -444,7 +444,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
|
||||
{
|
||||
$piggyBank->piggyBankEvents()->delete();
|
||||
foreach ($piggyBank->accounts as $account) {
|
||||
if (0 !== bccomp('0', $account->pivot->current_amount)) {
|
||||
if (0 !== bccomp('0', (string) $account->pivot->current_amount)) {
|
||||
event(new ChangedAmount($piggyBank, $account->pivot->current_amount, null, null));
|
||||
}
|
||||
}
|
||||
|
@@ -440,10 +440,12 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($group->transactionJournals as $journal) {
|
||||
$names = sprintf('%s%s', $names, $journal->date->format('Y-m-d-H:i:s'));
|
||||
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
if (-1 === bccomp('0', (string)$transaction->amount)) {
|
||||
$sum = bcadd($sum, $transaction->amount);
|
||||
$sum = bcadd($sum, (string) $transaction->amount);
|
||||
$names = sprintf('%s%s', $names, $transaction->account->name);
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Internal\Support;
|
||||
|
||||
use Deprecated;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Enums\AccountTypeEnum;
|
||||
use FireflyIII\Exceptions\DuplicateTransactionException;
|
||||
@@ -193,8 +194,8 @@ trait AccountServiceTrait
|
||||
/**
|
||||
* @throws FireflyException
|
||||
* *
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
protected function createOBGroup(Account $account, array $data): TransactionGroup
|
||||
{
|
||||
app('log')->debug('Now going to create an OB group.');
|
||||
|
@@ -825,7 +825,13 @@ class JournalUpdateService
|
||||
public function isCompareHashChanged(): bool
|
||||
{
|
||||
Log::debug(sprintf('Now in %s', __METHOD__));
|
||||
$compareHash = $this->transactionGroupRepository->getCompareHash($this->transactionGroup);
|
||||
$compareHash = hash('sha256', sprintf('%s', Carbon::now()->getTimestamp()));
|
||||
if (!$this->transactionGroup instanceof TransactionGroup) {
|
||||
$compareHash = $this->transactionGroupRepository->getCompareHash($this->transactionJournal->transactionGroup);
|
||||
}
|
||||
if ($this->transactionGroup instanceof TransactionGroup) {
|
||||
$this->transactionGroupRepository->getCompareHash($this->transactionGroup);
|
||||
}
|
||||
Log::debug(sprintf('Compare hash is "%s".', $compareHash));
|
||||
Log::debug(sprintf('Start compare hash is "%s".', $this->startCompareHash));
|
||||
|
||||
|
@@ -23,6 +23,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support;
|
||||
|
||||
use Deprecated;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
@@ -31,6 +32,7 @@ use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use NumberFormatter;
|
||||
|
||||
/**
|
||||
@@ -65,13 +67,13 @@ class Amount
|
||||
$fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
|
||||
$fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
|
||||
$fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
|
||||
$result = (string) $fmt->format((float) $rounded); // intentional float
|
||||
$result = (string)$fmt->format((float)$rounded); // intentional float
|
||||
|
||||
if (true === $coloured) {
|
||||
if (1 === bccomp((string) $rounded, '0')) {
|
||||
if (1 === bccomp((string)$rounded, '0')) {
|
||||
return sprintf('<span class="text-success money-positive">%s</span>', $result);
|
||||
}
|
||||
if (-1 === bccomp((string) $rounded, '0')) {
|
||||
if (-1 === bccomp((string)$rounded, '0')) {
|
||||
return sprintf('<span class="text-danger money-negative">%s</span>', $result);
|
||||
}
|
||||
|
||||
@@ -105,23 +107,21 @@ class Amount
|
||||
$amount = $journal[$field] ?? '0';
|
||||
// Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
|
||||
// fallback, the transaction has a foreign amount in $currency.
|
||||
if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === (int) $journal['foreign_currency_id']) {
|
||||
if ($convertToNative && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) {
|
||||
$amount = $journal['foreign_amount'];
|
||||
// Log::debug(sprintf('Overruled, amount is now %s', $amount));
|
||||
}
|
||||
|
||||
return (string) $amount;
|
||||
return (string)$amount;
|
||||
}
|
||||
|
||||
public function convertToNative(?User $user = null): bool
|
||||
{
|
||||
if (!$user instanceof User) {
|
||||
return true === Preferences::get('convert_to_native', false)->data && true === config('cer.enabled');
|
||||
// Log::debug(sprintf('convertToNative [a]: %s', var_export($result, true)));
|
||||
}
|
||||
|
||||
return true === Preferences::getForUser($user, 'convert_to_native', false)->data && true === config('cer.enabled');
|
||||
// Log::debug(sprintf('convertToNative [b]: %s', var_export($result, true)));
|
||||
}
|
||||
|
||||
public function getNativeCurrency(): TransactionCurrency
|
||||
@@ -179,9 +179,9 @@ class Amount
|
||||
return '0';
|
||||
}
|
||||
$amount = $sourceTransaction->{$field} ?? '0';
|
||||
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
|
||||
if ((int)$sourceTransaction->foreign_currency_id === $currency->id) {
|
||||
// use foreign amount instead!
|
||||
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount.
|
||||
$amount = (string)$sourceTransaction->foreign_amount; // hard coded to be foreign amount.
|
||||
}
|
||||
|
||||
return $amount;
|
||||
@@ -195,25 +195,19 @@ class Amount
|
||||
return $user->currencies()->orderBy('code', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function getDefaultCurrency(): TransactionCurrency
|
||||
{
|
||||
return $this->getNativeCurrency();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use getDefaultCurrencyByUserGroup instead
|
||||
*/
|
||||
#[Deprecated(message: 'use getDefaultCurrencyByUserGroup instead')]
|
||||
public function getDefaultCurrencyByUser(User $user): TransactionCurrency
|
||||
{
|
||||
return $this->getDefaultCurrencyByUserGroup($user->userGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
#[Deprecated]
|
||||
public function getDefaultCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency
|
||||
{
|
||||
return $this->getNativeCurrencyByUserGroup($userGroup);
|
||||
|
@@ -25,6 +25,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Support\Authentication;
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Support\Facades\Preferences;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\Guard;
|
||||
@@ -86,14 +87,14 @@ class RemoteUserGuard implements Guard
|
||||
|
||||
if (null !== $header) {
|
||||
$emailAddress = (string) (request()->server($header) ?? apache_request_headers()[$header] ?? null);
|
||||
$preference = app('preferences')->getForUser($retrievedUser, 'remote_guard_alt_email');
|
||||
$preference = Preferences::getForUser($retrievedUser, 'remote_guard_alt_email');
|
||||
|
||||
if ('' !== $emailAddress && null === $preference && $emailAddress !== $userID) {
|
||||
app('preferences')->setForUser($retrievedUser, 'remote_guard_alt_email', $emailAddress);
|
||||
Preferences::setForUser($retrievedUser, 'remote_guard_alt_email', $emailAddress);
|
||||
}
|
||||
// if the pref isn't null and the object returned isn't null, update the email address.
|
||||
if ('' !== $emailAddress && null !== $preference && $emailAddress !== $preference->data) {
|
||||
app('preferences')->setForUser($retrievedUser, 'remote_guard_alt_email', $emailAddress);
|
||||
Preferences::setForUser($retrievedUser, 'remote_guard_alt_email', $emailAddress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -27,6 +27,7 @@ namespace FireflyIII\Support\Cronjobs;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Jobs\CreateAutoBudgetLimits;
|
||||
use FireflyIII\Models\Configuration;
|
||||
use FireflyIII\Support\Facades\FireflyConfig;
|
||||
|
||||
/**
|
||||
* Class AutoBudgetCronjob
|
||||
@@ -36,7 +37,7 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
public function fire(): void
|
||||
{
|
||||
/** @var Configuration $config */
|
||||
$config = app('fireflyconfig')->get('last_ab_job', 0);
|
||||
$config = FireflyConfig::get('last_ab_job', 0);
|
||||
$lastTime = (int) $config->data;
|
||||
$diff = Carbon::now()->getTimestamp() - $lastTime;
|
||||
$diffForHumans = today(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
|
||||
@@ -78,7 +79,7 @@ class AutoBudgetCronjob extends AbstractCronjob
|
||||
$this->jobSucceeded = true;
|
||||
$this->message = 'Auto-budget cron job fired successfully.';
|
||||
|
||||
app('fireflyconfig')->set('last_ab_job', (int) $this->date->format('U'));
|
||||
FireflyConfig::set('last_ab_job', (int) $this->date->format('U'));
|
||||
app('log')->info('Done with auto budget cron job task.');
|
||||
}
|
||||
}
|
||||
|
@@ -50,9 +50,9 @@ class CurrencyForm
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @phpstan-param view-string $view
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function currencyField(string $name, string $view, mixed $value = null, ?array $options = null): string
|
||||
{
|
||||
|
@@ -106,7 +106,7 @@ trait AugumentData
|
||||
{
|
||||
/** @var AccountRepositoryInterface $repository */
|
||||
$repository = app(AccountRepositoryInterface::class);
|
||||
$accounts = $repository->getAccountsByType([AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value]);
|
||||
$accounts = $repository->getAccountsByType([AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value]);
|
||||
$grouped = $accounts->groupBy('id')->toArray();
|
||||
$return = [];
|
||||
foreach ($accountIds as $combinedId) {
|
||||
|
@@ -70,7 +70,7 @@ trait PeriodOverview
|
||||
protected JournalRepositoryInterface $journalRepos;
|
||||
|
||||
/**
|
||||
* This method returns "period entries", so nov-2015, dec-2015, etc etc (this depends on the users session range)
|
||||
* This method returns "period entries", so nov-2015, dec-2015, etc. (this depends on the users session range)
|
||||
* and for each period, the amount of money spent and earned. This is a complex operation which is cached for
|
||||
* performance reasons.
|
||||
*
|
||||
@@ -97,18 +97,26 @@ trait PeriodOverview
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
$transferredAway = [];
|
||||
$transferredIn = [];
|
||||
|
||||
// run a custom query because doing this with the collector is MEGA slow.
|
||||
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
|
||||
|
||||
// loop dates
|
||||
Log::debug(sprintf('Count of loops: %d', count($dates)));
|
||||
$loops = 0;
|
||||
// stop after 10 loops for memory reasons.
|
||||
foreach ($dates as $currentDate) {
|
||||
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
|
||||
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
if ($loops < 10) {
|
||||
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
$entries[]
|
||||
= [
|
||||
'title' => $title,
|
||||
@@ -119,6 +127,7 @@ trait PeriodOverview
|
||||
'transferred_away' => $this->groupByCurrency($transferredAway),
|
||||
'transferred_in' => $this->groupByCurrency($transferredIn),
|
||||
];
|
||||
++$loops;
|
||||
}
|
||||
$cache->store($entries);
|
||||
Timer::stop('account-period-total');
|
||||
@@ -548,27 +557,29 @@ trait PeriodOverview
|
||||
/** @var array $dates */
|
||||
$dates = app('navigation')->blockPeriods($start, $end, $range);
|
||||
$entries = [];
|
||||
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
$transferred = [];
|
||||
// collect all journals in this period (regardless of type)
|
||||
$collector = app(GroupCollectorInterface::class);
|
||||
$collector->setTypes($types)->setRange($start, $end);
|
||||
$genericSet = $collector->getExtractedJournals();
|
||||
$loops = 0;
|
||||
|
||||
foreach ($dates as $currentDate) {
|
||||
$spent = [];
|
||||
$earned = [];
|
||||
$transferred = [];
|
||||
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
|
||||
|
||||
// set to correct array
|
||||
if ('expenses' === $transactionType || 'withdrawal' === $transactionType) {
|
||||
$spent = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
if ('revenue' === $transactionType || 'deposit' === $transactionType) {
|
||||
$earned = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
if ('transfer' === $transactionType || 'transfers' === $transactionType) {
|
||||
$transferred = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
if ($loops < 10) {
|
||||
// set to correct array
|
||||
if ('expenses' === $transactionType || 'withdrawal' === $transactionType) {
|
||||
$spent = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
if ('revenue' === $transactionType || 'deposit' === $transactionType) {
|
||||
$earned = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
if ('transfer' === $transactionType || 'transfers' === $transactionType) {
|
||||
$transferred = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
|
||||
}
|
||||
}
|
||||
$entries[]
|
||||
= [
|
||||
@@ -579,6 +590,7 @@ trait PeriodOverview
|
||||
'earned' => $this->groupByCurrency($earned),
|
||||
'transferred' => $this->groupByCurrency($transferred),
|
||||
];
|
||||
++$loops;
|
||||
}
|
||||
|
||||
return $entries;
|
||||
|
@@ -34,7 +34,7 @@ use Illuminate\Contracts\Validation\Validator as ValidatorContract;
|
||||
use Illuminate\Routing\Route;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Route as RouteFacade;
|
||||
use Hash;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
use function Safe\parse_url;
|
||||
|
||||
|
407
app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
Normal file
407
app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
Normal file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Support\JsonApi\Enrichments;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\UserGroup;
|
||||
use FireflyIII\Support\Facades\Steam;
|
||||
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
|
||||
use FireflyIII\Support\Models\BillDateCalculator;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SubscriptionEnrichment implements EnrichmentInterface
|
||||
{
|
||||
private User $user;
|
||||
private UserGroup $userGroup;
|
||||
private Collection $collection;
|
||||
private bool $convertToNative = false;
|
||||
private ?Carbon $start = null;
|
||||
private ?Carbon $end = null;
|
||||
private array $subscriptionIds = [];
|
||||
private array $objectGroups = [];
|
||||
private array $mappedObjects = [];
|
||||
private array $paidDates = [];
|
||||
private array $notes = [];
|
||||
private array $payDates = [];
|
||||
private TransactionCurrency $nativeCurrency;
|
||||
private BillDateCalculator $calculator;
|
||||
|
||||
public function enrich(Collection $collection): Collection
|
||||
{
|
||||
$this->calculator = app(BillDateCalculator::class);
|
||||
$this->collection = $collection;
|
||||
$this->collectSubscriptionIds();
|
||||
$this->collectNotes();
|
||||
$this->collectObjectGroups();
|
||||
$this->collectPaidDates();
|
||||
$this->collectPayDates();
|
||||
|
||||
$notes = $this->notes;
|
||||
$objectGroups = $this->objectGroups;
|
||||
$paidDates = $this->paidDates;
|
||||
$payDates = $this->payDates;
|
||||
$this->collection = $this->collection->map(function (Bill $item) use ($notes, $objectGroups, $paidDates, $payDates) {
|
||||
$id = (int)$item->id;
|
||||
$currency = $item->transactionCurrency;
|
||||
$nem = $this->getNextExpectedMatch($payDates[$id] ?? []);
|
||||
|
||||
$meta = [
|
||||
'notes' => null,
|
||||
'object_group_id' => null,
|
||||
'object_group_title' => null,
|
||||
'object_group_order' => null,
|
||||
'last_paid_date' => $this->getLastPaidDate($paidDates[$id] ?? []),
|
||||
'paid_dates' => $this->filterPaidDates($paidDates[$id] ?? []),
|
||||
'pay_dates' => $payDates[$id] ?? [],
|
||||
'nem' => $nem,
|
||||
'nem_diff' => $this->getNextExpectedMatchDiff($nem, $payDates[$id] ?? []),
|
||||
];
|
||||
$amounts = [
|
||||
'amount_min' => Steam::bcround($item->amount_min, $currency->decimal_places),
|
||||
'amount_max' => Steam::bcround($item->amount_max, $currency->decimal_places),
|
||||
'average' => Steam::bcround(bcdiv(bcadd($item->amount_min, $item->amount_max), '2'), $currency->decimal_places),
|
||||
];
|
||||
|
||||
// add object group if available
|
||||
if (array_key_exists($id, $this->mappedObjects)) {
|
||||
$key = $this->mappedObjects[$id];
|
||||
$meta['object_group_id'] = $objectGroups[$key]['id'];
|
||||
$meta['object_group_title'] = $objectGroups[$key]['title'];
|
||||
$meta['object_group_order'] = $objectGroups[$key]['order'];
|
||||
}
|
||||
|
||||
// Add notes if available.
|
||||
if (array_key_exists($item->id, $notes)) {
|
||||
$meta['notes'] = $notes[$item->id];
|
||||
}
|
||||
|
||||
// Convert amounts to native currency if needed
|
||||
if ($this->convertToNative && $item->currency_id !== $this->nativeCurrency->id) {
|
||||
$converter = new ExchangeRateConverter();
|
||||
$amounts = [
|
||||
'amount_min' => Steam::bcround($converter->convert($item->transactionCurrency, $this->nativeCurrency, today(), $item->amount_min), $this->nativeCurrency->decimal_places),
|
||||
'amount_max' => Steam::bcround($converter->convert($item->transactionCurrency, $this->nativeCurrency, today(), $item->amount_max), $this->nativeCurrency->decimal_places),
|
||||
];
|
||||
$amounts['average'] = Steam::bcround(bcdiv(bcadd($amounts['amount_min'], $amounts['amount_max']), '2'), $this->nativeCurrency->decimal_places);
|
||||
}
|
||||
$item->amounts = $amounts;
|
||||
$item->meta = $meta;
|
||||
|
||||
return $item;
|
||||
});
|
||||
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function enrichSingle(array|Model $model): array|Model
|
||||
{
|
||||
Log::debug(__METHOD__);
|
||||
$collection = new Collection([$model]);
|
||||
$collection = $this->enrich($collection);
|
||||
|
||||
return $collection->first();
|
||||
}
|
||||
|
||||
private function collectNotes(): void
|
||||
{
|
||||
$notes = Note::query()->whereIn('noteable_id', $this->subscriptionIds)
|
||||
->whereNotNull('notes.text')
|
||||
->where('notes.text', '!=', '')
|
||||
->where('noteable_type', Bill::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
|
||||
;
|
||||
foreach ($notes as $note) {
|
||||
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
|
||||
}
|
||||
Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
|
||||
}
|
||||
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->userGroup = $user->userGroup;
|
||||
}
|
||||
|
||||
public function setUserGroup(UserGroup $userGroup): void
|
||||
{
|
||||
$this->userGroup = $userGroup;
|
||||
}
|
||||
|
||||
public function setConvertToNative(bool $convertToNative): void
|
||||
{
|
||||
$this->convertToNative = $convertToNative;
|
||||
}
|
||||
|
||||
public function setNative(TransactionCurrency $nativeCurrency): void
|
||||
{
|
||||
$this->nativeCurrency = $nativeCurrency;
|
||||
}
|
||||
|
||||
private function collectSubscriptionIds(): void
|
||||
{
|
||||
/** @var Bill $bill */
|
||||
foreach ($this->collection as $bill) {
|
||||
$this->subscriptionIds[] = (int)$bill->id;
|
||||
}
|
||||
$this->subscriptionIds = array_unique($this->subscriptionIds);
|
||||
}
|
||||
|
||||
private function collectObjectGroups(): void
|
||||
{
|
||||
$set = DB::table('object_groupables')
|
||||
->whereIn('object_groupable_id', $this->subscriptionIds)
|
||||
->where('object_groupable_type', Bill::class)
|
||||
->get(['object_groupable_id', 'object_group_id'])
|
||||
;
|
||||
|
||||
$ids = array_unique($set->pluck('object_group_id')->toArray());
|
||||
|
||||
foreach ($set as $entry) {
|
||||
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
|
||||
}
|
||||
|
||||
$groups = ObjectGroup::whereIn('id', $ids)->get(['id', 'title', 'order'])->toArray();
|
||||
foreach ($groups as $group) {
|
||||
$group['id'] = (int)$group['id'];
|
||||
$group['order'] = (int)$group['order'];
|
||||
$this->objectGroups[(int)$group['id']] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectPaidDates(): void
|
||||
{
|
||||
Log::debug('Now in collectPaidDates for bills');
|
||||
if (null === $this->start || null === $this->end) {
|
||||
Log::debug('Parameters are NULL, return empty array');
|
||||
$this->paidDates = [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 2023-07-1 sub one day from the start date to fix a possible bug (see #7704)
|
||||
// 2023-07-18 this particular date is used to search for the last paid date.
|
||||
// 2023-07-18 the cloned $searchDate is used to grab the correct transactions.
|
||||
/** @var Carbon $start */
|
||||
$start = clone $this->start;
|
||||
$searchStart = clone $start;
|
||||
$start->subDay();
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone $this->end;
|
||||
$searchEnd = clone $end;
|
||||
|
||||
// move the search dates to the start of the day.
|
||||
$searchStart->startOfDay();
|
||||
$searchEnd->endOfDay();
|
||||
|
||||
Log::debug(sprintf('Parameters are start: %s, end: %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
|
||||
Log::debug(sprintf('Search parameters are: start: %s, end: %s', $searchStart->format('Y-m-d H:i:s'), $searchEnd->format('Y-m-d H:i:s')));
|
||||
|
||||
// Get from database when bills were paid.
|
||||
$set = $this->user->transactionJournals()
|
||||
->whereIn('bill_id', $this->subscriptionIds)
|
||||
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
|
||||
->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
|
||||
->where('transactions.amount', '>', 0)
|
||||
->before($searchEnd)->after($searchStart)->get(
|
||||
[
|
||||
'transaction_journals.id',
|
||||
'transaction_journals.date',
|
||||
'transaction_journals.transaction_group_id',
|
||||
'transactions.transaction_currency_id',
|
||||
'currency.code AS transaction_currency_code',
|
||||
'currency.decimal_places AS transaction_currency_decimal_places',
|
||||
'transactions.foreign_currency_id',
|
||||
'foreign_currency.code AS foreign_currency_code',
|
||||
'foreign_currency.decimal_places AS foreign_currency_decimal_places',
|
||||
'transactions.amount',
|
||||
'transactions.foreign_amount',
|
||||
]
|
||||
)
|
||||
;
|
||||
Log::debug(sprintf('Count %d entries in set', $set->count()));
|
||||
|
||||
// for each bill, do a loop.
|
||||
/** @var Bill $subscription */
|
||||
foreach ($this->collection as $subscription) {
|
||||
// Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date.
|
||||
Log::debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d')));
|
||||
$lastPaidDate = $this->lastPaidDate($subscription, $set, $start);
|
||||
Log::debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d')));
|
||||
|
||||
// At this point the "next match" is exactly after the last time the bill was paid.
|
||||
$result = [];
|
||||
foreach ($set as $entry) {
|
||||
$array = [
|
||||
'transaction_group_id' => (string)$entry->transaction_group_id,
|
||||
'transaction_journal_id' => (string)$entry->id,
|
||||
'date' => $entry->date->toAtomString(),
|
||||
'date_object' => $entry->date,
|
||||
'currency_id' => $entry->transaction_currency_id,
|
||||
'currency_code' => $entry->transaction_currency_code,
|
||||
'currency_decimal_places' => $entry->transaction_currency_decimal_places,
|
||||
'amount' => Steam::bcround($entry->amount, $entry->transaction_currency_decimal_places),
|
||||
];
|
||||
if (null !== $entry->foreign_amount && null !== $entry->foreign_currency_code) {
|
||||
$array['foreign_currency_id'] = $entry->foreign_currency_id;
|
||||
$array['foreign_currency_code'] = $entry->foreign_currency_code;
|
||||
$array['foreign_currency_decimal_places'] = $entry->foreign_currency_decimal_places;
|
||||
$array['foreign_amount'] = Steam::bcround($entry->foreign_amount, $entry->foreign_currency_decimal_places);
|
||||
}
|
||||
|
||||
$result[] = $array;
|
||||
}
|
||||
$this->paidDates[$subscription->id] = $result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function setStart(?Carbon $start): void
|
||||
{
|
||||
$this->start = $start;
|
||||
}
|
||||
|
||||
public function setEnd(?Carbon $end): void
|
||||
{
|
||||
$this->end = $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest date in the set, or start when set is empty.
|
||||
*/
|
||||
protected function lastPaidDate(Bill $subscription, Collection $dates, Carbon $default): Carbon
|
||||
{
|
||||
$filtered = $dates->filter(function (TransactionJournal $journal) use ($subscription) {
|
||||
return $journal->bill_id === $subscription->id;
|
||||
});
|
||||
if (0 === $filtered->count()) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$latest = $filtered->first()->date;
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($filtered as $journal) {
|
||||
if ($journal->date->gte($latest)) {
|
||||
$latest = $journal->date;
|
||||
}
|
||||
}
|
||||
|
||||
return $latest;
|
||||
}
|
||||
|
||||
private function getLastPaidDate(array $paidData): ?Carbon
|
||||
{
|
||||
Log::debug('getLastPaidDate()');
|
||||
$return = null;
|
||||
foreach ($paidData as $entry) {
|
||||
if (null !== $return) {
|
||||
/** @var Carbon $current */
|
||||
$current = $entry['date_object'];
|
||||
if ($current->gt($return)) {
|
||||
$return = clone $current;
|
||||
}
|
||||
Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
if (null === $return) {
|
||||
/** @var Carbon $return */
|
||||
$return = $entry['date_object'];
|
||||
Log::debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
Log::debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d')));
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
private function collectPayDates(): void
|
||||
{
|
||||
/** @var Bill $subscription */
|
||||
foreach ($this->collection as $subscription) {
|
||||
$id = (int)$subscription->id;
|
||||
$lastPaidDate = $this->getLastPaidDate($paidDates[$id] ?? []);
|
||||
$payDates = $this->calculator->getPayDates($this->start, $this->end, $subscription->date, $subscription->repeat_freq, $subscription->skip, $lastPaidDate);
|
||||
$payDatesFormatted = [];
|
||||
foreach ($payDates as $string) {
|
||||
$date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
|
||||
if (!$date instanceof Carbon) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$payDatesFormatted[] = $date->toAtomString();
|
||||
}
|
||||
$this->payDates[$id] = $payDatesFormatted;
|
||||
}
|
||||
}
|
||||
|
||||
private function filterPaidDates(array $entries): array
|
||||
{
|
||||
return array_map(function (array $entry) {
|
||||
unset($entry['date_object']);
|
||||
|
||||
return $entry;
|
||||
}, $entries);
|
||||
}
|
||||
|
||||
private function getNextExpectedMatch(array $payDates): ?Carbon
|
||||
{
|
||||
// next expected match
|
||||
$nem = null;
|
||||
$firstPayDate = $payDates[0] ?? null;
|
||||
|
||||
if (null !== $firstPayDate) {
|
||||
$nemDate = Carbon::parse($firstPayDate, config('app.timezone'));
|
||||
if (!$nemDate instanceof Carbon) {
|
||||
$nemDate = today(config('app.timezone'));
|
||||
}
|
||||
$nem = $nemDate;
|
||||
|
||||
// nullify again when it's outside the current view range.
|
||||
if (
|
||||
(null !== $this->start && $nemDate->lt($this->start))
|
||||
|| (null !== $this->end && $nemDate->gt($this->end))
|
||||
) {
|
||||
$nem = null;
|
||||
$nemDate = null;
|
||||
$firstPayDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
return $nem;
|
||||
}
|
||||
|
||||
private function getNextExpectedMatchDiff(?Carbon $nem, array $payDates): string
|
||||
{
|
||||
if (null === $nem) {
|
||||
return trans('firefly.not_expected_period');
|
||||
}
|
||||
$nemDiff = trans('firefly.not_expected_period');
|
||||
// converting back and forth is bad code but OK.
|
||||
if ($nem->isToday()) {
|
||||
$nemDiff = trans('firefly.today');
|
||||
}
|
||||
|
||||
$current = $payDates[0] ?? null;
|
||||
if (null !== $current && !$nem->isToday()) {
|
||||
$temp2 = Carbon::parse($current, config('app.timezone'));
|
||||
if (!$temp2 instanceof Carbon) {
|
||||
$temp2 = today(config('app.timezone'));
|
||||
}
|
||||
$nemDiff = trans('firefly.bill_expected_date', ['date' => $temp2->diffForHumans(today(config('app.timezone')), CarbonInterface::DIFF_RELATIVE_TO_NOW)]);
|
||||
}
|
||||
unset($temp2);
|
||||
|
||||
return $nemDiff;
|
||||
}
|
||||
}
|
@@ -54,7 +54,7 @@ class TransactionGroupEnrichment implements EnrichmentInterface
|
||||
private array $notes; // @phpstan-ignore-line
|
||||
private array $tags;
|
||||
private User $user;
|
||||
private TransactionCurrency $nativeCurrency;
|
||||
private readonly TransactionCurrency $nativeCurrency;
|
||||
private UserGroup $userGroup;
|
||||
|
||||
public function __construct()
|
||||
@@ -149,7 +149,9 @@ class TransactionGroupEnrichment implements EnrichmentInterface
|
||||
continue;
|
||||
}
|
||||
if (in_array($name, $this->dateFields, true)) {
|
||||
$this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data);
|
||||
Log::debug(sprintf('Meta data for "%s" is a date : "%s"', $name, $data));
|
||||
$this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data, config('app.timezone'));
|
||||
Log::debug(sprintf('Meta data for "%s" converts to: "%s"', $name, $this->metaData[$entry['transaction_journal_id']][$name]->toW3CString()));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@@ -582,7 +582,7 @@ class Navigation
|
||||
*/
|
||||
public function preferredEndOfPeriod(Carbon $start, Carbon $end): string
|
||||
{
|
||||
if ((int) $start->diffInMonths($end, true) > 1) {
|
||||
if ((int) $start->diffInMonths($end, true) > 1 && (int) $start->diffInMonths($end, true) <= 12) {
|
||||
return 'endOfMonth';
|
||||
}
|
||||
|
||||
@@ -599,7 +599,7 @@ class Navigation
|
||||
*/
|
||||
public function preferredRangeFormat(Carbon $start, Carbon $end): string
|
||||
{
|
||||
if ((int) $start->diffInMonths($end, true) > 1) {
|
||||
if ((int) $start->diffInMonths($end, true) > 1 && (int) $start->diffInMonths($end, true) <= 12) {
|
||||
return '1M';
|
||||
}
|
||||
|
||||
@@ -616,7 +616,7 @@ class Navigation
|
||||
*/
|
||||
public function preferredSqlFormat(Carbon $start, Carbon $end): string
|
||||
{
|
||||
if ((int) $start->diffInMonths($end, true) > 1) {
|
||||
if ((int) $start->diffInMonths($end, true) > 1 && (int) $start->diffInMonths($end, true) <= 12) {
|
||||
return '%Y-%m';
|
||||
}
|
||||
|
||||
|
@@ -51,7 +51,7 @@ class TransactionSummarizer
|
||||
|
||||
public function groupByCurrencyId(array $journals, string $method = 'negative', bool $includeForeign = true): array
|
||||
{
|
||||
Log::debug(sprintf('Now in groupByCurrencyId([%d journals], "%s")', count($journals), $method));
|
||||
Log::debug(sprintf('Now in groupByCurrencyId([%d journals], "%s", %s)', count($journals), $method, var_export($includeForeign, true)));
|
||||
$array = [];
|
||||
foreach ($journals as $journal) {
|
||||
$field = 'amount';
|
||||
@@ -71,12 +71,12 @@ class TransactionSummarizer
|
||||
$foreignCurrencyDecimalPlaces = null;
|
||||
|
||||
if ($this->convertToNative) {
|
||||
Log::debug('convertToNative is true.');
|
||||
// Log::debug('convertToNative is true.');
|
||||
// if convert to native, use the native amount yes or no?
|
||||
$useNative = $this->default->id !== (int) $journal['currency_id'];
|
||||
$useForeign = $this->default->id === (int) $journal['foreign_currency_id'];
|
||||
if ($useNative) {
|
||||
Log::debug(sprintf('Journal #%d switches to native amount (original is %s)', $journal['transaction_journal_id'], $journal['currency_code']));
|
||||
// Log::debug(sprintf('Journal #%d switches to native amount (original is %s)', $journal['transaction_journal_id'], $journal['currency_code']));
|
||||
$field = 'native_amount';
|
||||
$currencyId = $this->default->id;
|
||||
$currencyName = $this->default->name;
|
||||
@@ -85,7 +85,7 @@ class TransactionSummarizer
|
||||
$currencyDecimalPlaces = $this->default->decimal_places;
|
||||
}
|
||||
if ($useForeign) {
|
||||
Log::debug(sprintf('Journal #%d switches to foreign amount (foreign is %s)', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
|
||||
// Log::debug(sprintf('Journal #%d switches to foreign amount (foreign is %s)', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
|
||||
$field = 'foreign_amount';
|
||||
$currencyId = (int) $journal['foreign_currency_id'];
|
||||
$currencyName = $journal['foreign_currency_name'];
|
||||
@@ -95,7 +95,7 @@ class TransactionSummarizer
|
||||
}
|
||||
}
|
||||
if (!$this->convertToNative) {
|
||||
Log::debug('convertToNative is false.');
|
||||
// Log::debug('convertToNative is false.');
|
||||
// use foreign amount?
|
||||
$foreignCurrencyId = (int) $journal['foreign_currency_id'];
|
||||
if (0 !== $foreignCurrencyId) {
|
||||
|
@@ -257,16 +257,16 @@ trait ConvertsDataTypes
|
||||
try {
|
||||
$carbon = Carbon::createFromFormat('Y-m-d', $value);
|
||||
} catch (InvalidDateException $e) { // @phpstan-ignore-line
|
||||
app('log')->error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage()));
|
||||
Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
} catch (InvalidFormatException $e) { // @phpstan-ignore-line
|
||||
app('log')->error(sprintf('[2] "%s" is of an invalid format: %s', $value, $e->getMessage()));
|
||||
Log::error(sprintf('[2] "%s" is of an invalid format: %s', $value, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
}
|
||||
if (!$carbon instanceof Carbon) {
|
||||
app('log')->error(sprintf('[2] "%s" is of an invalid format.', $value));
|
||||
Log::error(sprintf('[2] "%s" is of an invalid format.', $value));
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -278,11 +278,11 @@ trait ConvertsDataTypes
|
||||
try {
|
||||
$carbon = Carbon::parse($value);
|
||||
} catch (InvalidDateException $e) { // @phpstan-ignore-line
|
||||
app('log')->error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage()));
|
||||
Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
} catch (InvalidFormatException $e) {
|
||||
app('log')->error(sprintf('[4] "%s" is of an invalid format: %s', $value, $e->getMessage()));
|
||||
Log::error(sprintf('[4] "%s" is of an invalid format: %s', $value, $e->getMessage()));
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -306,6 +306,7 @@ trait ConvertsDataTypes
|
||||
protected function dateFromValue(?string $string): ?Carbon
|
||||
{
|
||||
if (null === $string) {
|
||||
|
||||
return null;
|
||||
}
|
||||
if ('' === $string) {
|
||||
@@ -319,11 +320,11 @@ trait ConvertsDataTypes
|
||||
// @ignoreException
|
||||
}
|
||||
if (!$carbon instanceof Carbon) {
|
||||
app('log')->debug(sprintf('Invalid date: %s', $string));
|
||||
Log::debug(sprintf('Invalid date: %s', $string));
|
||||
|
||||
return null;
|
||||
}
|
||||
app('log')->debug(sprintf('Date object: %s (%s)', $carbon->toW3cString(), $carbon->getTimezone()));
|
||||
Log::debug(sprintf('Date object: %s (%s) from "%s"', $carbon->toW3cString(), $carbon->getTimezone(), $string));
|
||||
|
||||
return $carbon;
|
||||
}
|
||||
@@ -383,7 +384,7 @@ trait ConvertsDataTypes
|
||||
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
|
||||
}
|
||||
if (!$result instanceof Carbon) {
|
||||
app('log')->debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
|
||||
Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field)));
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -93,7 +93,7 @@ class Steam
|
||||
|
||||
return [];
|
||||
}
|
||||
$defaultCurrency = app('amount')->getNativeCurrency();
|
||||
$defaultCurrency = Amount::getNativeCurrency();
|
||||
if ($convertToNative) {
|
||||
if ($defaultCurrency->id === $currency?->id) {
|
||||
Log::debug(sprintf('Unset [%s] for account #%d (no longer unset "native_balance")', $defaultCurrency->code, $account->id));
|
||||
@@ -224,7 +224,7 @@ class Steam
|
||||
$request->subDay()->endOfDay();
|
||||
Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
|
||||
$startBalance = $this->finalAccountBalance($account, $request);
|
||||
$nativeCurrency = app('amount')->getNativeCurrencyByUserGroup($account->user->userGroup);
|
||||
$nativeCurrency = Amount::getNativeCurrencyByUserGroup($account->user->userGroup);
|
||||
$accountCurrency = $this->getAccountCurrency($account);
|
||||
$hasCurrency = $accountCurrency instanceof TransactionCurrency;
|
||||
$currency = $accountCurrency ?? $nativeCurrency;
|
||||
@@ -294,7 +294,7 @@ class Steam
|
||||
$currentBalance[$entryCurrency->code] ??= '0';
|
||||
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string) $currentBalance[$entryCurrency->code]);
|
||||
|
||||
// if not convert to native, add the amount to "balance", do nothing else.
|
||||
// if not requested to convert to native, add the amount to "balance", do nothing else.
|
||||
if (!$convertToNative) {
|
||||
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
|
||||
}
|
||||
@@ -302,13 +302,13 @@ class Steam
|
||||
// if there is a request to convert, convert to "native_balance" and use "balance" for whichever amount is in the native currency.
|
||||
if ($convertToNative) {
|
||||
$nativeSumOfDay = $converter->convert($entryCurrency, $nativeCurrency, $carbon, $sumOfDay);
|
||||
$currentBalance['native_balance'] = bcadd((string) $currentBalance['native_balance'], $nativeSumOfDay);
|
||||
$currentBalance['native_balance'] = bcadd((string) ($currentBalance['native_balance'] ?? '0'), $nativeSumOfDay);
|
||||
// if it's the same currency as the entry, also add to balance (see other code).
|
||||
if ($currency->id === $entryCurrency->id) {
|
||||
$currentBalance['balance'] = bcadd((string) $currentBalance['balance'], $sumOfDay);
|
||||
}
|
||||
|
||||
}
|
||||
// just set it.
|
||||
// add to final array.
|
||||
$balances[$carbonKey] = $currentBalance;
|
||||
Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance);
|
||||
}
|
||||
@@ -342,7 +342,7 @@ class Steam
|
||||
|
||||
return $cache->get();
|
||||
}
|
||||
Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
|
||||
// Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
|
||||
if (null === $convertToNative) {
|
||||
$convertToNative = Amount::convertToNative($account->user);
|
||||
}
|
||||
@@ -401,7 +401,7 @@ class Steam
|
||||
// Log::debug(sprintf('Virtual balance makes the (native) total %s', $return['balance']));
|
||||
}
|
||||
$final = array_merge($return, $others);
|
||||
Log::debug('Final balance is', $final);
|
||||
// Log::debug('Final balance is', $final);
|
||||
$cache->store($final);
|
||||
|
||||
return $final;
|
||||
|
@@ -28,6 +28,7 @@ use FireflyIII\Models\RuleAction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\TransactionRules\Traits\RefreshNotesTrait;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
/**
|
||||
* Class SetDescription.
|
||||
@@ -51,7 +52,12 @@ class SetDescription implements ActionInterface
|
||||
$after = $this->action->getValue($journal);
|
||||
|
||||
// replace newlines.
|
||||
$after = str_replace(["\r", "\n", "\t", "\036", "\025"], '', $after);
|
||||
$after = trim(str_replace(["\r", "\n", "\t", "\036", "\025"], '', $after));
|
||||
|
||||
if ('' === $after) {
|
||||
Log::warning('Action resulted in an empty description, reset to default value.');
|
||||
$after = '(no description)';
|
||||
}
|
||||
|
||||
DB::table('transaction_journals')
|
||||
->where('id', '=', $journal['transaction_journal_id'])
|
||||
|
@@ -67,8 +67,8 @@ class AccountTransformer extends AbstractTransformer
|
||||
}
|
||||
|
||||
// get account type:
|
||||
$accountType = (string) config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = (string) config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
|
||||
$accountType = (string)config(sprintf('firefly.shortNamesByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = (string)config(sprintf('firefly.shortLiabilityNameByFullName.%s', $account->full_account_type));
|
||||
$liabilityType = '' === $liabilityType ? null : strtolower($liabilityType);
|
||||
|
||||
$liabilityDirection = $account->meta['liability_direction'] ?? null;
|
||||
@@ -89,10 +89,10 @@ class AccountTransformer extends AbstractTransformer
|
||||
$native = null;
|
||||
}
|
||||
|
||||
$decimalPlaces = (int) $account->meta['currency']?->decimal_places;
|
||||
$decimalPlaces = (int)$account->meta['currency']?->decimal_places;
|
||||
$decimalPlaces = 0 === $decimalPlaces ? 2 : $decimalPlaces;
|
||||
$openingBalance = Steam::bcround($openingBalance, $decimalPlaces);
|
||||
$includeNetWorth = 1 === (int) ($account->meta['include_net_worth'] ?? 0);
|
||||
$openingBalanceRounded = Steam::bcround($openingBalance, $decimalPlaces);
|
||||
$includeNetWorth = 1 === (int)($account->meta['include_net_worth'] ?? 0);
|
||||
$longitude = $account->meta['location']['longitude'] ?? null;
|
||||
$latitude = $account->meta['location']['latitude'] ?? null;
|
||||
$zoomLevel = $account->meta['location']['zoom_level'] ?? null;
|
||||
@@ -112,8 +112,55 @@ class AccountTransformer extends AbstractTransformer
|
||||
$currentBalance = Steam::bcround($finalBalance['balance'] ?? '0', $decimalPlaces);
|
||||
$nativeCurrentBalance = $this->convertToNative ? Steam::bcround($finalBalance['native_balance'] ?? '0', $native->decimal_places) : null;
|
||||
|
||||
// set up balances array:
|
||||
$balances = [];
|
||||
$balances[]
|
||||
= [
|
||||
'type' => 'current',
|
||||
'amount' => $currentBalance,
|
||||
'currency_id' => $account->meta['currency_id'] ?? null,
|
||||
'currency_code' => $account->meta['currency']?->code,
|
||||
'currency_symbol' => $account->meta['currency']?->symbol,
|
||||
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
|
||||
'date' => $date->toAtomString(),
|
||||
];
|
||||
if (null !== $nativeCurrentBalance) {
|
||||
$balances[] = [
|
||||
'type' => 'native_current',
|
||||
'amount' => $nativeCurrentBalance,
|
||||
'currency_id' => $native instanceof TransactionCurrency ? (string)$native->id : null,
|
||||
'currency_code' => $native?->code,
|
||||
'currency_symbol' => $native?->symbol,
|
||||
'ccurrency_decimal_places' => $native?->decimal_places,
|
||||
'date' => $date->toAtomString(),
|
||||
|
||||
];
|
||||
}
|
||||
if (null !== $openingBalance) {
|
||||
$balances[] = [
|
||||
'type' => 'opening',
|
||||
'amount' => $openingBalanceRounded,
|
||||
'currency_id' => $account->meta['currency_id'] ?? null,
|
||||
'currency_code' => $account->meta['currency']?->code,
|
||||
'currency_symbol' => $account->meta['currency']?->symbol,
|
||||
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
|
||||
'date' => $openingBalanceDate,
|
||||
];
|
||||
}
|
||||
if (null !== $account->virtual_balance) {
|
||||
$balances[] = [
|
||||
'type' => 'virtual',
|
||||
'amount' => Steam::bcround($account->virtual_balance, $decimalPlaces),
|
||||
'currency_id' => $account->meta['currency_id'] ?? null,
|
||||
'currency_code' => $account->meta['currency']?->code,
|
||||
'currency_symbol' => $account->meta['currency']?->symbol,
|
||||
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
|
||||
'date' => $date->toAtomString(),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => (string) $account->id,
|
||||
'id' => (string)$account->id,
|
||||
'created_at' => $account->created_at->toAtomString(),
|
||||
'updated_at' => $account->updated_at->toAtomString(),
|
||||
'active' => $account->active,
|
||||
@@ -125,7 +172,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
'currency_code' => $account->meta['currency']?->code,
|
||||
'currency_symbol' => $account->meta['currency']?->symbol,
|
||||
'currency_decimal_places' => $account->meta['currency']?->decimal_places,
|
||||
'native_currency_id' => $native instanceof TransactionCurrency ? (string) $native->id : null,
|
||||
'native_currency_id' => $native instanceof TransactionCurrency ? (string)$native->id : null,
|
||||
'native_currency_code' => $native?->code,
|
||||
'native_currency_symbol' => $native?->symbol,
|
||||
'native_currency_decimal_places' => $native?->decimal_places,
|
||||
@@ -140,7 +187,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
'bic' => $account->meta['BIC'] ?? null,
|
||||
'virtual_balance' => Steam::bcround($account->virtual_balance, $decimalPlaces),
|
||||
'native_virtual_balance' => $this->convertToNative ? Steam::bcround($account->native_virtual_balance, $native->decimal_places) : null,
|
||||
'opening_balance' => $openingBalance,
|
||||
'opening_balance' => $openingBalanceRounded,
|
||||
'native_opening_balance' => $nativeOpeningBalance,
|
||||
'opening_balance_date' => $openingBalanceDate,
|
||||
'liability_type' => $liabilityType,
|
||||
@@ -153,6 +200,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
'latitude' => $latitude,
|
||||
'zoom_level' => $zoomLevel,
|
||||
'last_activity' => array_key_exists('last_activity', $account->meta) ? $account->meta['last_activity']->toAtomString() : null,
|
||||
'balances' => $balances,
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'self',
|
||||
@@ -165,7 +213,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
private function getAccountRole(Account $account, string $accountType): ?string
|
||||
{
|
||||
$accountRole = $account->meta['account_role'] ?? null;
|
||||
if ('asset' !== $accountType || '' === (string) $accountRole) {
|
||||
if ('asset' !== $accountType || '' === (string)$accountRole) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -201,7 +249,7 @@ class AccountTransformer extends AbstractTransformer
|
||||
}
|
||||
$monthlyPaymentDate = $object->toAtomString();
|
||||
}
|
||||
if (10 !== strlen((string) $monthlyPaymentDate)) {
|
||||
if (10 !== strlen((string)$monthlyPaymentDate)) {
|
||||
$monthlyPaymentDate = Carbon::parse($monthlyPaymentDate, config('app.timezone'))->toAtomString();
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ class AttachmentTransformer extends AbstractTransformer
|
||||
private readonly AttachmentRepositoryInterface $repository;
|
||||
|
||||
/**
|
||||
* BillTransformer constructor.
|
||||
* AttachmentTransformer constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
|
@@ -24,145 +24,51 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Transformers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterface;
|
||||
use FireflyIII\Models\Bill;
|
||||
use FireflyIII\Models\ObjectGroup;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Support\Facades\Amount;
|
||||
use FireflyIII\Support\Models\BillDateCalculator;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class BillTransformer
|
||||
*/
|
||||
class BillTransformer extends AbstractTransformer
|
||||
{
|
||||
private readonly BillDateCalculator $calculator;
|
||||
private readonly bool $convertToNative;
|
||||
private readonly TransactionCurrency $default;
|
||||
private readonly BillRepositoryInterface $repository;
|
||||
private readonly TransactionCurrency $native;
|
||||
|
||||
/**
|
||||
* BillTransformer constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->repository = app(BillRepositoryInterface::class);
|
||||
$this->calculator = app(BillDateCalculator::class);
|
||||
$this->default = Amount::getNativeCurrency();
|
||||
$this->convertToNative = Amount::convertToNative();
|
||||
$this->native = Amount::getNativeCurrency();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the bill.
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveMethodLength")
|
||||
* @SuppressWarnings("PHPMD.NPathComplexity")
|
||||
*/
|
||||
public function transform(Bill $bill): array
|
||||
{
|
||||
$default = $this->parameters->get('defaultCurrency') ?? $this->default;
|
||||
$currency = $bill->transactionCurrency;
|
||||
|
||||
$paidData = $this->paidData($bill);
|
||||
$lastPaidDate = $this->getLastPaidDate($paidData);
|
||||
$start = $this->parameters->get('start') ?? today()->subYears(10);
|
||||
$end = $this->parameters->get('end') ?? today()->addYears(10);
|
||||
$payDates = $this->calculator->getPayDates($start, $end, $bill->date, $bill->repeat_freq, $bill->skip, $lastPaidDate);
|
||||
$currency = $bill->transactionCurrency;
|
||||
$notes = $this->repository->getNoteText($bill);
|
||||
$notes = '' === $notes ? null : $notes;
|
||||
$objectGroupId = null;
|
||||
$objectGroupOrder = null;
|
||||
$objectGroupTitle = null;
|
||||
$this->repository->setUser($bill->user);
|
||||
|
||||
/** @var null|ObjectGroup $objectGroup */
|
||||
$objectGroup = $bill->objectGroups->first();
|
||||
if (null !== $objectGroup) {
|
||||
$objectGroupId = $objectGroup->id;
|
||||
$objectGroupOrder = $objectGroup->order;
|
||||
$objectGroupTitle = $objectGroup->title;
|
||||
}
|
||||
|
||||
$paidDataFormatted = [];
|
||||
$payDatesFormatted = [];
|
||||
foreach ($paidData as $object) {
|
||||
$date = Carbon::createFromFormat('!Y-m-d', $object['date'], config('app.timezone'));
|
||||
if (!$date instanceof Carbon) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$object['date'] = $date->toAtomString();
|
||||
$paidDataFormatted[] = $object;
|
||||
}
|
||||
|
||||
foreach ($payDates as $string) {
|
||||
$date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
|
||||
if (!$date instanceof Carbon) {
|
||||
$date = today(config('app.timezone'));
|
||||
}
|
||||
$payDatesFormatted[] = $date->toAtomString();
|
||||
}
|
||||
// next expected match
|
||||
$nem = null;
|
||||
$nemDate = null;
|
||||
$nemDiff = trans('firefly.not_expected_period');
|
||||
$firstPayDate = $payDates[0] ?? null;
|
||||
|
||||
if (null !== $firstPayDate) {
|
||||
$nemDate = Carbon::createFromFormat('!Y-m-d', $firstPayDate, config('app.timezone'));
|
||||
if (!$nemDate instanceof Carbon) {
|
||||
$nemDate = today(config('app.timezone'));
|
||||
}
|
||||
$nem = $nemDate->toAtomString();
|
||||
|
||||
// nullify again when it's outside the current view range.
|
||||
if (
|
||||
(null !== $this->parameters->get('start') && $nemDate->lt($this->parameters->get('start')))
|
||||
|| (null !== $this->parameters->get('end') && $nemDate->gt($this->parameters->get('end')))
|
||||
) {
|
||||
$nem = null;
|
||||
$nemDate = null;
|
||||
$firstPayDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
// converting back and forth is bad code but OK.
|
||||
if (null !== $nemDate) {
|
||||
if ($nemDate->isToday()) {
|
||||
$nemDiff = trans('firefly.today');
|
||||
}
|
||||
|
||||
$current = $payDatesFormatted[0] ?? null;
|
||||
if (null !== $current && !$nemDate->isToday()) {
|
||||
$temp2 = Carbon::createFromFormat('Y-m-d\TH:i:sP', $current);
|
||||
if (!$temp2 instanceof Carbon) {
|
||||
$temp2 = today(config('app.timezone'));
|
||||
}
|
||||
$nemDiff = trans('firefly.bill_expected_date', ['date' => $temp2->diffForHumans(today(config('app.timezone')), CarbonInterface::DIFF_RELATIVE_TO_NOW)]);
|
||||
}
|
||||
unset($temp2);
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $bill->id,
|
||||
'created_at' => $bill->created_at->toAtomString(),
|
||||
'updated_at' => $bill->updated_at->toAtomString(),
|
||||
'currency_id' => (string) $bill->transaction_currency_id,
|
||||
'currency_id' => (string)$bill->transaction_currency_id,
|
||||
'currency_code' => $currency->code,
|
||||
'currency_symbol' => $currency->symbol,
|
||||
'currency_decimal_places' => $currency->decimal_places,
|
||||
'native_currency_id' => null === $default ? null : (string) $default->id,
|
||||
'native_currency_code' => $default?->code,
|
||||
'native_currency_symbol' => $default?->symbol,
|
||||
'native_currency_decimal_places' => $default?->decimal_places,
|
||||
|
||||
'native_currency_id' => (string)$this->native->id,
|
||||
'native_currency_code' => $this->native->code,
|
||||
'native_currency_symbol' => $this->native->symbol,
|
||||
'native_currency_decimal_places' => $this->native->decimal_places,
|
||||
|
||||
'name' => $bill->name,
|
||||
'amount_min' => app('steam')->bcround($bill->amount_min, $currency->decimal_places),
|
||||
'amount_max' => app('steam')->bcround($bill->amount_max, $currency->decimal_places),
|
||||
'native_amount_min' => $this->convertToNative ? app('steam')->bcround($bill->native_amount_min, $default->decimal_places) : null,
|
||||
'native_amount_max' => $this->convertToNative ? app('steam')->bcround($bill->native_amount_max, $default->decimal_places) : null,
|
||||
'amount_min' => $bill->amounts['amount_min'],
|
||||
'amount_max' => $bill->amounts['amount_max'],
|
||||
'amount_avg' => $bill->amounts['average'],
|
||||
'date' => $bill->date->toAtomString(),
|
||||
'end_date' => $bill->end_date?->toAtomString(),
|
||||
'extension_date' => $bill->extension_date?->toAtomString(),
|
||||
@@ -170,16 +76,21 @@ class BillTransformer extends AbstractTransformer
|
||||
'skip' => $bill->skip,
|
||||
'active' => $bill->active,
|
||||
'order' => $bill->order,
|
||||
'notes' => $notes,
|
||||
'object_group_id' => null !== $objectGroupId ? (string) $objectGroupId : null,
|
||||
'object_group_order' => $objectGroupOrder,
|
||||
'object_group_title' => $objectGroupTitle,
|
||||
'notes' => $bill->meta['notes'],
|
||||
'object_group_id' => $bill->meta['object_group_id'],
|
||||
'object_group_order' => $bill->meta['object_group_order'],
|
||||
'object_group_title' => $bill->meta['object_group_title'],
|
||||
|
||||
|
||||
'paid_dates' => $bill->meta['paid_dates'],
|
||||
'pay_dates' => $bill->meta['pay_dates'],
|
||||
'next_expected_match' => $bill->meta['nem']?->toAtomString(),
|
||||
'next_expected_match_diff' => $bill->meta['nem_diff'],
|
||||
|
||||
// these fields need work:
|
||||
'next_expected_match' => $nem,
|
||||
'next_expected_match_diff' => $nemDiff,
|
||||
'pay_dates' => $payDatesFormatted,
|
||||
'paid_dates' => $paidDataFormatted,
|
||||
// 'next_expected_match' => $nem,
|
||||
// 'next_expected_match_diff' => $nemDiff,
|
||||
// 'pay_dates' => $payDatesFormatted,
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'self',
|
||||
@@ -188,102 +99,4 @@ class BillTransformer extends AbstractTransformer
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data the bill was paid and predict the next expected match.
|
||||
*/
|
||||
protected function paidData(Bill $bill): array
|
||||
{
|
||||
app('log')->debug(sprintf('Now in paidData for bill #%d', $bill->id));
|
||||
if (null === $this->parameters->get('start') || null === $this->parameters->get('end')) {
|
||||
app('log')->debug('parameters are NULL, return empty array');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// 2023-07-1 sub one day from the start date to fix a possible bug (see #7704)
|
||||
// 2023-07-18 this particular date is used to search for the last paid date.
|
||||
// 2023-07-18 the cloned $searchDate is used to grab the correct transactions.
|
||||
/** @var Carbon $start */
|
||||
$start = clone $this->parameters->get('start');
|
||||
$searchStart = clone $start;
|
||||
$start->subDay();
|
||||
|
||||
/** @var Carbon $end */
|
||||
$end = clone $this->parameters->get('end');
|
||||
$searchEnd = clone $end;
|
||||
|
||||
// move the search dates to the start of the day.
|
||||
$searchStart->startOfDay();
|
||||
$searchEnd->endOfDay();
|
||||
|
||||
app('log')->debug(sprintf('Parameters are start: %s end: %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
|
||||
app('log')->debug(sprintf('Search parameters are: start: %s', $searchStart->format('Y-m-d')));
|
||||
|
||||
// Get from database when bill was paid.
|
||||
$set = $this->repository->getPaidDatesInRange($bill, $searchStart, $searchEnd);
|
||||
app('log')->debug(sprintf('Count %d entries in getPaidDatesInRange()', $set->count()));
|
||||
|
||||
// Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date.
|
||||
app('log')->debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d')));
|
||||
$lastPaidDate = $this->lastPaidDate($set, $start);
|
||||
app('log')->debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d')));
|
||||
|
||||
// At this point the "next match" is exactly after the last time the bill was paid.
|
||||
$result = [];
|
||||
foreach ($set as $entry) {
|
||||
$result[] = [
|
||||
'transaction_group_id' => (string) $entry->transaction_group_id,
|
||||
'transaction_journal_id' => (string) $entry->id,
|
||||
'date' => $entry->date->format('Y-m-d'),
|
||||
'date_object' => $entry->date,
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest date in the set, or start when set is empty.
|
||||
*/
|
||||
protected function lastPaidDate(Collection $dates, Carbon $default): Carbon
|
||||
{
|
||||
if (0 === $dates->count()) {
|
||||
return $default;
|
||||
}
|
||||
$latest = $dates->first()->date;
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($dates as $journal) {
|
||||
if ($journal->date->gte($latest)) {
|
||||
$latest = $journal->date;
|
||||
}
|
||||
}
|
||||
|
||||
return $latest;
|
||||
}
|
||||
|
||||
private function getLastPaidDate(array $paidData): ?Carbon
|
||||
{
|
||||
app('log')->debug('getLastPaidDate()');
|
||||
$return = null;
|
||||
foreach ($paidData as $entry) {
|
||||
if (null !== $return) {
|
||||
/** @var Carbon $current */
|
||||
$current = $entry['date_object'];
|
||||
if ($current->gt($return)) {
|
||||
$return = clone $current;
|
||||
}
|
||||
app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
if (null === $return) {
|
||||
/** @var Carbon $return */
|
||||
$return = $entry['date_object'];
|
||||
app('log')->debug(sprintf('Last paid date is: %s', $return->format('Y-m-d')));
|
||||
}
|
||||
}
|
||||
app('log')->debug(sprintf('Last paid date is: "%s"', $return?->format('Y-m-d')));
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
|
@@ -121,6 +121,15 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && 0 !== bccomp('0', (string) $transaction['foreign_amount'])) {
|
||||
$foreignAmount = app('steam')->positive($transaction['foreign_amount']);
|
||||
}
|
||||
|
||||
// set native amount to the normal amount if the currency matches.
|
||||
if ($transaction['native_currency']['id'] ?? null === $transaction['currency_id']) {
|
||||
$transaction['native_amount'] = $amount;
|
||||
}
|
||||
|
||||
if (array_key_exists('native_amount', $transaction) && null !== $transaction['native_amount']) {
|
||||
$transaction['native_amount'] = app('steam')->positive($transaction['native_amount']);
|
||||
}
|
||||
$type = $this->stringFromArray($transaction, 'transaction_type_type', TransactionTypeEnum::WITHDRAWAL->value);
|
||||
|
||||
// must be 0 (int) or NULL
|
||||
@@ -220,12 +229,12 @@ class TransactionGroupTransformer extends AbstractTransformer
|
||||
'sepa_ci' => $transaction['meta']['sepa_ci'] ?? null,
|
||||
'sepa_batch_id' => $transaction['meta']['sepa_batch_id'] ?? null,
|
||||
|
||||
'interest_date' => $transaction['meta_date']['interest_date'] ?? null,
|
||||
'book_date' => $transaction['meta_date']['book_date'] ?? null,
|
||||
'process_date' => $transaction['meta_date']['process_date'] ?? null,
|
||||
'due_date' => $transaction['meta_date']['due_date'] ?? null,
|
||||
'payment_date' => $transaction['meta_date']['payment_date'] ?? null,
|
||||
'invoice_date' => $transaction['meta_date']['invoice_date'] ?? null,
|
||||
'interest_date' => array_key_exists('interest_date', $transaction['meta_date']) ? $transaction['meta_date']['interest_date']->toW3CString() : null,
|
||||
'book_date' => array_key_exists('book_date', $transaction['meta_date']) ? $transaction['meta_date']['book_date']->toW3CString() : null,
|
||||
'process_date' => array_key_exists('process_date', $transaction['meta_date']) ? $transaction['meta_date']['process_date']->toW3CString() : null,
|
||||
'due_date' => array_key_exists('due_date', $transaction['meta_date']) ? $transaction['meta_date']['due_date']->toW3CString() : null,
|
||||
'payment_date' => array_key_exists('payment_date', $transaction['meta_date']) ? $transaction['meta_date']['payment_date']->toW3CString() : null,
|
||||
'invoice_date' => array_key_exists('invoice_date', $transaction['meta_date']) ? $transaction['meta_date']['invoice_date']->toW3CString() : null,
|
||||
// location data
|
||||
'longitude' => $transaction['location']['longitude'],
|
||||
'latitude' => $transaction['location']['latitude'],
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user