🤖 Auto commit for release 'develop' on 2026-01-25

This commit is contained in:
JC5
2026-01-25 10:55:27 +01:00
parent 96aafacf43
commit 34a7fd3ef0
85 changed files with 661 additions and 542 deletions
+30 -30
View File
@@ -1252,16 +1252,16 @@
},
{
"name": "symfony/console",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587"
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
"url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b",
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b",
"shasum": ""
},
"require": {
@@ -1318,7 +1318,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v8.0.3"
"source": "https://github.com/symfony/console/tree/v8.0.4"
},
"funding": [
{
@@ -1338,7 +1338,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:52:06+00:00"
"time": "2026-01-13T13:06:50+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1409,16 +1409,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v8.0.0",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "573f95783a2ec6e38752979db139f09fec033f03"
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/573f95783a2ec6e38752979db139f09fec033f03",
"reference": "573f95783a2ec6e38752979db139f09fec033f03",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47",
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47",
"shasum": ""
},
"require": {
@@ -1470,7 +1470,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.0"
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4"
},
"funding": [
{
@@ -1490,7 +1490,7 @@
"type": "tidelift"
}
],
"time": "2025-10-30T14:17:19+00:00"
"time": "2026-01-05T11:45:55+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -1640,16 +1640,16 @@
},
{
"name": "symfony/finder",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12"
"reference": "42e48eb02e07d5f3771d194d67da117eb824c8c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"url": "https://api.github.com/repos/symfony/finder/zipball/42e48eb02e07d5f3771d194d67da117eb824c8c1",
"reference": "42e48eb02e07d5f3771d194d67da117eb824c8c1",
"shasum": ""
},
"require": {
@@ -1684,7 +1684,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v8.0.3"
"source": "https://github.com/symfony/finder/tree/v8.0.4"
},
"funding": [
{
@@ -1704,7 +1704,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:52:06+00:00"
"time": "2026-01-12T12:37:40+00:00"
},
{
"name": "symfony/options-resolver",
@@ -2358,16 +2358,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78"
"reference": "10df72602d88c0a3fa685b822976a052611dd607"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78",
"url": "https://api.github.com/repos/symfony/process/zipball/10df72602d88c0a3fa685b822976a052611dd607",
"reference": "10df72602d88c0a3fa685b822976a052611dd607",
"shasum": ""
},
"require": {
@@ -2399,7 +2399,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v8.0.3"
"source": "https://github.com/symfony/process/tree/v8.0.4"
},
"funding": [
{
@@ -2419,7 +2419,7 @@
"type": "tidelift"
}
],
"time": "2025-12-19T10:01:18+00:00"
"time": "2026-01-23T11:07:10+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2576,16 +2576,16 @@
},
{
"name": "symfony/string",
"version": "v8.0.1",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc"
"reference": "758b372d6882506821ed666032e43020c4f57194"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc",
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc",
"url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
"reference": "758b372d6882506821ed666032e43020c4f57194",
"shasum": ""
},
"require": {
@@ -2642,7 +2642,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.0.1"
"source": "https://github.com/symfony/string/tree/v8.0.4"
},
"funding": [
{
@@ -2662,7 +2662,7 @@
"type": "tidelift"
}
],
"time": "2025-12-01T09:13:36+00:00"
"time": "2026-01-12T12:37:40+00:00"
}
],
"packages-dev": [],
@@ -158,7 +158,10 @@ class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference'];
}
}
@@ -155,7 +155,10 @@ class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // intentional float
}
}
@@ -88,9 +88,9 @@ class StoreController extends Controller
public function store(StoreRequest $request): JsonResponse
{
Log::debug('Now in API StoreController::store()');
$data = $request->getAll();
$data['user'] = auth()->user();
$data['user_group'] = $this->userGroup;
$data = $request->getAll();
$data['user'] = auth()->user();
$data['user_group'] = $this->userGroup;
Log::channel('audit')->info('Store new transaction over API.', $data);
@@ -110,21 +110,21 @@ class StoreController extends Controller
throw new ValidationException($validator);
}
Preferences::mark();
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $data['apply_rules'] ?? true;
$flags->fireWebhooks = $data['fire_webhooks'] ?? true;
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $data['apply_rules'] ?? true;
$flags->fireWebhooks = $data['fire_webhooks'] ?? true;
$flags->batchSubmission = $data['batch_submission'] ?? false;
Log::debug('CreatedSingleTransactionGroup');
event(new CreatedSingleTransactionGroup($transactionGroup, $flags));
$manager = $this->getManager();
$manager = $this->getManager();
/** @var User $admin */
$admin = auth()->user();
$admin = auth()->user();
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector
->setUser($admin)
->setUserGroup($this->userGroup)
@@ -134,20 +134,20 @@ class StoreController extends Controller
->withAPIInformation()
;
$selectedGroup = $collector->getGroups()->first();
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw HttpException::fromStatusCode(410, '200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
}
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);
$transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new Item($selectedGroup, $transformer, 'transactions');
$resource = new Item($selectedGroup, $transformer, 'transactions');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}
@@ -47,7 +47,7 @@ class StoreRequest extends FormRequest
*/
public function getAll(): array
{
$fields = ['order' => ['order', 'convertInteger']];
$fields = ['order' => ['order', 'convertInteger']];
$data = $this->getAllData($fields);
$data['name'] = $this->convertString('name');
$data['accounts'] = $this->parseAccounts($this->get('accounts'));
@@ -64,7 +64,7 @@ class StoreRequest extends FormRequest
return [
'group_title' => $this->convertString('group_title'),
'error_if_duplicate_hash' => $this->boolean('error_if_duplicate_hash'),
'batch_submission' => $this->boolean('batch_submission'),
'batch_submission' => $this->boolean('batch_submission'),
'apply_rules' => $this->boolean('apply_rules', true),
'fire_webhooks' => $this->boolean('fire_webhooks', true),
'transactions' => $this->getTransactionData(),
@@ -113,9 +113,7 @@ class UpdateRequest extends FormRequest
];
$this->booleanFields = ['reconciled'];
$this->arrayFields = ['tags'];
$data = [
'batch_submission' => false,
];
$data = ['batch_submission' => false];
if ($this->has('transactions')) {
$data['transactions'] = $this->getTransactionData();
}
@@ -68,7 +68,7 @@ class CorrectsMetaDataFields extends Command
private function rename(string $original, string $update): void
{
$total = DB::table('journal_meta')->where('name', '=', $original)->update(['name' => $update]);
$total = DB::table('journal_meta')->where('name', '=', $original)->update(['name' => $update]);
$this->count += $total;
}
}
@@ -295,7 +295,11 @@ class UpgradesTransferCurrencies extends Command
{
if (null === $this->sourceTransaction->transaction_currency_id && $this->sourceCurrency instanceof TransactionCurrency) {
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
$message = sprintf('Transaction #%d has no currency setting, now set to %s.', $this->sourceTransaction->id, $this->sourceCurrency->code);
$message = sprintf(
'Transaction #%d has no currency setting, now set to %s.',
$this->sourceTransaction->id,
$this->sourceCurrency->code
);
$this->friendlyInfo($message);
++$this->count;
$this->sourceTransaction->save();
@@ -335,7 +339,11 @@ class UpgradesTransferCurrencies extends Command
{
if (null === $this->destinationTransaction->transaction_currency_id && $this->destinationCurrency instanceof TransactionCurrency) {
$this->destinationTransaction->transaction_currency_id = $this->destinationCurrency->id;
$message = sprintf('Transaction #%d has no currency setting, now set to %s.', $this->destinationTransaction->id, $this->destinationCurrency->code);
$message = sprintf(
'Transaction #%d has no currency setting, now set to %s.',
$this->destinationTransaction->id,
$this->destinationCurrency->code
);
$this->friendlyInfo($message);
++$this->count;
$this->destinationTransaction->save();
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* CreatedSingleTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -33,8 +36,10 @@ class CreatedSingleTransactionGroup extends Event
/**
* Create a new event instance.
*/
public function __construct(public TransactionGroup $transactionGroup, public TransactionGroupEventFlags $flags) {
public function __construct(
public TransactionGroup $transactionGroup,
public TransactionGroupEventFlags $flags
) {
Log::debug(__METHOD__);
}
}
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* CreatedTransactionGroupBatch.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -32,5 +35,8 @@ class CreatedTransactionGroupInBatch extends Event
/**
* Create a new event instance.
*/
public function __construct(public Collection $collection, public array $flags) {}
public function __construct(
public Collection $collection,
public array $flags
) {}
}
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* TransactionGroupEventFlags.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -23,9 +26,8 @@ namespace FireflyIII\Events\Model\TransactionGroup;
class TransactionGroupEventFlags
{
public bool $applyRules = true;
public bool $fireWebhooks = true;
public bool $batchSubmission = false;
public bool $applyRules = true;
public bool $fireWebhooks = true;
public bool $batchSubmission = false;
public bool $recalculateCredit = true;
}
@@ -32,5 +32,7 @@ class NewInvitationCreated extends Event
{
use SerializesModels;
public function __construct(public InvitedUser $invitee) {}
public function __construct(
public InvitedUser $invitee
) {}
}
+5 -1
View File
@@ -37,5 +37,9 @@ class StoredTransactionGroup extends Event
/**
* Create a new event instance.
*/
public function __construct(public TransactionGroup $transactionGroup, public bool $applyRules, public bool $fireWebhooks) {}
public function __construct(
public TransactionGroup $transactionGroup,
public bool $applyRules,
public bool $fireWebhooks
) {}
}
+80 -78
View File
@@ -55,6 +55,7 @@ use FireflyIII\Validation\AccountValidator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use JsonException;
use function Safe\json_encode;
/**
@@ -66,17 +67,17 @@ class TransactionJournalFactory
{
use JournalServiceTrait;
private AccountRepositoryInterface $accountRepository;
private AccountValidator $accountValidator;
private BillRepositoryInterface $billRepository;
private CurrencyRepositoryInterface $currencyRepository;
private bool $errorOnHash = false;
private array $fields;
private PiggyBankEventFactory $piggyEventFactory;
private PiggyBankRepositoryInterface $piggyRepository;
private AccountRepositoryInterface $accountRepository;
private AccountValidator $accountValidator;
private BillRepositoryInterface $billRepository;
private CurrencyRepositoryInterface $currencyRepository;
private bool $errorOnHash = false;
private array $fields;
private PiggyBankEventFactory $piggyEventFactory;
private PiggyBankRepositoryInterface $piggyRepository;
private TransactionTypeRepositoryInterface $typeRepository;
private User $user;
private UserGroup $userGroup;
private User $user;
private UserGroup $userGroup;
/**
* Constructor.
@@ -108,11 +109,11 @@ class TransactionJournalFactory
{
Log::debug('Now in TransactionJournalFactory::create()');
// convert to special object.
//$dataObject = new NullArrayObject($data);
// $dataObject = new NullArrayObject($data);
Log::debug('Start of TransactionJournalFactory::create()');
$collection = new Collection();
$transactions = $data['transactions'] ?? [];
$collection = new Collection();
$transactions = $data['transactions'] ?? [];
if (0 === count($transactions)) {
Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
@@ -125,7 +126,7 @@ class TransactionJournalFactory
foreach ($transactions as $index => $row) {
$row['batch_submission'] = $batchSubmission;
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
$journal = $this->createJournal($row);
$journal = $this->createJournal($row);
if ($journal instanceof TransactionJournal) {
$collection->push($journal);
}
@@ -169,18 +170,18 @@ class TransactionJournalFactory
$this->errorIfDuplicate($row['import_hash_v2']);
// Some basic fields
$type = $this->typeRepository->findTransactionType(null, $row['type']);
$carbon = $row['date'] ?? today(config('app.timezone'));
$order = $row['order'] ?? 0;
$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']);
$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 && $bill instanceof Bill ? $bill->id : null;
$description = (string)$row['description'];
$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 && $bill instanceof Bill ? $bill->id : null;
$description = (string) $row['description'];
// Manipulate basic fields
$carbon->setTimezone(config('app.timezone'));
@@ -202,7 +203,7 @@ class TransactionJournalFactory
}
/** create or get source and destination accounts */
$sourceInfo = [
$sourceInfo = [
'id' => $row['source_id'],
'name' => $row['source_name'],
'iban' => $row['source_iban'],
@@ -211,7 +212,7 @@ class TransactionJournalFactory
'currency_id' => $currency->id,
];
$destInfo = [
$destInfo = [
'id' => $row['destination_id'],
'name' => $row['destination_name'],
'iban' => $row['destination_iban'],
@@ -221,8 +222,8 @@ class TransactionJournalFactory
];
Log::debug('Source info:', $sourceInfo);
Log::debug('Destination info:', $destInfo);
$destinationAccount = null;
$sourceAccount = null;
$destinationAccount = null;
$sourceAccount = null;
if (TransactionTypeEnum::DEPOSIT->value === $type->type) {
Log::debug('Transaction type is deposit, start with destination first.');
$destinationAccount = $this->getAccount($type->type, 'destination', $destInfo);
@@ -247,38 +248,38 @@ class TransactionJournalFactory
[$sourceAccount, $destinationAccount] = $this->reconciliationSanityCheck($sourceAccount, $destinationAccount);
}
$currency = $this->getCurrencyByAccount($type->type, $currency, $sourceAccount, $destinationAccount);
$foreignCurrency = $this->compareCurrencies($currency, $foreignCurrency);
$foreignCurrency = $this->getForeignByAccount($type->type, $foreignCurrency, $destinationAccount);
$description = $this->getDescription($description);
$currency = $this->getCurrencyByAccount($type->type, $currency, $sourceAccount, $destinationAccount);
$foreignCurrency = $this->compareCurrencies($currency, $foreignCurrency);
$foreignCurrency = $this->getForeignByAccount($type->type, $foreignCurrency, $destinationAccount);
$description = $this->getDescription($description);
Log::debug(sprintf(
'Currency is #%d "%s", foreign currency is #%d "%s"',
$currency->id,
$currency->code,
$foreignCurrency?->id,
$foreignCurrency?->code
));
'Currency is #%d "%s", foreign currency is #%d "%s"',
$currency->id,
$currency->code,
$foreignCurrency?->id,
$foreignCurrency?->code
));
Log::debug(sprintf('Date: %s (%s)', $carbon->toW3cString(), $carbon->getTimezone()->getName()));
/** Create a basic journal. */
$journal = TransactionJournal::create([
'user_id' => $this->user->id,
'user_group_id' => $this->userGroup->id,
'transaction_type_id' => $type->id,
'bill_id' => $billId,
'transaction_currency_id' => $currency->id,
'description' => substr($description, 0, 1000),
'date' => $carbon,
'date_tz' => $carbon->format('e'),
'order' => $order,
'tag_count' => 0,
'completed' => !$row['batch_submission'],
]);
$journal = TransactionJournal::create([
'user_id' => $this->user->id,
'user_group_id' => $this->userGroup->id,
'transaction_type_id' => $type->id,
'bill_id' => $billId,
'transaction_currency_id' => $currency->id,
'description' => substr($description, 0, 1000),
'date' => $carbon,
'date_tz' => $carbon->format('e'),
'order' => $order,
'tag_count' => 0,
'completed' => !$row['batch_submission'],
]);
Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
/** Create two transactions. */
$transactionFactory = app(TransactionFactory::class);
$transactionFactory = app(TransactionFactory::class);
$transactionFactory->setJournal($journal);
$transactionFactory->setAccount($sourceAccount);
$transactionFactory->setCurrency($currency);
@@ -287,7 +288,7 @@ class TransactionJournalFactory
$transactionFactory->setReconciled($row['reconciled'] ?? false);
try {
$negative = $transactionFactory->createNegative((string)$row['amount'], (string)$row['foreign_amount']);
$negative = $transactionFactory->createNegative((string) $row['amount'], (string) $row['foreign_amount']);
} catch (FireflyException $e) {
Log::error(sprintf('Exception creating negative transaction: %s', $e->getMessage()));
$this->forceDeleteOnError(new Collection()->push($journal));
@@ -296,7 +297,7 @@ class TransactionJournalFactory
}
/** @var TransactionFactory $transactionFactory */
$transactionFactory = app(TransactionFactory::class);
$transactionFactory = app(TransactionFactory::class);
$transactionFactory->setJournal($journal);
$transactionFactory->setAccount($destinationAccount);
$transactionFactory->setAccountInformation($destInfo);
@@ -308,8 +309,8 @@ class TransactionJournalFactory
// Firefly III will save the foreign currency information in such a way that both
// asset accounts can look at the "amount" and "transaction_currency_id" column and
// see the currency they expect to see.
$amount = (string)$row['amount'];
$foreignAmount = (string)$row['foreign_amount'];
$amount = (string) $row['amount'];
$foreignAmount = (string) $row['foreign_amount'];
if (
$foreignCurrency instanceof TransactionCurrency
&& $foreignCurrency->id !== $currency->id
@@ -317,8 +318,8 @@ class TransactionJournalFactory
) {
$transactionFactory->setCurrency($foreignCurrency);
$transactionFactory->setForeignCurrency($currency);
$amount = (string)$row['foreign_amount'];
$foreignAmount = (string)$row['amount'];
$amount = (string) $row['foreign_amount'];
$foreignAmount = (string) $row['amount'];
Log::debug('Swap primary/foreign amounts in transfer for new save method.');
}
@@ -377,17 +378,18 @@ class TransactionJournalFactory
/** @var null|TransactionJournalMeta $result */
$result = TransactionJournalMeta::withTrashed()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->whereNotNull('transaction_journals.id')
->where('transaction_journals.user_id', $this->user->id)
->where('data', json_encode($hash, JSON_THROW_ON_ERROR))
->with(['transactionJournal', 'transactionJournal.transactionGroup'])
->first(['journal_meta.*']);
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->whereNotNull('transaction_journals.id')
->where('transaction_journals.user_id', $this->user->id)
->where('data', json_encode($hash, JSON_THROW_ON_ERROR))
->with(['transactionJournal', 'transactionJournal.transactionGroup'])
->first(['journal_meta.*'])
;
if (null !== $result) {
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;
$groupId = (int) $group?->id;
throw new DuplicateTransactionException(sprintf('Duplicate of transaction #%d.', $groupId));
}
@@ -399,18 +401,18 @@ class TransactionJournalFactory
private function validateAccounts(array $data): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$transactionType = $data['type'] ?? 'invalid';
$transactionType = $data['type'] ?? 'invalid';
$this->accountValidator->setUser($this->user);
$this->accountValidator->setTransactionType($transactionType);
// validate source account.
$array = [
'id' => null !== $data['source_id'] ? (int)$data['source_id'] : null,
'name' => null !== $data['source_name'] ? (string)$data['source_name'] : null,
'iban' => null !== $data['source_iban'] ? (string)$data['source_iban'] : null,
'number' => null !== $data['source_number'] ? (string)$data['source_number'] : null,
$array = [
'id' => null !== $data['source_id'] ? (int) $data['source_id'] : null,
'name' => null !== $data['source_name'] ? (string) $data['source_name'] : null,
'iban' => null !== $data['source_iban'] ? (string) $data['source_iban'] : null,
'number' => null !== $data['source_number'] ? (string) $data['source_number'] : null,
];
$validSource = $this->accountValidator->validateSource($array);
$validSource = $this->accountValidator->validateSource($array);
// do something with result:
if (false === $validSource) {
@@ -419,11 +421,11 @@ class TransactionJournalFactory
Log::debug('Source seems valid.');
// validate destination account
$array = [
'id' => null !== $data['destination_id'] ? (int)$data['destination_id'] : null,
'name' => null !== $data['destination_name'] ? (string)$data['destination_name'] : null,
'iban' => null !== $data['destination_iban'] ? (string)$data['destination_iban'] : null,
'number' => null !== $data['destination_number'] ? (string)$data['destination_number'] : null,
$array = [
'id' => null !== $data['destination_id'] ? (int) $data['destination_id'] : null,
'name' => null !== $data['destination_name'] ? (string) $data['destination_name'] : null,
'iban' => null !== $data['destination_iban'] ? (string) $data['destination_iban'] : null,
'number' => null !== $data['destination_number'] ? (string) $data['destination_number'] : null,
];
$validDestination = $this->accountValidator->validateDestination($array);
@@ -508,7 +510,7 @@ class TransactionJournalFactory
// return user's default:
return Amount::getPrimaryCurrencyByUserGroup($this->user->userGroup);
}
$result = $preference ?? $currency;
$result = $preference ?? $currency;
Log::debug(sprintf('Currency is now #%d (%s) because of account #%d (%s)', $result->id, $result->code, $account->id, $account->name));
return $result;
@@ -578,7 +580,7 @@ class TransactionJournalFactory
{
Log::debug('Will now store piggy event.');
$piggyBank = $this->piggyRepository->findPiggyBank((int)$data['piggy_bank_id'], $data['piggy_bank_name']);
$piggyBank = $this->piggyRepository->findPiggyBank((int) $data['piggy_bank_id'], $data['piggy_bank_name']);
if ($piggyBank instanceof PiggyBank) {
$this->piggyEventFactory->create($journal, $piggyBank);
@@ -598,7 +600,7 @@ class TransactionJournalFactory
protected function storeMeta(TransactionJournal $journal, array $data, string $field): void
{
$set = ['journal' => $journal, 'name' => $field, 'data' => (string)($data[$field] ?? '')];
$set = ['journal' => $journal, 'name' => $field, 'data' => (string) ($data[$field] ?? '')];
if (array_key_exists($field, $data) && $data[$field] instanceof Carbon) {
$data[$field]->setTimezone(config('app.timezone'));
Log::debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName()));
@@ -55,8 +55,8 @@ class StoredGroupEventHandler
public function triggerRulesManually(TriggeredStoredTransactionGroup $event): void
{
// $newEvent = new StoredTransactionGroup($event->transactionGroup, true, false);
// $this->processRules($newEvent, $event->ruleGroup);
// $newEvent = new StoredTransactionGroup($event->transactionGroup, true, false);
// $this->processRules($newEvent, $event->ruleGroup);
}
/**
@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Events\Security\System\NewInvitationCreated;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
@@ -167,7 +167,7 @@ class LoginController extends Controller
*/
protected function sendFailedLoginResponse(Request $request): void
{
$exception = ValidationException::withMessages([$this->username() => [trans('auth.failed')]]);
$exception = ValidationException::withMessages([$this->username() => [trans('auth.failed')]]);
$exception->redirectTo = route('login');
throw $exception;
@@ -192,7 +192,10 @@ class IndexController extends Controller
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
if ($count > 0) {
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2');
$avg = bcdiv(
bcadd((string) $bill['amount_min'], (string) $bill['amount_max']),
'2'
);
$avg = bcmul($avg, (string) $count);
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
Log::debug(
@@ -197,7 +197,13 @@ class BudgetLimitController extends Controller
if ($request->expectsJson()) {
$array = $limit->toArray();
// add some extra metadata:
$spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
$spentArr = $this->opsRepository->sumExpenses(
$limit->start_date,
$limit->end_date,
null,
new Collection()->push($budget),
$currency
);
$array['spent'] = $spentArr[$currency->id]['sum'] ?? '0';
$array['left_formatted'] = Amount::formatAnything($limit->transactionCurrency, bcadd($array['spent'], (string) $array['amount']));
$array['amount_formatted'] = Amount::formatAnything($limit->transactionCurrency, $limit['amount']);
@@ -73,7 +73,7 @@ class BillController extends Controller
*/
foreach ($paid as $info) {
$amount = $info['sum'];
$label = (string) trans('firefly.paid_in_currency', ['currency' => $info['name']]);
$label = (string) trans('firefly.paid_in_currency', ['currency' => $info['name']]);
$chartData[$label] = ['amount' => $amount, 'currency_symbol' => $info['symbol'], 'currency_code' => $info['code']];
}
@@ -82,7 +82,7 @@ class BillController extends Controller
*/
foreach ($unpaid as $info) {
$amount = $info['sum'];
$label = (string) trans('firefly.unpaid_in_currency', ['currency' => $info['name']]);
$label = (string) trans('firefly.unpaid_in_currency', ['currency' => $info['name']]);
$chartData[$label] = ['amount' => $amount, 'currency_symbol' => $info['symbol'], 'currency_code' => $info['code']];
}
@@ -539,7 +539,13 @@ class BudgetController extends Controller
}
// get spent amount in this period for this currency.
$sum = $this->opsRepository->sumExpenses($currentStart, $currentEnd, $accounts, new Collection()->push($budget), $currency);
$sum = $this->opsRepository->sumExpenses(
$currentStart,
$currentEnd,
$accounts,
new Collection()->push($budget),
$currency
);
$amount = Steam::positive($sum[$currency->id]['sum'] ?? '0');
$chartData[0]['entries'][$title] = Steam::bcround($amount, $currency->decimal_places);
@@ -76,7 +76,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$budget = $journal['budget_name'] ?? (string) trans('firefly.no_budget');
$title = sprintf('%s (%s)', $budget, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -122,7 +126,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$category = $journal['category_name'] ?? (string) trans('firefly.no_category');
$title = sprintf('%s (%s)', $category, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -168,7 +176,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$name = $journal['destination_account_name'];
$title = sprintf('%s (%s)', $name, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -214,7 +226,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$name = $journal['source_account_name'];
$title = sprintf('%s (%s)', $name, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -95,7 +95,11 @@ class CategoryController extends Controller
'categories' => [],
];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']] ??= ['spent' => '0', 'earned' => '0', 'sum' => '0'];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']] ??= [
'spent' => '0',
'earned' => '0',
'sum' => '0',
];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['spent'] = bcadd(
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['spent'],
(string) $journal['amount']
@@ -122,7 +126,11 @@ class CategoryController extends Controller
'currency_decimal_places' => $currency['currency_decimal_places'],
'categories' => [],
];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']] ??= ['spent' => '0', 'earned' => '0', 'sum' => '0'];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']] ??= [
'spent' => '0',
'earned' => '0',
'sum' => '0',
];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['earned'] = bcadd(
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['earned'],
(string) $journal['amount']
@@ -77,7 +77,7 @@ class CreateController extends Controller
$newGroup = $service->cloneGroup($group);
// event!
$flags = new TransactionGroupEventFlags();
$flags = new TransactionGroupEventFlags();
event(new CreatedSingleTransactionGroup($group, $flags));
// event(new StoredTransactionGroup($newGroup, true, true));
@@ -110,29 +110,29 @@ class CreateController extends Controller
{
Preferences::mark();
$sourceId = (int) request()->get('source');
$destinationId = (int) request()->get('destination');
$sourceId = (int) request()->get('source');
$destinationId = (int) request()->get('destination');
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$cash = $accountRepository->getCashAccount();
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
$subTitleIcon = 'fa-plus';
$accountRepository = app(AccountRepositoryInterface::class);
$cash = $accountRepository->getCashAccount();
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
$subTitleIcon = 'fa-plus';
/** @var null|array $optionalFields */
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
if (!is_array($optionalFields)) {
$optionalFields = [];
}
// not really a fan of this, but meh.
$optionalDateFields = [
$optionalDateFields = [
'interest_date' => $optionalFields['interest_date'] ?? false,
'book_date' => $optionalFields['book_date'] ?? false,
'process_date' => $optionalFields['process_date'] ?? false,
@@ -142,13 +142,13 @@ class CreateController extends Controller
];
$optionalFields['external_url'] ??= false;
$optionalFields['location'] ??= false;
$optionalFields['location']
= $optionalFields['location'] && true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
$optionalFields['location'] = $optionalFields['location']
&& true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
// map info:
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
session()->put('preFilled', $preFilled);
@@ -83,29 +83,29 @@ class EditController extends Controller
}
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$expectedSourceTypes = config('firefly.expected_source_types');
$allowedSourceDests = config('firefly.source_dests');
$title = $transactionGroup->transactionJournals()->count() > 1
$repository = app(AccountRepositoryInterface::class);
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$expectedSourceTypes = config('firefly.expected_source_types');
$allowedSourceDests = config('firefly.source_dests');
$title = $transactionGroup->transactionJournals()->count() > 1
? $transactionGroup->title
: $transactionGroup->transactionJournals()->first()->description;
$subTitle = (string) trans('firefly.edit_transaction_title', ['description' => $title]);
$subTitleIcon = 'fa-plus';
$cash = $repository->getCashAccount();
$previousUrl = $this->rememberPreviousUrl('transactions.edit.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
$subTitle = (string) trans('firefly.edit_transaction_title', ['description' => $title]);
$subTitleIcon = 'fa-plus';
$cash = $repository->getCashAccount();
$previousUrl = $this->rememberPreviousUrl('transactions.edit.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
// settings necessary for v2
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
if (!is_array($optionalFields)) {
$optionalFields = [];
}
// not really a fan of this, but meh.
$optionalDateFields = [
$optionalDateFields = [
'interest_date' => $optionalFields['interest_date'] ?? false,
'book_date' => $optionalFields['book_date'] ?? false,
'process_date' => $optionalFields['process_date'] ?? false,
@@ -115,13 +115,13 @@ class EditController extends Controller
];
$optionalFields['external_url'] ??= false;
$optionalFields['location'] ??= false;
$optionalFields['location']
= $optionalFields['location'] && true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
$optionalFields['location'] = $optionalFields['location']
&& true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
// map info voor v2:
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
return view('transactions.edit', [
'cash' => $cash,
@@ -78,7 +78,7 @@ class EditController extends Controller
}
$subTitleIcon = 'fa-pencil';
$subTitle = (string) trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
$subTitle = (string) trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
$currency->symbol = htmlentities($currency->symbol);
// is currently enabled (for this user?)
+17 -19
View File
@@ -44,24 +44,22 @@ use Illuminate\View\Middleware\ShareErrorsFromSession;
*/
class Kernel extends HttpKernel
{
protected $middleware
= [
// SecureHeaders::class,
CheckForMaintenanceMode::class,
ValidatePostSize::class,
TrimStrings::class,
ConvertEmptyStringsToNull::class,
TrustProxies::class,
InstallationId::class,
];
protected $middlewareAliases
= [
'auth' => Authenticate::class,
'auth.basic' => AuthenticateWithBasicAuth::class,
'bindings' => Binder::class,
'can' => Authorize::class,
'guest' => RedirectIfAuthenticated::class,
'throttle' => ThrottleRequests::class,
];
protected $middleware = [
// SecureHeaders::class,
CheckForMaintenanceMode::class,
ValidatePostSize::class,
TrimStrings::class,
ConvertEmptyStringsToNull::class,
TrustProxies::class,
InstallationId::class,
];
protected $middlewareAliases = [
'auth' => Authenticate::class,
'auth.basic' => AuthenticateWithBasicAuth::class,
'bindings' => Binder::class,
'can' => Authorize::class,
'guest' => RedirectIfAuthenticated::class,
'throttle' => ThrottleRequests::class,
];
protected $middlewarePriority = [StartFireflySession::class, ShareErrorsFromSession::class, Authenticate::class, Binder::class, Authorize::class];
}
+2 -2
View File
@@ -384,8 +384,8 @@ class CreateRecurringTransactions implements ShouldQueue
Log::info(sprintf('Created new transaction group #%d', $group->id));
// trigger event:
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $recurrence->apply_rules;
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $recurrence->apply_rules;
event(new CreatedSingleTransactionGroup($group, $flags));
// event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
$this->groups->push($group);
@@ -1,4 +1,7 @@
<?php
declare(strict_types=1);
/*
* ProcessesNewTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
@@ -47,6 +50,7 @@ class ProcessesNewTransactionGroup implements ShouldQueue
Log::debug(sprintf('In ProcessesNewTransactionGroup::handle(#%d)', $event->transactionGroup->id));
if (true === $event->flags->batchSubmission) {
Log::debug(sprintf('Will do nothing for group #%d because it is part of a batch.', $event->transactionGroup->id));
return;
}
Log::debug(sprintf('Will join group #%d with all other open transaction groups and process them.', $event->transactionGroup->id));
@@ -55,6 +59,7 @@ class ProcessesNewTransactionGroup implements ShouldQueue
$set = $collection->merge($repository->getUncompletedJournals());
if (0 === $set->count()) {
Log::debug('Set is empty, never mind.');
return;
}
if (!$event->flags->applyRules) {
@@ -86,7 +91,6 @@ class ProcessesNewTransactionGroup implements ShouldQueue
}
$repository->markAsCompleted($set);
}
private function recalculateRunningBalance(Collection $set): void
@@ -94,27 +98,27 @@ class ProcessesNewTransactionGroup implements ShouldQueue
Log::debug('Now in recalculateRunningBalance');
// find the earliest date in the set, based on date and _internal_previous_date
$earliest = $set->pluck('date')->sort()->first();
$entries = TransactionJournalMeta
::whereIn('transaction_journal_id', $set->pluck('id')->toArray())
->where('name', '_internal_previous_date')
->get(['journal_meta.*']);
$entries = TransactionJournalMeta::whereIn('transaction_journal_id', $set->pluck('id')->toArray())->where('name', '_internal_previous_date')->get([
'journal_meta.*',
]);
$array = $entries->toArray();
if (count($array) > 0) {
usort($array, function (array $a, array $b) {
return Carbon::parse($a['data'])->gt(Carbon::parse($b['data']));
});
/** @var Carbon $date */
$date = Carbon::parse($array[0]['data']);
$earliest = $date->lt($earliest) ? $date : $earliest;
}
// get accounts
$accounts = Account
::leftJoin('transactions', 'transactions.account_id', 'accounts.id')
$accounts = Account::leftJoin('transactions', 'transactions.account_id', 'accounts.id')
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
->whereIn('transaction_journals.id', $set->pluck('id')->toArray())
->get(['accounts.*']);
->get(['accounts.*'])
;
AccountBalanceCalculator::optimizedCalculation($accounts, $earliest);
}
@@ -122,6 +126,7 @@ class ProcessesNewTransactionGroup implements ShouldQueue
private function removePeriodStatistics(Collection $set): void
{
Log::debug('Always remove period statistics');
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
$repository->deleteStatisticsForCollection($set);
@@ -133,7 +138,7 @@ class ProcessesNewTransactionGroup implements ShouldQueue
$groups = TransactionGroup::whereIn('id', array_unique($set->pluck('transaction_group_id')->toArray()))->get();
Log::debug(__METHOD__);
$user = $set->first()->user;
$user = $set->first()->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
@@ -164,9 +169,9 @@ class ProcessesNewTransactionGroup implements ShouldQueue
private function processRules(Collection $set): void
{
Log::debug(sprintf('Will now processRules for %d journal(s)', $set->count()));
$array = $set->pluck('id')->toArray();
$journalIds = implode(',', $array);
$user = $set->first()->user;
$array = $set->pluck('id')->toArray();
$journalIds = implode(',', $array);
$user = $set->first()->user;
Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
// collect rules:
@@ -176,14 +181,13 @@ class ProcessesNewTransactionGroup implements ShouldQueue
// add the groups to the rule engine.
// it should run the rules in the group and cancel the group if necessary.
Log::debug('Fire processRules with ALL store-journal rule groups.');
$groups = $ruleGroupRepository->getRuleGroupsWithRules('store-journal');
$groups = $ruleGroupRepository->getRuleGroupsWithRules('store-journal');
// create and fire rule engine.
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRuleGroups($groups);
$newRuleEngine->fire();
}
}
@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Listeners\Security\System;
use Exception;
use FireflyIII\Events\Security\System\NewInvitationCreated;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Mail\InvitationMail;
@@ -46,7 +45,6 @@ class NotifiesAboutNewInvitation implements ShouldQueue
$this->sendRegistrationInvite($event->invitee);
}
private function sendRegistrationInvite(InvitedUser $invitee): void
{
$email = $invitee->email;
@@ -88,5 +86,4 @@ class NotifiesAboutNewInvitation implements ShouldQueue
Log::error($e->getTraceAsString());
}
}
}
@@ -37,7 +37,7 @@ class RespondsToNewLogin implements ShouldQueue
public function handle(Login $event): void
{
$user = $event->user;
if (!($user instanceof User)) {
if (!$user instanceof User) {
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
@@ -85,5 +85,4 @@ class RespondsToNewLogin implements ShouldQueue
Preferences::mark();
}
}
}
+13 -13
View File
@@ -50,9 +50,9 @@ class Account extends Model
use ReturnsIntegerUserIdTrait;
use SoftDeletes;
protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban', 'native_virtual_balance'];
protected $fillable = ['user_id', 'user_group_id', 'account_type_id', 'name', 'active', 'virtual_balance', 'iban', 'native_virtual_balance'];
protected $hidden = ['encrypted'];
protected $hidden = ['encrypted'];
private bool $joinedAccountTypes = false;
/**
@@ -60,19 +60,19 @@ class Account extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(self | string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int)$value->id;
$value = (int) $value->id;
}
if (auth()->check()) {
$accountId = (int)$value;
$accountId = (int) $value;
/** @var User $user */
$user = auth()->user();
$user = auth()->user();
/** @var null|Account $account */
$account = $user->accounts()->with(['accountType'])->find($accountId);
$account = $user->accounts()->with(['accountType'])->find($accountId);
if (null !== $account) {
return $account;
}
@@ -129,7 +129,7 @@ class Account extends Model
public function setVirtualBalanceAttribute(mixed $value): void
{
$value = (string)$value;
$value = (string) $value;
if ('' === $value) {
$value = null;
}
@@ -148,7 +148,7 @@ class Account extends Model
protected function accountId(): Attribute
{
return Attribute::make(get: static fn($value): int => (int)$value);
return Attribute::make(get: static fn ($value): int => (int) $value);
}
/**
@@ -174,7 +174,7 @@ class Account extends Model
*/
protected function accountTypeId(): Attribute
{
return Attribute::make(get: static fn($value): int => (int)$value);
return Attribute::make(get: static fn ($value): int => (int) $value);
}
#[Scope]
@@ -216,12 +216,12 @@ class Account extends Model
protected function iban(): Attribute
{
return Attribute::make(get: static fn($value): ?string => null === $value ? null : trim(str_replace(' ', '', (string)$value)));
return Attribute::make(get: static fn ($value): ?string => null === $value ? null : trim(str_replace(' ', '', (string) $value)));
}
protected function order(): Attribute
{
return Attribute::make(get: static fn($value): int => (int)$value);
return Attribute::make(get: static fn ($value): int => (int) $value);
}
/**
@@ -229,7 +229,7 @@ class Account extends Model
*/
protected function virtualBalance(): Attribute
{
return Attribute::make(get: static fn($value): string => (string)$value);
return Attribute::make(get: static fn ($value): string => (string) $value);
}
public function primaryPeriodStatistics(): MorphMany
+2 -2
View File
@@ -64,8 +64,8 @@ class Attachment extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$attachmentId = (int) $value;
+2 -2
View File
@@ -61,8 +61,8 @@ class AvailableBudget extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$availableBudgetId = (int) $value;
+2 -2
View File
@@ -76,8 +76,8 @@ class Bill extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$billId = (int) $value;
+2 -2
View File
@@ -55,8 +55,8 @@ class Budget extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$budgetId = (int) $value;
+2 -2
View File
@@ -55,8 +55,8 @@ class BudgetLimit extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$budgetLimitId = (int) $value;
+2 -2
View File
@@ -54,8 +54,8 @@ class Category extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$categoryId = (int) $value;
+2 -2
View File
@@ -44,8 +44,8 @@ class InvitedUser extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$attemptId = (int) $value;
+2 -2
View File
@@ -43,8 +43,8 @@ class LinkType extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$linkTypeId = (int) $value;
+2 -2
View File
@@ -47,8 +47,8 @@ class ObjectGroup extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$objectGroupId = (int) $value;
+2 -2
View File
@@ -62,8 +62,8 @@ class PiggyBank extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$piggyBankId = (int) $value;
+2 -2
View File
@@ -44,8 +44,8 @@ class Preference extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
/** @var User $user */
+2 -2
View File
@@ -76,8 +76,8 @@ class Recurrence extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$recurrenceId = (int) $value;
+2 -2
View File
@@ -54,8 +54,8 @@ class Rule extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$ruleId = (int) $value;
+2 -2
View File
@@ -56,8 +56,8 @@ class RuleGroup extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$ruleGroupId = (int) $value;
+2 -2
View File
@@ -54,8 +54,8 @@ class Tag extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$tagId = (int) $value;
+2 -2
View File
@@ -50,8 +50,8 @@ class TransactionCurrency extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$currencyId = (int) $value;
+2 -2
View File
@@ -57,8 +57,8 @@ class TransactionGroup extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
Log::debug(sprintf('Now in %s("%s")', __METHOD__, $value));
if (auth()->check()) {
+2 -2
View File
@@ -81,8 +81,8 @@ class TransactionJournal extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$journalId = (int) $value;
+2 -2
View File
@@ -43,8 +43,8 @@ class TransactionJournalLink extends Model
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
+2 -2
View File
@@ -77,8 +77,8 @@ class TransactionType extends Model
if (!auth()->check()) {
throw new NotFoundHttpException();
}
if($value instanceof self) {
$value = (string)$value->type;
if ($value instanceof self) {
$value = (string) $value->type;
}
$transactionType = self::where('type', ucfirst($value))->first();
if (null !== $transactionType) {
+2 -2
View File
@@ -47,8 +47,8 @@ class UserGroup extends Model
public static function routeBinder(self|string $value): self
{
if (auth()->check()) {
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
$userGroupId = (int) $value;
+2 -2
View File
@@ -133,8 +133,8 @@ class Webhook extends Model
public static function routeBinder(self|string $value): self
{
if (auth()->check()) {
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
$webhookId = (int) $value;
+2 -2
View File
@@ -45,8 +45,8 @@ class WebhookAttempt extends Model
public static function routeBinder(self|string $value): self
{
if (auth()->check()) {
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
$attemptId = (int) $value;
+2 -2
View File
@@ -47,8 +47,8 @@ class WebhookMessage extends Model
public static function routeBinder(self|string $value): self
{
if (auth()->check()) {
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
$messageId = (int) $value;
@@ -58,7 +58,7 @@ class SubscriptionsOverdueReminder extends Notification
$info = [];
$count = 0;
foreach ($this->overdue as $item) {
$current = ['bill' => $item['bill']];
$current = ['bill' => $item['bill']];
$current['pay_dates'] = array_map(static fn (string $date): string => new Carbon($date)->isoFormat((string) trans(
'config.month_and_day_moment_js'
)), $item['dates']['pay_dates']);
+15 -18
View File
@@ -40,24 +40,21 @@ use Override;
*/
class EventServiceProvider extends ServiceProvider
{
protected $listen
= [
// is a Transaction Journal related event.
// StoredTransactionGroup::class => ['FireflyIII\Handlers\Events\StoredGroupEventHandler@runAllHandlers'],
// TriggeredStoredTransactionGroup::class => ['FireflyIII\Handlers\Events\StoredGroupEventHandler@triggerRulesManually'],
// is a Transaction Journal related event.
// UpdatedTransactionGroup::class => ['FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers'],
// DestroyedTransactionGroup::class => ['FireflyIII\Handlers\Events\DestroyedGroupEventHandler@runAllHandlers'],
// API related events:
// AccessTokenCreated::class => ['FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated'],
// account related events:
// StoredAccount::class => ['FireflyIII\Handlers\Events\StoredAccountEventHandler@recalculateCredit'],
// UpdatedAccount::class => ['FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit'],
// preferences
// UserGroupChangedPrimaryCurrency::class => ['FireflyIII\Handlers\Events\PreferencesEventHandler@resetPrimaryCurrencyAmounts'],
];
protected $listen = [
// is a Transaction Journal related event.
// StoredTransactionGroup::class => ['FireflyIII\Handlers\Events\StoredGroupEventHandler@runAllHandlers'],
// TriggeredStoredTransactionGroup::class => ['FireflyIII\Handlers\Events\StoredGroupEventHandler@triggerRulesManually'],
// is a Transaction Journal related event.
// UpdatedTransactionGroup::class => ['FireflyIII\Handlers\Events\UpdatedGroupEventHandler@runAllHandlers'],
// DestroyedTransactionGroup::class => ['FireflyIII\Handlers\Events\DestroyedGroupEventHandler@runAllHandlers'],
// API related events:
// AccessTokenCreated::class => ['FireflyIII\Handlers\Events\APIEventHandler@accessTokenCreated'],
// account related events:
// StoredAccount::class => ['FireflyIII\Handlers\Events\StoredAccountEventHandler@recalculateCredit'],
// UpdatedAccount::class => ['FireflyIII\Handlers\Events\UpdatedAccountEventHandler@recalculateCredit'],
// preferences
// UserGroupChangedPrimaryCurrency::class => ['FireflyIII\Handlers\Events\PreferencesEventHandler@resetPrimaryCurrencyAmounts'],
];
/**
* Register any events for your application.
@@ -39,6 +39,7 @@ use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Override;
/**
* Class JournalRepository.
@@ -251,13 +252,17 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
return $journal;
}
#[\Override]
#[Override]
public function getUncompletedJournals(): Collection
{
return $this->userGroup->transactionJournals()->where('completed', false)->get(['transaction_journals.*']);
return $this->userGroup
->transactionJournals()
->where('completed', false)
->get(['transaction_journals.*'])
;
}
#[\Override]
#[Override]
public function markAsCompleted(Collection $set): void
{
TransactionJournal::whereIn('id', $set->pluck('id')->toArray())->update(['completed' => true]);
@@ -56,7 +56,7 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic
{
$stat = new PeriodStatistic();
$stat = new PeriodStatistic();
$stat->primaryStatable()->associate($model);
$stat->transaction_currency_id = $currencyId;
$stat->user_group_id = $this->getUserGroup()->id;
@@ -70,16 +70,16 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
$stat->save();
Log::debug(sprintf(
'Saved #%d [currency #%d, Model %s #%d, %s to %s, %d, %s] as new statistic.',
$stat->id,
$model::class,
$model->id,
$stat->transaction_currency_id,
$stat->start->toW3cString(),
$stat->end->toW3cString(),
$count,
$amount
));
'Saved #%d [currency #%d, Model %s #%d, %s to %s, %d, %s] as new statistic.',
$stat->id,
$model::class,
$model->id,
$stat->transaction_currency_id,
$stat->start->toW3cString(),
$stat->end->toW3cString(),
$count,
$amount
));
return $stat;
}
@@ -102,20 +102,20 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
->where('type', 'LIKE', sprintf('%s%%', $prefix))
->where('start', '>=', $start)
->where('end', '<=', $end)
->get();
->get()
;
}
#[Override]
public function savePrefixedStatistic(
string $prefix,
int $currencyId,
int $currencyId,
Carbon $start,
Carbon $end,
string $type,
int $count,
int $count,
string $amount
): PeriodStatistic
{
): PeriodStatistic {
$stat = new PeriodStatistic();
$stat->transaction_currency_id = $currencyId;
$stat->user_group_id = $this->getUserGroup()->id;
@@ -129,15 +129,15 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
$stat->save();
Log::debug(sprintf(
'Saved #%d [currency #%d, type "%s", %s to %s, %d, %s] as new statistic.',
$stat->id,
$stat->transaction_currency_id,
$stat->type,
$stat->start->toW3cString(),
$stat->end->toW3cString(),
$count,
$amount
));
'Saved #%d [currency #%d, type "%s", %s to %s, %d, %s] as new statistic.',
$stat->id,
$stat->transaction_currency_id,
$stat->type,
$stat->start->toW3cString(),
$stat->end->toW3cString(),
$count,
$amount
));
return $stat;
}
@@ -145,15 +145,18 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
#[Override]
public function deleteStatisticsForPrefix(string $prefix, Collection $dates): void
{
$count = $this->userGroup->periodStatistics()
->where(function (Builder $q) use ($dates) {
foreach ($dates as $date) {
$q->where(function (Builder $q1) use ($date) {
$q1->where('start', '<=', $date)->where('end', '>=', $date);
});
}
})
->where('type', 'LIKE', sprintf('%s%%', $prefix))->delete();
$count = $this->userGroup
->periodStatistics()
->where(function (Builder $q) use ($dates): void {
foreach ($dates as $date) {
$q->where(function (Builder $q1) use ($date): void {
$q1->where('start', '<=', $date)->where('end', '>=', $date);
});
}
})
->where('type', 'LIKE', sprintf('%s%%', $prefix))
->delete()
;
Log::debug(sprintf('Deleted %d entries for prefix "%s"', $count, $prefix));
}
@@ -161,44 +164,66 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface, U
{
if (0 === count($objects)) {
Log::debug(sprintf('Nothing to delete in deleteStatisticsForType("%s")', $class));
return;
}
$count = PeriodStatistic
::where('primary_statable_type', $class)
$count = PeriodStatistic::where('primary_statable_type', $class)
->whereIn('primary_statable_id', $objects->pluck('id')->toArray())
->where(function (Builder $q) use ($dates) {
->where(function (Builder $q) use ($dates): void {
foreach ($dates as $date) {
$q->where(function (Builder $q1) use ($date) {
$q->where(function (Builder $q1) use ($date): void {
$q1->where('start', '<=', $date)->where('end', '>=', $date);
});
}
})
->delete();
->delete()
;
Log::debug(sprintf('Delete %d statistics for %dx %s', $count, $objects->count(), $class));
}
#[\Override]
public function deleteStatisticsForCollection(Collection $set)
#[Override]
public function deleteStatisticsForCollection(Collection $set): void
{
Log::debug(sprintf('Delete statistics for %d transaction journals.', count($set)));
// collect all transactions:
$transactions = Transaction::whereIn('transaction_journal_id', $set->pluck('id')->toArray())->get(['transactions.*']);
// collect all accounts and delete stats:
$accounts = Account::whereIn('id', $transactions->pluck('account_id')->toArray())->get(['accounts.*']);
$dates = $set->pluck('date');
$accounts = Account::whereIn('id', $transactions->pluck('account_id')->toArray())->get(['accounts.*']);
$dates = $set->pluck('date');
$this->deleteStatisticsForType(Account::class, $accounts, $dates);
// collect all categories, and remove stats.
$categories = Category::whereIn('id', DB::table('category_transaction_journal')->whereIn('transaction_journal_id', $set->pluck('id')->toArray())->get(['category_transaction_journal.category_id'])->pluck('category_id')->toArray())->get(['categories.*']);
$categories = Category::whereIn(
'id',
DB::table('category_transaction_journal')
->whereIn('transaction_journal_id', $set->pluck('id')->toArray())
->get(['category_transaction_journal.category_id'])
->pluck('category_id')
->toArray()
)->get(['categories.*']);
$this->deleteStatisticsForType(Category::class, $categories, $dates);
// budgets, same thing
$budgets = Budget::whereIn('id', DB::table('budget_transaction_journal')->whereIn('transaction_journal_id', $set->pluck('id')->toArray())->get(['budget_transaction_journal.budget_id'])->pluck('budget_id')->toArray())->get(['budgets.*']);
$budgets = Budget::whereIn(
'id',
DB::table('budget_transaction_journal')
->whereIn('transaction_journal_id', $set->pluck('id')->toArray())
->get(['budget_transaction_journal.budget_id'])
->pluck('budget_id')
->toArray()
)->get(['budgets.*']);
$this->deleteStatisticsForType(Budget::class, $budgets, $dates);
// tags
$tags = Tag::whereIn('id', DB::table('tag_transaction_journal')->whereIn('transaction_journal_id', $set->pluck('id')->toArray())->get(['tag_transaction_journal.tag_id'])->pluck('tag_id')->toArray())->get(['tags.*']);
$tags = Tag::whereIn(
'id',
DB::table('tag_transaction_journal')
->whereIn('transaction_journal_id', $set->pluck('id')->toArray())
->get(['tag_transaction_journal.tag_id'])
->pluck('tag_id')
->toArray()
)->get(['tags.*']);
$this->deleteStatisticsForType(Tag::class, $tags, $dates);
// remove for no tag, no cat, etc.
@@ -26,7 +26,6 @@ namespace FireflyIII\Repositories\PeriodStatistic;
use Carbon\Carbon;
use FireflyIII\Models\PeriodStatistic;
use FireflyIII\Models\UserGroup;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
@@ -172,7 +172,11 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
$listedJournals[] = $journalId;
$array[$currencyId]['tags'][$tagId] ??= ['id' => $tagId, 'name' => $tagName, 'transaction_journals' => []];
$array[$currencyId]['tags'][$tagId] ??= [
'id' => $tagId,
'name' => $tagName,
'transaction_journals' => [],
];
$journalId = (int) $journal['transaction_journal_id'];
$array[$currencyId]['tags'][$tagId]['transaction_journals'][$journalId] = [
'amount' => Steam::positive($journal['amount']),
@@ -42,11 +42,11 @@ use Illuminate\Support\Facades\Log;
*/
class CreditRecalculateService
{
private ?Account $account = null;
private ?TransactionGroup $group = null;
private Collection $journals;
private ?Account $account = null;
private ?TransactionGroup $group = null;
private Collection $journals;
private AccountRepositoryInterface $repository;
private array $work = [];
private array $work = [];
public function __construct()
{
@@ -70,6 +70,7 @@ class CreditRecalculateService
}
if (0 === count($this->work)) {
Log::debug('No work found for recalculate() to do.');
return;
}
$this->processWork();
@@ -79,13 +80,13 @@ class CreditRecalculateService
{
Log::debug('Now in collectFromJournals()');
$valid = config('firefly.valid_liabilities');
$accounts = Account
::leftJoin('transactions', 'transactions.account_id', 'accounts.id')
$accounts = Account::leftJoin('transactions', 'transactions.account_id', 'accounts.id')
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
->whereIn('transaction_journals.id', $transactionJournals->pluck('id')->toArray())
->whereIn('account_types.type', $valid)
->get(['accounts.*']);
->get(['accounts.*'])
;
if ($accounts->count() > 0) {
Log::debug(sprintf('Found %d account(s) to process.', $accounts->count()));
foreach ($accounts as $account) {
@@ -104,7 +105,6 @@ class CreditRecalculateService
$this->collectFromJournals($this->journals);
}
/**
* @throws FireflyException
*/
@@ -114,7 +114,7 @@ class CreditRecalculateService
$destination = $this->getDestinationAccount($journal);
// destination or source must be liability.
$valid = config('firefly.valid_liabilities');
$valid = config('firefly.valid_liabilities');
if (in_array($destination->accountType->type, $valid, true)) {
$this->work[] = $destination;
}
@@ -137,7 +137,7 @@ class CreditRecalculateService
private function getAccountByDirection(TransactionJournal $journal, string $direction): Account
{
/** @var null|Transaction $transaction */
$transaction = $journal->transactions()->where('amount', $direction, '0')->first();
$transaction = $journal->transactions()->where('amount', $direction, '0')->first();
if (null === $transaction) {
throw new FireflyException(sprintf('Cannot find "%s"-transaction of journal #%d', $direction, $journal->id));
}
@@ -180,19 +180,19 @@ class CreditRecalculateService
Log::debug(sprintf('Now processing account #%d ("%s").', $account->id, $account->name));
// get opening balance (if present)
$this->repository->setUser($account->user);
$direction = (string)$this->repository->getMetaValue($account, 'liability_direction');
$direction = (string) $this->repository->getMetaValue($account, 'liability_direction');
$openingBalance = $this->repository->getOpeningBalance($account);
// Log::debug(sprintf('Found opening balance transaction journal #%d', $openingBalance->id));
// if account direction is "debit" ("I owe this amount") the opening balance must always be AWAY from the account:
if ($openingBalance instanceof TransactionJournal && 'debit' === $direction) {
$this->validateOpeningBalance($account, $openingBalance);
}
$startOfDebt = $this->repository->getOpeningBalanceAmount($account, false) ?? '0';
$leftOfDebt = Steam::positive($startOfDebt);
$startOfDebt = $this->repository->getOpeningBalanceAmount($account, false) ?? '0';
$leftOfDebt = Steam::positive($startOfDebt);
// Log::debug(sprintf('Start of debt is "%s", so initial left of debt is "%s"', \FireflyIII\Support\Facades\Steam::bcround($startOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2)));
/** @var AccountMetaFactory $factory */
$factory = app(AccountMetaFactory::class);
$factory = app(AccountMetaFactory::class);
// amount is positive or negative, doesn't matter.
$factory->crud($account, 'start_of_debt', $startOfDebt);
@@ -200,11 +200,12 @@ class CreditRecalculateService
// Log::debug(sprintf('Debt direction is "%s"', $direction));
// now loop all transactions (except opening balance and credit thing)
$transactions = $account
$transactions = $account
->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->orderBy('transaction_journals.date', 'ASC')
->get(['transactions.*']);
->get(['transactions.*'])
;
$transactions->count();
// Log::debug(sprintf('Found %d transaction(s) to process.', $total));
@@ -226,7 +227,7 @@ class CreditRecalculateService
$source = $openingBalance->transactions()->where('amount', '<', 0)->first();
/** @var Transaction $dest */
$dest = $openingBalance->transactions()->where('amount', '>', 0)->first();
$dest = $openingBalance->transactions()->where('amount', '>', 0)->first();
if ($source->account_id !== $account->id) {
Log::info(sprintf('Liability #%d has a reversed opening balance. Will fix this now.', $account->id));
Log::debug(sprintf('Source amount "%s" is now "%s"', $source->amount, Steam::positive($source->amount)));
@@ -257,7 +258,7 @@ class CreditRecalculateService
*/
private function processTransaction(Account $account, string $direction, Transaction $transaction, string $leftOfDebt): string
{
$journal = $transaction->transactionJournal;
$journal = $transaction->transactionJournal;
// here be null pointers.
if (null === $journal) {
@@ -284,15 +285,15 @@ class CreditRecalculateService
// Log::debug(sprintf('Liability direction is "%s"', $direction));
// amount to use depends on the currency:
$usedAmount = $this->getAmountToUse($transaction, $accountCurrency, $foreignCurrency);
$isSameAccount = $account->id === $transaction->account_id;
$isDebit = 'debit' === $direction;
$isCredit = 'credit' === $direction;
$usedAmount = $this->getAmountToUse($transaction, $accountCurrency, $foreignCurrency);
$isSameAccount = $account->id === $transaction->account_id;
$isDebit = 'debit' === $direction;
$isCredit = 'credit' === $direction;
if ($isSameAccount && $isCredit && $this->isWithdrawalIn($usedAmount, $type)) { // case 1
$usedAmount = Steam::positive($usedAmount);
return bcadd($leftOfDebt, (string)$usedAmount);
return bcadd($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case 1 (withdrawal into credit liability): %s + %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
@@ -300,7 +301,7 @@ class CreditRecalculateService
if ($isSameAccount && $isCredit && $this->isWithdrawalOut($usedAmount, $type)) { // case 2
$usedAmount = Steam::positive($usedAmount);
return bcsub($leftOfDebt, (string)$usedAmount);
return bcsub($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case 2 (withdrawal away from liability): %s - %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
@@ -308,7 +309,7 @@ class CreditRecalculateService
if ($isSameAccount && $isCredit && $this->isDepositOut($usedAmount, $type)) { // case 3
$usedAmount = Steam::positive($usedAmount);
return bcsub($leftOfDebt, (string)$usedAmount);
return bcsub($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case 3 (deposit away from liability): %s - %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
@@ -316,35 +317,35 @@ class CreditRecalculateService
if ($isSameAccount && $isCredit && $this->isDepositIn($usedAmount, $type)) { // case 4
$usedAmount = Steam::positive($usedAmount);
return bcadd($leftOfDebt, (string)$usedAmount);
return bcadd($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case 4 (deposit into credit liability): %s + %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
if ($isSameAccount && $isCredit && $this->isTransferIn($usedAmount, $type)) { // case 5
$usedAmount = Steam::positive($usedAmount);
return bcadd($leftOfDebt, (string)$usedAmount);
return bcadd($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case 5 (transfer into credit liability): %s + %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
if ($isSameAccount && $isDebit && $this->isWithdrawalIn($usedAmount, $type)) { // case 6
$usedAmount = Steam::positive($usedAmount);
return bcsub($leftOfDebt, (string)$usedAmount);
return bcsub($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case 6 (withdrawal into debit liability): %s - %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
if ($isSameAccount && $isDebit && $this->isDepositOut($usedAmount, $type)) { // case 7
$usedAmount = Steam::positive($usedAmount);
return bcadd($leftOfDebt, (string)$usedAmount);
return bcadd($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case 7 (deposit away from liability): %s + %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
if ($isSameAccount && $isDebit && $this->isWithdrawalOut($usedAmount, $type)) { // case 8
$usedAmount = Steam::positive($usedAmount);
return bcadd($leftOfDebt, (string)$usedAmount);
return bcadd($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case 8 (withdrawal away from liability): %s + %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
@@ -352,7 +353,7 @@ class CreditRecalculateService
if ($isSameAccount && $isDebit && $this->isTransferIn($usedAmount, $type)) { // case 9
$usedAmount = Steam::positive($usedAmount);
return bcsub($leftOfDebt, (string)$usedAmount);
return bcsub($leftOfDebt, (string) $usedAmount);
// 2024-10-05, #9225 this used to say you would owe more, but a transfer INTO a debit from wherever means you owe LESS.
// Log::debug(sprintf('Case 9 (transfer into debit liability, means you owe LESS): %s - %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
@@ -360,7 +361,7 @@ class CreditRecalculateService
if ($isSameAccount && $isDebit && $this->isTransferOut($usedAmount, $type)) { // case 10
$usedAmount = Steam::positive($usedAmount);
return bcadd($leftOfDebt, (string)$usedAmount);
return bcadd($leftOfDebt, (string) $usedAmount);
// 2024-10-05, #9225 this used to say you would owe less, but a transfer OUT OF a debit from wherever means you owe MORE.
// Log::debug(sprintf('Case 10 (transfer out of debit liability, means you owe MORE): %s + %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
@@ -370,7 +371,7 @@ class CreditRecalculateService
if (in_array($type, [TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::TRANSFER->value], true)) {
$usedAmount = Steam::negative($usedAmount);
return bcadd($leftOfDebt, (string)$usedAmount);
return bcadd($leftOfDebt, (string) $usedAmount);
// Log::debug(sprintf('Case X (all other cases): %s + %s = %s', \FireflyIII\Support\Facades\Steam::bcround($leftOfDebt, 2), \FireflyIII\Support\Facades\Steam::bcround($usedAmount, 2), \FireflyIII\Support\Facades\Steam::bcround($result, 2)));
}
@@ -36,7 +36,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use FireflyIII\Rules\UniqueIban;
use FireflyIII\Support\NullArrayObject;
use Illuminate\Support\Facades\Log;
use Safe\Exceptions\JsonException;
@@ -170,7 +170,7 @@ class AccountBalanceGrouped
{
$this->primary = $primary;
$primaryCurrencyId = $primary->id;
$this->currencies = [$primary->id => $primary]; // currency cache
$this->currencies = [$primary->id => $primary]; // currency cache
$this->data[$primaryCurrencyId] = [
'currency_id' => (string) $primaryCurrencyId,
'currency_symbol' => $primary->symbol,
@@ -222,7 +222,14 @@ trait AugumentData
$currentEnd->addMonth();
}
// primary currency amount.
$expenses = $opsRepository->sumExpenses($currentStart, $currentEnd, null, $budgetCollection, $entry->transactionCurrency, $this->convertToPrimary);
$expenses = $opsRepository->sumExpenses(
$currentStart,
$currentEnd,
null,
$budgetCollection,
$entry->transactionCurrency,
$this->convertToPrimary
);
$spent = $expenses[$currency->id]['sum'] ?? '0';
$entry->pc_spent = $spent;
@@ -157,7 +157,7 @@ trait GetConfigurationData
// previous year:
$yearBegin = today(config('app.timezone'))->subYear()->startOfYear();
$index = (string) trans('firefly.previous_year', ['year' => $yearBegin->year]);
$index = (string) trans('firefly.previous_year', ['year' => $yearBegin->year]);
$ranges[$index] = [$yearBegin, $yearBegin->clone()->endOfYear()];
// everything
@@ -326,7 +326,8 @@ class AccountEnrichment implements EnrichmentInterface
'zoom_level' => (int) $location['zoom_level'],
];
}
// Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
// Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
}
private function collectMetaData(): void
@@ -382,7 +383,8 @@ class AccountEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void
@@ -183,7 +183,8 @@ class BudgetEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void
@@ -152,7 +152,10 @@ class BudgetLimitEnrichment implements EnrichmentInterface
private function collectCurrencies(): void
{
$this->currencies[$this->primaryCurrency->id] = $this->primaryCurrency;
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->whereNot('id', $this->primaryCurrency->id)->get();
$currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->whereNot(
'id',
$this->primaryCurrency->id
)->get();
foreach ($currencies as $currency) {
$this->currencies[(int) $currency->id] = $currency;
}
@@ -191,7 +194,8 @@ class BudgetLimitEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function filterToBudget(array $expenses, int $budget): array
@@ -132,7 +132,8 @@ class CategoryEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectTransactions(): void
@@ -262,7 +262,8 @@ class PiggyBankEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void
@@ -327,7 +327,8 @@ class RecurringEnrichment implements EnrichmentInterface
$this->notes[$notableId] = (string) $note['text'];
Log::debug(sprintf('Collected note #%d for recurrence #%d', $note['id'], $notableId));
}
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectPiggyBankInfo(array $piggyBankIds): void
@@ -357,7 +358,10 @@ class RecurringEnrichment implements EnrichmentInterface
/** @var RecurrenceRepetition $repetition */
foreach ($set as $repetition) {
$recurrence = $this->collection->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)->first();
$recurrence = $this->collection
->filter(static fn (Recurrence $item): bool => (int) $item->id === (int) $repetition->recurrence_id)
->first()
;
$fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date);
$recurrenceId = (int) $repetition->recurrence_id;
$repId = (int) $repetition->id;
@@ -205,7 +205,8 @@ class SubscriptionEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void
@@ -206,7 +206,8 @@ class TransactionGroupEnrichment implements EnrichmentInterface
'zoom_level' => (int) $location['zoom_level'],
];
}
// Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
// Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
}
private function collectMetaData(): void
@@ -242,7 +243,8 @@ class TransactionGroupEnrichment implements EnrichmentInterface
foreach ($notes as $note) {
$this->notes[(int) $note['noteable_id']] = (string) $note['text'];
}
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
// Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectTags(): void
+1 -1
View File
@@ -398,7 +398,7 @@ trait ConvertsDataTypes
protected function getCarbonDate(string $field): ?Carbon
{
$data = (string) $this->get($field);
//Log::debug(sprintf('Date string is "%s"', $data));
// Log::debug(sprintf('Date string is "%s"', $data));
if ('' === $data) {
return null;
@@ -65,7 +65,7 @@ class QueryParser implements QueryParserInterface
// Log::debug('BACKSLASH!');
// escaped quote, pretend it's a normal char and continue two places (skipping the actual character).
$tokenUnderConstruction .= '\\'.$nextChar;
$this->position += 2;
$this->position += 2;
continue;
}
+1
View File
@@ -131,6 +131,7 @@ class Steam
}
$final = array_merge($return, $sumsByCode);
$result[$account->id] = $final;
// Log::debug(sprintf('Final balance for account #%d is', $account->id), $final);
}
+2 -2
View File
@@ -88,8 +88,8 @@ class User extends Authenticatable
*/
public static function routeBinder(self|string $value): self
{
if($value instanceof self) {
$value = (int)$value->id;
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$userId = (int) $value;
Generated
+108 -111
View File
@@ -6428,16 +6428,16 @@
},
{
"name": "symfony/cache",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/cache.git",
"reference": "ef8c7dbfe613d2773d0b5e68b2ef2db72c8b025f"
"reference": "5d3fcada5e1b80157cfdfd1f9dbbd63f95ef6f13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/cache/zipball/ef8c7dbfe613d2773d0b5e68b2ef2db72c8b025f",
"reference": "ef8c7dbfe613d2773d0b5e68b2ef2db72c8b025f",
"url": "https://api.github.com/repos/symfony/cache/zipball/5d3fcada5e1b80157cfdfd1f9dbbd63f95ef6f13",
"reference": "5d3fcada5e1b80157cfdfd1f9dbbd63f95ef6f13",
"shasum": ""
},
"require": {
@@ -6504,7 +6504,7 @@
"psr6"
],
"support": {
"source": "https://github.com/symfony/cache/tree/v8.0.3"
"source": "https://github.com/symfony/cache/tree/v8.0.4"
},
"funding": [
{
@@ -6524,7 +6524,7 @@
"type": "tidelift"
}
],
"time": "2025-12-28T10:45:32+00:00"
"time": "2026-01-23T12:59:31+00:00"
},
{
"name": "symfony/cache-contracts",
@@ -6681,16 +6681,16 @@
},
{
"name": "symfony/console",
"version": "v7.4.3",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6"
"reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/732a9ca6cd9dfd940c639062d5edbde2f6727fb6",
"reference": "732a9ca6cd9dfd940c639062d5edbde2f6727fb6",
"url": "https://api.github.com/repos/symfony/console/zipball/41e38717ac1dd7a46b6bda7d6a82af2d98a78894",
"reference": "41e38717ac1dd7a46b6bda7d6a82af2d98a78894",
"shasum": ""
},
"require": {
@@ -6755,7 +6755,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v7.4.3"
"source": "https://github.com/symfony/console/tree/v7.4.4"
},
"funding": [
{
@@ -6775,7 +6775,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:50:43+00:00"
"time": "2026-01-13T11:36:38+00:00"
},
{
"name": "symfony/css-selector",
@@ -6915,16 +6915,16 @@
},
{
"name": "symfony/error-handler",
"version": "v7.4.0",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/error-handler.git",
"reference": "48be2b0653594eea32dcef130cca1c811dcf25c2"
"reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/48be2b0653594eea32dcef130cca1c811dcf25c2",
"reference": "48be2b0653594eea32dcef130cca1c811dcf25c2",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/8da531f364ddfee53e36092a7eebbbd0b775f6b8",
"reference": "8da531f364ddfee53e36092a7eebbbd0b775f6b8",
"shasum": ""
},
"require": {
@@ -6973,7 +6973,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/error-handler/tree/v7.4.0"
"source": "https://github.com/symfony/error-handler/tree/v7.4.4"
},
"funding": [
{
@@ -6993,20 +6993,20 @@
"type": "tidelift"
}
],
"time": "2025-11-05T14:29:59+00:00"
"time": "2026-01-20T16:42:42+00:00"
},
{
"name": "symfony/event-dispatcher",
"version": "v8.0.0",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "573f95783a2ec6e38752979db139f09fec033f03"
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/573f95783a2ec6e38752979db139f09fec033f03",
"reference": "573f95783a2ec6e38752979db139f09fec033f03",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47",
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47",
"shasum": ""
},
"require": {
@@ -7058,7 +7058,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.0"
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4"
},
"funding": [
{
@@ -7078,7 +7078,7 @@
"type": "tidelift"
}
],
"time": "2025-10-30T14:17:19+00:00"
"time": "2026-01-05T11:45:55+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -7158,16 +7158,16 @@
},
{
"name": "symfony/expression-language",
"version": "v8.0.0",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/expression-language.git",
"reference": "43f520aef59d2fd089662d721b39e0101bb69232"
"reference": "83989cfbcf2ba08a400be339a88468b05ddc21c9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/expression-language/zipball/43f520aef59d2fd089662d721b39e0101bb69232",
"reference": "43f520aef59d2fd089662d721b39e0101bb69232",
"url": "https://api.github.com/repos/symfony/expression-language/zipball/83989cfbcf2ba08a400be339a88468b05ddc21c9",
"reference": "83989cfbcf2ba08a400be339a88468b05ddc21c9",
"shasum": ""
},
"require": {
@@ -7201,7 +7201,7 @@
"description": "Provides an engine that can compile and evaluate expressions",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/expression-language/tree/v8.0.0"
"source": "https://github.com/symfony/expression-language/tree/v8.0.4"
},
"funding": [
{
@@ -7221,20 +7221,20 @@
"type": "tidelift"
}
],
"time": "2025-11-12T15:46:48+00:00"
"time": "2026-01-05T09:27:50+00:00"
},
{
"name": "symfony/finder",
"version": "v7.4.3",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "fffe05569336549b20a1be64250b40516d6e8d06"
"reference": "01b24a145bbeaa7141e75887ec904c34a6728a5f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/fffe05569336549b20a1be64250b40516d6e8d06",
"reference": "fffe05569336549b20a1be64250b40516d6e8d06",
"url": "https://api.github.com/repos/symfony/finder/zipball/01b24a145bbeaa7141e75887ec904c34a6728a5f",
"reference": "01b24a145bbeaa7141e75887ec904c34a6728a5f",
"shasum": ""
},
"require": {
@@ -7269,7 +7269,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v7.4.3"
"source": "https://github.com/symfony/finder/tree/v7.4.4"
},
"funding": [
{
@@ -7289,20 +7289,20 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:50:43+00:00"
"time": "2026-01-12T12:19:02+00:00"
},
{
"name": "symfony/http-client",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-client.git",
"reference": "ea062691009cc2b7bb87734fef20e02671cbd50b"
"reference": "ee9cc4a32fd1cb202b052ba9905a3c873363a465"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/ea062691009cc2b7bb87734fef20e02671cbd50b",
"reference": "ea062691009cc2b7bb87734fef20e02671cbd50b",
"url": "https://api.github.com/repos/symfony/http-client/zipball/ee9cc4a32fd1cb202b052ba9905a3c873363a465",
"reference": "ee9cc4a32fd1cb202b052ba9905a3c873363a465",
"shasum": ""
},
"require": {
@@ -7365,7 +7365,7 @@
"http"
],
"support": {
"source": "https://github.com/symfony/http-client/tree/v8.0.3"
"source": "https://github.com/symfony/http-client/tree/v8.0.4"
},
"funding": [
{
@@ -7385,7 +7385,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:52:06+00:00"
"time": "2026-01-23T16:34:51+00:00"
},
{
"name": "symfony/http-client-contracts",
@@ -7467,16 +7467,16 @@
},
{
"name": "symfony/http-foundation",
"version": "v7.4.3",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
"reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52"
"reference": "977a554a34cf8edc95ca351fbecb1bb1ad05cc94"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/a70c745d4cea48dbd609f4075e5f5cbce453bd52",
"reference": "a70c745d4cea48dbd609f4075e5f5cbce453bd52",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/977a554a34cf8edc95ca351fbecb1bb1ad05cc94",
"reference": "977a554a34cf8edc95ca351fbecb1bb1ad05cc94",
"shasum": ""
},
"require": {
@@ -7525,7 +7525,7 @@
"description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.4.3"
"source": "https://github.com/symfony/http-foundation/tree/v7.4.4"
},
"funding": [
{
@@ -7545,20 +7545,20 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:23:49+00:00"
"time": "2026-01-09T12:14:21+00:00"
},
{
"name": "symfony/http-kernel",
"version": "v7.4.3",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-kernel.git",
"reference": "885211d4bed3f857b8c964011923528a55702aa5"
"reference": "48b067768859f7b68acf41dfb857a5a4be00acdd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/885211d4bed3f857b8c964011923528a55702aa5",
"reference": "885211d4bed3f857b8c964011923528a55702aa5",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/48b067768859f7b68acf41dfb857a5a4be00acdd",
"reference": "48b067768859f7b68acf41dfb857a5a4be00acdd",
"shasum": ""
},
"require": {
@@ -7644,7 +7644,7 @@
"description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/http-kernel/tree/v7.4.3"
"source": "https://github.com/symfony/http-kernel/tree/v7.4.4"
},
"funding": [
{
@@ -7664,20 +7664,20 @@
"type": "tidelift"
}
],
"time": "2025-12-31T08:43:57+00:00"
"time": "2026-01-24T22:13:01+00:00"
},
{
"name": "symfony/mailer",
"version": "v7.4.3",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/mailer.git",
"reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4"
"reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mailer/zipball/e472d35e230108231ccb7f51eb6b2100cac02ee4",
"reference": "e472d35e230108231ccb7f51eb6b2100cac02ee4",
"url": "https://api.github.com/repos/symfony/mailer/zipball/7b750074c40c694ceb34cb926d6dffee231c5cd6",
"reference": "7b750074c40c694ceb34cb926d6dffee231c5cd6",
"shasum": ""
},
"require": {
@@ -7728,7 +7728,7 @@
"description": "Helps sending emails",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/mailer/tree/v7.4.3"
"source": "https://github.com/symfony/mailer/tree/v7.4.4"
},
"funding": [
{
@@ -7748,7 +7748,7 @@
"type": "tidelift"
}
],
"time": "2025-12-16T08:02:06+00:00"
"time": "2026-01-08T08:25:11+00:00"
},
{
"name": "symfony/mailgun-mailer",
@@ -7822,16 +7822,16 @@
},
{
"name": "symfony/mime",
"version": "v7.4.0",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/mime.git",
"reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a"
"reference": "40945014c0a9471ccfe19673c54738fa19367a3c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/mime/zipball/bdb02729471be5d047a3ac4a69068748f1a6be7a",
"reference": "bdb02729471be5d047a3ac4a69068748f1a6be7a",
"url": "https://api.github.com/repos/symfony/mime/zipball/40945014c0a9471ccfe19673c54738fa19367a3c",
"reference": "40945014c0a9471ccfe19673c54738fa19367a3c",
"shasum": ""
},
"require": {
@@ -7887,7 +7887,7 @@
"mime-type"
],
"support": {
"source": "https://github.com/symfony/mime/tree/v7.4.0"
"source": "https://github.com/symfony/mime/tree/v7.4.4"
},
"funding": [
{
@@ -7907,7 +7907,7 @@
"type": "tidelift"
}
],
"time": "2025-11-16T10:14:42+00:00"
"time": "2026-01-08T16:12:55+00:00"
},
{
"name": "symfony/options-resolver",
@@ -8811,16 +8811,16 @@
},
{
"name": "symfony/process",
"version": "v7.4.3",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f"
"reference": "626f07a53f4b4e2f00e11824cc29f928d797783b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/2f8e1a6cdf590ca63715da4d3a7a3327404a523f",
"reference": "2f8e1a6cdf590ca63715da4d3a7a3327404a523f",
"url": "https://api.github.com/repos/symfony/process/zipball/626f07a53f4b4e2f00e11824cc29f928d797783b",
"reference": "626f07a53f4b4e2f00e11824cc29f928d797783b",
"shasum": ""
},
"require": {
@@ -8852,7 +8852,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v7.4.3"
"source": "https://github.com/symfony/process/tree/v7.4.4"
},
"funding": [
{
@@ -8872,20 +8872,20 @@
"type": "tidelift"
}
],
"time": "2025-12-19T10:00:43+00:00"
"time": "2026-01-20T09:23:51+00:00"
},
{
"name": "symfony/psr-http-message-bridge",
"version": "v7.4.0",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/psr-http-message-bridge.git",
"reference": "0101ff8bd0506703b045b1670960302d302a726c"
"reference": "929ffe10bbfbb92e711ac3818d416f9daffee067"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/0101ff8bd0506703b045b1670960302d302a726c",
"reference": "0101ff8bd0506703b045b1670960302d302a726c",
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/929ffe10bbfbb92e711ac3818d416f9daffee067",
"reference": "929ffe10bbfbb92e711ac3818d416f9daffee067",
"shasum": ""
},
"require": {
@@ -8940,7 +8940,7 @@
"psr-7"
],
"support": {
"source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.0"
"source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.4.4"
},
"funding": [
{
@@ -8960,20 +8960,20 @@
"type": "tidelift"
}
],
"time": "2025-11-13T08:38:49+00:00"
"time": "2026-01-03T23:30:35+00:00"
},
{
"name": "symfony/routing",
"version": "v7.4.3",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/routing.git",
"reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090"
"reference": "0798827fe2c79caeed41d70b680c2c3507d10147"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090",
"reference": "5d3fd7adf8896c2fdb54e2f0f35b1bcbd9e45090",
"url": "https://api.github.com/repos/symfony/routing/zipball/0798827fe2c79caeed41d70b680c2c3507d10147",
"reference": "0798827fe2c79caeed41d70b680c2c3507d10147",
"shasum": ""
},
"require": {
@@ -9025,7 +9025,7 @@
"url"
],
"support": {
"source": "https://github.com/symfony/routing/tree/v7.4.3"
"source": "https://github.com/symfony/routing/tree/v7.4.4"
},
"funding": [
{
@@ -9045,7 +9045,7 @@
"type": "tidelift"
}
],
"time": "2025-12-19T10:00:43+00:00"
"time": "2026-01-12T12:19:02+00:00"
},
{
"name": "symfony/service-contracts",
@@ -9136,16 +9136,16 @@
},
{
"name": "symfony/string",
"version": "v8.0.1",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc"
"reference": "758b372d6882506821ed666032e43020c4f57194"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc",
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc",
"url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
"reference": "758b372d6882506821ed666032e43020c4f57194",
"shasum": ""
},
"require": {
@@ -9202,7 +9202,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.0.1"
"source": "https://github.com/symfony/string/tree/v8.0.4"
},
"funding": [
{
@@ -9222,20 +9222,20 @@
"type": "tidelift"
}
],
"time": "2025-12-01T09:13:36+00:00"
"time": "2026-01-12T12:37:40+00:00"
},
{
"name": "symfony/translation",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
"reference": "60a8f11f0e15c48f2cc47c4da53873bb5b62135d"
"reference": "db70c8ce7db74fd2da7b1d268db46b2a8ce32c10"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/60a8f11f0e15c48f2cc47c4da53873bb5b62135d",
"reference": "60a8f11f0e15c48f2cc47c4da53873bb5b62135d",
"url": "https://api.github.com/repos/symfony/translation/zipball/db70c8ce7db74fd2da7b1d268db46b2a8ce32c10",
"reference": "db70c8ce7db74fd2da7b1d268db46b2a8ce32c10",
"shasum": ""
},
"require": {
@@ -9295,7 +9295,7 @@
"description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/translation/tree/v8.0.3"
"source": "https://github.com/symfony/translation/tree/v8.0.4"
},
"funding": [
{
@@ -9315,7 +9315,7 @@
"type": "tidelift"
}
],
"time": "2025-12-21T10:59:45+00:00"
"time": "2026-01-13T13:06:50+00:00"
},
{
"name": "symfony/translation-contracts",
@@ -9401,16 +9401,16 @@
},
{
"name": "symfony/uid",
"version": "v7.4.0",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/uid.git",
"reference": "2498e9f81b7baa206f44de583f2f48350b90142c"
"reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/uid/zipball/2498e9f81b7baa206f44de583f2f48350b90142c",
"reference": "2498e9f81b7baa206f44de583f2f48350b90142c",
"url": "https://api.github.com/repos/symfony/uid/zipball/7719ce8aba76be93dfe249192f1fbfa52c588e36",
"reference": "7719ce8aba76be93dfe249192f1fbfa52c588e36",
"shasum": ""
},
"require": {
@@ -9455,7 +9455,7 @@
"uuid"
],
"support": {
"source": "https://github.com/symfony/uid/tree/v7.4.0"
"source": "https://github.com/symfony/uid/tree/v7.4.4"
},
"funding": [
{
@@ -9475,20 +9475,20 @@
"type": "tidelift"
}
],
"time": "2025-09-25T11:02:55+00:00"
"time": "2026-01-03T23:30:35+00:00"
},
{
"name": "symfony/var-dumper",
"version": "v7.4.3",
"version": "v7.4.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/var-dumper.git",
"reference": "7e99bebcb3f90d8721890f2963463280848cba92"
"reference": "0e4769b46a0c3c62390d124635ce59f66874b282"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/7e99bebcb3f90d8721890f2963463280848cba92",
"reference": "7e99bebcb3f90d8721890f2963463280848cba92",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/0e4769b46a0c3c62390d124635ce59f66874b282",
"reference": "0e4769b46a0c3c62390d124635ce59f66874b282",
"shasum": ""
},
"require": {
@@ -9542,7 +9542,7 @@
"dump"
],
"support": {
"source": "https://github.com/symfony/var-dumper/tree/v7.4.3"
"source": "https://github.com/symfony/var-dumper/tree/v7.4.4"
},
"funding": [
{
@@ -9562,7 +9562,7 @@
"type": "tidelift"
}
],
"time": "2025-12-18T07:04:31+00:00"
"time": "2026-01-01T22:13:48+00:00"
},
{
"name": "symfony/var-exporter",
@@ -13088,8 +13088,5 @@
"ext-xmlwriter": "*"
},
"platform-dev": {},
"platform-overrides": {
"php": "8.4"
},
"plugin-api-version": "2.9.0"
}
+2 -2
View File
@@ -78,8 +78,8 @@ return [
'running_balance_column' => (bool)envNonEmpty('USE_RUNNING_BALANCE', true), // this is only the default value, is not used.
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-01-24',
'build_time' => 1769259315,
'version' => 'develop/2026-01-25',
'build_time' => 1769334802,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.
+1 -1
View File
@@ -43,7 +43,7 @@ class ConfigSeeder extends Seeder
return;
}
$version = (int) config('firefly.db_version');
$version = (int) config('firefly.db_version');
$entry->data = $version;
$entry->save();
}
+3 -3
View File
@@ -4131,9 +4131,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.9.17",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.17.tgz",
"integrity": "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ==",
"version": "2.9.18",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz",
"integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==",
"dev": true,
"license": "Apache-2.0",
"bin": {