Compare commits

..

57 Commits

Author SHA1 Message Date
github-actions
4aaea89f2c Auto commit for release 'branch-v6.2' on 2024-12-21 2024-12-21 12:27:07 +01:00
James Cole
4fbf7b38fb Do not pull in invalid transactions 2024-12-21 11:40:34 +01:00
James Cole
76075401f9 Recalculate amounts. 2024-12-21 11:33:58 +01:00
James Cole
e2a20dd63d Command to recalculate foreign amounts. 2024-12-21 11:20:48 +01:00
James Cole
b52a1f3eb1 Respond to currency changes. 2024-12-21 07:12:11 +01:00
James Cole
7fd5a88122 Add more column conversions. 2024-12-20 05:31:16 +01:00
James Cole
1a1baa5cda Add observers for amounts. 2024-12-20 05:20:37 +01:00
James Cole
577d671a0c Add native amount column 2024-12-19 06:21:17 +01:00
James Cole
380029ffd8 Fix missing method. 2024-12-18 16:57:11 +01:00
github-actions
76cc27a267 Auto commit for release 'branch-v6.2' on 2024-12-18 2024-12-18 16:45:41 +01:00
James Cole
e8a6f30e4e Merge branch 'develop' into v6.2
# Conflicts:
#	composer.lock
#	config/firefly.php
#	package-lock.json
2024-12-18 16:39:22 +01:00
github-actions
fe6021a3d6 Auto commit for release 'v6.1.25' on 2024-12-18 2024-12-18 16:36:08 +01:00
James Cole
563c54702b Update changelog. 2024-12-18 16:31:37 +01:00
James Cole
4d67d27ba0 Fix #9546 2024-12-18 16:29:19 +01:00
James Cole
edf3876a57 Merge branch 'main' into develop 2024-12-18 16:26:35 +01:00
James Cole
2d10f255c2 Fix reference to old method. 2024-12-18 16:18:06 +01:00
James Cole
ee76cc6761 Fix methods 2024-12-18 16:13:32 +01:00
James Cole
f197e6623b Merge pull request #9551 from firefly-iii/dependabot/github_actions/github/command-1.3.0 2024-12-16 06:59:19 +01:00
James Cole
42a9809450 Merge pull request #9550 from firefly-iii/dependabot/npm_and_yarn/develop/chartjs-chart-sankey-0.14.0 2024-12-16 06:59:10 +01:00
James Cole
444f80a933 Merge pull request #9549 from firefly-iii/dependabot/npm_and_yarn/develop/ag-grid-community/styles-33.0.2 2024-12-16 06:59:01 +01:00
dependabot[bot]
4b985c818a Bump github/command from 1.2.2 to 1.3.0
Bumps [github/command](https://github.com/github/command) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/github/command/releases)
- [Commits](https://github.com/github/command/compare/v1.2.2...v1.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:49:31 +00:00
github-actions
2e62fe7b72 Auto commit for release 'develop' on 2024-12-16 2024-12-16 04:22:19 +01:00
dependabot[bot]
ccd182aed9 Bump chartjs-chart-sankey from 0.13.0 to 0.14.0
Bumps [chartjs-chart-sankey](https://github.com/kurkle/chartjs-chart-sankey) from 0.13.0 to 0.14.0.
- [Release notes](https://github.com/kurkle/chartjs-chart-sankey/releases)
- [Commits](https://github.com/kurkle/chartjs-chart-sankey/compare/v0.13.0...v0.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:06:22 +00:00
dependabot[bot]
615eef3fdd Bump @ag-grid-community/styles from 32.3.3 to 33.0.2
Bumps [@ag-grid-community/styles](https://github.com/ag-grid/ag-grid) from 32.3.3 to 33.0.2.
- [Release notes](https://github.com/ag-grid/ag-grid/releases)
- [Commits](https://github.com/ag-grid/ag-grid/compare/v32.3.3...v33.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:06:13 +00:00
James Cole
d4e4907363 Add option for manual activation. 2024-12-15 19:59:36 +01:00
James Cole
c205e93876 Fix https://github.com/firefly-iii/firefly-iii/issues/7288 2024-12-15 19:51:46 +01:00
James Cole
3b24bb99bb Merge branch 'v6.2' of github.com:firefly-iii/firefly-iii into v6.2 2024-12-15 19:51:37 +01:00
James Cole
2814cd1b2a Better name 2024-12-15 19:51:30 +01:00
github-actions
5285e1ac14 Auto commit for release 'branch-v6.2' on 2024-12-15 2024-12-15 16:16:13 +01:00
James Cole
48a999cf91 Fix old reference. 2024-12-15 16:11:48 +01:00
James Cole
3117c8846e Rename branch release. 2024-12-15 12:31:36 +01:00
github-actions
6bb297e76f Auto commit for release 'branch-v6.2' on 2024-12-15 2024-12-15 10:32:00 +01:00
James Cole
bc698f67ea Merge branch 'main' into v6.2 2024-12-15 09:07:04 +01:00
James Cole
f1c859aaa3 Update build job 2024-12-15 08:57:55 +01:00
James Cole
2d0aa207d4 Fix various piggy bank errors 2024-12-15 08:50:57 +01:00
James Cole
4a75e9c262 Merge branch 'v6.2' of github.com:firefly-iii/firefly-iii into v6.2 2024-12-15 08:20:52 +01:00
James Cole
ee7c4b8863 Fix user test notification. 2024-12-15 08:20:42 +01:00
github-actions
f782979d6c Auto commit for release 'develop' on 2024-12-15 2024-12-15 04:09:28 +01:00
github-actions
53cce6510c Auto commit for release 'branch-v6.2' on 2024-12-14 2024-12-14 22:20:13 +01:00
github-actions
53252b84fd Auto commit for release 'develop' on 2024-12-14 2024-12-14 22:13:19 +01:00
James Cole
8f3cf38f77 Remove exception because it breaks the upgrade. 2024-12-14 22:12:56 +01:00
James Cole
f33766a062 Make column nullable so migration does not fail. 2024-12-14 22:06:53 +01:00
James Cole
ac5e62c65d Merge branch 'main' into v6.2 2024-12-14 21:56:26 +01:00
James Cole
f0e2913802 Ignore phpcs errors. 2024-12-14 21:50:46 +01:00
James Cole
1fa928b98f Should work from here 2024-12-14 21:45:20 +01:00
James Cole
1c691cca33 Duh, that wasn't smart. 2024-12-14 21:39:30 +01:00
James Cole
69e1eb3eff echo debug and upgrade to 8.4 2024-12-14 21:37:45 +01:00
James Cole
90794cb515 Experimental branch build. 2024-12-14 21:35:14 +01:00
James Cole
277f5e538f Merge pull request #9534 from firefly-iii/dependabot/composer/composer-736964bd2b 2024-12-10 04:33:36 +01:00
dependabot[bot]
89f197b9d4 Bump league/commonmark in the composer group across 1 directory
Bumps the composer group with 1 update in the / directory: [league/commonmark](https://github.com/thephpleague/commonmark).


Updates `league/commonmark` from 2.5.3 to 2.6.0
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/2.6/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/2.5.3...2.6.0)

---
updated-dependencies:
- dependency-name: league/commonmark
  dependency-type: direct:production
  dependency-group: composer
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-10 01:17:43 +00:00
github-actions
69b2c1f4d2 Auto commit for release 'develop' on 2024-12-09 2024-12-09 04:21:46 +01:00
James Cole
1ccda0b598 Merge pull request #9516 from firefly-iii/dependabot/npm_and_yarn/develop/vite-6.0.1
Bump vite from 5.4.11 to 6.0.1
2024-12-04 06:54:15 +01:00
James Cole
eb3b67ffd6 Merge pull request #9515 from firefly-iii/dependabot/composer/develop/laravel-json-api/laravel-5.0.0
Bump laravel-json-api/laravel from 4.1.1 to 5.0.0
2024-12-04 06:39:04 +01:00
dependabot[bot]
591b795aa3 Bump vite from 5.4.11 to 6.0.1
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.4.11 to 6.0.1.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@6.0.1/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 03:59:55 +00:00
dependabot[bot]
ac21ed7d18 Bump laravel-json-api/laravel from 4.1.1 to 5.0.0
Bumps [laravel-json-api/laravel](https://github.com/laravel-json-api/laravel) from 4.1.1 to 5.0.0.
- [Release notes](https://github.com/laravel-json-api/laravel/releases)
- [Changelog](https://github.com/laravel-json-api/laravel/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/laravel-json-api/laravel/compare/v4.1.1...v5.0.0)

---
updated-dependencies:
- dependency-name: laravel-json-api/laravel
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 03:55:52 +00:00
github-actions
d9c66a2db0 Auto commit for release 'develop' on 2024-12-02 2024-12-02 04:21:56 +01:00
github-actions
6f02eff020 Auto commit for release 'develop' on 2024-11-29 2024-11-29 06:56:47 +01:00
53 changed files with 1612 additions and 569 deletions

View File

@@ -97,13 +97,13 @@
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
},
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {

View File

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

View File

@@ -4,9 +4,13 @@ on:
workflow_dispatch:
inputs:
version:
description: 'Release "v1.2.3" or "develop"'
description: 'Release "v1.2.3" or "develop" or "branch-abc"'
required: true
default: 'develop'
phpversion:
description: 'PHP version'
required: true
default: '8.3'
schedule:
- cron: '0 3 * * MON'
@@ -23,6 +27,11 @@ jobs:
if [[ "develop" == "$version" ]]; then
git checkout --track origin/develop
git pull
elif [[ "$version" == branch* ]]; then
PULLBRANCH=${version:7}
echo "The branch is '$PULLBRANCH' ($version)"
git checkout --track origin/$PULLBRANCH
git pull
else
git config user.name github-actions
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
@@ -36,7 +45,7 @@ jobs:
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
php-version: ${{ github.event.inputs.phpversion }}
extensions: mbstring, intl, zip, bcmath
- name: crowdin action
uses: crowdin/github-action@v2
@@ -133,7 +142,7 @@ jobs:
rm -rf vendor composer.lock
composer update --no-dev --no-scripts --no-plugins -q
sudo chown -R runner:docker resources/lang
.ci/phpcs.sh
.ci/phpcs.sh || true
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v6
with:
@@ -166,6 +175,16 @@ jobs:
tarName=FireflyIII-develop.tar.gz
fi
# if this is a branch build, also slightly different variable names.
if [[ "$version" == branch* ]]; then
[[ -z $(git status --untracked-files=normal --porcelain) ]] && echo "this branch is clean, no need to push..." && exit 0;
# branch builds overrule develop
releaseName=$version-$(date +'%Y%m%d')
originalName=$releaseName
zipName=FireflyIII-$version.zip
tarName=FireflyIII-$version.tar.gz
fi
# in both cases, if the release or tag already exists, add ".1" until it no longer exists.
tagFound=true
tagCount=1
@@ -207,12 +226,13 @@ jobs:
gpg --armor --detach-sign $zipName
gpg --armor --detach-sign $tarName
# create a development (nightly) release:
# describe the development release.
if [[ "develop" == "$version" ]]; then
echo 'Develop release.'
# add text to output.txt (instructions)
rm output.txt
echo "Bi-weekly development release of Firefly III with the latest fixes, translations and features. Docker users can find this release under the \`develop\` tag." >> output.txt
touch output.txt
sudo chown -R runner:docker output.txt
echo "Weekly development release of Firefly III with the latest fixes, translations and features. Docker users can find this release under the \`develop\` tag." >> output.txt
echo "" >> output.txt
echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
echo "" >> output.txt
@@ -220,15 +240,62 @@ 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
fi
# describe a branch release
if [[ "$version" == branch* ]]; then
echo 'Branch release.'
rm output.txt
touch output.txt
sudo chown -R runner:docker output.txt
echo "Irregular BRANCH release of Firefly III. This release contains specific features or changes. Docker users can find this release under the \`$version\` tag." >> output.txt
echo "" >> output.txt
echo "This release was created on **$(date +'%Y-%m-%d')** and may contain unexpected bugs. Data loss is rare but is not impossible. The releases are signed, and you can verify them using the [Firefly III releases PGP key](https://docs.firefly-iii.org/explanation/more-information/signatures/)." >> output.txt
echo "" >> output.txt
echo "* Please read the 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 "" >> output.txt
echo ":warning: Please be careful with this branch pre-release, as it may not work as expected." >> output.txt
fi
# describe the main release
if [[ "develop" != "$version" ]] && [[ "$version" != branch* ]]; then
sudo chown -R runner:docker output.txt
echo 'Main release.'
echo '' >> output.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
fi
# create a development release:
if [[ "develop" == "$version" ]]; then
# create the release:
echo "Create nightly release."
git tag -a $releaseName -m "Nightly development release '$version' on $(date +'%Y-%m-%d')"
echo "Create develop release."
git tag -a $releaseName -m "Development release '$version' on $(date +'%Y-%m-%d')"
git push origin $releaseName
gh release create $releaseName -p --verify-tag \
-t "Development release for $(date +'%Y-%m-%d')" \
-F output.txt
fi
# create a branch release:
if [[ "$version" == branch* ]]; then
# create the release:
echo "Create branch release."
git tag -a $releaseName -m "Branch release '$version' on $(date +'%Y-%m-%d')"
git push origin $releaseName
gh release create $releaseName -p --verify-tag \
-t "Branch release for $(date +'%Y-%m-%d')" \
-F output.txt
fi
# create a development (nightly) release:
if [[ "develop" == "$version" ]] || [[ "$version" == branch* ]]; then
# add zip file to release.
gh release upload $releaseName $zipName
gh release upload $releaseName $tarName
@@ -247,16 +314,6 @@ jobs:
gh release upload $releaseName HEAD.txt
else
echo 'MAIN (real) release'
sudo chown -R runner:docker output.txt
# add text to output.txt (more instructions)
echo '' >> output.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
echo "Create default release."
git tag -a $releaseName -m "Here be changelog"
git push origin $releaseName
gh release create $releaseName -F output.txt -t "$releaseName" --verify-tag

View File

@@ -103,7 +103,7 @@ class PiggyBankController extends Controller
/** @var PiggyBank $piggy */
foreach ($piggies as $piggy) {
$currency = $piggy->transactionCurrency;
$currentAmount = $this->piggyRepository->getRepetition($piggy)->current_amount ?? '0';
$currentAmount = $this->piggyRepository->getCurrentAmount($piggy);
$objectGroup = $piggy->objectGroups()->first();
$response[] = [
'id' => (string)$piggy->id,

View File

@@ -62,7 +62,6 @@ class AccountController extends Controller
*/
public function search(Request $request): JsonResponse|Response
{
app('log')->debug('Now in account search()');
$manager = $this->getManager();
$query = trim((string)$request->get('query'));
$field = trim((string)$request->get('field'));
@@ -70,6 +69,7 @@ class AccountController extends Controller
if ('' === $query || !in_array($field, $this->validFields, true)) {
return response(null, 422);
}
app('log')->debug(sprintf('Now in account search("%s", "%s")', $field, $query));
$types = $this->mapAccountTypes($type);
/** @var AccountSearch $search */

View File

@@ -119,7 +119,7 @@ class StoreRequest extends FormRequest
'description' => 'min:1|max:32768|nullable',
'rule_group_id' => 'belongsToUser:rule_groups|required_without:rule_group_title',
'rule_group_title' => 'nullable|min:1|max:255|required_without:rule_group_id|belongsToUser:rule_groups,title',
'trigger' => 'required|in:store-journal,update-journal',
'trigger' => 'required|in:store-journal,update-journal,manual-activation',
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue|max:1024',
'triggers.*.stop_processing' => [new IsBoolean()],

View File

@@ -138,7 +138,7 @@ class UpdateRequest extends FormRequest
'description' => 'min:1|max:32768|nullable',
'rule_group_id' => 'belongsToUser:rule_groups',
'rule_group_title' => 'nullable|min:1|max:255|belongsToUser:rule_groups,title',
'trigger' => 'in:store-journal,update-journal',
'trigger' => 'in:store-journal,update-journal.manual-activation',
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue|max:1024',
'triggers.*.stop_processing' => [new IsBoolean()],

View File

@@ -31,7 +31,6 @@ use FireflyIII\Models\Bill;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\CurrencyExchangeRate;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\RecurrenceTransaction;
use FireflyIII\Models\RuleTrigger;
use Illuminate\Console\Command;
@@ -58,8 +57,6 @@ class CorrectAmounts extends Command
$this->fixBudgetLimits();
// currency_exchange_rates must be positive
$this->fixExchangeRates();
// piggy_bank_repetitions must be positive
$this->fixRepetitions();
// piggy_banks must be positive
$this->fixPiggyBanks();
// recurrences_transactions amount must be positive
@@ -161,24 +158,6 @@ class CorrectAmounts extends Command
$this->friendlyInfo(sprintf('Corrected %d currency exchange rate(s).', $count));
}
private function fixRepetitions(): void
{
$set = PiggyBankRepetition::where('current_amount', '<', 0)->get();
$count = $set->count();
if (0 === $count) {
$this->friendlyPositive('All piggy bank repetition amounts are positive.');
return;
}
/** @var PiggyBankRepetition $item */
foreach ($set as $item) {
$item->current_amount = app('steam')->positive($item->current_amount);
$item->save();
}
$this->friendlyInfo(sprintf('Corrected %d piggy bank repetition amount(s).', $count));
}
private function fixPiggyBanks(): void
{
$set = PiggyBank::where('target_amount', '<', 0)->get();

View File

@@ -10,6 +10,7 @@ use Illuminate\Console\Command;
*/
class CorrectionSkeleton extends Command
{
use ShowsFriendlyMessages;
protected $description = 'DESCRIPTION HERE';
protected $signature = 'firefly-iii:CORR_COMMAND';

View File

@@ -0,0 +1,229 @@
<?php
declare(strict_types=1);
/*
* RecalculateNativeAmounts.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Handlers\Observer\TransactionObserver;
use FireflyIII\Models\Account;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface;
use FireflyIII\Repositories\UserGroups\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Query\Builder as DatabaseBuilder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class RecalculateNativeAmounts extends Command
{
use ShowsFriendlyMessages;
protected $description = 'Recalculate native amounts for all objects.';
protected $signature = 'firefly-iii:recalculate-native-amounts';
/**
* Execute the console command.
*/
public function handle(): int
{
Log::debug('Will update all native amounts. This may take some time.');
$this->friendlyWarning('Recalculating native amounts for all objects. This may take some time!');
/** @var UserGroupRepositoryInterface $repository */
$repository = app(UserGroupRepositoryInterface::class);
/** @var UserGroup $userGroup */
foreach ($repository->getAll() as $userGroup) {
$this->recalculateForGroup($userGroup);
}
$this->friendlyInfo('Recalculated all native amounts.');
return 0;
}
private function recalculateForGroup(UserGroup $userGroup): void
{
Log::debug(sprintf('Now recalculating for user group #%d', $userGroup->id));
$this->recalculateAccounts($userGroup);
// do a check with the group's currency so we can skip some stuff.
Preferences::mark();
$currency = app('amount')->getDefaultCurrencyByUserGroup($userGroup);
$this->recalculatePiggyBanks($userGroup, $currency);
$this->recalculateBudgets($userGroup, $currency);
$this->recalculateAvailableBudgets($userGroup, $currency);
$this->recalculateBills($userGroup, $currency);
$this->calculateTransactions($userGroup, $currency);
}
private function recalculateAccounts(UserGroup $userGroup): void
{
$set = $userGroup->accounts()->where(function (EloquentBuilder $q): void {
$q->whereNotNull('virtual_balance');
$q->orWhere('virtual_balance', '!=', '');
})->get();
/** @var Account $account */
foreach ($set as $account) {
$account->touch();
}
Log::debug(sprintf('Recalculated %d accounts', $set->count()));
}
private function recalculatePiggyBanks(UserGroup $userGroup, TransactionCurrency $currency): void
{
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$set = $repository->getPiggyBanks();
$set = $set->filter(
static function (PiggyBank $piggyBank) use ($currency) {
return $currency->id !== $piggyBank->transaction_currency_id;
}
);
foreach ($set as $piggyBank) {
$piggyBank->encrypted = false;
$piggyBank->save();
foreach ($piggyBank->accounts as $account) {
$account->pivot->native_current_amount = null;
if (0 !== bccomp($account->pivot->current_amount, '0')) {
$account->pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $currency, today(), $account->pivot->current_amount);
}
$account->pivot->save();
}
$this->recalculatePiggyBankEvents($piggyBank);
}
Log::debug(sprintf('Recalculated %d piggy banks.', $set->count()));
}
private function recalculatePiggyBankEvents(PiggyBank $piggyBank): void
{
$set = $piggyBank->piggyBankEvents()->get();
$set->each(
static function (PiggyBankEvent $event): void {
$event->touch();
}
);
Log::debug(sprintf('Recalculated %d piggy bank events.', $set->count()));
}
private function recalculateBudgets(UserGroup $userGroup, TransactionCurrency $currency): void
{
$set = $userGroup->budgets()->get();
/** @var Budget $budget */
foreach ($set as $budget) {
$this->recalculateBudgetLimits($budget, $currency);
$this->recalculateAutoBudgets($budget, $currency);
}
Log::debug(sprintf('Recalculated %d budgets.', $set->count()));
}
private function recalculateBudgetLimits(Budget $budget, TransactionCurrency $currency): void
{
$set = $budget->budgetlimits()->where('transaction_currency_id', '!=', $currency->id)->get();
/** @var BudgetLimit $limit */
foreach ($set as $limit) {
Log::debug(sprintf('Will now touch BL #%d', $limit->id));
$limit->touch();
Log::debug(sprintf('Done with touch BL #%d', $limit->id));
}
Log::debug(sprintf('Recalculated %d budget limits.', $set->count()));
}
private function recalculateAutoBudgets(Budget $budget, TransactionCurrency $currency): void
{
$set = $budget->autoBudgets()->where('transaction_currency_id', '!=', $currency->id)->get();
/** @var AutoBudget $autoBudget */
foreach ($set as $autoBudget) {
$autoBudget->touch();
}
Log::debug(sprintf('Recalculated %d auto budgets.', $set->count()));
}
private function recalculateBills(UserGroup $userGroup, TransactionCurrency $currency): void
{
$set = $userGroup->bills()->where('transaction_currency_id', '!=', $currency->id)->get();
/** @var Bill $bill */
foreach ($set as $bill) {
$bill->touch();
}
Log::debug(sprintf('Recalculated %d bills.', $set->count()));
}
private function recalculateAvailableBudgets(UserGroup $userGroup, TransactionCurrency $currency): void
{
Log::debug('Start with available budgets.');
$set = $userGroup->availableBudgets()->where('transaction_currency_id', '!=', $currency->id)->get();
/** @var AvailableBudget $budget */
foreach ($set as $budget) {
$budget->touch();
}
Log::debug(sprintf('Recalculated %d available budgets.', $set->count()));
}
private function calculateTransactions(UserGroup $userGroup, TransactionCurrency $currency): void
{
// custom query because of the potential size of this update.
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (DatabaseBuilder $q) use ($currency): void {
$q->whereNot('transactions.transaction_currency_id', $currency->id)
->orWhereNot('transactions.foreign_currency_id', $currency->id)
;
})
->get(['transactions.id'])
;
TransactionObserver::$recalculate = false;
foreach ($set as $item) {
// here we are.
$transaction = Transaction::find($item->id);
$transaction->touch();
}
TransactionObserver::$recalculate = true;
Log::debug(sprintf('Recalculated %d transactions.', $set->count()));
}
}

View File

@@ -98,12 +98,12 @@ class UpgradeMultiPiggyBanks extends Command
// update piggy bank to have a currency.
$piggyBank->transaction_currency_id = $currency->id;
$piggyBank->save();
$piggyBank->saveQuietly();
// store current amount in account association.
$piggyBank->accounts()->sync([$piggyBank->account->id => ['current_amount' => $repetition->current_amount]]);
$piggyBank->account_id = null;
$piggyBank->save();
$piggyBank->saveQuietly();
// remove all repetitions (no longer used)
$piggyBank->piggyBankRepetitions()->delete();

View File

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

View File

@@ -0,0 +1,43 @@
<?php
/*
* UserGroupChangedDefaultCurrency.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Preferences;
use FireflyIII\Events\Event;
use FireflyIII\Models\UserGroup;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class UserGroupChangedDefaultCurrency extends Event
{
use SerializesModels;
public UserGroup $userGroup;
public function __construct(UserGroup $userGroup)
{
Log::debug('User group changed default currency.');
$this->userGroup = $userGroup;
}
}

View File

@@ -32,6 +32,7 @@ use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\User;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Log;
/**
* Class PiggyBankFactory
@@ -87,7 +88,7 @@ class PiggyBankFactory
try {
/** @var PiggyBank $piggyBank */
$piggyBank = PiggyBank::create($piggyBankData);
$piggyBank = PiggyBank::createQuietly($piggyBankData);
} catch (QueryException $e) {
app('log')->error(sprintf('Could not store piggy bank: %s', $e->getMessage()), $piggyBankData);
@@ -102,7 +103,6 @@ class PiggyBankFactory
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
$piggyBank->objectGroups()->sync([$objectGroup->id]);
$piggyBank->save();
}
}
// try also with ID
@@ -111,10 +111,12 @@ class PiggyBankFactory
$objectGroup = $this->findObjectGroupById($objectGroupId);
if (null !== $objectGroup) {
$piggyBank->objectGroups()->sync([$objectGroup->id]);
$piggyBank->save();
}
}
Log::debug('Touch piggy bank');
$piggyBank->encrypted = false;
$piggyBank->save();
$piggyBank->touch();
return $piggyBank;
}
@@ -183,7 +185,7 @@ class PiggyBankFactory
$order = $data['order'];
}
$piggyBank->order = $order;
$piggyBank->save();
$piggyBank->saveQuietly();
return $piggyBank;
}
@@ -221,7 +223,22 @@ class PiggyBankFactory
public function linkToAccountIds(PiggyBank $piggyBank, array $accounts): void
{
Log::debug(sprintf('Linking piggy bank #%d to %d accounts.', $piggyBank->id, count($accounts)), $accounts);
// collect current current_amount so the sync does not remove them.
// TODO this is a tedious check. Feels like a hack.
$toBeLinked = [];
foreach($piggyBank->accounts as $account) {
foreach($accounts as $info) {
if($account->id === $info['account_id']) {
if(array_key_exists($account->id, $accounts)) {
$toBeLinked[$account->id] = ['current_amount' => $account->pivot->current_amount];
Log::debug(sprintf('Prefilled for account #%d with amount %s', $account->id, $account->pivot->current_amount));
}
}
}
}
/** @var array $info */
foreach ($accounts as $info) {
$account = $this->accountRepository->find((int) ($info['account_id'] ?? 0));
@@ -230,13 +247,14 @@ class PiggyBankFactory
}
if (array_key_exists('current_amount', $info)) {
$toBeLinked[$account->id] = ['current_amount' => $info['current_amount']];
//$piggyBank->accounts()->syncWithoutDetaching([$account->id => ['current_amount' => $info['current_amount'] ?? '0']]);
Log::debug(sprintf('Will link account #%d with amount %s', $account->id, $account->pivot->current_amount));
}
if (!array_key_exists('current_amount', $info)) {
$toBeLinked[$account->id] = [];
//$piggyBank->accounts()->syncWithoutDetaching([$account->id]);
$toBeLinked[$account->id] ??= [];
Log::debug(sprintf('Will link account #%d with info: ', $account->id), $toBeLinked[$account->id]);
}
}
Log::debug(sprintf('Link information: %s', json_encode($toBeLinked)));
$piggyBank->accounts()->sync($toBeLinked);
}
}

View File

@@ -0,0 +1,135 @@
<?php
/*
* PreferencesEventHandler.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\Preferences\UserGroupChangedDefaultCurrency;
use FireflyIII\Models\Budget;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\UserGroups\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\UserGroups\PiggyBank\PiggyBankRepositoryInterface;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class PreferencesEventHandler
{
public function resetNativeAmounts(UserGroupChangedDefaultCurrency $event): void
{
// Reset the native amounts for all objects that have it.
Log::debug('Resetting native amounts for all objects.');
$tables = [
// !!! this array is also in the migration
'accounts' => ['native_virtual_balance'],
'available_budgets' => ['native_amount'],
'bills' => ['native_amount_min', 'native_amount_max'],
// 'transactions' => ['native_amount', 'native_foreign_amount']
];
foreach ($tables as $table => $columns) {
foreach ($columns as $column) {
Log::debug(sprintf('Resetting column %s in table %s.', $column, $table));
DB::table($table)->where('user_group_id', $event->userGroup->id)->update([$column => null]);
}
}
$this->resetPiggyBanks($event->userGroup);
$this->resetBudgets($event->userGroup);
$this->resetTransactions($event->userGroup);
}
private function resetPiggyBanks(UserGroup $userGroup): void
{
$repository = app(PiggyBankRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$piggyBanks = $repository->getPiggyBanks();
/** @var PiggyBank $piggyBank */
foreach ($piggyBanks as $piggyBank) {
if (null !== $piggyBank->native_target_amount) {
Log::debug(sprintf('Resetting native_target_amount for piggy bank #%d.', $piggyBank->id));
$piggyBank->native_target_amount = null;
$piggyBank->saveQuietly();
}
foreach ($piggyBank->accounts as $account) {
if (null !== $account->pivot->native_current_amount) {
Log::debug(sprintf('Resetting native_current_amount for piggy bank #%d and account #%d.', $piggyBank->id, $account->id));
$account->pivot->native_current_amount = null;
$account->pivot->save();
}
}
foreach ($piggyBank->piggyBankEvents as $event) {
if (null !== $event->native_amount) {
Log::debug(sprintf('Resetting native_amount for piggy bank #%d and event #%d.', $piggyBank->id, $event->id));
$event->native_amount = null;
$event->saveQuietly();
}
}
}
}
private function resetBudgets(UserGroup $userGroup): void
{
$repository = app(BudgetRepositoryInterface::class);
$repository->setUserGroup($userGroup);
$set = $repository->getBudgets();
/** @var Budget $budget */
foreach ($set as $budget) {
foreach ($budget->autoBudgets as $autoBudget) {
if (null !== $autoBudget->native_amount) {
if (null !== $autoBudget->native_amount) {
Log::debug(sprintf('Resetting native_amount for budget #%d and auto budget #%d.', $budget->id, $autoBudget->id));
$autoBudget->native_amount = null;
$autoBudget->saveQuietly();
}
}
}
foreach ($budget->budgetlimits as $limit) {
if (null !== $limit->native_amount) {
Log::debug(sprintf('Resetting native_amount for budget #%d and budget limit #%d.', $budget->id, $limit->id));
$limit->native_amount = null;
$limit->saveQuietly();
}
}
}
}
private function resetTransactions(UserGroup $userGroup): void
{
// custom query because of the potential size of this update.
$success = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(static function (Builder $q): void {
$q->whereNotNull('native_amount')
->orWhereNotNull('native_foreign_amount')
;
})
->update(['native_amount' => null, 'native_foreign_amount' => null])
;
Log::debug(sprintf('Updated %d transactions.', $success));
}
}

View File

@@ -26,6 +26,9 @@ namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Account;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
/**
* Class AccountObserver
@@ -53,4 +56,35 @@ class AccountObserver
$account->notes()->delete();
$account->locations()->delete();
}
public function created(Account $account): void
{
Log::debug('Observe "created" of an account.');
$this->updateNativeAmount($account);
}
public function updated(Account $account): void
{
Log::debug('Observe "updated" of an account.');
$this->updateNativeAmount($account);
}
private function updateNativeAmount(Account $account): void
{
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($account->user->userGroup);
$repository = app(AccountRepositoryInterface::class);
$currency = $repository->getAccountCurrency($account);
if (null !== $currency && $currency->id !== $userCurrency->id && '' !== (string) $account->virtual_balance && 0 !== bccomp($account->virtual_balance, '0')) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$account->native_virtual_balance = $converter->convert($currency, $userCurrency, today(), $account->virtual_balance);
}
if ('' === (string) $account->virtual_balance || ('' !== (string) $account->virtual_balance && 0 === bccomp($account->virtual_balance, '0'))) {
$account->virtual_balance = null;
$account->native_virtual_balance = null;
}
$account->saveQuietly();
Log::debug('Account native virtual balance is updated.');
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* AutoBudgetObserver.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
class AutoBudgetObserver
{
public function updated(AutoBudget $autoBudget): void
{
Log::debug('Observe "updated" of an auto budget.');
$this->updateNativeAmount($autoBudget);
}
public function created(AutoBudget $autoBudget): void
{
Log::debug('Observe "created" of an auto budget.');
$this->updateNativeAmount($autoBudget);
}
private function updateNativeAmount(AutoBudget $autoBudget): void
{
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($autoBudget->budget->user->userGroup);
$autoBudget->native_amount = null;
if ($autoBudget->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$autoBudget->native_amount = $converter->convert($autoBudget->transactionCurrency, $userCurrency, today(), $autoBudget->amount);
}
$autoBudget->saveQuietly();
Log::debug('Auto budget native amount is updated.');
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* AutoBudgetObserver.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
class AvailableBudgetObserver
{
public function updated(AvailableBudget $availableBudget): void
{
Log::debug('Observe "updated" of an available budget.');
$this->updateNativeAmount($availableBudget);
}
public function created(AvailableBudget $availableBudget): void
{
Log::debug('Observe "created" of an available budget.');
$this->updateNativeAmount($availableBudget);
}
private function updateNativeAmount(AvailableBudget $availableBudget): void
{
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($availableBudget->user->userGroup);
$availableBudget->native_amount = null;
if ($availableBudget->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$availableBudget->native_amount = $converter->convert($availableBudget->transactionCurrency, $userCurrency, today(), $availableBudget->amount);
}
$availableBudget->saveQuietly();
Log::debug('Available budget native amount is updated.');
}
}

View File

@@ -24,6 +24,8 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Bill;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
/**
* Class BillObserver
@@ -38,4 +40,31 @@ class BillObserver
}
$bill->notes()->delete();
}
public function updated(Bill $bill): void
{
Log::debug('Observe "updated" of a bill.');
$this->updateNativeAmount($bill);
}
public function created(Bill $bill): void
{
Log::debug('Observe "created" of a bill.');
$this->updateNativeAmount($bill);
}
private function updateNativeAmount(Bill $bill): void
{
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($bill->user->userGroup);
$bill->native_amount_min = null;
$bill->native_amount_max = null;
if ($bill->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$bill->native_amount_min = $converter->convert($bill->transactionCurrency, $userCurrency, today(), $bill->amount_min);
$bill->native_amount_max = $converter->convert($bill->transactionCurrency, $userCurrency, today(), $bill->amount_max);
}
$bill->saveQuietly();
Log::debug('Bill native amounts are updated.');
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* BudgetLimitObserver.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
class BudgetLimitObserver
{
public function updated(BudgetLimit $budgetLimit): void
{
Log::debug('Observe "updated" of a budget limit.');
$this->updateNativeAmount($budgetLimit);
}
public function created(BudgetLimit $budgetLimit): void
{
Log::debug('Observe "created" of a budget limit.');
$this->updateNativeAmount($budgetLimit);
}
private function updateNativeAmount(BudgetLimit $budgetLimit): void
{
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($budgetLimit->budget->user->userGroup);
$budgetLimit->native_amount = null;
if ($budgetLimit->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$budgetLimit->native_amount = $converter->convert($budgetLimit->transactionCurrency, $userCurrency, today(), $budgetLimit->amount);
}
$budgetLimit->saveQuietly();
Log::debug('Budget limit native amounts are updated.');
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* AutoBudgetObserver.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
class PiggyBankEventObserver
{
public function updated(PiggyBankEvent $event): void
{
Log::debug('Observe "updated" of a piggy bank event.');
$this->updateNativeAmount($event);
}
public function created(PiggyBankEvent $event): void
{
Log::debug('Observe "created" of a piggy bank event.');
$this->updateNativeAmount($event);
}
private function updateNativeAmount(PiggyBankEvent $event): void
{
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($event->piggyBank->accounts()->first()->user->userGroup);
$event->native_amount = null;
if ($event->piggyBank->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$event->native_amount = $converter->convert($event->piggyBank->transactionCurrency, $userCurrency, today(), $event->amount);
}
$event->saveQuietly();
Log::debug('Piggy bank event native amount is updated.');
}
}

View File

@@ -25,25 +25,46 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
/**
* Class PiggyBankObserver
*/
class PiggyBankObserver
{
public function updated(PiggyBank $piggyBank): void
{
Log::debug('Observe "updated" of a piggy bank.');
$this->updateNativeAmount($piggyBank);
}
public function created(PiggyBank $piggyBank): void
{
app('log')->debug('Observe "created" of a piggy bank. DO NOTHING.');
Log::debug('Observe "created" of a piggy bank.');
$this->updateNativeAmount($piggyBank);
}
// $repetition = new PiggyBankRepetition();
// $repetition->piggyBank()->associate($piggyBank);
// $repetition->start_date = $piggyBank->start_date;
// $repetition->start_date_tz = $piggyBank->start_date->format('e');
// $repetition->target_date = $piggyBank->target_date;
// $repetition->target_date_tz = $piggyBank->target_date?->format('e');
// $repetition->current_amount = '0';
// $repetition->save();
private function updateNativeAmount(PiggyBank $piggyBank): void
{
$group = $piggyBank->accounts()->first()?->user->userGroup;
if (null === $group) {
Log::debug(sprintf('No account(s) yet for piggy bank #%d.', $piggyBank->id));
return;
}
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($group);
if (null === $userCurrency) {
return;
}
$piggyBank->native_target_amount = null;
if ($piggyBank->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$piggyBank->native_target_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $piggyBank->target_amount);
}
$piggyBank->saveQuietly();
Log::debug('Piggy bank native target amount is updated.');
}
/**

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Transaction;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Support\Facades\Log;
@@ -32,6 +33,8 @@ use Illuminate\Support\Facades\Log;
*/
class TransactionObserver
{
public static bool $recalculate = true;
public function deleting(?Transaction $transaction): void
{
app('log')->debug('Observe "deleting" of a transaction.');
@@ -41,18 +44,46 @@ class TransactionObserver
public function updated(Transaction $transaction): void
{
Log::debug('Observe "updated" of a transaction.');
if (1 === bccomp($transaction->amount, '0')) {
Log::debug('Trigger recalculateForJournal');
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
if (config('firefly.feature_flags.running_balance_column') && self::$recalculate) {
if (1 === bccomp($transaction->amount, '0')) {
Log::debug('Trigger recalculateForJournal');
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
}
}
$this->updateNativeAmount($transaction);
}
public function created(Transaction $transaction): void
{
Log::debug('Observe "created" of a transaction.');
if (1 === bccomp($transaction->amount, '0')) {
Log::debug('Trigger recalculateForJournal');
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
if (config('firefly.feature_flags.running_balance_column')) {
if (1 === bccomp($transaction->amount, '0') && self::$recalculate) {
Log::debug('Trigger recalculateForJournal');
AccountBalanceCalculator::recalculateForJournal($transaction->transactionJournal);
}
}
$this->updateNativeAmount($transaction);
}
private function updateNativeAmount(Transaction $transaction): void
{
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($transaction->transactionJournal->user->userGroup);
$transaction->native_amount = null;
$transaction->native_foreign_amount = null;
// first normal amount
if ($transaction->transactionCurrency->id !== $userCurrency->id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$transaction->native_amount = $converter->convert($transaction->transactionCurrency, $userCurrency, $transaction->transactionJournal->date, $transaction->amount);
}
// then foreign amount
if ($transaction->foreignCurrency?->id !== $userCurrency->id && null !== $transaction->foreign_amount && null !== $transaction->foreignCurrency) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$transaction->native_foreign_amount = $converter->convert($transaction->foreignCurrency, $userCurrency, $transaction->transactionJournal->date, $transaction->foreign_amount);
}
$transaction->saveQuietly();
Log::debug('Transaction native amounts are updated.');
}
}

View File

@@ -463,7 +463,6 @@ class GroupCollector implements GroupCollectorInterface
$this->query->orWhereIn('transaction_journals.transaction_group_id', $groupIds);
}
$result = $this->query->get($this->fields);
// now to parse this into an array.
$collection = $this->parseArray($result);
@@ -1049,6 +1048,7 @@ class GroupCollector implements GroupCollectorInterface
->whereNull('transaction_groups.deleted_at')
->whereNull('transaction_journals.deleted_at')
->whereNull('source.deleted_at')
->whereNotNull('transaction_groups.id')
->whereNull('destination.deleted_at')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order', 'ASC')

View File

@@ -70,10 +70,8 @@ class IndexController extends Controller
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$collection = $this->repository->getAll();
$total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
// order so default is on top:
// order so default and enabled are on top:
$collection = $collection->sortBy(
static function (TransactionCurrency $currency) {
$default = true === $currency->userGroupDefault ? 0 : 1;
@@ -82,6 +80,8 @@ class IndexController extends Controller
return sprintf('%s-%s-%s', $default, $enabled, $currency->code);
}
);
$total = $collection->count();
$collection = $collection->slice(($page - 1) * $pageSize, $pageSize);
$currencies = new LengthAwarePaginator($collection, $total, $pageSize, $page);
$currencies->setPath(route('currencies.index'));

View File

@@ -144,7 +144,7 @@ class RuleFormRequest extends FormRequest
'description' => 'min:1|max:32768|nullable',
'stop_processing' => 'boolean',
'rule_group_id' => 'required|belongsToUser:rule_groups',
'trigger' => 'required|in:store-journal,update-journal',
'trigger' => 'required|in:store-journal,update-journal,manual-activation',
'triggers.*.type' => 'required|in:'.implode(',', $validTriggers),
'triggers.*.value' => sprintf('required_if:triggers.*.type,%s|max:1024|min:1|ruleTriggerValue', $contextTriggers),
'actions.*.type' => 'required|in:'.implode(',', $validActions),

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model;
@@ -87,7 +86,7 @@ class PiggyBank extends Model
public function account(): BelongsTo
{
throw new FireflyException('This method is not available on PiggyBank.');
return $this->belongsTo(Account::class);
}
public function attachments(): MorphMany
@@ -118,7 +117,7 @@ class PiggyBank extends Model
public function accounts(): BelongsToMany
{
return $this->belongsToMany(Account::class)->withPivot('current_amount');
return $this->belongsToMany(Account::class)->withPivot(['current_amount', 'native_current_amount']);
}
public function piggyBankRepetitions(): HasMany

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
@@ -150,7 +151,7 @@ class UserGroup extends Model
*/
public function piggyBanks(): HasManyThrough
{
return $this->hasManyThrough(PiggyBank::class, Account::class);
throw new FireflyException('This user group method is EOL.');
}
public function recurrences(): HasMany

View File

@@ -24,7 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Test;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
@@ -38,20 +38,17 @@ class UserTestNotificationSlack extends Notification
{
use Queueable;
private OwnerNotifiable $owner;
private User $user;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(OwnerNotifiable $owner)
public function __construct(User $user)
{
$this->owner = $owner;
$this->user = $user;
}
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toArray(OwnerNotifiable $notifiable)
public function toArray(User $user)
{
return [
];
@@ -60,7 +57,7 @@ class UserTestNotificationSlack extends Notification
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function toSlack(OwnerNotifiable $notifiable)
public function toSlack(User $user)
{
return new SlackMessage()->content((string) trans('email.admin_test_subject'));
// return new SlackMessage()->text((string) trans('email.admin_test_subject'))->to($url);
@@ -69,7 +66,7 @@ class UserTestNotificationSlack extends Notification
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function via(OwnerNotifiable $notifiable)
public function via(User $user)
{
return ['slack'];
}

View File

@@ -34,6 +34,7 @@ use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray;
use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject;
use FireflyIII\Events\NewVersionAvailable;
use FireflyIII\Events\Preferences\UserGroupChangedDefaultCurrency;
use FireflyIII\Events\RegisteredUser;
use FireflyIII\Events\RequestedNewPassword;
use FireflyIII\Events\RequestedReportOnJournals;
@@ -59,9 +60,13 @@ use FireflyIII\Events\UserChangedEmail;
use FireflyIII\Events\WarnUserAboutBill;
use FireflyIII\Handlers\Observer\AccountObserver;
use FireflyIII\Handlers\Observer\AttachmentObserver;
use FireflyIII\Handlers\Observer\AutoBudgetObserver;
use FireflyIII\Handlers\Observer\AvailableBudgetObserver;
use FireflyIII\Handlers\Observer\BillObserver;
use FireflyIII\Handlers\Observer\BudgetLimitObserver;
use FireflyIII\Handlers\Observer\BudgetObserver;
use FireflyIII\Handlers\Observer\CategoryObserver;
use FireflyIII\Handlers\Observer\PiggyBankEventObserver;
use FireflyIII\Handlers\Observer\PiggyBankObserver;
use FireflyIII\Handlers\Observer\RecurrenceObserver;
use FireflyIII\Handlers\Observer\RecurrenceTransactionObserver;
@@ -75,10 +80,14 @@ use FireflyIII\Handlers\Observer\WebhookMessageObserver;
use FireflyIII\Handlers\Observer\WebhookObserver;
use FireflyIII\Models\Account;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\AutoBudget;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\Category;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankEvent;
use FireflyIII\Models\Recurrence;
use FireflyIII\Models\RecurrenceTransaction;
use FireflyIII\Models\Rule;
@@ -247,6 +256,10 @@ class EventServiceProvider extends ServiceProvider
MFAManyFailedAttempts::class => [
'FireflyIII\Handlers\Events\Security\MFAHandler@sendMFAFailedAttemptsMail',
],
// preferences
UserGroupChangedDefaultCurrency::class => [
'FireflyIII\Handlers\Events\PreferencesEventHandler@resetNativeAmounts',
],
];
/**
@@ -260,11 +273,15 @@ class EventServiceProvider extends ServiceProvider
private function registerObservers(): void
{
Attachment::observe(new AttachmentObserver());
PiggyBank::observe(new PiggyBankObserver());
Account::observe(new AccountObserver());
AutoBudget::observe(new AutoBudgetObserver());
AvailableBudget::observe(new AvailableBudgetObserver());
Bill::observe(new BillObserver());
Budget::observe(new BudgetObserver());
BudgetLimit::observe(new BudgetLimitObserver());
Category::observe(new CategoryObserver());
PiggyBank::observe(new PiggyBankObserver());
PiggyBankEvent::observe(new PiggyBankEventObserver());
Recurrence::observe(new RecurrenceObserver());
RecurrenceTransaction::observe(new RecurrenceTransactionObserver());
Rule::observe(new RuleObserver());

View File

@@ -33,7 +33,7 @@ use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Support\Facades\Log;
/**
@@ -46,25 +46,35 @@ trait ModifiesPiggyBanks
public function addAmountToRepetition(PiggyBankRepetition $repetition, string $amount, TransactionJournal $journal): void
{
throw new FireflyException('[a] Piggy bank repetitions are EOL.');
app('log')->debug(sprintf('addAmountToRepetition: %s', $amount));
Log::debug(sprintf('addAmountToRepetition: %s', $amount));
if (-1 === bccomp($amount, '0')) {
app('log')->debug('Remove amount.');
Log::debug('Remove amount.');
$this->removeAmount($repetition->piggyBank, bcmul($amount, '-1'), $journal);
}
if (1 === bccomp($amount, '0')) {
app('log')->debug('Add amount.');
Log::debug('Add amount.');
$this->addAmount($repetition->piggyBank, $amount, $journal);
}
}
public function removeAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcsub($currentAmount, $amount);
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcsub($currentAmount, $amount);
$pivot->native_current_amount = null;
// also update native_current_amount.
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup);
if ($userCurrency->id !== $piggyBank->transaction_currency_id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount);
}
$pivot->save();
app('log')->debug('removeAmount [a]: Trigger change for negative amount.');
Log::debug('ChangedAmount: removeAmount [a]: Trigger change for negative amount.');
event(new ChangedAmount($piggyBank, bcmul($amount, '-1'), $journal, null));
return true;
@@ -90,12 +100,22 @@ trait ModifiesPiggyBanks
public function addAmount(PiggyBank $piggyBank, Account $account, string $amount, ?TransactionJournal $journal = null): bool
{
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcadd($currentAmount, $amount);
$currentAmount = $this->getCurrentAmount($piggyBank, $account);
$pivot = $piggyBank->accounts()->where('accounts.id', $account->id)->first()->pivot;
$pivot->current_amount = bcadd($currentAmount, $amount);
$pivot->native_current_amount = null;
// also update native_current_amount.
$userCurrency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup);
if ($userCurrency->id !== $piggyBank->transaction_currency_id) {
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount);
}
$pivot->save();
app('log')->debug('addAmount [b]: Trigger change for positive amount.');
Log::debug('ChangedAmount: addAmount [b]: Trigger change for positive amount.');
event(new ChangedAmount($piggyBank, $amount, $journal, null));
return true;
@@ -109,21 +129,21 @@ trait ModifiesPiggyBanks
$savedSoFar = $this->getCurrentAmount($piggyBank);
$maxAmount = $leftOnAccount;
app('log')->debug(sprintf('Left on account: %s on %s', $leftOnAccount, $today->format('Y-m-d H:i:s')));
app('log')->debug(sprintf('Saved so far: %s', $savedSoFar));
Log::debug(sprintf('Left on account: %s on %s', $leftOnAccount, $today->format('Y-m-d H:i:s')));
Log::debug(sprintf('Saved so far: %s', $savedSoFar));
if (0 !== bccomp($piggyBank->target_amount, '0')) {
$leftToSave = bcsub($piggyBank->target_amount, $savedSoFar);
$maxAmount = 1 === bccomp($leftOnAccount, $leftToSave) ? $leftToSave : $leftOnAccount;
app('log')->debug(sprintf('Left to save: %s', $leftToSave));
app('log')->debug(sprintf('Maximum amount: %s', $maxAmount));
Log::debug(sprintf('Left to save: %s', $leftToSave));
Log::debug(sprintf('Maximum amount: %s', $maxAmount));
}
$compare = bccomp($amount, $maxAmount);
$result = $compare <= 0;
app('log')->debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
return $result;
}
@@ -168,11 +188,11 @@ trait ModifiesPiggyBanks
$repetition->save();
if (-1 === bccomp($difference, '0')) {
app('log')->debug('addAmount [c]: Trigger change for negative amount.');
Log::debug('ChangedAmount: addAmount [c]: Trigger change for negative amount.');
event(new ChangedAmount($piggyBank, $difference, null, null));
}
if (1 === bccomp($difference, '0')) {
app('log')->debug('addAmount [d]: Trigger change for positive amount.');
Log::debug('ChangedAmount: addAmount [d]: Trigger change for positive amount.');
event(new ChangedAmount($piggyBank, $difference, null, null));
}
@@ -203,7 +223,7 @@ trait ModifiesPiggyBanks
public function setOrder(PiggyBank $piggyBank, int $newOrder): bool
{
$oldOrder = $piggyBank->order;
// app('log')->debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
// Log::debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
if ($newOrder > $oldOrder) {
PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
@@ -214,7 +234,7 @@ trait ModifiesPiggyBanks
;
$piggyBank->order = $newOrder;
app('log')->debug(sprintf('[1] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
Log::debug(sprintf('[1] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
$piggyBank->save();
return true;
@@ -228,7 +248,7 @@ trait ModifiesPiggyBanks
;
$piggyBank->order = $newOrder;
app('log')->debug(sprintf('[2] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
Log::debug(sprintf('[2] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
$piggyBank->save();
return true;
@@ -255,12 +275,12 @@ trait ModifiesPiggyBanks
{
$piggyBank = $this->updateProperties($piggyBank, $data);
if (array_key_exists('notes', $data)) {
$this->updateNote($piggyBank, (string)$data['notes']);
$this->updateNote($piggyBank, (string) $data['notes']);
}
// update the order of the piggy bank:
$oldOrder = $piggyBank->order;
$newOrder = (int)($data['order'] ?? $oldOrder);
$newOrder = (int) ($data['order'] ?? $oldOrder);
if ($oldOrder !== $newOrder) {
$this->setOrder($piggyBank, $newOrder);
}
@@ -271,13 +291,15 @@ trait ModifiesPiggyBanks
$factory->linkToAccountIds($piggyBank, $data['accounts']);
// if the piggy bank is now smaller than the current relevant rep,
// remove money from the rep.
// if the piggy bank is now smaller than the sum of the money saved,
// remove money from all accounts until the piggy bank is the right amount.
$currentAmount = $this->getCurrentAmount($piggyBank);
if (1 === bccomp($currentAmount, '100') && 0 !== bccomp($piggyBank->target_amount, '0')) {
if (1 === bccomp($currentAmount, $piggyBank->target_amount) && 0 !== bccomp($piggyBank->target_amount, '0')) {
Log::debug(sprintf('Current amount is %s, target amount is %s', $currentAmount, $piggyBank->target_amount));
$difference = bcsub($piggyBank->target_amount, $currentAmount);
// an amount will be removed, create "negative" event:
Log::debug(sprintf('ChangedAmount: is triggered with difference "%s"', $difference));
event(new ChangedAmount($piggyBank, $difference, null, null));
// question is, from which account(s) to remove the difference?
@@ -287,7 +309,7 @@ trait ModifiesPiggyBanks
// update using name:
if (array_key_exists('object_group_title', $data)) {
$objectGroupTitle = (string)$data['object_group_title'];
$objectGroupTitle = (string) $data['object_group_title'];
if ('' !== $objectGroupTitle) {
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
@@ -303,7 +325,7 @@ trait ModifiesPiggyBanks
// try also with ID:
if (array_key_exists('object_group_id', $data)) {
$objectGroupId = (int)($data['object_group_id'] ?? 0);
$objectGroupId = (int) ($data['object_group_id'] ?? 0);
if (0 !== $objectGroupId) {
$objectGroup = $this->findObjectGroupById($objectGroupId);
if (null !== $objectGroup) {

View File

@@ -82,8 +82,11 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function find(int $piggyBankId): ?PiggyBank
{
// phpstan doesn't get the Model.
return $this->user->piggyBanks()->find($piggyBankId); // @phpstan-ignore-line
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*'])
;
}
/**
@@ -91,7 +94,11 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
*/
public function findByName(string $name): ?PiggyBank
{
return $this->user->piggyBanks()->where('piggy_banks.name', $name)->first(['piggy_banks.*']);
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.name', $name)->first(['piggy_banks.*'])
;
}
public function getAttachments(PiggyBank $piggyBank): Collection
@@ -270,14 +277,12 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
*/
public function getPiggyBanksWithAmount(): Collection
{
$currency = app('amount')->getDefaultCurrency();
$set = $this->getPiggyBanks();
$set = $this->getPiggyBanks();
/** @var PiggyBank $piggy */
foreach ($set as $piggy) {
$currentAmount = $this->getRepetition($piggy)->current_amount ?? '0';
$piggy->name = $piggy->name.' ('.app('amount')->formatAnything($currency, $currentAmount, false).')';
$currentAmount = $this->getCurrentAmount($piggy);
$piggy->name = sprintf('%s (%s)', $piggy->name, app('amount')->formatAnything($piggy->transactionCurrency, $currentAmount, false));
}
return $set;
@@ -287,7 +292,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
{
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', auth()->user()->id)
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
@@ -349,7 +354,16 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function searchPiggyBank(string $query, int $limit): Collection
{
$search = $this->user->piggyBanks();
$search = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->distinct()
;
if ('' !== $query) {
$search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query));
}
@@ -357,13 +371,24 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
->orderBy('piggy_banks.name', 'ASC')
;
return $search->take($limit)->get();
return $search->take($limit)->get(['piggy_banks.*']);
}
#[\Override]
public function purgeAll(): void
{
throw new FireflyException('TODO Not implemented');
PiggyBank::withTrashed()
->whereNotNull('piggy_banks.deleted_at')
->leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->delete()
;
}
#[\Override]

View File

@@ -484,6 +484,9 @@ class RuleRepository implements RuleRepositoryInterface
if (array_key_exists('trigger', $data) && 'update-journal' === $data['trigger']) {
$this->setRuleTrigger('update-journal', $rule);
}
if (array_key_exists('trigger', $data) && 'manual-activation' === $data['trigger']) {
$this->setRuleTrigger('manual-activation', $rule);
}
if (array_key_exists('trigger', $data) && 'store-journal' === $data['trigger']) {
$this->setRuleTrigger('store-journal', $rule);
}

View File

@@ -42,4 +42,13 @@ class BudgetRepository implements BudgetRepositoryInterface
->get()
;
}
public function getBudgets(): Collection
{
return $this->userGroup->budgets()
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get()
;
}
}

View File

@@ -35,6 +35,8 @@ interface BudgetRepositoryInterface
{
public function getActiveBudgets(): Collection;
public function getBudgets(): Collection;
public function setUser(User $user): void;
public function setUserGroup(UserGroup $userGroup): void;

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\UserGroups\Currency;
use FireflyIII\Events\Preferences\UserGroupChangedDefaultCurrency;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\TransactionCurrencyFactory;
use FireflyIII\Models\AccountMeta;
@@ -38,6 +39,7 @@ use FireflyIII\Services\Internal\Destroy\CurrencyDestroyService;
use FireflyIII\Services\Internal\Update\CurrencyUpdateService;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class CurrencyRepository
@@ -77,7 +79,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// is being used in accounts:
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((string)$currency->id))->count();
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((string) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -85,7 +87,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// second search using integer check.
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((int)$currency->id))->count();
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((int) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -179,7 +181,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
return $entry->id === $current->id;
});
$isDefault = $local->contains(static function (TransactionCurrency $entry) use ($current) {
return 1 === (int)$entry->pivot->group_default && $entry->id === $current->id;
return 1 === (int) $entry->pivot->group_default && $entry->id === $current->id;
});
$current->userGroupEnabled = $hasId;
$current->userGroupDefault = $isDefault;
@@ -193,7 +195,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
$all = $this->userGroup->currencies()->orderBy('code', 'ASC')->withPivot(['group_default'])->get();
$all->map(static function (TransactionCurrency $current) {
$current->userGroupEnabled = true;
$current->userGroupDefault = 1 === (int)$current->pivot->group_default;
$current->userGroupDefault = 1 === (int) $current->pivot->group_default;
return $current;
});
@@ -260,10 +262,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface
public function findCurrencyNull(?int $currencyId, ?string $currencyCode): ?TransactionCurrency
{
app('log')->debug('Now in findCurrencyNull()');
$result = $this->find((int)$currencyId);
$result = $this->find((int) $currencyId);
if (null === $result) {
app('log')->debug(sprintf('Searching for currency with code %s...', $currencyCode));
$result = $this->findByCode((string)$currencyCode);
$result = $this->findByCode((string) $currencyCode);
}
if (null !== $result && false === $result->enabled) {
app('log')->debug(sprintf('Also enabled currency %s', $result->code));
@@ -308,12 +310,18 @@ class CurrencyRepository implements CurrencyRepositoryInterface
public function makeDefault(TransactionCurrency $currency): void
{
$current = app('amount')->getDefaultCurrencyByUserGroup($this->userGroup);
app('log')->debug(sprintf('Enabled + made default currency %s for user #%d', $currency->code, $this->userGroup->id));
$this->userGroup->currencies()->detach($currency->id);
foreach ($this->userGroup->currencies()->get() as $item) {
$this->userGroup->currencies()->updateExistingPivot($item->id, ['group_default' => false]);
}
$this->userGroup->currencies()->syncWithoutDetaching([$currency->id => ['group_default' => true]]);
if ($current->id !== $currency->id) {
Log::debug('Trigger on a different default currency.');
// clear all native amounts through an event.
event(new UserGroupChangedDefaultCurrency($this->userGroup));
}
}
public function searchCurrency(string $search, int $limit): Collection
@@ -354,9 +362,6 @@ class CurrencyRepository implements CurrencyRepositoryInterface
if (false === $enabled && true === $default) {
$enabled = true;
}
if (false === $default) {
app('log')->warning(sprintf('Set default=false will NOT do anything for currency %s', $currency->code));
}
// update currency with current user specific settings
$currency->refreshForUser($this->user);
@@ -375,12 +380,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// currency must be made default.
if (true === $default) {
app('log')->debug(sprintf('Enabled + made default currency %s for user #%d', $currency->code, $this->userGroup->id));
$this->userGroup->currencies()->detach($currency->id);
foreach ($this->userGroup->currencies()->get() as $item) {
$this->userGroup->currencies()->updateExistingPivot($item->id, ['group_default' => false]);
}
$this->userGroup->currencies()->syncWithoutDetaching([$currency->id => ['group_default' => true]]);
$this->makeDefault($currency);
}
/** @var CurrencyUpdateService $service */

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\UserGroups\PiggyBank;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
@@ -36,14 +37,15 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function getPiggyBanks(): Collection
{
return $this->userGroup->piggyBanks()
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_group_id', $this->userGroup->id)
->with(
[
'account',
'objectGroups',
]
)
->orderBy('order', 'ASC')->get()
->orderBy('piggy_banks.order', 'ASC')->distinct()->get(['piggy_banks.*'])
;
}
}

View File

@@ -43,10 +43,16 @@ class ExchangeRateConverter
private bool $noPreparedRates = false;
private array $prepared = [];
private int $queryCount = 0;
private bool $ignoreSettings = false;
public function setIgnoreSettings(bool $ignoreSettings): void
{
$this->ignoreSettings = $ignoreSettings;
}
public function enabled(): bool
{
return false !== config('cer.enabled');
return false !== config('cer.enabled') || true === $this->ignoreSettings;
}
/**
@@ -161,7 +167,7 @@ class ExchangeRateConverter
/** @var null|CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
?->currencyExchangeRates()
->where('from_currency_id', $from)
->where('to_currency_id', $to)
->where('date', '<=', $date)

View File

@@ -243,7 +243,7 @@ class AccountBalanceCalculator
$object->balance = $balance[0];
$object->date = $balance[1];
$object->date_tz = $balance[1]?->format('e');
$object->save();
$object->saveQuietly();
}
}
}

View File

@@ -161,7 +161,7 @@ class Navigation
public function startOfPeriod(Carbon $theDate, string $repeatFreq): Carbon
{
$date = clone $theDate;
Log::debug(sprintf('Now in startOfPeriod("%s", "%s")', $date->toIso8601String(), $repeatFreq));
// Log::debug(sprintf('Now in startOfPeriod("%s", "%s")', $date->toIso8601String(), $repeatFreq));
$functionMap = [
'1D' => 'startOfDay',
'daily' => 'startOfDay',
@@ -186,25 +186,25 @@ class Navigation
if (array_key_exists($repeatFreq, $functionMap)) {
$function = $functionMap[$repeatFreq];
Log::debug(sprintf('Function is ->%s()', $function));
// Log::debug(sprintf('Function is ->%s()', $function));
if (array_key_exists($function, $parameterMap)) {
Log::debug(sprintf('Parameter map, function becomes ->%s(%s)', $function, implode(', ', $parameterMap[$function])));
// Log::debug(sprintf('Parameter map, function becomes ->%s(%s)', $function, implode(', ', $parameterMap[$function])));
$date->{$function}($parameterMap[$function][0]); // @phpstan-ignore-line
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
// Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
return $date;
}
$date->{$function}(); // @phpstan-ignore-line
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
// Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
return $date;
}
if ('half-year' === $repeatFreq || '6M' === $repeatFreq) {
$skipTo = $date->month > 7 ? 6 : 0;
$date->startOfYear()->addMonths($skipTo);
Log::debug(sprintf('Custom call for "%s": addMonths(%d)', $repeatFreq, $skipTo));
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
// Log::debug(sprintf('Custom call for "%s": addMonths(%d)', $repeatFreq, $skipTo));
// Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
return $date;
}
@@ -220,13 +220,13 @@ class Navigation
default => null,
};
if (null !== $result) {
Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
// Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
return $result;
}
if ('custom' === $repeatFreq) {
Log::debug(sprintf('Custom, result is "%s"', $date->toIso8601String()));
// Log::debug(sprintf('Custom, result is "%s"', $date->toIso8601String()));
return $date; // the date is already at the start.
}
@@ -238,7 +238,7 @@ class Navigation
public function endOfPeriod(Carbon $end, string $repeatFreq): Carbon
{
$currentEnd = clone $end;
Log::debug(sprintf('Now in endOfPeriod("%s", "%s").', $currentEnd->toIso8601String(), $repeatFreq));
// Log::debug(sprintf('Now in endOfPeriod("%s", "%s").', $currentEnd->toIso8601String(), $repeatFreq));
$functionMap = [
'1D' => 'endOfDay',
@@ -327,7 +327,7 @@ class Navigation
if (in_array($repeatFreq, $subDay, true)) {
$currentEnd->subDay();
}
Log::debug(sprintf('Final result: %s', $currentEnd->toIso8601String()));
// Log::debug(sprintf('Final result: %s', $currentEnd->toIso8601String()));
return $currentEnd;
}

View File

@@ -46,8 +46,9 @@ class Rule extends AbstractExtension
'allJournalTriggers',
static function () {
return [
'store-journal' => (string)trans('firefly.rule_trigger_store_journal'),
'update-journal' => (string)trans('firefly.rule_trigger_update_journal'),
'store-journal' => (string)trans('firefly.rule_trigger_store_journal'),
'update-journal' => (string)trans('firefly.rule_trigger_update_journal'),
'manual-activation' => (string)trans('firefly.rule_trigger_manual'),
];
}
);

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## 6.1.25 - 2024-12-19
### Fixed
- [Issue 9492](https://github.com/firefly-iii/firefly-iii/issues/9492) (API: `PUT /api/v1/transactions/ID` does not work with only `source_name`) reported by @dreautall
- [Issue 9497](https://github.com/firefly-iii/firefly-iii/issues/9497) ('Attempt to read property "user" on null' error when running `php artisan firefly-iii:upgrade-database` on updating to v6.1.24) reported by @ChrisCarini
- [Discussion 9546](https://github.com/orgs/firefly-iii/discussions/9546) (Using the Data Importer to import data is very slowly) started by @shangyuok
## 6.1.24 - 2024-11-24
### Fixed

View File

@@ -88,7 +88,7 @@
"guzzlehttp/guzzle": "^7.8",
"jc5/google2fa-laravel": "^2.0",
"jc5/recovery": "^2",
"laravel-json-api/laravel": "^4.0",
"laravel-json-api/laravel": "^5.0",
"laravel-json-api/non-eloquent": "^4.0",
"laravel-notification-channels/pushover": "^4.0",
"laravel/framework": "^11",

211
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a739782c3c7121d1e9c0d31105b5974a",
"content-hash": "232d05ea287cfd97dde3fb95805a9b5a",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -545,12 +545,12 @@
"type": "library",
"extra": {
"laravel": {
"providers": [
"Diglactic\\Breadcrumbs\\ServiceProvider"
],
"aliases": {
"Breadcrumbs": "Diglactic\\Breadcrumbs\\Breadcrumbs"
}
},
"providers": [
"Diglactic\\Breadcrumbs\\ServiceProvider"
]
}
},
"autoload": {
@@ -1809,20 +1809,20 @@
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"Google2FA": "PragmaRX\\Google2FALaravel\\Facade"
},
"providers": [
"PragmaRX\\Google2FALaravel\\ServiceProvider"
]
},
"component": "package",
"frameworks": [
"Laravel"
],
"branch-alias": {
"dev-master": "0.2-dev"
},
"laravel": {
"providers": [
"PragmaRX\\Google2FALaravel\\ServiceProvider"
],
"aliases": {
"Google2FA": "PragmaRX\\Google2FALaravel\\Facade"
}
}
},
"autoload": {
@@ -1936,20 +1936,21 @@
},
{
"name": "laravel-json-api/core",
"version": "v4.3.2",
"version": "v5.0.1",
"source": {
"type": "git",
"url": "https://github.com/laravel-json-api/core.git",
"reference": "9227259244ddc61cffd85e33102a91a5b25d7d70"
"reference": "e2f6696580166f7b6384318e28168252c2bd20f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel-json-api/core/zipball/9227259244ddc61cffd85e33102a91a5b25d7d70",
"reference": "9227259244ddc61cffd85e33102a91a5b25d7d70",
"url": "https://api.github.com/repos/laravel-json-api/core/zipball/e2f6696580166f7b6384318e28168252c2bd20f4",
"reference": "e2f6696580166f7b6384318e28168252c2bd20f4",
"shasum": ""
},
"require": {
"ext-json": "*",
"illuminate/auth": "^11.0",
"illuminate/contracts": "^11.0",
"illuminate/http": "^11.0",
"illuminate/support": "^11.0",
@@ -1961,7 +1962,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-4.x": "4.x-dev"
"dev-develop": "5.x-dev"
}
},
"autoload": {
@@ -1994,9 +1995,9 @@
],
"support": {
"issues": "https://github.com/laravel-json-api/core/issues",
"source": "https://github.com/laravel-json-api/core/tree/v4.3.2"
"source": "https://github.com/laravel-json-api/core/tree/v5.0.1"
},
"time": "2024-11-30T16:23:18+00:00"
"time": "2024-11-30T16:31:42+00:00"
},
{
"name": "laravel-json-api/eloquent",
@@ -2199,21 +2200,21 @@
},
{
"name": "laravel-json-api/laravel",
"version": "v4.1.1",
"version": "v5.0.2",
"source": {
"type": "git",
"url": "https://github.com/laravel-json-api/laravel.git",
"reference": "5ef2e8588dd1310e644ff527cf82fb1150cdfc8d"
"reference": "53045c6a55b4923e3cfadc085fd5f0b7c8c79cfc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel-json-api/laravel/zipball/5ef2e8588dd1310e644ff527cf82fb1150cdfc8d",
"reference": "5ef2e8588dd1310e644ff527cf82fb1150cdfc8d",
"url": "https://api.github.com/repos/laravel-json-api/laravel/zipball/53045c6a55b4923e3cfadc085fd5f0b7c8c79cfc",
"reference": "53045c6a55b4923e3cfadc085fd5f0b7c8c79cfc",
"shasum": ""
},
"require": {
"ext-json": "*",
"laravel-json-api/core": "^4.3.2",
"laravel-json-api/core": "^5.0.1",
"laravel-json-api/eloquent": "^4.4",
"laravel-json-api/encoder-neomerx": "^4.1",
"laravel-json-api/exceptions": "^3.1",
@@ -2239,7 +2240,7 @@
]
},
"branch-alias": {
"dev-develop": "4.x-dev"
"dev-develop": "5.x-dev"
}
},
"autoload": {
@@ -2271,9 +2272,9 @@
],
"support": {
"issues": "https://github.com/laravel-json-api/laravel/issues",
"source": "https://github.com/laravel-json-api/laravel/tree/v4.1.1"
"source": "https://github.com/laravel-json-api/laravel/tree/v5.0.2"
},
"time": "2024-11-30T17:59:00+00:00"
"time": "2024-12-03T20:43:07+00:00"
},
{
"name": "laravel-json-api/neomerx-json-api",
@@ -2609,16 +2610,16 @@
},
{
"name": "laravel/framework",
"version": "v11.35.1",
"version": "v11.36.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "dcfa130ede1a6fa4343dc113410963e791ad34fb"
"reference": "df06f5163f4550641fdf349ebc04916a61135a64"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/dcfa130ede1a6fa4343dc113410963e791ad34fb",
"reference": "dcfa130ede1a6fa4343dc113410963e791ad34fb",
"url": "https://api.github.com/repos/laravel/framework/zipball/df06f5163f4550641fdf349ebc04916a61135a64",
"reference": "df06f5163f4550641fdf349ebc04916a61135a64",
"shasum": ""
},
"require": {
@@ -2639,7 +2640,7 @@
"guzzlehttp/uri-template": "^1.0",
"laravel/prompts": "^0.1.18|^0.2.0|^0.3.0",
"laravel/serializable-closure": "^1.3|^2.0",
"league/commonmark": "^2.2.1",
"league/commonmark": "^2.6",
"league/flysystem": "^3.25.1",
"league/flysystem-local": "^3.25.1",
"league/uri": "^7.5.1",
@@ -2654,7 +2655,7 @@
"symfony/console": "^7.0.3",
"symfony/error-handler": "^7.0.3",
"symfony/finder": "^7.0.3",
"symfony/http-foundation": "^7.0.3",
"symfony/http-foundation": "^7.2.0",
"symfony/http-kernel": "^7.0.3",
"symfony/mailer": "^7.0.3",
"symfony/mime": "^7.0.3",
@@ -2820,7 +2821,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2024-12-12T18:25:58+00:00"
"time": "2024-12-17T22:32:08+00:00"
},
{
"name": "laravel/passport",
@@ -2959,16 +2960,16 @@
},
{
"name": "laravel/sanctum",
"version": "v4.0.6",
"version": "v4.0.7",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "9e069e36d90b1e1f41886efa0fe9800a6b354694"
"reference": "698064236a46df016e64a7eb059b1414e0b281df"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/9e069e36d90b1e1f41886efa0fe9800a6b354694",
"reference": "9e069e36d90b1e1f41886efa0fe9800a6b354694",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/698064236a46df016e64a7eb059b1414e0b281df",
"reference": "698064236a46df016e64a7eb059b1414e0b281df",
"shasum": ""
},
"require": {
@@ -3019,20 +3020,20 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2024-11-26T21:18:33+00:00"
"time": "2024-12-11T16:40:21+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.0",
"version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
"reference": "0d8d3d8086984996df86596a86dea60398093a81"
"reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/0d8d3d8086984996df86596a86dea60398093a81",
"reference": "0d8d3d8086984996df86596a86dea60398093a81",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/613b2d4998f85564d40497e05e89cb6d9bd1cbe8",
"reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8",
"shasum": ""
},
"require": {
@@ -3080,7 +3081,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
"time": "2024-11-19T01:38:44+00:00"
"time": "2024-12-16T15:26:28+00:00"
},
{
"name": "laravel/slack-notification-channel",
@@ -3538,16 +3539,16 @@
},
{
"name": "league/csv",
"version": "9.20.0",
"version": "9.20.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/csv.git",
"reference": "553579df208641ada6ffb450b3a151e2fcfa4f31"
"reference": "491d1e79e973a7370c7571dc0fe4a7241f4936ee"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/553579df208641ada6ffb450b3a151e2fcfa4f31",
"reference": "553579df208641ada6ffb450b3a151e2fcfa4f31",
"url": "https://api.github.com/repos/thephpleague/csv/zipball/491d1e79e973a7370c7571dc0fe4a7241f4936ee",
"reference": "491d1e79e973a7370c7571dc0fe4a7241f4936ee",
"shasum": ""
},
"require": {
@@ -3621,7 +3622,7 @@
"type": "github"
}
],
"time": "2024-12-13T15:49:27+00:00"
"time": "2024-12-18T10:11:15+00:00"
},
{
"name": "league/event",
@@ -3937,16 +3938,16 @@
},
{
"name": "league/oauth2-server",
"version": "8.5.4",
"version": "8.5.5",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/oauth2-server.git",
"reference": "ab7714d073844497fd222d5d0a217629089936bc"
"reference": "cc8778350f905667e796b3c2364a9d3bd7a73518"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/ab7714d073844497fd222d5d0a217629089936bc",
"reference": "ab7714d073844497fd222d5d0a217629089936bc",
"url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/cc8778350f905667e796b3c2364a9d3bd7a73518",
"reference": "cc8778350f905667e796b3c2364a9d3bd7a73518",
"shasum": ""
},
"require": {
@@ -4013,7 +4014,7 @@
],
"support": {
"issues": "https://github.com/thephpleague/oauth2-server/issues",
"source": "https://github.com/thephpleague/oauth2-server/tree/8.5.4"
"source": "https://github.com/thephpleague/oauth2-server/tree/8.5.5"
},
"funding": [
{
@@ -4021,7 +4022,7 @@
"type": "github"
}
],
"time": "2023-08-25T22:35:12+00:00"
"time": "2024-12-20T23:06:10+00:00"
},
{
"name": "league/uri",
@@ -4479,10 +4480,6 @@
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.x-dev",
"dev-2.x": "2.x-dev"
},
"laravel": {
"providers": [
"Carbon\\Laravel\\ServiceProvider"
@@ -4492,6 +4489,10 @@
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-2.x": "2.x-dev",
"dev-master": "3.x-dev"
}
},
"autoload": {
@@ -5531,16 +5532,16 @@
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.42",
"version": "3.0.43",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98"
"reference": "709ec107af3cb2f385b9617be72af8cf62441d02"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/db92f1b1987b12b13f248fe76c3a52cadb67bb98",
"reference": "db92f1b1987b12b13f248fe76c3a52cadb67bb98",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/709ec107af3cb2f385b9617be72af8cf62441d02",
"reference": "709ec107af3cb2f385b9617be72af8cf62441d02",
"shasum": ""
},
"require": {
@@ -5621,7 +5622,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.42"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.43"
},
"funding": [
{
@@ -5637,7 +5638,7 @@
"type": "tidelift"
}
],
"time": "2024-09-16T03:06:04+00:00"
"time": "2024-12-14T21:12:59+00:00"
},
{
"name": "pragmarx/google2fa",
@@ -6596,16 +6597,16 @@
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.14-dev"
},
"laravel": {
"providers": [
"TwigBridge\\ServiceProvider"
],
"aliases": {
"Twig": "TwigBridge\\Facade\\Twig"
}
},
"providers": [
"TwigBridge\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "0.14-dev"
}
},
"autoload": {
@@ -6955,12 +6956,12 @@
"type": "library",
"extra": {
"laravel": {
"providers": [
"Spatie\\Html\\HtmlServiceProvider"
],
"aliases": {
"Html": "Spatie\\Html\\Facades\\Html"
}
},
"providers": [
"Spatie\\Html\\HtmlServiceProvider"
]
}
},
"autoload": {
@@ -10926,16 +10927,16 @@
},
{
"name": "barryvdh/laravel-ide-helper",
"version": "v3.2.2",
"version": "v3.3.0",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-ide-helper.git",
"reference": "07e3bd8796f3d1414801a03d3783f9d3ec9efc08"
"reference": "b7675670f75914bf34afdea52a6c2fe3781f7c44"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/07e3bd8796f3d1414801a03d3783f9d3ec9efc08",
"reference": "07e3bd8796f3d1414801a03d3783f9d3ec9efc08",
"url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/b7675670f75914bf34afdea52a6c2fe3781f7c44",
"reference": "b7675670f75914bf34afdea52a6c2fe3781f7c44",
"shasum": ""
},
"require": {
@@ -11004,7 +11005,7 @@
],
"support": {
"issues": "https://github.com/barryvdh/laravel-ide-helper/issues",
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.2.2"
"source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.3.0"
},
"funding": [
{
@@ -11016,7 +11017,7 @@
"type": "github"
}
],
"time": "2024-10-29T14:00:16+00:00"
"time": "2024-12-18T08:24:19+00:00"
},
{
"name": "barryvdh/reflection-docblock",
@@ -11228,13 +11229,13 @@
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
},
"phpstan": {
"includes": [
"extension.neon"
]
},
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"autoload": {
@@ -11669,16 +11670,16 @@
},
{
"name": "maximebf/debugbar",
"version": "v1.23.4",
"version": "v1.23.5",
"source": {
"type": "git",
"url": "https://github.com/maximebf/php-debugbar.git",
"reference": "0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca"
"url": "https://github.com/php-debugbar/php-debugbar.git",
"reference": "eeabd61a1f19ba5dcd5ac4585a477130ee03ce25"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca",
"reference": "0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca",
"url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/eeabd61a1f19ba5dcd5ac4585a477130ee03ce25",
"reference": "eeabd61a1f19ba5dcd5ac4585a477130ee03ce25",
"shasum": ""
},
"require": {
@@ -11730,10 +11731,10 @@
"debugbar"
],
"support": {
"issues": "https://github.com/maximebf/php-debugbar/issues",
"source": "https://github.com/maximebf/php-debugbar/tree/v1.23.4"
"issues": "https://github.com/php-debugbar/php-debugbar/issues",
"source": "https://github.com/php-debugbar/php-debugbar/tree/v1.23.5"
},
"time": "2024-12-05T10:36:51+00:00"
"time": "2024-12-15T19:20:42+00:00"
},
{
"name": "mockery/mockery",
@@ -12349,16 +12350,16 @@
},
{
"name": "phpstan/phpstan",
"version": "1.12.12",
"version": "1.12.13",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan.git",
"reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0"
"reference": "9b469068840cfa031e1deaf2fa1886d00e20680f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0",
"reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b469068840cfa031e1deaf2fa1886d00e20680f",
"reference": "9b469068840cfa031e1deaf2fa1886d00e20680f",
"shasum": ""
},
"require": {
@@ -12403,7 +12404,7 @@
"type": "github"
}
],
"time": "2024-11-28T22:13:23+00:00"
"time": "2024-12-17T17:00:20+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -12824,16 +12825,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.39",
"version": "10.5.40",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "4e89eff200b801db58f3d580ad7426431949eaa9"
"reference": "e6ddda95af52f69c1e0c7b4f977cccb58048798c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4e89eff200b801db58f3d580ad7426431949eaa9",
"reference": "4e89eff200b801db58f3d580ad7426431949eaa9",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e6ddda95af52f69c1e0c7b4f977cccb58048798c",
"reference": "e6ddda95af52f69c1e0c7b4f977cccb58048798c",
"shasum": ""
},
"require": {
@@ -12905,7 +12906,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.39"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.40"
},
"funding": [
{
@@ -12921,7 +12922,7 @@
"type": "tidelift"
}
],
"time": "2024-12-11T10:51:07+00:00"
"time": "2024-12-21T05:49:06+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@@ -23,7 +23,7 @@ return new class () extends Migration {
});
Schema::table('piggy_banks', static function (Blueprint $table): void {
// 3. add currency
$table->integer('transaction_currency_id', false, true)->after('account_id');
$table->integer('transaction_currency_id', false, true)->after('account_id')->nullable();
$table->foreign('transaction_currency_id', 'unique_currency')->references('id')->on('transaction_currencies')->onDelete('cascade');
});
Schema::table('piggy_banks', static function (Blueprint $table): void {

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class () extends Migration {
private array $tables = [
// !!! this array is also in PreferencesEventHandler + RecalculateNativeAmountsCommand
'accounts' => ['native_virtual_balance'], // works.
'account_piggy_bank' => ['native_current_amount'], // works
'auto_budgets' => ['native_amount'], // works
'available_budgets' => ['native_amount'], // works
'bills' => ['native_amount_min', 'native_amount_max'], // works
'budget_limits' => ['native_amount'], // works
'piggy_bank_events' => ['native_amount'], // works
'piggy_banks' => ['native_target_amount'], // works
'transactions' => ['native_amount', 'native_foreign_amount'], // works
// TODO button to recalculate all native amounts on selected pages?
];
/**
* Run the migrations.
*/
public function up(): void
{
foreach ($this->tables as $table => $fields) {
foreach ($fields as $field) {
Schema::table($table, static function (Blueprint $table) use ($field): void {
// add amount column
$table->decimal($field, 32, 12)->nullable();
});
}
}
}
/**
* Reverse the migrations.
*/
public function down(): void
{
foreach ($this->tables as $table => $fields) {
foreach ($fields as $field) {
Schema::table($table, static function (Blueprint $table) use ($field): void {
// add amount column
$table->dropColumn($field);
});
}
}
}
};

612
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
"flash_success": "Sukces!",
"close": "Zamknij",
"select_dest_account": "Please select or type a valid destination account name",
"select_source_account": "Please select or type a valid source account name",
"select_source_account": "Wybierz lub wpisz prawid\u0142ow\u0105 nazw\u0119 konta \u017ar\u00f3d\u0142owego",
"split_transaction_title": "Opis podzielonej transakcji",
"errors_submission": "Co\u015b posz\u0142o nie tak w czasie zapisu. Prosz\u0119, sprawd\u017a b\u0142\u0119dy poni\u017cej.",
"split": "Podziel",

View File

@@ -129,7 +129,7 @@
"logs": "\u041b\u043e\u0433\u0438",
"response": "\u041e\u0442\u0432\u0435\u0442",
"visit_webhook_url": "\u041f\u043e\u0441\u0435\u0442\u0438\u0442\u044c URL \u0432\u0435\u0431\u0445\u0443\u043a\u0430",
"reset_webhook_secret": "\u0421\u0431\u0440\u043e\u0441\u0438\u0442\u044c \u0441\u0435\u043a\u0440\u0435\u0442 webhook"
"reset_webhook_secret": ""
},
"form": {
"url": "\u0421\u0441\u044b\u043b\u043a\u0430",

View File

@@ -12,14 +12,14 @@
"laravel-vite-plugin": "^1.0.5",
"patch-package": "^8.0.0",
"sass": "^1.78.0",
"vite": "^5",
"vite": "^6",
"vite-plugin-manifest-sri": "^0.2.0"
},
"dependencies": {
"@ag-grid-community/client-side-row-model": "^32.0.2",
"@ag-grid-community/core": "^32.0.2",
"@ag-grid-community/infinite-row-model": "^32.0.2",
"@ag-grid-community/styles": "^32.0.0",
"@ag-grid-community/styles": "^33.0.2",
"@fortawesome/fontawesome-free": "^6.4.0",
"@popperjs/core": "^2.11.8",
"admin-lte": "^4.0.0-alpha3",
@@ -29,7 +29,7 @@
"bootstrap5-tags": "^1.7",
"chart.js": "^4.4.0",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-chart-sankey": "^0.13.0",
"chartjs-chart-sankey": "^0.14.0",
"date-fns": "^4.0.0",
"i18next": "^24.0.0",
"i18next-chained-backend": "^4.6.2",

View File

@@ -793,6 +793,7 @@ return [
// actions and triggers
'rule_trigger_store_journal' => 'When a transaction is created',
'rule_trigger_update_journal' => 'When a transaction is updated',
'rule_trigger_manual' => 'Only when user-activated',
'rule_trigger_user_action' => 'User action is ":trigger_value"',
// OLD values (remove non-doubles later):

View File

@@ -96,10 +96,9 @@
</td>
<td>
<div class="btn-group btn-group-xs test_buttons">
{# show which transactions would match #}
<a href="{{ route('rules.search',rule.id) }}" class="btn btn-default {% if false == rule.strict %}test_rule_triggers{% endif %}" data-id="{{ rule.id }}" title="{{ 'test_rule_triggers'|_ }}"><span data-id="{{ rule.id }}" class="fa fa-fw fa-flask"></span></a>
{% if rule.active %}
{# show which transactions would match #}
<a href="{{ route('rules.search',rule.id) }}" class="btn btn-default {% if false == rule.strict %}test_rule_triggers{% endif %}" data-id="{{ rule.id }}" title="{{ 'test_rule_triggers'|_ }}"><span data-id="{{ rule.id }}" class="fa fa-fw fa-flask"></span></a>
{# actually execute rule #}
<a href="{{ route('rules.select-transactions',rule.id) }}" class="btn btn-default" title=" {{ trans('firefly.apply_rule_selection', {title: rule.title}) }}"><span class="fa fa-fw fa-power-off "></span></a>
{% endif %}

View File

@@ -284,19 +284,13 @@ Route::group(
Route::get('show/{budget}', ['uses' => 'Budget\ShowController@show', 'as' => 'show']);
Route::get('show/{budget}/{budgetLimit}', ['uses' => 'Budget\ShowController@showByBudgetLimit', 'as' => 'show.limit']);
Route::get('list/no-budget/all', ['uses' => 'Budget\ShowController@noBudgetAll', 'as' => 'no-budget-all']);
Route::get('list/no-budget/{start_date?}/{end_date?}', ['uses' => 'Budget\ShowController@noBudget', 'as' => 'no-budget'])
->where(['start_date' => DATEFORMAT])
->where(['end_date' => DATEFORMAT])
;
Route::get('list/no-budget/{start_date?}/{end_date?}', ['uses' => 'Budget\ShowController@noBudget', 'as' => 'no-budget']);
// reorder budgets
Route::post('reorder', ['uses' => 'Budget\IndexController@reorder', 'as' => 'reorder']);
// index
Route::get('{start_date?}/{end_date?}', ['uses' => 'Budget\IndexController@index', 'as' => 'index'])
->where(['start_date' => DATEFORMAT])
->where(['end_date' => DATEFORMAT])
;
Route::get('{start_date?}/{end_date?}', ['uses' => 'Budget\IndexController@index', 'as' => 'index']);
}
);