Compare commits

...

59 Commits

Author SHA1 Message Date
github-actions[bot]
d38c59c5b1 Merge pull request #10313 from firefly-iii/release-1747625288
🤖 Automatically merge the PR into the develop branch.
2025-05-19 05:28:15 +02:00
JC5
a4205afb7b 🤖 Auto commit for release 'develop' on 2025-05-19 2025-05-19 05:28:08 +02:00
github-actions[bot]
75e187ee34 Merge pull request #10303 from firefly-iii/release-1747228276
🤖 Automatically merge the PR into the develop branch.
2025-05-14 15:11:26 +02:00
JC5
a37ed82bb4 🤖 Auto commit for release 'develop' on 2025-05-14 2025-05-14 15:11:16 +02:00
Sander Dorigo
a7233de561 Downgrade 2025-05-14 15:06:10 +02:00
github-actions[bot]
e87c5eee6b Merge pull request #10301 from firefly-iii/release-1747184469
🤖 Automatically merge the PR into the develop branch.
2025-05-14 03:01:16 +02:00
JC5
7a75290709 🤖 Auto commit for release 'develop' on 2025-05-14 2025-05-14 03:01:10 +02:00
James Cole
981f6df9ee Merge branch 'main' into develop 2025-05-14 02:57:20 +02:00
James Cole
c4572c66d3 Restore and update packages. 2025-05-14 02:56:53 +02:00
github-actions[bot]
8e7d750a5a Merge pull request #10298 from firefly-iii/release-1747132859
🤖 Automatically merge the PR into the develop branch.
2025-05-13 12:41:07 +02:00
JC5
94961466f9 🤖 Auto commit for release 'develop' on 2025-05-13 2025-05-13 12:40:59 +02:00
Sander Dorigo
0f3fe45b06 Make parameter optional 2025-05-13 12:36:26 +02:00
Sander Dorigo
0650457ea5 Experimental extra fix for #10265 2025-05-12 13:25:01 +02:00
github-actions[bot]
2e1ce03f13 Merge pull request #10296 from firefly-iii/release-1747034656
🤖 Automatically merge the PR into the develop branch.
2025-05-12 09:24:22 +02:00
JC5
9b345db623 🤖 Auto commit for release 'develop' on 2025-05-12 2025-05-12 09:24:16 +02:00
Sander Dorigo
c1afcc5219 add code improvement 2025-05-12 09:20:38 +02:00
Sander Dorigo
fb394b7f45 Fix nullpointer 2025-05-12 09:19:42 +02:00
Sander Dorigo
381598f1bb Fix optional web routes 2025-05-12 08:39:21 +02:00
James Cole
25b7a76da9 Merge pull request #10294 from firefly-iii/dependabot/github_actions/github/command-2.0.1 2025-05-12 06:37:59 +02:00
dependabot[bot]
4b03ebe3cb Bump github/command from 2.0.0 to 2.0.1
Bumps [github/command](https://github.com/github/command) from 2.0.0 to 2.0.1.
- [Release notes](https://github.com/github/command/releases)
- [Commits](https://github.com/github/command/compare/v2.0.0...v2.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-12 03:26:53 +00:00
github-actions[bot]
664c59136a Merge pull request #10293 from firefly-iii/release-1747020359
🤖 Automatically merge the PR into the develop branch.
2025-05-12 05:26:07 +02:00
JC5
27b61aae73 🤖 Auto commit for release 'develop' on 2025-05-12 2025-05-12 05:26:00 +02:00
github-actions[bot]
d90fcd1889 Merge pull request #10288 from firefly-iii/release-1746966536
🤖 Automatically merge the PR into the develop branch.
2025-05-11 14:29:03 +02:00
JC5
979d5c579b 🤖 Auto commit for release 'develop' on 2025-05-11 2025-05-11 14:28:56 +02:00
James Cole
5124ca1738 Introduce change for CI to catch. 2025-05-11 14:24:59 +02:00
James Cole
ac5d9b656a Merge pull request #10287 from firefly-iii/add-safe
Include safe methods and require the correct package.
2025-05-11 14:19:33 +02:00
James Cole
9a02739251 Include safe methods and require the correct package. 2025-05-11 14:19:07 +02:00
James Cole
4af2aadc48 Merge pull request #10286 from firefly-iii/remove-safe-methods
Remove safe methods.
2025-05-11 14:09:02 +02:00
James Cole
84779b8d02 Remove safe methods. 2025-05-11 14:08:32 +02:00
github-actions[bot]
145e8d23f0 Merge pull request #10285 from firefly-iii/release-1746964861
🤖 Automatically merge the PR into the develop branch.
2025-05-11 14:01:08 +02:00
JC5
d0ba0583a5 🤖 Auto commit for release 'develop' on 2025-05-11 2025-05-11 14:01:01 +02:00
James Cole
17d8b54280 Merge pull request #10284 from firefly-iii/fix-missing-import-2
Temp commit because the build fails otherwise and I haven't fixed tha…
2025-05-11 13:56:41 +02:00
James Cole
2cf0bfe3c4 Temp commit because the build fails otherwise and I haven't fixed that yet. 2025-05-11 13:56:07 +02:00
James Cole
070a8cf148 Remove safe functions 2025-05-11 13:16:09 +02:00
github-actions[bot]
f94c21446a Merge pull request #10283 from firefly-iii/release-1746961946
🤖 Automatically merge the PR into the develop branch.
2025-05-11 13:12:33 +02:00
JC5
1ef1873016 🤖 Auto commit for release 'develop' on 2025-05-11 2025-05-11 13:12:26 +02:00
James Cole
32e4e29e7c Merge pull request #10282 from firefly-iii/fix-missing-import
Fix missing import.
2025-05-11 13:08:28 +02:00
James Cole
65ca0dd9e0 Fix missing import. 2025-05-11 13:07:37 +02:00
github-actions[bot]
3385e12c01 Merge pull request #10281 from firefly-iii/release-1746961196
🤖 Automatically merge the PR into the develop branch.
2025-05-11 13:00:03 +02:00
JC5
3566a4afa3 🤖 Auto commit for release 'develop' on 2025-05-11 2025-05-11 12:59:56 +02:00
James Cole
68ff033342 Merge pull request #10279 from firefly-iii/fix-10265-addendum
Fix 10265 addendum
2025-05-11 08:33:52 +02:00
James Cole
f141b0be5c Merge branch 'develop' into fix-10265-addendum
# Conflicts:
#	app/Factory/TransactionJournalFactory.php
2025-05-11 08:33:35 +02:00
James Cole
d399dd160f Merge pull request #10278 from firefly-iii/fix-10265
Fix #10265
2025-05-11 08:31:10 +02:00
James Cole
4c8ed784cd Final check for transaction types 2025-05-11 08:29:31 +02:00
James Cole
fb3402acd4 Fix storage issue with transactions. 2025-05-11 08:00:38 +02:00
James Cole
8cad430816 Fix validation error in transaction processing. 2025-05-11 07:35:20 +02:00
James Cole
01502b456e Disable fix for the moment. 2025-05-11 07:29:18 +02:00
James Cole
9b6314066b Clean up logs. 2025-05-11 07:23:35 +02:00
James Cole
e61dadcbc6 Implement correction method. 2025-05-11 07:20:17 +02:00
James Cole
f7baf5b2fd Better debug info 2025-05-11 07:11:00 +02:00
James Cole
0545826d57 Do not try to correct transactions between asset / liability with foreign amount info. 2025-05-11 07:09:48 +02:00
James Cole
a29904704c Fix #10265 2025-05-11 07:01:36 +02:00
James Cole
899d374222 Merge pull request #10271 from firefly-iii/various-updates
Fix account list and remove sanctum
2025-05-08 20:22:24 +02:00
James Cole
07ff2305fd Fix account list and remove sanctum 2025-05-08 20:22:01 +02:00
James Cole
ee96dd3ab6 Merge pull request #10270 from firefly-iii/main
Update packages
2025-05-08 20:21:12 +02:00
James Cole
a766d3a133 Merge pull request #10263 from firefly-iii/dependabot/composer/composer-761f56e718 2025-05-06 07:22:08 +02:00
dependabot[bot]
67545b0371 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.6.2 to 2.7.0
- [Release notes](https://github.com/thephpleague/commonmark/releases)
- [Changelog](https://github.com/thephpleague/commonmark/blob/2.7/CHANGELOG.md)
- [Commits](https://github.com/thephpleague/commonmark/compare/2.6.2...2.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-06 01:59:56 +00:00
James Cole
6b86d825ea Merge pull request #10261 from firefly-iii/update-packages
Update packages
2025-05-05 07:48:50 +02:00
James Cole
31dbc57e8b Update packages 2025-05-05 07:48:13 +02:00
42 changed files with 1191 additions and 1128 deletions

View File

@@ -1,21 +1,64 @@
<?php
/*
* rector.php
* Copyright (c) 2025 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/../app',
__DIR__ . '/../bootstrap',
__DIR__ . '/../config',
__DIR__ . '/../public',
__DIR__ . '/../resources',
__DIR__ . '/../routes',
__DIR__ . '/../tests',
// __DIR__ . '/../app',
__DIR__ . '/../app/Http',
// __DIR__ . '/../bootstrap',
// __DIR__ . '/../config',
// __DIR__ . '/../public',
// __DIR__ . '/../resources',
// __DIR__ . '/../routes',
// __DIR__ . '/../tests',
])
// uncomment to reach your current PHP version
// uncomment to reach your current PHP version
->withPhpSets()
->withPreparedSets(
codingStyle : false, // leave false
privatization: false, // leave false.
naming : false, // leave false
instanceOf : true,
earlyReturn : false,
strictBooleans : false,
carbon : false,
rectorPreset : false,
phpunitCodeQuality : false,
doctrineCodeQuality: false,
symfonyCodeQuality : false,
symfonyConfigs : false
)
->withComposerBased(
twig: true,
doctrine: true,
phpunit: true,
symfony: true)
->withTypeCoverageLevel(0)
->withDeadCodeLevel(0)
->withCodeQualityLevel(0);
->withCodeQualityLevel(0)
->withImportNames(removeUnusedImports: true);// import statements instead of full classes.

View File

@@ -296,13 +296,6 @@ STATIC_CRON_TOKEN=
# However if you know what you're doing you can significantly speed up container start times.
# Set each value to true to enable, or false to disable.
# Set this to true to build all locales supported by Firefly III.
# This may take quite some time (several minutes) and is generally not recommended.
# If you wish to change or alter the list of locales, start your Docker container with
# `docker run -v locale.gen:/etc/locale.gen -e DKR_BUILD_LOCALE=true`
# and make sure your preferred locales are in your own locale.gen.
DKR_BUILD_LOCALE=false
# Check if the SQLite database exists. Can be skipped if you're not using SQLite.
# Won't significantly speed up things.
DKR_CHECK_SQLITE=true

View File

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

View File

@@ -25,9 +25,11 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands\Correction;
use FireflyIII\Console\Commands\ShowsFriendlyMessages;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
@@ -47,7 +49,12 @@ class CorrectsUnevenAmount extends Command
public function handle(): int
{
$this->count = 0;
// convert transfers with foreign currency info where the amount is NOT uneven (it should be)
$this->convertOldStyleTransfers();
// convert old-style transactions between assets and liabilities.
$this->convertOldStyleTransactions();
$this->fixUnevenAmounts();
$this->matchCurrencies();
if (true === config('firefly.feature_flags.running_balance_column')) {
@@ -72,8 +79,6 @@ class CorrectsUnevenAmount extends Command
;
$count = 0;
Log::debug(sprintf('Found %d potential journal(s)', $transactions->count()));
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
/** @var null|TransactionJournal $journal */
@@ -115,10 +120,15 @@ class CorrectsUnevenAmount extends Command
++$count;
}
}
if (0 === $count) {
return;
}
$this->friendlyPositive(sprintf('Fixed %d transfer(s) with unbalanced amounts.', $count));
}
private function fixUnevenAmounts(): void
{
Log::debug('fixUnevenAmounts()');
$journals = DB::table('transactions')
->groupBy('transaction_journal_id')
->whereNull('deleted_at')
@@ -207,8 +217,8 @@ class CorrectsUnevenAmount extends Command
}
// may still be able to salvage this journal if it is a transfer with foreign currency info
if ($this->isForeignCurrencyTransfer($journal)) {
Log::debug(sprintf('Can skip foreign currency transfer #%d.', $journal->id));
if ($this->isForeignCurrencyTransfer($journal) || $this->isBetweenAssetAndLiability($journal)) {
Log::debug(sprintf('Can skip foreign currency transfer / asset+liability transaction #%d.', $journal->id));
return;
}
@@ -271,13 +281,13 @@ class CorrectsUnevenAmount extends Command
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
if (!$this->isForeignCurrencyTransfer($journal)) {
if (!$this->isForeignCurrencyTransfer($journal) && !$this->isBetweenAssetAndLiability($journal)) {
Transaction::where('transaction_journal_id', $journal->id)->update(['transaction_currency_id' => $journal->transaction_currency_id]);
++$count;
continue;
}
Log::debug(sprintf('Can skip foreign currency transfer #%d.', $journal->id));
Log::debug(sprintf('Can skip foreign currency transfer or transaction between asset and liability #%d.', $journal->id));
}
if (0 === $count) {
return;
@@ -285,4 +295,155 @@ class CorrectsUnevenAmount extends Command
$this->friendlyPositive(sprintf('Fixed %d journal(s) with mismatched currencies.', $journals->count()));
}
private function isBetweenAssetAndLiability(TransactionJournal $journal): bool
{
/** @var Transaction $sourceTransaction */
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
/** @var Transaction $destinationTransaction */
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
if (null === $sourceTransaction || null === $destinationTransaction) {
Log::warning('Either transaction is false, stop.');
return false;
}
if (null === $sourceTransaction->foreign_amount || null === $destinationTransaction->foreign_amount) {
Log::warning('Either foreign amount is false, stop.');
return false;
}
$source = $sourceTransaction->account;
$destination = $destinationTransaction->account;
if (null === $source || null === $destination) {
Log::warning('Either is false, stop.');
return false;
}
$sourceTypes = [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
// source is liability, destination is asset
if (in_array($source->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $destination->accountType->type) {
Log::debug('Source is a liability account, destination is an asset account, return TRUE.');
return true;
}
// source is asset, destination is liability
if (in_array($destination->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $source->accountType->type) {
Log::debug('Destination is a liability account, source is an asset account, return TRUE.');
return true;
}
return false;
}
private function convertOldStyleTransactions(): void
{
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
Log::debug('convertOldStyleTransactions()');
$count = 0;
$transactions = Transaction::distinct()
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id')
->leftJoin('accounts', 'accounts.id', 'transactions.account_id')
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
->whereNot('transaction_types.type', TransactionTypeEnum::TRANSFER->value)
->whereNotNull('foreign_currency_id')
->whereNotNull('foreign_amount')
->whereIn('account_types.type', [AccountTypeEnum::ASSET->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value])
->get(['transactions.transaction_journal_id'])
;
/** @var Transaction $transaction */
foreach ($transactions as $transaction) {
/** @var null|TransactionJournal $journal */
$journal = TransactionJournal::find($transaction->transaction_journal_id);
$repository->setUser($journal->user);
if (null === $journal) {
Log::debug('Found no journal, continue.');
continue;
}
if (!$this->isBetweenAssetAndLiability($journal)) {
Log::debug('Not between asset and liability, continue.');
continue;
}
$source = $journal->transactions()->where('amount', '<', 0)->first();
$destination = $journal->transactions()->where('amount', '>', 0)->first();
$sourceAccount = $source->account;
$destAccount = $destination->account;
$sourceCurrency = $repository->getAccountCurrency($sourceAccount);
$destCurrency = $repository->getAccountCurrency($destAccount);
if (null === $source || null === $destination) {
Log::debug('Either transaction is NULL, continue.');
continue;
}
if (0 === bccomp($source->amount, $source->foreign_amount) && 0 === bccomp($source->foreign_amount, $source->amount)) {
Log::debug('Already fixed, continue.');
continue;
}
// source transaction. Switch info when does not match.
if ((int) $source->transaction_currency_id !== (int) $sourceCurrency->id) {
Log::debug(sprintf('Ready to swap data in transaction #%d.', $source->id));
// swap amounts.
$amount = $source->amount;
$currency = $source->transaction_currency_id;
$source->amount = $source->foreign_amount;
$source->transaction_currency_id = $source->foreign_currency_id;
$source->foreign_amount = $amount;
$source->foreign_currency_id = $currency;
$source->saveQuietly();
$source->refresh();
// Log::debug(sprintf('source->amount = %s', $source->amount));
// Log::debug(sprintf('source->transaction_currency_id = %s', $source->transaction_currency_id));
// Log::debug(sprintf('source->foreign_amount = %s', $source->foreign_amount));
// Log::debug(sprintf('source->foreign_currency_id = %s', $source->foreign_currency_id));
++$count;
}
// same but for destination
if ((int) $destination->transaction_currency_id !== (int) $destCurrency->id) {
++$count;
Log::debug(sprintf('Ready to swap data in transaction #%d.', $destination->id));
// swap amounts.
$amount = $destination->amount;
$currency = $destination->transaction_currency_id;
$destination->amount = $destination->foreign_amount;
$destination->transaction_currency_id = $destination->foreign_currency_id;
$destination->foreign_amount = $amount;
$destination->foreign_currency_id = $currency;
$destination->balance_dirty = true;
$destination->saveQuietly();
$destination->refresh();
// Log::debug(sprintf('destination->amount = %s', $destination->amount));
// Log::debug(sprintf('destination->transaction_currency_id = %s', $destination->transaction_currency_id));
// Log::debug(sprintf('destination->foreign_amount = %s', $destination->foreign_amount));
// Log::debug(sprintf('destination->foreign_currency_id = %s', $destination->foreign_currency_id));
}
// // only fix the destination transaction
// $destination->foreign_currency_id = $source->transaction_currency_id;
// $destination->foreign_amount = app('steam')->positive($source->amount);
// $destination->transaction_currency_id = $source->foreign_currency_id;
// $destination->amount = app('steam')->positive($source->foreign_amount);
// $destination->balance_dirty = true;
// $source->balance_dirty = true;
// $destination->save();
// $source->save();
// $this->friendlyWarning(sprintf('Corrected foreign amounts of transaction #%d.', $journal->id));
}
if (0 === $count) {
return;
}
$this->friendlyPositive(sprintf('Fixed %d journal(s) with unbalanced amounts.', $count));
}
}

View File

@@ -273,7 +273,7 @@ class ExportsData extends Command
*/
private function exportData(array $options, array $data): void
{
$date = \Safe\date('Y_m_d');
$date = date('Y_m_d');
foreach ($data as $key => $content) {
$file = sprintf('%s%s_%s.csv', $options['directory'], $date, $key);
if (false === $options['force'] && file_exists($file)) {

View File

@@ -111,8 +111,8 @@ class OutputsInstructions extends Command
*/
private function showLogo(): void
{
$today = \Safe\date('m-d');
$month = \Safe\date('m');
$today = date('m-d');
$month = date('m');
// variation in colors and effects just because I can!
// default is Ukraine flag:
$colors = ['blue', 'blue', 'blue', 'yellow', 'yellow', 'yellow', 'default', 'default'];

View File

@@ -28,7 +28,7 @@ use Illuminate\Support\Facades\Log;
use Safe\Exceptions\InfoException;
try {
set_time_limit(0);
\Safe\set_time_limit(0);
} catch (InfoException) {
Log::warning('set_time_limit returned false. This could be an issue, unless you also run XDebug.');
}

View File

@@ -366,8 +366,8 @@ class UpgradesToGroups extends Command
{
$groupId = DB::table('transaction_groups')->insertGetId(
[
'created_at' => \Safe\date('Y-m-d H:i:s'),
'updated_at' => \Safe\date('Y-m-d H:i:s'),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
'title' => null,
'user_id' => $array['user_id'],
]

View File

@@ -45,6 +45,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
// temp
/**
* Class Handler
*/
@@ -227,7 +228,7 @@ class Handler extends ExceptionHandler
$data = [
'class' => $e::class,
'errorMessage' => $e->getMessage(),
'time' => \Safe\date('r'),
'time' => date('r'),
'stackTrace' => $e->getTraceAsString(),
'file' => $e->getFile(),
'line' => $e->getLine(),

View File

@@ -25,6 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Factory;
use Carbon\Carbon;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
@@ -101,15 +102,15 @@ class TransactionJournalFactory
*/
public function create(array $data): Collection
{
app('log')->debug('Now in TransactionJournalFactory::create()');
Log::debug('Now in TransactionJournalFactory::create()');
// convert to special object.
$dataObject = new NullArrayObject($data);
app('log')->debug('Start of TransactionJournalFactory::create()');
Log::debug('Start of TransactionJournalFactory::create()');
$collection = new Collection();
$transactions = $dataObject['transactions'] ?? [];
if (0 === count($transactions)) {
app('log')->error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
return new Collection();
}
@@ -117,26 +118,26 @@ class TransactionJournalFactory
try {
/** @var array $row */
foreach ($transactions as $index => $row) {
app('log')->debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
$journal = $this->createJournal(new NullArrayObject($row));
if (null !== $journal) {
$collection->push($journal);
}
if (null === $journal) {
app('log')->error('The createJournal() method returned NULL. This may indicate an error.');
Log::error('The createJournal() method returned NULL. This may indicate an error.');
}
}
} catch (DuplicateTransactionException $e) {
app('log')->warning('TransactionJournalFactory::create() caught a duplicate journal in createJournal()');
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
Log::warning('TransactionJournalFactory::create() caught a duplicate journal in createJournal()');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->forceDeleteOnError($collection);
throw new DuplicateTransactionException($e->getMessage(), 0, $e);
} catch (FireflyException $e) {
app('log')->warning('TransactionJournalFactory::create() caught an exception.');
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
Log::warning('TransactionJournalFactory::create() caught an exception.');
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->forceDeleteOnError($collection);
throw new FireflyException($e->getMessage(), 0, $e);
@@ -156,6 +157,7 @@ class TransactionJournalFactory
*/
private function createJournal(NullArrayObject $row): ?TransactionJournal
{
Log::debug('Now in TransactionJournalFactory::createJournal()');
$row['import_hash_v2'] = $this->hashArray($row);
$this->errorIfDuplicate($row['import_hash_v2']);
@@ -164,7 +166,11 @@ class TransactionJournalFactory
$type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? today(config('app.timezone'));
$order = $row['order'] ?? 0;
Log::debug('Find currency or return default.');
$currency = $this->currencyRepository->findCurrency((int) $row['currency_id'], $row['currency_code']);
Log::debug('Find foreign currency or return NULL.');
$foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']);
$bill = $this->billRepository->findBill((int) $row['bill_id'], $row['bill_name']);
$billId = TransactionTypeEnum::WITHDRAWAL->value === $type->type && null !== $bill ? $bill->id : null;
@@ -183,8 +189,8 @@ class TransactionJournalFactory
// validate source and destination using a new Validator.
$this->validateAccounts($row);
} catch (FireflyException $e) {
app('log')->error('Could not validate source or destination.');
app('log')->error($e->getMessage());
Log::error('Could not validate source or destination.');
Log::error($e->getMessage());
return null;
}
@@ -207,11 +213,12 @@ class TransactionJournalFactory
'bic' => $row['destination_bic'],
'currency_id' => $currency->id,
];
app('log')->debug('Source info:', $sourceInfo);
app('log')->debug('Destination info:', $destInfo);
Log::debug('Source info:', $sourceInfo);
Log::debug('Destination info:', $destInfo);
$sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo);
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
app('log')->debug('Done with getAccount(2x)');
Log::debug('Done with getAccount(2x)');
// this is the moment for a reconciliation sanity check (again).
if (TransactionTypeEnum::RECONCILIATION->value === $type->type) {
@@ -223,7 +230,8 @@ class TransactionJournalFactory
$foreignCurrency = $this->getForeignByAccount($type->type, $foreignCurrency, $destinationAccount);
$description = $this->getDescription($description);
app('log')->debug(sprintf('Date: %s (%s)', $carbon->toW3cString(), $carbon->getTimezone()->getName()));
Log::debug(sprintf('Currency is #%d "%s", foreign currency is #%d "%s"', $currency->id, $currency->code, $foreignCurrency?->id, $foreignCurrency));
Log::debug(sprintf('Date: %s (%s)', $carbon->toW3cString(), $carbon->getTimezone()->getName()));
/** Create a basic journal. */
$journal = TransactionJournal::create(
@@ -241,7 +249,7 @@ class TransactionJournalFactory
'completed' => 0,
]
);
app('log')->debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
/** Create two transactions. */
$transactionFactory = app(TransactionFactory::class);
@@ -255,7 +263,7 @@ class TransactionJournalFactory
try {
$negative = $transactionFactory->createNegative((string) $row['amount'], (string) $row['foreign_amount']);
} catch (FireflyException $e) {
app('log')->error(sprintf('Exception creating negative transaction: %s', $e->getMessage()));
Log::error(sprintf('Exception creating negative transaction: %s', $e->getMessage()));
$this->forceDeleteOnError(new Collection([$journal]));
throw new FireflyException($e->getMessage(), 0, $e);
@@ -277,7 +285,7 @@ class TransactionJournalFactory
$amount = (string) $row['amount'];
$foreignAmount = (string) $row['foreign_amount'];
if (null !== $foreignCurrency && $foreignCurrency->id !== $currency->id
&& TransactionTypeEnum::TRANSFER->value === $type->type
&& (TransactionTypeEnum::TRANSFER->value === $type->type || $this->isBetweenAssetAndLiability($sourceAccount, $destinationAccount))
) {
$transactionFactory->setCurrency($foreignCurrency);
$transactionFactory->setForeignCurrency($currency);
@@ -289,7 +297,7 @@ class TransactionJournalFactory
try {
$transactionFactory->createPositive($amount, $foreignAmount);
} catch (FireflyException $e) {
app('log')->error(sprintf('Exception creating positive transaction: %s', $e->getMessage()));
Log::error(sprintf('Exception creating positive transaction: %s', $e->getMessage()));
$this->forceTrDelete($negative);
$this->forceDeleteOnError(new Collection([$journal]));
@@ -317,11 +325,11 @@ class TransactionJournalFactory
try {
$json = \Safe\json_encode($dataRow, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
app('log')->error(sprintf('Could not encode dataRow: %s', $e->getMessage()));
Log::error(sprintf('Could not encode dataRow: %s', $e->getMessage()));
$json = microtime();
}
$hash = hash('sha256', $json);
app('log')->debug(sprintf('The hash is: %s', $hash), $dataRow);
Log::debug(sprintf('The hash is: %s', $hash), $dataRow);
return $hash;
}
@@ -333,11 +341,11 @@ class TransactionJournalFactory
*/
private function errorIfDuplicate(string $hash): void
{
app('log')->debug(sprintf('In errorIfDuplicate(%s)', $hash));
Log::debug(sprintf('In errorIfDuplicate(%s)', $hash));
if (false === $this->errorOnHash) {
return;
}
app('log')->debug('Will verify duplicate!');
Log::debug('Will verify duplicate!');
/** @var null|TransactionJournalMeta $result */
$result = TransactionJournalMeta::withTrashed()
@@ -349,7 +357,7 @@ class TransactionJournalFactory
->first(['journal_meta.*'])
;
if (null !== $result) {
app('log')->warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash));
Log::warning(sprintf('Found a duplicate in errorIfDuplicate because hash %s is not unique!', $hash));
$journal = $result->transactionJournal()->withTrashed()->first();
$group = $journal?->transactionGroup()->withTrashed()->first();
$groupId = (int) $group?->id;
@@ -363,7 +371,7 @@ class TransactionJournalFactory
*/
private function validateAccounts(NullArrayObject $data): void
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
$transactionType = $data['type'] ?? 'invalid';
$this->accountValidator->setUser($this->user);
$this->accountValidator->setTransactionType($transactionType);
@@ -381,7 +389,7 @@ class TransactionJournalFactory
if (false === $validSource) {
throw new FireflyException(sprintf('Source: %s', $this->accountValidator->sourceError));
}
app('log')->debug('Source seems valid.');
Log::debug('Source seems valid.');
// validate destination account
$array = [
@@ -416,28 +424,28 @@ class TransactionJournalFactory
private function reconciliationSanityCheck(?Account $sourceAccount, ?Account $destinationAccount): array
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s', __METHOD__));
if (null !== $sourceAccount && null !== $destinationAccount) {
app('log')->debug('Both accounts exist, simply return them.');
Log::debug('Both accounts exist, simply return them.');
return [$sourceAccount, $destinationAccount];
}
if (null === $destinationAccount) {
app('log')->debug('Destination account is NULL, source account is not.');
Log::debug('Destination account is NULL, source account is not.');
$account = $this->accountRepository->getReconciliation($sourceAccount);
app('log')->debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type));
Log::debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type));
return [$sourceAccount, $account];
}
if (null === $sourceAccount) { // @phpstan-ignore-line
app('log')->debug('Source account is NULL, destination account is not.');
Log::debug('Source account is NULL, destination account is not.');
$account = $this->accountRepository->getReconciliation($destinationAccount);
app('log')->debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type));
Log::debug(sprintf('Will return account #%d ("%s") of type "%s"', $account->id, $account->name, $account->accountType->type));
return [$account, $destinationAccount];
}
app('log')->debug('Unused fallback'); // @phpstan-ignore-line
Log::debug('Unused fallback'); // @phpstan-ignore-line
return [$sourceAccount, $destinationAccount];
}
@@ -447,7 +455,15 @@ class TransactionJournalFactory
*/
private function getCurrencyByAccount(string $type, ?TransactionCurrency $currency, Account $source, Account $destination): TransactionCurrency
{
app('log')->debug('Now in getCurrencyByAccount()');
Log::debug('Now in getCurrencyByAccount()');
/*
* Deze functie moet bij een transactie van liability naar asset wel degelijk de currency
* van de liability teruggeven en niet die van de destination. Fix voor #10265
*/
if ($this->isBetweenAssetAndLiability($source, $destination) && TransactionTypeEnum::DEPOSIT->value === $type) {
return $this->getCurrency($currency, $source);
}
return match ($type) {
default => $this->getCurrency($currency, $source),
@@ -460,7 +476,7 @@ class TransactionJournalFactory
*/
private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency
{
app('log')->debug('Now in getCurrency()');
Log::debug(sprintf('Now in getCurrency(#%d, "%s")', $currency?->id, $account->name));
/** @var null|TransactionCurrency $preference */
$preference = $this->accountRepository->getAccountCurrency($account);
@@ -469,7 +485,7 @@ class TransactionJournalFactory
return app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
}
$result = $preference ?? $currency;
app('log')->debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name));
Log::debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name));
return $result;
}
@@ -479,6 +495,7 @@ class TransactionJournalFactory
*/
private function compareCurrencies(?TransactionCurrency $currency, ?TransactionCurrency $foreignCurrency): ?TransactionCurrency
{
Log::debug(sprintf('Now in compareCurrencies("%s", "%s")', $currency?->code, $foreignCurrency?->code));
if (null === $currency) {
return null;
}
@@ -494,6 +511,7 @@ class TransactionJournalFactory
*/
private function getForeignByAccount(string $type, ?TransactionCurrency $foreignCurrency, Account $destination): ?TransactionCurrency
{
Log::debug(sprintf('Now in getForeignByAccount("%s", #%d, "%s")', $type, $foreignCurrency?->id, $destination->name));
if (TransactionTypeEnum::TRANSFER->value === $type) {
return $this->getCurrency($foreignCurrency, $destination);
}
@@ -514,12 +532,12 @@ class TransactionJournalFactory
*/
private function forceDeleteOnError(Collection $collection): void
{
app('log')->debug(sprintf('forceDeleteOnError on collection size %d item(s)', $collection->count()));
Log::debug(sprintf('forceDeleteOnError on collection size %d item(s)', $collection->count()));
$service = app(JournalDestroyService::class);
/** @var TransactionJournal $journal */
foreach ($collection as $journal) {
app('log')->debug(sprintf('forceDeleteOnError on journal #%d', $journal->id));
Log::debug(sprintf('forceDeleteOnError on journal #%d', $journal->id));
$service->destroy($journal);
}
}
@@ -534,17 +552,17 @@ class TransactionJournalFactory
*/
private function storePiggyEvent(TransactionJournal $journal, NullArrayObject $data): void
{
app('log')->debug('Will now store piggy event.');
Log::debug('Will now store piggy event.');
$piggyBank = $this->piggyRepository->findPiggyBank((int) $data['piggy_bank_id'], $data['piggy_bank_name']);
if (null !== $piggyBank) {
$this->piggyEventFactory->create($journal, $piggyBank);
app('log')->debug('Create piggy event.');
Log::debug('Create piggy event.');
return;
}
app('log')->debug('Create no piggy event');
Log::debug('Create no piggy event');
}
private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void
@@ -563,11 +581,11 @@ class TransactionJournalFactory
];
if ($data[$field] instanceof Carbon) {
$data[$field]->setTimezone(config('app.timezone'));
app('log')->debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName()));
Log::debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName()));
$set['data'] = $data[$field]->format('Y-m-d H:i:s');
}
app('log')->debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data']));
Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data']));
/** @var TransactionJournalMetaFactory $factory */
$factory = app(TransactionJournalMetaFactory::class);
@@ -590,7 +608,7 @@ class TransactionJournalFactory
{
$this->errorOnHash = $errorOnHash;
if (true === $errorOnHash) {
app('log')->info('Will trigger duplication alert for this journal.');
Log::info('Will trigger duplication alert for this journal.');
}
}
@@ -605,4 +623,25 @@ class TransactionJournalFactory
$this->piggyRepository->setUserGroup($userGroup);
$this->accountRepository->setUserGroup($userGroup);
}
private function isBetweenAssetAndLiability(Account $source, Account $destination): bool
{
$sourceTypes = [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
// source is liability, destination is asset
if (in_array($source->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $destination->accountType->type) {
Log::debug('Source is a liability account, destination is an asset account, return TRUE.');
return true;
}
// source is asset, destination is liability
if (in_array($destination->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $source->accountType->type) {
Log::debug('Destination is a liability account, source is an asset account, return TRUE.');
return true;
}
Log::debug('Not between asset and liability, return FALSE');
return false;
}
}

View File

@@ -73,7 +73,7 @@ class VersionCheckEventHandler
$diff = $now - $lastCheckTime->data;
Log::debug(sprintf('Last check time is %d, current time is %d, difference is %d', $lastCheckTime->data, $now, $diff));
if ($diff < 604800) {
Log::debug(sprintf('Checked for updates less than a week ago (on %s).', \Safe\date('Y-m-d H:i:s', $lastCheckTime->data)));
Log::debug(sprintf('Checked for updates less than a week ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data)));
return;
}
@@ -105,7 +105,7 @@ class VersionCheckEventHandler
$diff = $now - $lastCheckTime->data;
Log::debug(sprintf('Last warning time is %d, current time is %d, difference is %d', $lastCheckTime->data, $now, $diff));
if ($diff < 604800 * 4) {
Log::debug(sprintf('Warned about updates less than four weeks ago (on %s).', \Safe\date('Y-m-d H:i:s', $lastCheckTime->data)));
Log::debug(sprintf('Warned about updates less than four weeks ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data)));
return;
}

View File

@@ -163,7 +163,9 @@ class IndexController extends Controller
/** @var Carbon $end */
$end = clone session('end', today(config('app.timezone'))->endOfMonth());
$start->subDay();
// 2025-05-11 removed this so start is exactly the start of the month.
// $start->subDay();
$ids = $accounts->pluck('id')->toArray();
Log::debug(sprintf('index start: finalAccountsBalance("%s")', $start->format('Y-m-d H:i:s')));

View File

@@ -91,7 +91,7 @@ class IndexController extends Controller
$generator->setStart($firstDate);
$result = $generator->export();
$name = sprintf('%s_transaction_export.csv', \Safe\date('Y_m_d'));
$name = sprintf('%s_transaction_export.csv', date('Y_m_d'));
$quoted = sprintf('"%s"', addcslashes($name, '"\\'));
// headers for CSV file.

View File

@@ -153,7 +153,7 @@ class RecurrenceController extends Controller
*/
public function suggest(Request $request): JsonResponse
{
$string = '' === (string) $request->get('date') ? \Safe\date('Y-m-d') : (string) $request->get('date');
$string = '' === (string) $request->get('date') ? date('Y-m-d') : (string) $request->get('date');
$today = today(config('app.timezone'))->startOfDay();
try {

View File

@@ -110,7 +110,7 @@ class PreferencesController extends Controller
if (is_array($fiscalYearStartStr)) {
$fiscalYearStartStr = '01-01';
}
$fiscalYearStart = sprintf('%s-%s', \Safe\date('Y'), (string) $fiscalYearStartStr);
$fiscalYearStart = sprintf('%s-%s', date('Y'), (string) $fiscalYearStartStr);
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$availableDarkModes = config('firefly.available_dark_modes');
@@ -273,7 +273,7 @@ class PreferencesController extends Controller
$customFiscalYear = 1 === (int) $request->get('customFiscalYear');
$string = \Safe\strtotime((string) $request->get('fiscalYearStart'));
if (false !== $string) {
$fiscalYearStart = \Safe\date('m-d', $string);
$fiscalYearStart = date('m-d', $string);
Preferences::set('customFiscalYear', $customFiscalYear);
Preferences::set('fiscalYearStart', $fiscalYearStart);
}

View File

@@ -50,30 +50,36 @@ class ReturnsAvailableChannels
private static function returnOwnerChannels(): array
{
$channels = ['mail'];
$slackUrl = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
if (UrlValidator::isValidWebhookURL($slackUrl)) {
$channels[] = 'slack';
$channels = ['mail'];
if (true === config('notifications.channels.slack.enabled', false)) {
$slackUrl = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
if (UrlValidator::isValidWebhookURL($slackUrl)) {
$channels[] = 'slack';
}
}
// validate presence of of Ntfy settings.
if ('' !== (string) app('fireflyconfig')->getEncrypted('ntfy_topic', '')->data) {
Log::debug('Enabled ntfy.');
$channels[] = NtfyChannel::class;
}
if ('' === (string) app('fireflyconfig')->getEncrypted('ntfy_topic', '')->data) {
Log::warning('No topic name for Ntfy, channel is disabled.');
if (true === config('notifications.channels.ntfy.enabled', false)) {
// validate presence of of Ntfy settings.
if ('' !== (string) app('fireflyconfig')->getEncrypted('ntfy_topic', '')->data) {
Log::debug('Enabled ntfy.');
$channels[] = NtfyChannel::class;
}
if ('' === (string) app('fireflyconfig')->getEncrypted('ntfy_topic', '')->data) {
Log::warning('No topic name for Ntfy, channel is disabled.');
}
}
// pushover
$pushoverAppToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data;
if ('' === $pushoverAppToken || '' === $pushoverUserToken) {
Log::warning('[b] No Pushover token, channel is disabled.');
}
if ('' !== $pushoverAppToken && '' !== $pushoverUserToken) {
Log::debug('Enabled pushover.');
$channels[] = PushoverChannel::class;
if (true === config('notifications.channels.pushover.enabled', false)) {
$pushoverAppToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data;
if ('' === $pushoverAppToken || '' === $pushoverUserToken) {
Log::warning('[b] No Pushover token, channel is disabled.');
}
if ('' !== $pushoverAppToken && '' !== $pushoverUserToken) {
Log::debug('Enabled pushover.');
$channels[] = PushoverChannel::class;
}
}
Log::debug(sprintf('Final channel set in ReturnsAvailableChannels: %s ', implode(', ', $channels)));
@@ -84,31 +90,38 @@ class ReturnsAvailableChannels
private static function returnUserChannels(User $user): array
{
Log::debug(sprintf('Checking channels for user #%d', $user->id));
$channels = ['mail'];
$slackUrl = (string) app('preferences')->getEncryptedForUser($user, 'slack_webhook_url', '')->data;
if (UrlValidator::isValidWebhookURL($slackUrl)) {
$channels[] = 'slack';
$channels = ['mail'];
if (true === config('notifications.channels.slack.enabled', false)) {
$slackUrl = (string) app('preferences')->getEncryptedForUser($user, 'slack_webhook_url', '')->data;
if (UrlValidator::isValidWebhookURL($slackUrl)) {
$channels[] = 'slack';
}
}
// validate presence of of Ntfy settings.
$ntfyTopic = (string) app('preferences')->getEncryptedForUser($user, 'ntfy_topic', '')->data;
if ('' !== $ntfyTopic) {
Log::debug(sprintf('Enabled ntfy, "%s"', $ntfyTopic));
$channels[] = NtfyChannel::class;
}
if ('' === (string) app('preferences')->getEncryptedForUser($user, 'ntfy_topic', '')->data) {
Log::warning('No topic name for Ntfy, channel is disabled.');
if (true === config('notifications.channels.nfy.enabled', false)) {
$ntfyTopic = (string) app('preferences')->getEncryptedForUser($user, 'ntfy_topic', '')->data;
if ('' !== $ntfyTopic) {
Log::debug(sprintf('Enabled ntfy, "%s"', $ntfyTopic));
$channels[] = NtfyChannel::class;
}
if ('' === (string) app('preferences')->getEncryptedForUser($user, 'ntfy_topic', '')->data) {
Log::warning('No topic name for Ntfy, channel is disabled.');
}
}
// pushover
$pushoverAppToken = (string) app('preferences')->getEncryptedForUser($user, 'pushover_app_token', '')->data;
$pushoverUserToken = (string) app('preferences')->getEncryptedForUser($user, 'pushover_user_token', '')->data;
if ('' === $pushoverAppToken || '' === $pushoverUserToken) {
Log::warning('[b] No Pushover token, channel is disabled.');
}
if ('' !== $pushoverAppToken && '' !== $pushoverUserToken) {
Log::debug('Enabled pushover.');
$channels[] = PushoverChannel::class;
if (true === config('notifications.channels.slack.enabled', false)) {
$pushoverAppToken = (string) app('preferences')->getEncryptedForUser($user, 'pushover_app_token', '')->data;
$pushoverUserToken = (string) app('preferences')->getEncryptedForUser($user, 'pushover_user_token', '')->data;
if ('' === $pushoverAppToken || '' === $pushoverUserToken) {
Log::warning('[b] No Pushover token, channel is disabled.');
}
if ('' !== $pushoverAppToken && '' !== $pushoverUserToken) {
Log::debug('Enabled pushover.');
$channels[] = PushoverChannel::class;
}
}
Log::debug(sprintf('Final channel set in ReturnsAvailableChannels (user): %s ', implode(', ', $channels)));

View File

@@ -133,7 +133,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
return $searchResult;
}
}
app('log')->debug('Found nothing');
app('log')->debug('Found no bill in findBill()');
return null;
}

View File

@@ -65,17 +65,17 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
*/
public function currencyInUseAt(TransactionCurrency $currency): ?string
{
app('log')->debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code));
Log::debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code));
$countJournals = $this->countJournals($currency);
if ($countJournals > 0) {
app('log')->info(sprintf('Count journals is %d, return true.', $countJournals));
Log::info(sprintf('Count journals is %d, return true.', $countJournals));
return 'journals';
}
// is the only currency left
if (1 === $this->getAll()->count()) {
app('log')->info('Is the last currency in the system, return true. ');
Log::info('Is the last currency in the system, return true. ');
return 'last_left';
}
@@ -83,7 +83,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
// is being used in accounts:
$meta = AccountMeta::where('name', 'currency_id')->where('data', \Safe\json_encode((string) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
return 'account_meta';
}
@@ -91,7 +91,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
// second search using integer check.
$meta = AccountMeta::where('name', 'currency_id')->where('data', \Safe\json_encode((int) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
return 'account_meta';
}
@@ -99,7 +99,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
// is being used in bills:
$bills = Bill::where('transaction_currency_id', $currency->id)->count();
if ($bills > 0) {
app('log')->info(sprintf('Used in %d bills as currency, return true. ', $bills));
Log::info(sprintf('Used in %d bills as currency, return true. ', $bills));
return 'bills';
}
@@ -109,7 +109,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
$recurringForeign = RecurrenceTransaction::where('foreign_currency_id', $currency->id)->count();
if ($recurringAmount > 0 || $recurringForeign > 0) {
app('log')->info(sprintf('Used in %d recurring transactions as (foreign) currency id, return true. ', $recurringAmount + $recurringForeign));
Log::info(sprintf('Used in %d recurring transactions as (foreign) currency id, return true. ', $recurringAmount + $recurringForeign));
return 'recurring';
}
@@ -120,7 +120,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
->where('account_meta.name', 'currency_id')->where('account_meta.data', \Safe\json_encode($currency->id))->count()
;
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
return 'account_meta';
}
@@ -128,7 +128,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
// is being used in available budgets
$availableBudgets = AvailableBudget::where('transaction_currency_id', $currency->id)->count();
if ($availableBudgets > 0) {
app('log')->info(sprintf('Used in %d available budgets as currency, return true. ', $availableBudgets));
Log::info(sprintf('Used in %d available budgets as currency, return true. ', $availableBudgets));
return 'available_budgets';
}
@@ -136,7 +136,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
// is being used in budget limits
$budgetLimit = BudgetLimit::where('transaction_currency_id', $currency->id)->count();
if ($budgetLimit > 0) {
app('log')->info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit));
Log::info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit));
return 'budget_limits';
}
@@ -144,7 +144,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
// is the default currency for the user or the system
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
app('log')->info('Is the default currency of the user, return true.');
Log::info('Is the default currency of the user, return true.');
return 'current_default';
}
@@ -152,12 +152,12 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
// is the default currency for the user or the system
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
app('log')->info('Is the default currency of the user group, return true.');
Log::info('Is the default currency of the user group, return true.');
return 'current_default';
}
app('log')->debug('Currency is not used, return false.');
Log::debug('Currency is not used, return false.');
return null;
}
@@ -237,15 +237,15 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
$result = $this->findCurrencyNull($currencyId, $currencyCode);
if (null === $result) {
app('log')->debug('Grabbing default currency for this user...');
Log::debug('Grabbing default currency for this user...');
/** @var null|TransactionCurrency $result */
$result = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
}
app('log')->debug(sprintf('Final result: %s', $result->code));
Log::debug(sprintf('Final result: %s', $result->code));
if (false === $result->enabled) {
app('log')->debug(sprintf('Also enabled currency %s', $result->code));
Log::debug(sprintf('Also enabled currency %s', $result->code));
$this->enable($result);
}
@@ -257,16 +257,22 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
*/
public function findCurrencyNull(?int $currencyId, ?string $currencyCode): ?TransactionCurrency
{
app('log')->debug('Now in findCurrencyNull()');
Log::debug(sprintf('Now in findCurrencyNull(%s, "%s")', var_export($currencyId, true), $currencyCode));
$result = $this->find((int) $currencyId);
if (null !== $result) {
Log::debug(sprintf('Found currency by ID: %s', $result->code));
return $result;
}
if (null === $result) {
app('log')->debug(sprintf('Searching for currency with code %s...', $currencyCode));
Log::debug(sprintf('Searching for currency with code "%s"...', $currencyCode));
$result = $this->findByCode((string) $currencyCode);
}
if (null !== $result && false === $result->enabled) {
app('log')->debug(sprintf('Also enabled currency %s', $result->code));
Log::debug(sprintf('Also enabled currency %s', $result->code));
$this->enable($result);
}
Log::debug('Found no currency, returning NULL.');
return $result;
}
@@ -321,7 +327,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
->where('date', $date->format('Y-m-d'))->first()
;
if (null !== $rate) {
app('log')->debug(sprintf('Found cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));
Log::debug(sprintf('Found cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));
return $rate;
}
@@ -380,7 +386,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
public function update(TransactionCurrency $currency, array $data): TransactionCurrency
{
app('log')->debug('Now in update()');
Log::debug('Now in update()');
// can be true, false, null
$enabled = array_key_exists('enabled', $data) ? $data['enabled'] : null;
// can be true, false, but method only responds to "true".
@@ -396,12 +402,12 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
// currency is enabled, must be disabled.
if (false === $enabled) {
app('log')->debug(sprintf('Disabled currency %s for user #%d', $currency->code, $this->userGroup->id));
Log::debug(sprintf('Disabled currency %s for user #%d', $currency->code, $this->userGroup->id));
$this->userGroup->currencies()->detach($currency->id);
}
// currency must be enabled
if (true === $enabled) {
app('log')->debug(sprintf('Enabled currency %s for user #%d', $currency->code, $this->userGroup->id));
Log::debug(sprintf('Enabled currency %s for user #%d', $currency->code, $this->userGroup->id));
$this->userGroup->currencies()->detach($currency->id);
$this->userGroup->currencies()->syncWithoutDetaching([$currency->id => ['group_default' => false]]);
}
@@ -420,7 +426,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
public function makeDefault(TransactionCurrency $currency): void
{
$current = app('amount')->getNativeCurrencyByUserGroup($this->userGroup);
app('log')->debug(sprintf('Enabled + made default currency %s for user #%d', $currency->code, $this->userGroup->id));
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]);

View File

@@ -82,7 +82,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
return $searchResult;
}
}
app('log')->debug('Found nothing');
app('log')->debug('Found no piggy bank.');
return null;
}

View File

@@ -54,7 +54,7 @@ class UserRepository implements UserRepositoryInterface
// save old email as pref
app('preferences')->setForUser($user, 'previous_email_latest', $oldEmail);
app('preferences')->setForUser($user, 'previous_email_'.\Safe\date('Y-m-d-H-i-s'), $oldEmail);
app('preferences')->setForUser($user, 'previous_email_'.date('Y-m-d-H-i-s'), $oldEmail);
// set undo and confirm token:
app('preferences')->setForUser($user, 'email_change_undo_token', bin2hex(random_bytes(16)));
@@ -389,7 +389,7 @@ class UserRepository implements UserRepositoryInterface
// save old email as pref
app('preferences')->setForUser($user, 'admin_previous_email_latest', $oldEmail);
app('preferences')->setForUser($user, 'admin_previous_email_'.\Safe\date('Y-m-d-H-i-s'), $oldEmail);
app('preferences')->setForUser($user, 'admin_previous_email_'.date('Y-m-d-H-i-s'), $oldEmail);
$user->email = $newEmail;
$user->save();

View File

@@ -65,17 +65,17 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/
public function currencyInUseAt(TransactionCurrency $currency): ?string
{
app('log')->debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code));
Log::debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code));
$countJournals = $this->countJournals($currency);
if ($countJournals > 0) {
app('log')->info(sprintf('Count journals is %d, return true.', $countJournals));
Log::info(sprintf('Count journals is %d, return true.', $countJournals));
return 'journals';
}
// is the only currency left
if (1 === $this->getAll()->count()) {
app('log')->info('Is the last currency in the system, return true. ');
Log::info('Is the last currency in the system, return true. ');
return 'last_left';
}
@@ -83,7 +83,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// is being used in accounts:
$meta = AccountMeta::where('name', 'currency_id')->where('data', \Safe\json_encode((string) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
return 'account_meta';
}
@@ -91,7 +91,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// second search using integer check.
$meta = AccountMeta::where('name', 'currency_id')->where('data', \Safe\json_encode((int) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
return 'account_meta';
}
@@ -99,7 +99,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// is being used in bills:
$bills = Bill::where('transaction_currency_id', $currency->id)->count();
if ($bills > 0) {
app('log')->info(sprintf('Used in %d bills as currency, return true. ', $bills));
Log::info(sprintf('Used in %d bills as currency, return true. ', $bills));
return 'bills';
}
@@ -109,7 +109,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
$recurringForeign = RecurrenceTransaction::where('foreign_currency_id', $currency->id)->count();
if ($recurringAmount > 0 || $recurringForeign > 0) {
app('log')->info(sprintf('Used in %d recurring transactions as (foreign) currency id, return true. ', $recurringAmount + $recurringForeign));
Log::info(sprintf('Used in %d recurring transactions as (foreign) currency id, return true. ', $recurringAmount + $recurringForeign));
return 'recurring';
}
@@ -120,7 +120,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
->where('account_meta.name', 'currency_id')->where('account_meta.data', \Safe\json_encode($currency->id))->count()
;
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
return 'account_meta';
}
@@ -128,7 +128,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// is being used in available budgets
$availableBudgets = AvailableBudget::where('transaction_currency_id', $currency->id)->count();
if ($availableBudgets > 0) {
app('log')->info(sprintf('Used in %d available budgets as currency, return true. ', $availableBudgets));
Log::info(sprintf('Used in %d available budgets as currency, return true. ', $availableBudgets));
return 'available_budgets';
}
@@ -136,7 +136,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// is being used in budget limits
$budgetLimit = BudgetLimit::where('transaction_currency_id', $currency->id)->count();
if ($budgetLimit > 0) {
app('log')->info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit));
Log::info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit));
return 'budget_limits';
}
@@ -144,7 +144,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// is the default currency for the user or the system
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
app('log')->info('Is the default currency of the user, return true.');
Log::info('Is the default currency of the user, return true.');
return 'current_default';
}
@@ -152,12 +152,12 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// is the default currency for the user or the system
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
app('log')->info('Is the default currency of the user group, return true.');
Log::info('Is the default currency of the user group, return true.');
return 'current_default';
}
app('log')->debug('Currency is not used, return false.');
Log::debug('Currency is not used, return false.');
return null;
}
@@ -240,15 +240,15 @@ class CurrencyRepository implements CurrencyRepositoryInterface
$result = $this->findCurrencyNull($currencyId, $currencyCode);
if (null === $result) {
app('log')->debug('Grabbing default currency for this user...');
Log::debug('Grabbing default currency for this user...');
/** @var null|TransactionCurrency $result */
$result = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
}
app('log')->debug(sprintf('Final result: %s', $result->code));
Log::debug(sprintf('Final result: %s', $result->code));
if (false === $result->enabled) {
app('log')->debug(sprintf('Also enabled currency %s', $result->code));
Log::debug(sprintf('Also enabled currency %s', $result->code));
$this->enable($result);
}
@@ -260,14 +260,14 @@ class CurrencyRepository implements CurrencyRepositoryInterface
*/
public function findCurrencyNull(?int $currencyId, ?string $currencyCode): ?TransactionCurrency
{
app('log')->debug('Now in findCurrencyNull()');
Log::debug(sprintf('Now in findCurrencyNull("%s", "%s")', $currencyId, $currencyCode));
$result = $this->find((int) $currencyId);
if (null === $result) {
app('log')->debug(sprintf('Searching for currency with code %s...', $currencyCode));
Log::debug(sprintf('Searching for currency with code "%s"...', $currencyCode));
$result = $this->findByCode((string) $currencyCode);
}
if (null !== $result && false === $result->enabled) {
app('log')->debug(sprintf('Also enabled currency %s', $result->code));
Log::debug(sprintf('Also enabled currency %s', $result->code));
$this->enable($result);
}
@@ -335,7 +335,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
public function update(TransactionCurrency $currency, array $data): TransactionCurrency
{
app('log')->debug('Now in update()');
Log::debug('Now in update()');
// can be true, false, null
$enabled = array_key_exists('enabled', $data) ? $data['enabled'] : null;
// can be true, false, but method only responds to "true".
@@ -351,12 +351,12 @@ class CurrencyRepository implements CurrencyRepositoryInterface
// currency is enabled, must be disabled.
if (false === $enabled) {
app('log')->debug(sprintf('Disabled currency %s for user #%d', $currency->code, $this->userGroup->id));
Log::debug(sprintf('Disabled currency %s for user #%d', $currency->code, $this->userGroup->id));
$this->userGroup->currencies()->detach($currency->id);
}
// currency must be enabled
if (true === $enabled) {
app('log')->debug(sprintf('Enabled currency %s for user #%d', $currency->code, $this->userGroup->id));
Log::debug(sprintf('Enabled currency %s for user #%d', $currency->code, $this->userGroup->id));
$this->userGroup->currencies()->detach($currency->id);
$this->userGroup->currencies()->syncWithoutDetaching([$currency->id => ['group_default' => false]]);
}
@@ -375,7 +375,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
public function makeDefault(TransactionCurrency $currency): void
{
$current = app('amount')->getNativeCurrencyByUserGroup($this->userGroup);
app('log')->debug(sprintf('Enabled + made default currency %s for user #%d', $currency->code, $this->userGroup->id));
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]);

View File

@@ -27,6 +27,7 @@ namespace FireflyIII\Services\Internal\Update;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Exceptions\FireflyException;
@@ -723,14 +724,17 @@ class JournalUpdateService
// the correct fields to update in the destination transaction are NOT the foreign amount and currency
// but rather the normal amount and currency. This is new behavior.
$isTransfer = TransactionTypeEnum::TRANSFER->value === $this->transactionJournal->transactionType->type;
if ($isTransfer) {
// also check if it is not between an asset account and a liability, because then the same rule applies.
$isBetween = $this->isBetweenAssetAndLiability();
if ($isTransfer || $isBetween) {
Log::debug('Switch amounts, store in amount and not foreign_amount');
$dest->transaction_currency_id = $foreignCurrency->id;
$dest->amount = app('steam')->positive($foreignAmount);
$dest->foreign_amount = app('steam')->positive($source->amount);
$dest->foreign_currency_id = $source->transaction_currency_id;
}
if (!$isTransfer) {
if (!$isTransfer && !$isBetween) {
$dest->foreign_currency_id = $foreignCurrency->id;
$dest->foreign_amount = app('steam')->positive($foreignAmount);
}
@@ -768,4 +772,48 @@ class JournalUpdateService
$this->sourceTransaction->refresh();
$this->destinationTransaction->refresh();
}
private function isBetweenAssetAndLiability(): bool
{
/** @var Transaction $sourceTransaction */
$sourceTransaction = $this->transactionJournal->transactions()->where('amount', '<', 0)->first();
/** @var Transaction $destinationTransaction */
$destinationTransaction = $this->transactionJournal->transactions()->where('amount', '>', 0)->first();
if (null === $sourceTransaction || null === $destinationTransaction) {
Log::warning('Either transaction is false, stop.');
return false;
}
if (null === $sourceTransaction->foreign_amount || null === $destinationTransaction->foreign_amount) {
Log::warning('Either foreign amount is false, stop.');
return false;
}
$source = $sourceTransaction->account;
$destination = $destinationTransaction->account;
if (null === $source || null === $destination) {
Log::warning('Either is false, stop.');
return false;
}
$sourceTypes = [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
// source is liability, destination is asset
if (in_array($source->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $destination->accountType->type) {
Log::debug('Source is a liability account, destination is an asset account, return TRUE.');
return true;
}
// source is asset, destination is liability
if (in_array($destination->accountType->type, $sourceTypes, true) && AccountTypeEnum::ASSET->value === $source->accountType->type) {
Log::debug('Destination is a liability account, source is an asset account, return TRUE.');
return true;
}
return false;
}
}

View File

@@ -67,7 +67,7 @@ class RemoteUserGuard implements Guard
if (function_exists('apache_request_headers')) {
Log::debug('Use apache_request_headers to find user ID.');
$userID = request()->server($header) ?? \Safe\apache_request_headers()[$header] ?? null;
$userID = request()->server($header) ?? apache_request_headers()[$header] ?? null;
}
if (null === $userID || '' === $userID) {
@@ -85,7 +85,7 @@ class RemoteUserGuard implements Guard
$header = config('auth.guard_email');
if (null !== $header) {
$emailAddress = (string) (request()->server($header) ?? \Safe\apache_request_headers()[$header] ?? null);
$emailAddress = (string) (request()->server($header) ?? apache_request_headers()[$header] ?? null);
$preference = app('preferences')->getForUser($retrievedUser, 'remote_guard_alt_email');
if ('' !== $emailAddress && null === $preference && $emailAddress !== $userID) {

View File

@@ -63,7 +63,7 @@ class UpdateCheckCronjob extends AbstractCronjob
$this->jobFired = false;
$this->jobErrored = false;
$this->jobSucceeded = true;
$this->message = sprintf('Checked for updates less than a week ago (on %s).', \Safe\date('Y-m-d H:i:s', $lastCheckTime->data));
$this->message = sprintf('Checked for updates less than a week ago (on %s).', date('Y-m-d H:i:s', $lastCheckTime->data));
return;
}

View File

@@ -50,7 +50,7 @@ class AccountEnrichment implements EnrichmentInterface
private array $accountIds;
private array $accountTypeIds;
private array $accountTypes;
private Collection $collection;
private Collection $collection;
private array $currencies;
private array $locations;
private array $meta;
@@ -59,6 +59,7 @@ class AccountEnrichment implements EnrichmentInterface
private array $openingBalances;
private User $user;
private UserGroup $userGroup;
private array $lastActivities;
public function __construct()
{
@@ -69,6 +70,7 @@ class AccountEnrichment implements EnrichmentInterface
$this->accountTypes = [];
$this->meta = [];
$this->notes = [];
$this->lastActivities = [];
$this->locations = [];
// $this->repository = app(AccountRepositoryInterface::class);
// $this->currencyRepository = app(CurrencyRepositoryInterface::class);
@@ -100,6 +102,7 @@ class AccountEnrichment implements EnrichmentInterface
$this->getAccountTypes();
$this->collectMetaData();
$this->collectNotes();
$this->collectLastActivities();
$this->collectLocations();
$this->collectOpeningBalances();
$this->appendCollectedData();
@@ -229,7 +232,8 @@ class AccountEnrichment implements EnrichmentInterface
$notes = $this->notes;
$openingBalances = $this->openingBalances;
$locations = $this->locations;
$this->collection = $this->collection->map(function (Account $item) use ($accountTypes, $meta, $currencies, $notes, $openingBalances, $locations) {
$lastActivities = $this->lastActivities;
$this->collection = $this->collection->map(function (Account $item) use ($accountTypes, $meta, $currencies, $notes, $openingBalances, $locations, $lastActivities) {
$item->full_account_type = $accountTypes[(int) $item->account_type_id] ?? null;
$accountMeta = [
'currency' => null,
@@ -264,6 +268,9 @@ class AccountEnrichment implements EnrichmentInterface
if (array_key_exists($item->id, $locations)) {
$accountMeta['location'] = $locations[$item->id];
}
if (array_key_exists($item->id, $lastActivities)) {
$accountMeta['last_activity'] = $lastActivities[$item->id];
}
$item->meta = $accountMeta;
return $item;
@@ -274,4 +281,9 @@ class AccountEnrichment implements EnrichmentInterface
{
$this->native = $native;
}
private function collectLastActivities(): void
{
$this->lastActivities = Steam::getLastActivities($this->accountIds);
}
}

View File

@@ -211,7 +211,7 @@ class General extends AbstractExtension
{
return new TwigFunction(
'phpdate',
static fn (string $str): string => \Safe\date($str)
static fn (string $str): string => date($str)
);
}

View File

@@ -152,6 +152,7 @@ class AccountTransformer extends AbstractTransformer
'longitude' => $longitude,
'latitude' => $latitude,
'zoom_level' => $zoomLevel,
'last_activity' => array_key_exists('last_activity', $account->meta) ? $account->meta['last_activity']->toAtomString() : null,
'links' => [
[
'rel' => 'self',

View File

@@ -305,7 +305,7 @@ class AccountValidator
return $first;
}
}
app('log')->debug('Found nothing!');
app('log')->debug('Found nothing in findExistingAccount()');
return null;
}

View File

@@ -214,7 +214,7 @@ trait TransactionValidation
$destination = $accountValidator->destination;
Log::debug(sprintf('Source: #%d "%s (%s)"', $source->id, $source->name, $source->accountType->type));
Log::debug(sprintf('Destination: #%d "%s" (%s)', $destination->id, $destination->name, $source->accountType->type));
Log::debug(sprintf('Destination: #%d "%s" (%s)', $destination->id, $destination->name, $destination->accountType->type));
if (!$this->isLiabilityOrAsset($source) || !$this->isLiabilityOrAsset($destination)) {
Log::debug('Any account must be liability or asset account to continue.');
@@ -233,16 +233,16 @@ trait TransactionValidation
return;
}
Log::debug(sprintf('Source account expects %s', $sourceCurrency->code));
Log::debug(sprintf('Destination account expects %s', $destinationCurrency->code));
Log::debug(sprintf('Source account expects #%d: %s', $sourceCurrency->id, $sourceCurrency->code));
Log::debug(sprintf('Destination account expects #%d: %s', $destinationCurrency->id, $destinationCurrency->code));
Log::debug(sprintf('Amount is %s', $transaction['amount']));
if (TransactionTypeEnum::DEPOSIT->value === ucfirst($transactionType)) {
Log::debug(sprintf('Processing as a "%s"', $transactionType));
// use case: deposit from liability account to an asset account
// the foreign amount must be in the currency of the source
// the amount must be in the currency of the destination
// the amount must be in the currency of the SOURCE
// the foreign amount must be in the currency of the DESTINATION
// no foreign currency information is present:
if (!$this->hasForeignCurrencyInfo($transaction)) {
@@ -255,7 +255,7 @@ trait TransactionValidation
$foreignCurrencyCode = $transaction['foreign_currency_code'] ?? false;
$foreignCurrencyId = (int) ($transaction['foreign_currency_id'] ?? 0);
Log::debug(sprintf('Foreign currency code seems to be #%d "%s"', $foreignCurrencyId, $foreignCurrencyCode), $transaction);
if ($foreignCurrencyCode !== $sourceCurrency->code && $foreignCurrencyId !== $sourceCurrency->id) {
if ($foreignCurrencyCode !== $destinationCurrency->code && $foreignCurrencyId !== $destinationCurrency->id) {
$validator->errors()->add(sprintf('transactions.%d.foreign_currency_code', $index), (string) trans('validation.require_foreign_src'));
return;

View File

@@ -88,9 +88,9 @@
"jc5/google2fa-laravel": "^2.0",
"jc5/recovery": "^2",
"laravel-notification-channels/pushover": "^4.0",
"laravel/framework": "^11",
"laravel/framework": "^12",
"laravel/passport": "^12",
"laravel/sanctum": "^4",
"laravel/sanctum": "^4.1",
"laravel/slack-notification-channel": "^3.3",
"laravel/ui": "^4.2",
"league/commonmark": "^2",
@@ -99,7 +99,7 @@
"mailersend/laravel-driver": "^2.7",
"nunomaduro/collision": "^8",
"pragmarx/google2fa": "^8.0",
"predis/predis": "^2.2",
"predis/predis": "^3",
"psr/log": "<4",
"ramsey/uuid": "^4.7",
"rcrowe/twigbridge": "^0.14",
@@ -109,7 +109,7 @@
"symfony/expression-language": "^7.0",
"symfony/http-client": "^7.1",
"symfony/mailgun-mailer": "^7.1",
"wijourdil/ntfy-notification-channel": "^3.0"
"thecodingmachine/safe": "^3.1"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.9",
@@ -123,7 +123,7 @@
"phpstan/phpstan": "^2",
"phpstan/phpstan-deprecation-rules": "^2",
"phpstan/phpstan-strict-rules": "^2",
"phpunit/phpunit": "^11",
"phpunit/phpunit": "^12",
"rector/rector": "^2.0",
"thecodingmachine/phpstan-safe-rule": "^1.4"
},

968
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -78,7 +78,7 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2025-05-05',
'version' => 'develop/2025-05-19',
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 25,

View File

@@ -26,7 +26,7 @@ return [
'channels' => [
'email' => ['enabled' => true, 'ui_configurable' => 0],
'slack' => ['enabled' => true, 'ui_configurable' => 1],
'ntfy' => ['enabled' => true, 'ui_configurable' => 1],
'ntfy' => ['enabled' => false, 'ui_configurable' => 1],
'pushover' => ['enabled' => true, 'ui_configurable' => 1],
// 'gotify' => ['enabled' => false, 'ui_configurable' => 0],
// 'pushbullet' => ['enabled' => false, 'ui_configurable' => 0],

583
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -154,7 +154,7 @@
"url": "URL",
"active": "H\u00e0nh \u0111\u1ed9ng",
"interest_date": "Ng\u00e0y l\u00e3i",
"administration_currency": "Native currency",
"administration_currency": "Ti\u1ec1n t\u1ec7 b\u1ea3n \u0111\u1ecba",
"title": "Ti\u00eau \u0111\u1ec1",
"date": "Ng\u00e0y",
"book_date": "Ng\u00e0y \u0111\u1eb7t s\u00e1ch",
@@ -169,7 +169,7 @@
"webhook_delivery": "Ph\u00e2n ph\u1ed1i",
"from_currency_to_currency": "{from} &rarr; {to}",
"to_currency_from_currency": "{to} &rarr; {from}",
"rate": "Rate"
"rate": "T\u1ef7 l\u1ec7"
},
"list": {
"title": "Ti\u00eau \u0111\u1ec1",

View File

@@ -20,6 +20,7 @@
import {api} from "../../../../boot/axios";
import format from "date-fns/format";
import {getCacheKey} from "../../../../support/get-cache-key.js";
export default class Get {
@@ -37,6 +38,31 @@ export default class Get {
return api.get('/api/v1/accounts/' + identifier, {params: params});
}
/**
*
* @param params
* @returns {Promise<AxiosResponse<any>>}
*/
index(params) {
// first, check API in some consistent manner.
// then, load if necessary.
const cacheKey = getCacheKey('/api/v1/accounts', params);
const cacheValid = window.store.get('cacheValid');
let cachedData = window.store.get(cacheKey);
if (cacheValid && typeof cachedData !== 'undefined') {
console.log('Cache is valid, return cache.');
return Promise.resolve(cachedData);
}
// if not, store in cache and then return res.
return api.get('/api/v1/accounts', {params: params}).then(response => {
console.log('Cache is invalid, return fresh.');
window.store.set(cacheKey, response.data);
return Promise.resolve({data: response.data.data, meta: response.data.meta});
});
}
/**
*

View File

@@ -383,7 +383,8 @@ let index = function () {
// one page only.o
(new Get()).index(params).then(response => {
console.log(response);
this.totalPages = response.meta.lastPage;
this.totalPages = response.meta.pagination.total_pages;
console.log('a');
for (let i = 0; i < response.data.length; i++) {
if (response.data.hasOwnProperty(i)) {
let current = response.data[i];

View File

@@ -259,7 +259,17 @@
{% if transaction.transaction_type_type == 'Deposit' %}
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% elseif transaction.transaction_type_type == 'Withdrawal' %}
{{ formatAmountBySymbol(transaction.source_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% if 'Loan' == transaction.destination_account_type or 'Mortgage' == transaction.destination_account_type or 'Debt' == transaction.destination_account_type %}
{% if currency.id == transaction.currency_id %}
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% endif %}
{% if currency.id == transaction.foreign_currency_id %}
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.foreign_currency_symbol, transaction.foreign_currency_decimal_places) }}
{% endif %}
{% else %}
{{ formatAmountBySymbol(transaction.source_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}
{% endif %}
{% elseif transaction.transaction_type_type == 'Opening balance' %}
{% if transaction.source_account_type == 'Initial balance account' %}
{{ formatAmountBySymbol(transaction.destination_balance_after, transaction.currency_symbol, transaction.currency_decimal_places) }}

View File

@@ -9,7 +9,7 @@
<div class="card">
<div class="card-header">
<h3 class="card-title">
<a :href="'{{ route('accounts.show', '') }}/' + account.id"
<a :href="'{{ route('accounts.show', '0') }}/' + account.id"
x-text="account.name"></a>
<span class="small">
@@ -42,7 +42,7 @@
<template x-if="group.transactions[0].type === 'transfer'">
<span class="text-muted fa-solid fa-arrows-rotate fa-fw"></span>
</template>
<a :href="'{{route('transactions.show', '') }}/' + group.id" x-text="group.title"></a><br/></span>
<a :href="'{{route('transactions.show', '0') }}/' + group.id" x-text="group.title"></a><br/></span>
</template>
<ul class="list-unstyled list-no-margin">
<template x-for="transaction in group.transactions">
@@ -61,7 +61,7 @@
<template x-if="transaction.type == 'transfer'">
<span class="text-muted fa-solid fa-arrows-rotate fa-fw"></span>
</template>
<a :href="'{{route('transactions.show', '') }}/' + group.id" x-text="transaction.description"></a>
<a :href="'{{route('transactions.show', '0') }}/' + group.id" x-text="transaction.description"></a>
</span>
</template>
</li>

View File

@@ -30,7 +30,7 @@
<template x-for="bill in group.bills">
<tr>
<td>
<a :href="'{{ route('subscriptions.show',['']) }}/' + bill.id" :title="bill.name">
<a :href="'{{ route('subscriptions.show',['0']) }}/' + bill.id" :title="bill.name">
<span x-text="bill.name"></span>
</a>
<template x-if="bill.paid">

View File

@@ -840,7 +840,7 @@ Route::group(
Route::get('default', ['uses' => 'ShowController@showDefault', 'as' => 'show.default']);
Route::get('native', ['uses' => 'ShowController@showDefault', 'as' => 'show.native']);
Route::get('{currency_code}', ['uses' => 'ShowController@show', 'as' => 'show']);
Route::put('{currency_code}', ['uses' => 'UpdateController@update', 'as' => 'update']);
Route::put('{currency_code?}', ['uses' => 'UpdateController@update', 'as' => 'update']);
Route::delete('{currency_code}', ['uses' => 'DestroyController@destroy', 'as' => 'delete']);
Route::post('{currency_code}/enable', ['uses' => 'UpdateController@enable', 'as' => 'enable']);

View File

@@ -1303,8 +1303,8 @@ Route::group(
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/mass', 'as' => 'transactions.mass.'],
static function (): void {
Route::get('edit/{journalList}', ['uses' => 'MassController@edit', 'as' => 'edit']);
Route::get('delete/{journalList}', ['uses' => 'MassController@delete', 'as' => 'delete']);
Route::get('edit/{journalList?}', ['uses' => 'MassController@edit', 'as' => 'edit']);
Route::get('delete/{journalList?}', ['uses' => 'MassController@delete', 'as' => 'delete']);
Route::post('update', ['uses' => 'MassController@update', 'as' => 'update']);
Route::post('destroy', ['uses' => 'MassController@destroy', 'as' => 'destroy']);
}
@@ -1314,7 +1314,7 @@ Route::group(
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/bulk', 'as' => 'transactions.bulk.'],
static function (): void {
Route::get('edit/{journalList}', ['uses' => 'BulkController@edit', 'as' => 'edit']);
Route::get('edit/{journalList?}', ['uses' => 'BulkController@edit', 'as' => 'edit']);
Route::post('update', ['uses' => 'BulkController@update', 'as' => 'update']);
}
);