Auto commit for release 'develop' on 2025-03-14

This commit is contained in:
github-actions
2025-03-14 17:45:16 +01:00
parent f6642c075d
commit fc98d66ef4
90 changed files with 1933 additions and 1840 deletions

View File

@@ -406,16 +406,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.70.2",
"version": "v3.72.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "1ca468270efbb75ce0c7566a79cca8ea2888584d"
"reference": "900389362c43d116fee1ffc51f7878145fa61b57"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1ca468270efbb75ce0c7566a79cca8ea2888584d",
"reference": "1ca468270efbb75ce0c7566a79cca8ea2888584d",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/900389362c43d116fee1ffc51f7878145fa61b57",
"reference": "900389362c43d116fee1ffc51f7878145fa61b57",
"shasum": ""
},
"require": {
@@ -445,18 +445,18 @@
"symfony/stopwatch": "^5.4 || ^6.4 || ^7.0"
},
"require-dev": {
"facile-it/paraunit": "^1.3.1 || ^2.5",
"infection/infection": "^0.29.10",
"justinrainbow/json-schema": "^5.3 || ^6.0",
"facile-it/paraunit": "^1.3.1 || ^2.6",
"infection/infection": "^0.29.14",
"justinrainbow/json-schema": "^5.3 || ^6.2",
"keradus/cli-executor": "^2.1",
"mikey179/vfsstream": "^1.6.12",
"php-coveralls/php-coveralls": "^2.7",
"php-cs-fixer/accessible-object": "^1.1",
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
"phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.7",
"symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.0",
"symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.0"
"phpunit/phpunit": "^9.6.22 || ^10.5.45 || ^11.5.12",
"symfony/var-dumper": "^5.4.48 || ^6.4.18 || ^7.2.3",
"symfony/yaml": "^5.4.45 || ^6.4.18 || ^7.2.3"
},
"suggest": {
"ext-dom": "For handling output formats in XML",
@@ -497,7 +497,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.70.2"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.72.0"
},
"funding": [
{
@@ -505,7 +505,7 @@
"type": "github"
}
],
"time": "2025-03-03T21:07:23+00:00"
"time": "2025-03-13T11:25:37+00:00"
},
{
"name": "psr/container",

View File

@@ -82,7 +82,7 @@ class UpdateRequest extends FormRequest
'accounts' => 'required',
'accounts.*' => 'array|required',
'accounts.*.account_id' => ['required', 'numeric', 'belongsToUser:accounts,id'],
'accounts.*.current_amount' => ['numeric','nullable', new IsValidZeroOrMoreAmount(true)],
'accounts.*.current_amount' => ['numeric', 'nullable', new IsValidZeroOrMoreAmount(true)],
'object_group_id' => 'numeric|belongsToUser:object_groups,id',
'object_group_title' => ['min:1', 'max:255'],
'transaction_currency_id' => 'exists:transaction_currencies,id|nullable',

View File

@@ -122,7 +122,8 @@ class CorrectsUnevenAmount extends Command
$journals = DB::table('transactions')
->groupBy('transaction_journal_id')
->whereNull('deleted_at')
->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')]);
->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')])
;
/** @var \stdClass $entry */
foreach ($journals as $entry) {

View File

@@ -1,4 +1,6 @@
<?php
declare(strict_types=1);
/*
* ValidatesEnvironmentVariables.php
* Copyright (c) 2025 james@firefly-iii.org.
@@ -33,12 +35,12 @@ class ValidatesEnvironmentVariables extends Command
*
* @var string
*/
protected $signature = 'integrity:validates-environment-variables';
protected $signature = 'integrity:validates-environment-variables';
/**
* The console command description.
*
* @var string|null
* @var null|string
*/
protected $description = 'Makes sure you use the correct variables.';
@@ -50,19 +52,21 @@ class ValidatesEnvironmentVariables extends Command
$this->validateLanguage();
$this->validateGuard();
$this->validateStaticToken();
return Command::SUCCESS;
}
private function validateLanguage(): void
{
$language = config('firefly.default_language');
$locale = config('firefly.default_locale');
$options = array_keys(config('firefly.languages'));
$language = config('firefly.default_language');
$locale = config('firefly.default_locale');
$options = array_keys(config('firefly.languages'));
if (!in_array($language, $options, true)) {
$this->friendlyError(sprintf('DEFAULT_LANGUAGE "%s" is not a valid language for Firefly III.', $language));
$this->friendlyError('Please check your .env file and make sure you use a valid setting.');
$this->friendlyError(sprintf('Valid languages are: %s', implode(', ', $options)));
exit(1);
}
$options[] = 'equal';
@@ -70,6 +74,7 @@ class ValidatesEnvironmentVariables extends Command
$this->friendlyError(sprintf('DEFAULT_LOCALE "%s" is not a valid local for Firefly III.', $locale));
$this->friendlyError('Please check your .env file and make sure you use a valid setting.');
$this->friendlyError(sprintf('Valid locales are: %s', implode(', ', $options)));
exit(1);
}
}
@@ -81,6 +86,7 @@ class ValidatesEnvironmentVariables extends Command
$this->friendlyError(sprintf('AUTHENTICATION_GUARD "%s" is not a valid guard for Firefly III.', $guard));
$this->friendlyError('Please check your .env file and make sure you use a valid setting.');
$this->friendlyError('Valid guards are: web, remote_user_guard');
exit(1);
}
}
@@ -88,9 +94,10 @@ class ValidatesEnvironmentVariables extends Command
private function validateStaticToken(): void
{
$token = (string) config('firefly.static_cron_token');
if (0 !== strlen($token) && 32 !== strlen($token)) {
if ('' !== $token && 32 !== strlen($token)) {
$this->friendlyError('STATIC_CRON_TOKEN must be empty or a 32-character string.');
$this->friendlyError('Please check your .env file and make sure you use a valid setting.');
exit(1);
}
}

View File

@@ -55,8 +55,8 @@ class BillFactory
$skip = array_key_exists('skip', $data) ? $data['skip'] : 0;
$active = array_key_exists('active', $data) ? $data['active'] : 0;
$data['extension_date'] = $data['extension_date'] ?? null;
$data['end_date'] = $data['end_date'] ?? null;
$data['extension_date'] ??= null;
$data['end_date'] ??= null;
/** @var Bill $bill */
$bill = Bill::create(

View File

@@ -67,7 +67,7 @@ class PiggyBankFactory
public function store(array $data): PiggyBank
{
$piggyBankData = $data;
$piggyBankData = $data;
// unset some fields
unset($piggyBankData['object_group_title'], $piggyBankData['transaction_currency_code'], $piggyBankData['transaction_currency_id'], $piggyBankData['accounts'], $piggyBankData['object_group_id'], $piggyBankData['notes']);
@@ -91,11 +91,11 @@ class PiggyBankFactory
throw new FireflyException('400005: Could not store new piggy bank.', 0, $e);
}
$piggyBank = $this->setOrder($piggyBank, $data);
$piggyBank = $this->setOrder($piggyBank, $data);
$this->linkToAccountIds($piggyBank, $data['accounts']);
$this->piggyBankRepository->updateNote($piggyBank, $data['notes']);
$objectGroupTitle = $data['object_group_title'] ?? '';
$objectGroupTitle = $data['object_group_title'] ?? '';
if ('' !== $objectGroupTitle) {
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
@@ -103,7 +103,7 @@ class PiggyBankFactory
}
}
// try also with ID
$objectGroupId = (int) ($data['object_group_id'] ?? 0);
$objectGroupId = (int) ($data['object_group_id'] ?? 0);
if (0 !== $objectGroupId) {
$objectGroup = $this->findObjectGroupById($objectGroupId);
if (null !== $objectGroup) {
@@ -111,7 +111,7 @@ class PiggyBankFactory
}
}
Log::debug('Touch piggy bank');
$piggyBank->encrypted = false;
$piggyBank->encrypted = false;
$piggyBank->save();
$piggyBank->touch();
@@ -144,10 +144,11 @@ class PiggyBankFactory
// first find by ID:
if ($piggyBankId > 0) {
$piggyBank = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.id', $piggyBankId)
->first(['piggy_banks.*']);
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.id', $piggyBankId)
->first(['piggy_banks.*'])
;
if (null !== $piggyBank) {
return $piggyBank;
}
@@ -168,16 +169,17 @@ class PiggyBankFactory
public function findByName(string $name): ?PiggyBank
{
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.name', $name)
->first(['piggy_banks.*']);
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.name', $name)
->first(['piggy_banks.*'])
;
}
private function setOrder(PiggyBank $piggyBank, array $data): PiggyBank
{
$this->resetOrder();
$order = $this->getMaxOrder() + 1;
$order = $this->getMaxOrder() + 1;
if (array_key_exists('order', $data)) {
$order = $data['order'];
}
@@ -192,14 +194,15 @@ class PiggyBankFactory
{
// TODO duplicate code
$set = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*']);
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*'])
;
$current = 1;
foreach ($set as $piggyBank) {
if ($piggyBank->order !== $current) {

View File

@@ -74,29 +74,29 @@ class NetWorth implements NetWorthInterface
return $cache->get();
}
Log::debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d H:i:s')));
$default = Amount::getNativeCurrency();
$netWorth = [];
$default = Amount::getNativeCurrency();
$netWorth = [];
Log::debug(sprintf('NetWorth: finalAccountsBalance("%s")', $date->format('Y-m-d H:i:s')));
$balances = Steam::finalAccountsBalance($accounts, $date);
$balances = Steam::finalAccountsBalance($accounts, $date);
/** @var Account $account */
foreach ($accounts as $account) {
Log::debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name));
$currency = $this->accountRepository->getAccountCurrency($account) ?? $default;
$useNative = $convertToNative && $default->id !== $currency->id;
$currency = $useNative ? $default : $currency;
$currencyCode = $currency->code;
$balance = '0';
$nativeBalance = '0';
$currency = $this->accountRepository->getAccountCurrency($account) ?? $default;
$useNative = $convertToNative && $default->id !== $currency->id;
$currency = $useNative ? $default : $currency;
$currencyCode = $currency->code;
$balance = '0';
$nativeBalance = '0';
if (array_key_exists($account->id, $balances)) {
$balance = $balances[$account->id]['balance'] ?? '0';
$nativeBalance = $balances[$account->id]['native_balance'] ?? '0';
}
Log::debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance));
// always subtract virtual balance again.
$balance = '' !== (string) $account->virtual_balance ? bcsub($balance, $account->virtual_balance) : $balance;
$nativeBalance = '' !== (string) $account->native_virtual_balance ? bcsub($nativeBalance, $account->native_virtual_balance) : $nativeBalance;
$amountToUse = $useNative ? $nativeBalance : $balance;
$balance = '' !== (string) $account->virtual_balance ? bcsub($balance, $account->virtual_balance) : $balance;
$nativeBalance = '' !== (string) $account->native_virtual_balance ? bcsub($nativeBalance, $account->native_virtual_balance) : $nativeBalance;
$amountToUse = $useNative ? $nativeBalance : $balance;
Log::debug(sprintf('Will use %s %s', $currencyCode, $amountToUse));
$netWorth[$currencyCode] ??= [
@@ -115,7 +115,7 @@ class NetWorth implements NetWorthInterface
return $netWorth;
}
public function setUser(null | Authenticatable | User $user): void
public function setUser(null|Authenticatable|User $user): void
{
if (!$user instanceof User) {
return;
@@ -130,7 +130,7 @@ class NetWorth implements NetWorthInterface
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->accountRepository->setUserGroup($userGroup);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->currencyRepos->setUserGroup($this->userGroup);
}
@@ -147,16 +147,16 @@ class NetWorth implements NetWorthInterface
Log::debug(sprintf('SumNetWorth: finalAccountsBalance("%s")', $date->format('Y-m-d H:i:s')));
$balances = Steam::finalAccountsBalance($accounts, $date);
foreach ($accounts as $account) {
$currency = $this->accountRepository->getAccountCurrency($account);
$balance = $balances[$account->id]['balance'] ?? '0';
$currency = $this->accountRepository->getAccountCurrency($account);
$balance = $balances[$account->id]['balance'] ?? '0';
// always subtract virtual balance.
$virtualBalance = $account->virtual_balance;
$virtualBalance = $account->virtual_balance;
if ('' !== $virtualBalance) {
$balance = bcsub($balance, $virtualBalance);
}
$return[$currency->id] ??= [
$return[$currency->id] ??= [
'id' => (string) $currency->id,
'name' => $currency->name,
'symbol' => $currency->symbol,

View File

@@ -83,7 +83,7 @@ class ShowController extends Controller
public function show(Request $request, Account $account, ?Carbon $start = null, ?Carbon $end = null)
{
$objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type));
$objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type));
if (!$this->isEditableAccount($account)) {
return $this->redirectAccountToAccount($account);
@@ -130,18 +130,20 @@ class ShowController extends Controller
}
Log::debug('Collect transactions');
Timer::start('collection');
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector
->setAccounts(new Collection([$account]))
->setLimit($pageSize)
->setPage($page)
->withAPIInformation()
->setRange($start, $end);
->setRange($start, $end)
;
// this search will not include transaction groups where this asset account (or liability)
// is just part of ONE of the journals. To force this:
$collector->setExpandGroupSearch(true);
$groups = $collector->getPaginatedGroups();
$groups = $collector->getPaginatedGroups();
Log::debug('End collect transactions');
Timer::stop('collection');
@@ -149,21 +151,21 @@ class ShowController extends Controller
// enrich data in arrays.
// enrich
// $enrichment = new TransactionGroupEnrichment();
// $enrichment->setUser(auth()->user());
// $groups->setCollection($enrichment->enrich($groups->getCollection()));
// $enrichment = new TransactionGroupEnrichment();
// $enrichment->setUser(auth()->user());
// $groups->setCollection($enrichment->enrich($groups->getCollection()));
$groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
$showAll = false;
$showAll = false;
// correct
$now = today()->endOfDay();
$now = today()->endOfDay();
if ($now->gt($end) || $now->lt($start)) {
$now = $end;
}
Log::debug(sprintf('show: Call finalAccountBalance with date/time "%s"', $now->toIso8601String()));
$balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToNative, $accountCurrency);
$balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToNative, $accountCurrency);
return view(
'accounts.show',
@@ -207,7 +209,7 @@ class ShowController extends Controller
$today = today(config('app.timezone'));
$accountCurrency = $this->repository->getAccountCurrency($account);
$start = $this->repository->oldestJournalDate($account) ?? today(config('app.timezone'))->startOfMonth();
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
$subTitleIcon = config('firefly.subIconsByIdentifier.'.$account->accountType->type);
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
@@ -217,20 +219,20 @@ class ShowController extends Controller
$end->endOfDay();
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation()->withCategoryInformation();
// this search will not include transaction groups where this asset account (or liability)
// is just part of ONE of the journals. To force this:
$collector->setExpandGroupSearch(true);
$groups = $collector->getPaginatedGroups();
$groups = $collector->getPaginatedGroups();
$groups->setPath(route('accounts.show.all', [$account->id]));
$chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$showAll = true;
$chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$showAll = true;
// correct
Log::debug(sprintf('showAll: Call finalAccountBalance with date/time "%s"', $end->toIso8601String()));
$balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToNative, $accountCurrency);
$balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToNative, $accountCurrency);
return view(
'accounts.show',

View File

@@ -52,8 +52,8 @@ class AcceptHeaders
// if bad 'Content-Type' header, refuse service.
// some routes are exempt from this.
$exempt = [
'api.v1.data.bulk.transactions'
$exempt = [
'api.v1.data.bulk.transactions',
];
if (('POST' === $method || 'PUT' === $method) && !$request->hasHeader('Content-Type') && !in_array($request->route()->getName(), $exempt, true)) {

View File

@@ -45,7 +45,6 @@ use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Override;
/**
* Class AccountRepository.
@@ -76,7 +75,8 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
/** @var Account $account */
foreach ($accounts as $account) {
$byName = $this->user->accounts()->where('name', $account->name)
->where('id', '!=', $account->id)->first();
->where('id', '!=', $account->id)->first()
;
if (null !== $byName) {
$result->push($account);
$result->push($byName);
@@ -85,7 +85,8 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
}
if (null !== $account->iban) {
$byIban = $this->user->accounts()->where('iban', $account->iban)
->where('id', '!=', $account->id)->first();
->where('id', '!=', $account->id)->first()
;
if (null !== $byIban) {
$result->push($account);
$result->push($byIban);
@@ -111,7 +112,8 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
$q1->where('account_meta.name', '=', 'account_number');
$q1->where('account_meta.data', '=', $json);
}
);
)
;
if (0 !== count($types)) {
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
@@ -138,7 +140,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function findByName(string $name, array $types): ?Account
{
$query = $this->user->accounts();
$query = $this->user->accounts();
if (0 !== count($types)) {
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
@@ -160,7 +162,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
return $account;
}
#[Override]
#[\Override]
public function getAccountBalances(Account $account): Collection
{
return $account->accountBalances;
@@ -192,10 +194,10 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
{
$query = $this->user->accounts()->with(
[ // @phpstan-ignore-line
'accountmeta' => static function (HasMany $query): void {
$query->where('name', 'account_role');
},
'attachments',
'accountmeta' => static function (HasMany $query): void {
$query->where('name', 'account_role');
},
'attachments',
]
);
if (0 !== count($types)) {
@@ -211,7 +213,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function getAttachments(Account $account): Collection
{
$set = $account->attachments()->get();
$set = $account->attachments()->get();
/** @var Storage $disk */
$disk = Storage::disk('upload');
@@ -233,7 +235,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function getCashAccount(): Account
{
/** @var AccountType $type */
$type = AccountType::where('type', AccountTypeEnum::CASH->value)->first();
$type = AccountType::where('type', AccountTypeEnum::CASH->value)->first();
/** @var AccountFactory $factory */
$factory = app(AccountFactory::class);
@@ -245,9 +247,10 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function getCreditTransactionGroup(Account $account): ?TransactionGroup
{
$journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionTypeEnum::LIABILITY_CREDIT->value])
->first(['transaction_journals.*']);
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionTypeEnum::LIABILITY_CREDIT->value])
->first(['transaction_journals.*'])
;
return $journal?->transactionGroup;
}
@@ -256,9 +259,9 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
{
$query = $this->user->accounts()->with(
[ // @phpstan-ignore-line
'accountmeta' => static function (HasMany $query): void {
$query->where('name', 'account_role');
},
'accountmeta' => static function (HasMany $query): void {
$query->where('name', 'account_role');
},
]
);
if (0 !== count($types)) {
@@ -291,10 +294,11 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
*/
public function getOpeningBalanceAmount(Account $account, bool $convertToNative): ?string
{
$journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionTypeEnum::OPENING_BALANCE->value, TransactionTypeEnum::LIABILITY_CREDIT->value])
->first(['transaction_journals.*']);
$journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionTypeEnum::OPENING_BALANCE->value, TransactionTypeEnum::LIABILITY_CREDIT->value])
->first(['transaction_journals.*'])
;
if (null === $journal) {
return null;
}
@@ -315,9 +319,10 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function getOpeningBalanceDate(Account $account): ?string
{
return TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionTypeEnum::OPENING_BALANCE->value, TransactionTypeEnum::LIABILITY_CREDIT->value])
->first(['transaction_journals.*'])?->date->format('Y-m-d H:i:s');
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionTypeEnum::OPENING_BALANCE->value, TransactionTypeEnum::LIABILITY_CREDIT->value])
->first(['transaction_journals.*'])?->date->format('Y-m-d H:i:s')
;
}
public function getOpeningBalanceGroup(Account $account): ?TransactionGroup
@@ -330,9 +335,10 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function getOpeningBalance(Account $account): ?TransactionJournal
{
return TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionTypeEnum::OPENING_BALANCE->value])
->first(['transaction_journals.*']);
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionTypeEnum::OPENING_BALANCE->value])
->first(['transaction_journals.*'])
;
}
public function getPiggyBanks(Account $account): Collection
@@ -352,18 +358,19 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
$name = trans('firefly.reconciliation_account_name', ['name' => $account->name, 'currency' => $currency->code]);
/** @var AccountType $type */
$type = AccountType::where('type', AccountTypeEnum::RECONCILIATION->value)->first();
$type = AccountType::where('type', AccountTypeEnum::RECONCILIATION->value)->first();
/** @var null|Account $current */
$current = $this->user->accounts()->where('account_type_id', $type->id)
->where('name', $name)
->first();
$current = $this->user->accounts()->where('account_type_id', $type->id)
->where('name', $name)
->first()
;
if (null !== $current) {
return $current;
}
$data = [
$data = [
'account_type_id' => null,
'account_type_name' => AccountTypeEnum::RECONCILIATION->value,
'active' => true,
@@ -373,7 +380,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
];
/** @var AccountFactory $factory */
$factory = app(AccountFactory::class);
$factory = app(AccountFactory::class);
$factory->setUser($account->user);
return $factory->create($data);
@@ -381,8 +388,8 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function getAccountCurrency(Account $account): ?TransactionCurrency
{
$type = $account->accountType->type;
$list = config('firefly.valid_currency_account_types');
$type = $account->accountType->type;
$list = config('firefly.valid_currency_account_types');
// return null if not in this list.
if (!in_array($type, $list, true)) {
@@ -446,7 +453,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function maxOrder(string $type): int
{
$sets = [
$sets = [
AccountTypeEnum::ASSET->value => [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value],
AccountTypeEnum::EXPENSE->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::BENEFICIARY->value],
AccountTypeEnum::REVENUE->value => [AccountTypeEnum::REVENUE->value],
@@ -462,7 +469,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
}
$specials = [AccountTypeEnum::CASH->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::IMPORT->value, AccountTypeEnum::RECONCILIATION->value];
$order = (int) $this->getAccountsByType($specials)->max('order');
$order = (int) $this->getAccountsByType($specials)->max('order');
app('log')->debug(sprintf('Return max order of "%s" set (specials!): %d', $type, $order));
return $order;
@@ -513,12 +520,13 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
{
/** @var null|TransactionJournal $first */
$first = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->orderBy('transaction_journals.date', 'ASC')
->orderBy('transaction_journals.order', 'DESC')
->where('transaction_journals.user_id', $this->user->id)
->orderBy('transaction_journals.id', 'ASC')
->first(['transaction_journals.id']);
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->orderBy('transaction_journals.date', 'ASC')
->orderBy('transaction_journals.order', 'DESC')
->where('transaction_journals.user_id', $this->user->id)
->orderBy('transaction_journals.id', 'ASC')
->first(['transaction_journals.id'])
;
if (null !== $first) {
/** @var null|TransactionJournal */
return TransactionJournal::find($first->id);
@@ -554,10 +562,11 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
}
}
// reset the rest to zero.
$all = [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::MORTGAGE->value];
$all = [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::MORTGAGE->value];
$this->user->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->whereNotIn('account_types.type', $all)
->update(['order' => 0]);
->whereNotIn('account_types.type', $all)
->update(['order' => 0])
;
}
/**
@@ -574,11 +583,12 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function searchAccount(string $query, array $types, int $limit): Collection
{
$dbQuery = $this->user->accounts()
->where('active', true)
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType']);
->where('active', true)
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType'])
;
if ('' !== $query) {
// split query on spaces just in case:
$parts = explode(' ', $query);
@@ -598,12 +608,13 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
public function searchAccountNr(string $query, array $types, int $limit): Collection
{
$dbQuery = $this->user->accounts()->distinct()
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
->where('accounts.active', true)
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType', 'accountMeta']);
->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
->where('accounts.active', true)
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType', 'accountMeta'])
;
if ('' !== $query) {
// split query on spaces just in case:
$parts = explode(' ', $query);
@@ -642,15 +653,16 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
return $factory->create($data);
}
#[\Override] public function periodCollection(Account $account, Carbon $start, Carbon $end): array
#[\Override]
public function periodCollection(Account $account, Carbon $start, Carbon $end): array
{
return $account->transactions()
->leftJoin('transaction_journals','transaction_journals.id','=','transactions.transaction_journal_id')
->leftJoin('transaction_types','transaction_types.id','=','transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies','transaction_currencies.id','=','transactions.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currencies','foreign_currencies.id','=','transactions.foreign_currency_id')
->where('transaction_journals.date','>=',$start)
->where('transaction_journals.date','<=',$end)
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', '=', 'transactions.foreign_currency_id')
->where('transaction_journals.date', '>=', $start)
->where('transaction_journals.date', '<=', $end)
->get([
// currencies
'transaction_currencies.id as currency_id',
@@ -667,7 +679,7 @@ class AccountRepository implements AccountRepositoryInterface, UserGroupInterfac
'foreign_currencies.decimal_places as foreign_decimal_places',
// fields
'transaction_journals.date', 'transaction_types.type', 'transaction_journals.transaction_currency_id', 'transactions.amount'])
'transaction_journals.date', 'transaction_types.type', 'transaction_journals.transaction_currency_id', 'transactions.amount'])
->toArray();
}

View File

@@ -47,28 +47,28 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
*/
public function getAccountReport(Collection $accounts, Carbon $start, Carbon $end): array
{
$yesterday = clone $start;
$yesterday = clone $start;
$yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including.
$end->endOfDay(); // needs to be end of day to be correct.
Log::debug(sprintf('getAccountReport: finalAccountsBalance("%s")', $yesterday->format('Y-m-d H:i:s')));
Log::debug(sprintf('getAccountReport: finalAccountsBalance("%s")', $end->format('Y-m-d H:i:s')));
$startSet = Steam::finalAccountsBalance($accounts, $yesterday);
$endSet = Steam::finalAccountsBalance($accounts, $end);
$startSet = Steam::finalAccountsBalance($accounts, $yesterday);
$endSet = Steam::finalAccountsBalance($accounts, $end);
Log::debug('Start of accountreport');
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$defaultCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
$return = [
$return = [
'accounts' => [],
'sums' => [],
];
/** @var Account $account */
foreach ($accounts as $account) {
$id = $account->id;
$currency = $repository->getAccountCurrency($account) ?? $defaultCurrency;
$id = $account->id;
$currency = $repository->getAccountCurrency($account) ?? $defaultCurrency;
$return['sums'][$currency->id] ??= [
'start' => '0',
'end' => '0',
@@ -79,7 +79,7 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
'currency_name' => $currency->name,
'currency_decimal_places' => $currency->decimal_places,
];
$entry = [
$entry = [
'name' => $account->name,
'id' => $account->id,
'currency_id' => $currency->id,
@@ -90,9 +90,9 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
];
// get first journal date:
$first = $repository->oldestJournal($account);
$entry['start_balance'] = $startSet[$account->id]['balance'] ?? '0';
$entry['end_balance'] = $endSet[$account->id]['balance'] ?? '0';
$first = $repository->oldestJournal($account);
$entry['start_balance'] = $startSet[$account->id]['balance'] ?? '0';
$entry['end_balance'] = $endSet[$account->id]['balance'] ?? '0';
// first journal exists, and is on start, then this is the actual opening balance:
if (null !== $first && $first->date->isSameDay($yesterday) && TransactionTypeEnum::OPENING_BALANCE->value === $first->transactionType->type) {
@@ -127,13 +127,13 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$collector->setSourceAccounts($accounts)->setRange($start, $end);
$collector->excludeDestinationAccounts($accounts);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::TRANSFER->value])->withAccountInformation();
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
$report = $this->groupExpenseByDestination($journals);
$report = $this->groupExpenseByDestination($journals);
// sort the result
// Obtain a list of columns
$sum = [];
$sum = [];
foreach ($report['accounts'] as $accountId => $row) {
$sum[$accountId] = (float) $row['sum']; // intentional float
}
@@ -151,9 +151,9 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$defaultCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
$currencies = [$defaultCurrency->id => $defaultCurrency];
$report = [
$currencyRepos = app(CurrencyRepositoryInterface::class);
$currencies = [$defaultCurrency->id => $defaultCurrency];
$report = [
'accounts' => [],
'sums' => [],
];
@@ -163,8 +163,8 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$sourceId = (int) $journal['destination_account_id'];
$currencyId = (int) $journal['currency_id'];
$key = sprintf('%s-%s', $sourceId, $currencyId);
$currencies[$currencyId] ??= $currencyRepos->find($currencyId);
$report['accounts'][$key] ??= [
$currencies[$currencyId] ??= $currencyRepos->find($currencyId);
$report['accounts'][$key] ??= [
'id' => $sourceId,
'name' => $journal['destination_account_name'],
'sum' => '0',
@@ -189,7 +189,7 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$report['accounts'][$key]['average'] = bcdiv($report['accounts'][$key]['sum'], (string) $report['accounts'][$key]['count']);
}
$currencyId = $report['accounts'][$key]['currency_id'];
$report['sums'][$currencyId] ??= [
$report['sums'][$currencyId] ??= [
'sum' => '0',
'currency_id' => $report['accounts'][$key]['currency_id'],
'currency_name' => $report['accounts'][$key]['currency_name'],
@@ -217,11 +217,11 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$collector->setDestinationAccounts($accounts)->setRange($start, $end);
$collector->excludeSourceAccounts($accounts);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::TRANSFER->value])->withAccountInformation();
$report = $this->groupIncomeBySource($collector->getExtractedJournals());
$report = $this->groupIncomeBySource($collector->getExtractedJournals());
// sort the result
// Obtain a list of columns
$sum = [];
$sum = [];
foreach ($report['accounts'] as $accountId => $row) {
$sum[$accountId] = (float) $row['sum']; // intentional float
}
@@ -239,20 +239,20 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$defaultCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
/** @var CurrencyRepositoryInterface $currencyRepos */
$currencyRepos = app(CurrencyRepositoryInterface::class);
$currencies = [$defaultCurrency->id => $defaultCurrency];
$report = [
$currencyRepos = app(CurrencyRepositoryInterface::class);
$currencies = [$defaultCurrency->id => $defaultCurrency];
$report = [
'accounts' => [],
'sums' => [],
];
/** @var array $journal */
foreach ($array as $journal) {
$sourceId = (int) $journal['source_account_id'];
$currencyId = (int) $journal['currency_id'];
$key = sprintf('%s-%s', $sourceId, $currencyId);
$sourceId = (int) $journal['source_account_id'];
$currencyId = (int) $journal['currency_id'];
$key = sprintf('%s-%s', $sourceId, $currencyId);
if (!array_key_exists($key, $report['accounts'])) {
$currencies[$currencyId] ??= $currencyRepos->find($currencyId);
$currencies[$currencyId] ??= $currencyRepos->find($currencyId);
$report['accounts'][$key] = [
'id' => $sourceId,
'name' => $journal['source_account_name'],
@@ -276,7 +276,7 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface
$report['accounts'][$key]['average'] = bcdiv($report['accounts'][$key]['sum'], (string) $report['accounts'][$key]['count']);
}
$currencyId = $report['accounts'][$key]['currency_id'];
$report['sums'][$currencyId] ??= [
$report['sums'][$currencyId] ??= [
'sum' => '0',
'currency_id' => $report['accounts'][$key]['currency_id'],
'currency_name' => $report['accounts'][$key]['currency_name'],

View File

@@ -70,8 +70,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
{
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$journalId = (int) $journal['transaction_journal_id'];
$currencyId = (int) $journal['currency_id'];
$journalId = (int) $journal['transaction_journal_id'];
$array[$currencyId] ??= [
'currency_id' => $journal['currency_id'],
'currency_name' => $journal['currency_name'],
@@ -123,8 +123,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $accounts = null,
?Collection $expense = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::WITHDRAWAL->value, $start, $end, $accounts, $expense, $currency);
return $this->groupByCurrency($journals, 'negative');
@@ -141,8 +140,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $accounts = null,
?Collection $opposing = null,
?TransactionCurrency $currency = null
): array
{
): array {
$start->startOfDay();
$end->endOfDay();
@@ -175,14 +173,15 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
if (null !== $currency) {
$collector->setCurrency($currency);
}
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
// same but for foreign currencies:
if (null !== $currency) {
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([$type])->withAccountInformation()
->setForeignCurrency($currency);
->setForeignCurrency($currency)
;
if (TransactionTypeEnum::WITHDRAWAL->value === $type) {
if (null !== $accounts) {
$collector->setSourceAccounts($accounts);
@@ -200,10 +199,10 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
}
$result = $collector->getExtractedJournals();
$result = $collector->getExtractedJournals();
// do not use array_merge because you want keys to overwrite (otherwise you get double results):
$journals = $result + $journals;
$journals = $result + $journals;
}
return $journals;
@@ -225,8 +224,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $accounts = null,
?Collection $expense = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::WITHDRAWAL->value, $start, $end, $accounts, $expense, $currency);
return $this->groupByDirection($journals, 'destination', 'negative');
@@ -248,8 +246,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $accounts = null,
?Collection $expense = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::WITHDRAWAL->value, $start, $end, $accounts, $expense, $currency);
return $this->groupByDirection($journals, 'source', 'negative');
@@ -264,8 +261,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $accounts = null,
?Collection $revenue = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::DEPOSIT->value, $start, $end, $accounts, $revenue, $currency);
return $this->groupByCurrency($journals, 'positive');
@@ -280,8 +276,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $accounts = null,
?Collection $revenue = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::DEPOSIT->value, $start, $end, $accounts, $revenue, $currency);
return $this->groupByDirection($journals, 'destination', 'positive');
@@ -296,8 +291,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $accounts = null,
?Collection $revenue = null,
?TransactionCurrency $currency = null
): array
{
): array {
$journals = $this->getTransactionsForSum(TransactionTypeEnum::DEPOSIT->value, $start, $end, $accounts, $revenue, $currency);
return $this->groupByDirection($journals, 'source', 'positive');
@@ -318,7 +312,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
foreach ($journals as $journal) {
$return = $this->groupByEitherJournal($return, $journal);
}
$final = [];
$final = [];
foreach ($return as $array) {
$array['difference_float'] = (float) $array['difference'];
$array['in_float'] = (float) $array['in'];
@@ -331,12 +325,12 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
private function groupByEitherJournal(array $return, array $journal): array
{
$sourceId = $journal['source_account_id'];
$destinationId = $journal['destination_account_id'];
$currencyId = $journal['currency_id'];
$sourceKey = sprintf('%d-%d', $currencyId, $sourceId);
$destKey = sprintf('%d-%d', $currencyId, $destinationId);
$amount = app('steam')->positive($journal['amount']);
$sourceId = $journal['source_account_id'];
$destinationId = $journal['destination_account_id'];
$currencyId = $journal['currency_id'];
$sourceKey = sprintf('%d-%d', $currencyId, $sourceId);
$destKey = sprintf('%d-%d', $currencyId, $destinationId);
$amount = app('steam')->positive($journal['amount']);
// source first
$return[$sourceKey] ??= [
@@ -353,7 +347,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
];
// dest next:
$return[$destKey] ??= [
$return[$destKey] ??= [
'id' => (string) $destinationId,
'name' => $journal['destination_account_name'],
'difference' => '0',
@@ -371,15 +365,15 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$return[$sourceKey]['difference'] = bcadd($return[$sourceKey]['out'], $return[$sourceKey]['in']);
// destination account? money comes in:
$return[$destKey]['in'] = bcadd($return[$destKey]['in'], $amount);
$return[$destKey]['difference'] = bcadd($return[$destKey]['out'], $return[$destKey]['in']);
$return[$destKey]['in'] = bcadd($return[$destKey]['in'], $amount);
$return[$destKey]['difference'] = bcadd($return[$destKey]['out'], $return[$destKey]['in']);
// foreign currency
if (null !== $journal['foreign_currency_id'] && null !== $journal['foreign_amount']) {
$currencyId = $journal['foreign_currency_id'];
$sourceKey = sprintf('%d-%d', $currencyId, $sourceId);
$destKey = sprintf('%d-%d', $currencyId, $destinationId);
$amount = app('steam')->positive($journal['foreign_amount']);
$currencyId = $journal['foreign_currency_id'];
$sourceKey = sprintf('%d-%d', $currencyId, $sourceId);
$destKey = sprintf('%d-%d', $currencyId, $destinationId);
$amount = app('steam')->positive($journal['foreign_amount']);
// same as above:
// source first
@@ -397,7 +391,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
];
// dest next:
$return[$destKey] ??= [
$return[$destKey] ??= [
'id' => (string) $destinationId,
'name' => $journal['destination_account_name'],
'difference' => '0',
@@ -414,8 +408,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$return[$sourceKey]['difference'] = bcadd($return[$sourceKey]['out'], $return[$sourceKey]['in']);
// destination account? money comes in:
$return[$destKey]['in'] = bcadd($return[$destKey]['in'], $amount);
$return[$destKey]['difference'] = bcadd($return[$destKey]['out'], $return[$destKey]['in']);
$return[$destKey]['in'] = bcadd($return[$destKey]['in'], $amount);
$return[$destKey]['difference'] = bcadd($return[$destKey]['out'], $return[$destKey]['in']);
}
return $return;

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Attachment;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\AttachmentFactory;
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
@@ -36,7 +35,6 @@ use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
use League\Flysystem\UnableToDeleteFile;
use LogicException;
/**
* Class AttachmentRepository.
@@ -46,14 +44,14 @@ class AttachmentRepository implements AttachmentRepositoryInterface, UserGroupIn
use UserGroupTrait;
/**
* @throws Exception
* @throws \Exception
*/
public function destroy(Attachment $attachment): bool
{
/** @var AttachmentHelperInterface $helper */
$helper = app(AttachmentHelperInterface::class);
$path = $helper->getAttachmentLocation($attachment);
$path = $helper->getAttachmentLocation($attachment);
try {
Storage::disk('upload')->delete($path);
@@ -120,7 +118,7 @@ class AttachmentRepository implements AttachmentRepositoryInterface, UserGroupIn
/** @var AttachmentFactory $factory */
$factory = app(AttachmentFactory::class);
$factory->setUser($this->user);
$result = $factory->create($data);
$result = $factory->create($data);
if (null === $result) {
throw new FireflyException('Could not store attachment.');
}
@@ -160,14 +158,14 @@ class AttachmentRepository implements AttachmentRepositoryInterface, UserGroupIn
if (null !== $dbNote) {
try {
$dbNote->delete();
} catch (LogicException $e) {
} catch (\LogicException $e) {
app('log')->error($e->getMessage());
}
}
return true;
}
$dbNote = $attachment->notes()->first();
$dbNote = $attachment->notes()->first();
if (null === $dbNote) {
$dbNote = new Note();
$dbNote->noteable()->associate($attachment);

View File

@@ -41,7 +41,7 @@ class ALERepository implements ALERepositoryInterface
public function store(array $data): AuditLogEntry
{
$auditLogEntry = new AuditLogEntry();
$auditLogEntry = new AuditLogEntry();
$auditLogEntry->auditable()->associate($data['auditable']);
$auditLogEntry->changer()->associate($data['changer']);

View File

@@ -62,7 +62,8 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
$search->whereLike('name', sprintf('%%%s', $query));
}
$search->orderBy('name', 'ASC')
->where('active', true);
->where('active', true)
;
return $search->take($limit)->get();
}
@@ -74,7 +75,8 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
$search->whereLike('name', sprintf('%s%%', $query));
}
$search->orderBy('name', 'ASC')
->where('active', true);
->where('active', true)
;
return $search->take($limit)->get();
}
@@ -176,9 +178,10 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function getBills(): Collection
{
return $this->user->bills()
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('name', 'ASC')->get();
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('name', 'ASC')->get()
;
}
public function getBillsForAccounts(Collection $accounts): Collection
@@ -202,24 +205,25 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
$ids = $accounts->pluck('id')->toArray();
return $this->user->bills()
->leftJoin(
'transaction_journals',
static function (JoinClause $join): void {
$join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at');
}
)
->leftJoin(
'transactions',
static function (JoinClause $join): void {
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0);
}
)
->whereIn('transactions.account_id', $ids)
->whereNull('transaction_journals.deleted_at')
->orderBy('bills.active', 'DESC')
->orderBy('bills.name', 'ASC')
->groupBy($fields)
->get($fields);
->leftJoin(
'transaction_journals',
static function (JoinClause $join): void {
$join->on('transaction_journals.bill_id', '=', 'bills.id')->whereNull('transaction_journals.deleted_at');
}
)
->leftJoin(
'transactions',
static function (JoinClause $join): void {
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0);
}
)
->whereIn('transactions.account_id', $ids)
->whereNull('transaction_journals.deleted_at')
->orderBy('bills.active', 'DESC')
->orderBy('bills.name', 'ASC')
->groupBy($fields)
->get($fields)
;
}
/**
@@ -244,7 +248,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function getOverallAverage(Bill $bill): array
{
/** @var JournalRepositoryInterface $repos */
$repos = app(JournalRepositoryInterface::class);
$repos = app(JournalRepositoryInterface::class);
$repos->setUser($this->user);
// get and sort on currency
@@ -257,7 +261,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
$transaction = $journal->transactions()->where('amount', '<', 0)->first();
$currencyId = (int) $journal->transaction_currency_id;
$currency = $journal->transactionCurrency;
$result[$currencyId] ??= [
$result[$currencyId] ??= [
'sum' => '0',
'native_sum' => '0',
'count' => 0,
@@ -292,8 +296,9 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function getPaginator(int $size): LengthAwarePaginator
{
return $this->user->bills()
->orderBy('active', 'DESC')
->orderBy('name', 'ASC')->paginate($size);
->orderBy('active', 'DESC')
->orderBy('name', 'ASC')->paginate($size)
;
}
/**
@@ -306,13 +311,14 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
Log::debug(sprintf('Search for linked journals between %s and %s', $start->toW3cString(), $end->toW3cString()));
return $bill->transactionJournals()
->before($end)->after($start)->get(
->before($end)->after($start)->get(
[
'transaction_journals.id',
'transaction_journals.date',
'transaction_journals.transaction_group_id',
]
);
)
;
}
/**
@@ -321,10 +327,11 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function getRulesForBill(Bill $bill): Collection
{
return $this->user->rules()
->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
->where('rule_actions.action_type', 'link_to_bill')
->where('rule_actions.action_value', $bill->name)
->get(['rules.*']);
->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
->where('rule_actions.action_type', 'link_to_bill')
->where('rule_actions.action_value', $bill->name)
->get(['rules.*'])
;
}
/**
@@ -335,15 +342,16 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
*/
public function getRulesForBills(Collection $collection): array
{
$rules = $this->user->rules()
->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
->where('rule_actions.action_type', 'link_to_bill')
->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active']);
$array = [];
$rules = $this->user->rules()
->leftJoin('rule_actions', 'rule_actions.rule_id', '=', 'rules.id')
->where('rule_actions.action_type', 'link_to_bill')
->get(['rules.id', 'rules.title', 'rule_actions.action_value', 'rules.active'])
;
$array = [];
/** @var Rule $rule */
foreach ($rules as $rule) {
$array[$rule->action_value] ??= [];
$array[$rule->action_value] ??= [];
$array[$rule->action_value][] = ['id' => $rule->id, 'title' => $rule->title, 'active' => $rule->active];
}
$return = [];
@@ -357,27 +365,28 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function getYearAverage(Bill $bill, Carbon $date): array
{
/** @var JournalRepositoryInterface $repos */
$repos = app(JournalRepositoryInterface::class);
$repos = app(JournalRepositoryInterface::class);
$repos->setUser($this->user);
// get and sort on currency
$result = [];
$result = [];
$journals = $bill->transactionJournals()
->where('date', '>=', $date->year . '-01-01 00:00:00')
->where('date', '<=', $date->year . '-12-31 23:59:59')
->get();
->where('date', '>=', $date->year.'-01-01 00:00:00')
->where('date', '<=', $date->year.'-12-31 23:59:59')
->get()
;
/** @var TransactionJournal $journal */
foreach ($journals as $journal) {
/** @var null|Transaction $transaction */
$transaction = $journal->transactions()->where('amount', '<', 0)->first();
$transaction = $journal->transactions()->where('amount', '<', 0)->first();
if (null === $transaction) {
continue;
}
$currencyId = (int) $journal->transaction_currency_id;
$currency = $journal->transactionCurrency;
$result[$currencyId] ??= [
$result[$currencyId] ??= [
'sum' => '0',
'native_sum' => '0',
'count' => 0,
@@ -427,7 +436,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
*/
public function nextExpectedMatch(Bill $bill, Carbon $date): Carbon
{
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($bill->id);
$cache->addProperty('nextExpectedMatch');
$cache->addProperty($date);
@@ -435,17 +444,17 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
return $cache->get();
}
// find the most recent date for this bill NOT in the future. Cache this date:
$start = clone $bill->date;
$start = clone $bill->date;
$start->startOfDay();
app('log')->debug('nextExpectedMatch: Start is ' . $start->format('Y-m-d'));
app('log')->debug('nextExpectedMatch: Start is '.$start->format('Y-m-d'));
while ($start < $date) {
app('log')->debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d H:i:s'), $date->format('Y-m-d H:i:s')));
$start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
app('log')->debug('Start is now ' . $start->format('Y-m-d H:i:s'));
app('log')->debug('Start is now '.$start->format('Y-m-d H:i:s'));
}
$end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
$end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
$end->endOfDay();
// see if the bill was paid in this period.
@@ -457,8 +466,8 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
$start = clone $end;
$end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip);
}
app('log')->debug('nextExpectedMatch: Final start is ' . $start->format('Y-m-d'));
app('log')->debug('nextExpectedMatch: Matching end is ' . $end->format('Y-m-d'));
app('log')->debug('nextExpectedMatch: Final start is '.$start->format('Y-m-d'));
app('log')->debug('nextExpectedMatch: Matching end is '.$end->format('Y-m-d'));
$cache->store($start);
@@ -519,8 +528,8 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
foreach ($bills as $bill) {
/** @var Collection $set */
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
$return[(int) $currency->id] ??= [
'id' => (string) $currency->id,
'name' => $currency->name,
@@ -529,7 +538,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
'decimal_places' => $currency->decimal_places,
'sum' => '0',
];
$setAmount = '0';
$setAmount = '0';
/** @var TransactionJournal $transactionJournal */
foreach ($set as $transactionJournal) {
@@ -546,10 +555,10 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
public function getActiveBills(): Collection
{
return $this->user->bills()
->where('active', true)
->orderBy('bills.name', 'ASC')
->get(['bills.*', DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount')]) // @phpstan-ignore-line
;
->where('active', true)
->orderBy('bills.name', 'ASC')
->get(['bills.*', DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount')]) // @phpstan-ignore-line
;
}
public function sumUnpaidInRange(Carbon $start, Carbon $end): array
@@ -563,9 +572,9 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
/** @var Bill $bill */
foreach ($bills as $bill) {
// app('log')->debug(sprintf('Processing bill #%d ("%s")', $bill->id, $bill->name));
$dates = $this->getPayDatesInRange($bill, $start, $end);
$count = $bill->transactionJournals()->after($start)->before($end)->count();
$total = $dates->count() - $count;
$dates = $this->getPayDatesInRange($bill, $start, $end);
$count = $bill->transactionJournals()->after($start)->before($end)->count();
$total = $dates->count() - $count;
// app('log')->debug(sprintf('Pay dates: %d, count: %d, left: %d', $dates->count(), $count, $total));
// app('log')->debug('dates', $dates->toArray());
@@ -574,10 +583,10 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
// Log::debug(sprintf('min field is %s, max field is %s', $minField, $maxField));
if ($total > 0) {
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
$average = bcdiv(bcadd($bill->{$maxField} ?? '0', $bill->{$minField} ?? '0'), '2');
$currency = $convertToNative && $bill->transactionCurrency->id !== $default->id ? $default : $bill->transactionCurrency;
$average = bcdiv(bcadd($bill->{$maxField} ?? '0', $bill->{$minField} ?? '0'), '2');
Log::debug(sprintf('Amount to pay is %s %s (%d times)', $currency->code, $average, $total));
$return[$currency->id] ??= [
$return[$currency->id] ??= [
'id' => (string) $currency->id,
'name' => $currency->name,
'symbol' => $currency->symbol,
@@ -615,7 +624,7 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
// app('log')->debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
$currentStart = clone $nextExpectedMatch;
$currentStart = clone $nextExpectedMatch;
}
return $set;

View File

@@ -48,9 +48,9 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
/** @var AvailableBudget $availableBudget */
foreach ($availableBudgets as $availableBudget) {
$start = $availableBudget->start_date->format('Y-m-d');
$end = $availableBudget->end_date->format('Y-m-d');
$key = sprintf('%s-%s-%s', $availableBudget->transaction_currency_id, $start, $end);
$start = $availableBudget->start_date->format('Y-m-d');
$end = $availableBudget->end_date->format('Y-m-d');
$key = sprintf('%s-%s-%s', $availableBudget->transaction_currency_id, $start, $end);
if (array_key_exists($key, $exists)) {
app('log')->debug(sprintf('Found duplicate AB: %s %s, %s-%s. Has been deleted', $availableBudget->transaction_currency_id, $availableBudget->amount, $start, $end));
$availableBudget->delete();
@@ -64,7 +64,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
*/
public function get(?Carbon $start = null, ?Carbon $end = null): Collection
{
$query = $this->user->availableBudgets()->with(['transactionCurrency']);
$query = $this->user->availableBudgets()->with(['transactionCurrency']);
if (null !== $start && null !== $end) {
$query->where(
static function (Builder $q1) use ($start, $end): void {
@@ -105,21 +105,23 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
{
/** @var null|AvailableBudget */
return $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))
->first();
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))
->first()
;
}
public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string
{
$amount = '0';
$amount = '0';
/** @var null|AvailableBudget $availableBudget */
$availableBudget = $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first();
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first()
;
if (null !== $availableBudget) {
$amount = $availableBudget->amount;
}
@@ -132,14 +134,15 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
Log::debug(sprintf('Now in %s(%s, %s)', __METHOD__, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
$return = [];
$availableBudgets = $this->user->availableBudgets()
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->get();
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->get()
;
Log::debug(sprintf('Found %d available budgets', $availableBudgets->count()));
// use native amount if necessary?
$convertToNative = Amount::convertToNative($this->user);
$default = Amount::getNativeCurrency();
$convertToNative = Amount::convertToNative($this->user);
$default = Amount::getNativeCurrency();
/** @var AvailableBudget $availableBudget */
foreach ($availableBudgets as $availableBudget) {
@@ -184,9 +187,10 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
public function getAvailableBudgetsByExactDate(Carbon $start, Carbon $end): Collection
{
return $this->user->availableBudgets()
->where('start_date', '=', $start->format('Y-m-d'))
->where('end_date', '=', $end->format('Y-m-d'))
->get();
->where('start_date', '=', $start->format('Y-m-d'))
->where('end_date', '=', $end->format('Y-m-d'))
->get()
;
}
public function getByCurrencyDate(Carbon $start, Carbon $end, TransactionCurrency $currency): ?AvailableBudget
@@ -196,7 +200,8 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first();
->where('end_date', $end->format('Y-m-d'))->first()
;
}
/**
@@ -205,12 +210,13 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
public function setAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end, string $amount): AvailableBudget
{
/** @var null|AvailableBudget */
$availableBudget = $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first();
$availableBudget = $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first()
;
if (null === $availableBudget) {
$availableBudget = new AvailableBudget();
$availableBudget = new AvailableBudget();
$availableBudget->user()->associate($this->user);
$availableBudget->transactionCurrency()->associate($currency);
$availableBudget->start_date = $start->startOfDay();
@@ -230,7 +236,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface, U
if ($start instanceof Carbon) {
$start = $data['start']->startOfDay();
}
$end = $data['end'];
$end = $data['end'];
if ($end instanceof Carbon) {
$end = $data['end']->endOfDay();
}

View File

@@ -36,7 +36,6 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Override;
/**
* Class BudgetLimitRepository
@@ -51,10 +50,10 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
*/
public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string
{
$query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
$query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
// same complex where query as below.
->where(
->where(
static function (Builder $q5) use ($start, $end): void {
$q5->where(
static function (Builder $q1) use ($start, $end): void {
@@ -64,27 +63,30 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
}
)
->orWhere(
static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
}
);
->orWhere(
static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
}
)
;
}
)
->orWhere(
static function (Builder $q4) use ($start, $end): void {
// or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
}
);
->orWhere(
static function (Builder $q4) use ($start, $end): void {
// or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
}
)
;
}
)
->where('budget_limits.transaction_currency_id', $currency->id)
->whereNull('budgets.deleted_at')
->where('budgets.active', true)
->where('budgets.user_id', $this->user->id);
->where('budget_limits.transaction_currency_id', $currency->id)
->whereNull('budgets.deleted_at')
->where('budgets.active', true)
->where('budgets.user_id', $this->user->id)
;
if (null !== $budgets && $budgets->count() > 0) {
$query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray());
}
@@ -136,17 +138,19 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
// both are NULL:
if (null === $start && null === $end) {
return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget'])
->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at')
->get(['budget_limits.*']);
->with(['budget'])
->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at')
->get(['budget_limits.*'])
;
}
// one of the two is NULL.
if (null === $start xor null === $end) {
$query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget'])
->whereNull('budgets.deleted_at')
->where('budgets.user_id', $this->user->id);
->with(['budget'])
->whereNull('budgets.deleted_at')
->where('budgets.user_id', $this->user->id)
;
if (null !== $end) {
// end date must be before $end.
$query->where('end_date', '<=', $end->format('Y-m-d 00:00:00'));
@@ -161,36 +165,39 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
// neither are NULL:
return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget'])
->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at')
->where(
static function (Builder $q5) use ($start, $end): void {
$q5->where(
static function (Builder $q1) use ($start, $end): void {
$q1->where(
static function (Builder $q2) use ($start, $end): void {
$q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
}
)
->orWhere(
static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
}
);
}
)
->orWhere(
static function (Builder $q4) use ($start, $end): void {
// or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
}
);
}
)->get(['budget_limits.*']);
->with(['budget'])
->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at')
->where(
static function (Builder $q5) use ($start, $end): void {
$q5->where(
static function (Builder $q1) use ($start, $end): void {
$q1->where(
static function (Builder $q2) use ($start, $end): void {
$q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
}
)
->orWhere(
static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
}
)
;
}
)
->orWhere(
static function (Builder $q4) use ($start, $end): void {
// or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
}
)
;
}
)->get(['budget_limits.*'])
;
}
public function getBudgetLimits(Budget $budget, ?Carbon $start = null, ?Carbon $end = null): Collection
@@ -215,38 +222,41 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
// when both dates are set:
return $budget->budgetlimits()
->where(
static function (Builder $q5) use ($start, $end): void {
$q5->where(
static function (Builder $q1) use ($start, $end): void {
// budget limit ends within period
$q1->where(
static function (Builder $q2) use ($start, $end): void {
$q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 23:59:59'));
}
)
// budget limit start within period
->orWhere(
static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59'));
}
);
}
)
->orWhere(
static function (Builder $q4) use ($start, $end): void {
// or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 23:59:59'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00'));
}
);
}
)->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']);
->where(
static function (Builder $q5) use ($start, $end): void {
$q5->where(
static function (Builder $q1) use ($start, $end): void {
// budget limit ends within period
$q1->where(
static function (Builder $q2) use ($start, $end): void {
$q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 23:59:59'));
}
)
// budget limit start within period
->orWhere(
static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59'));
}
)
;
}
)
->orWhere(
static function (Builder $q4) use ($start, $end): void {
// or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 23:59:59'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00'));
}
)
;
}
)->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*'])
;
}
#[Override]
#[\Override]
public function getNoteText(BudgetLimit $budgetLimit): string
{
return (string) $budgetLimit->notes()->first()?->text;
@@ -259,34 +269,35 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
{
// if no currency has been provided, use the user's default currency:
/** @var TransactionCurrencyFactory $factory */
$factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
$factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
if (null === $currency) {
$currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
}
$currency->enabled = true;
$currency->enabled = true;
$currency->save();
// find the budget:
/** @var null|Budget $budget */
$budget = $this->user->budgets()->find((int) $data['budget_id']);
$budget = $this->user->budgets()->find((int) $data['budget_id']);
if (null === $budget) {
throw new FireflyException('200004: Budget does not exist.');
}
// find limit with same date range and currency.
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $data['start_date']->format('Y-m-d'))
->where('budget_limits.end_date', $data['end_date']->format('Y-m-d'))
->where('budget_limits.transaction_currency_id', $currency->id)
->first(['budget_limits.*']);
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $data['start_date']->format('Y-m-d'))
->where('budget_limits.end_date', $data['end_date']->format('Y-m-d'))
->where('budget_limits.transaction_currency_id', $currency->id)
->first(['budget_limits.*'])
;
if (null !== $limit) {
throw new FireflyException('200027: Budget limit already exists.');
}
app('log')->debug('No existing budget limit, create a new one');
// or create one and return it.
$limit = new BudgetLimit();
$limit = new BudgetLimit();
$limit->budget()->associate($budget);
$limit->start_date = $data['start_date']->format('Y-m-d');
$limit->end_date = $data['end_date']->format('Y-m-d');
@@ -294,7 +305,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
$limit->transaction_currency_id = $currency->id;
$limit->save();
$noteText = (string) ($data['notes'] ?? '');
$noteText = (string) ($data['notes'] ?? '');
if ('' !== $noteText) {
$this->setNoteText($limit, $noteText);
}
@@ -308,12 +319,13 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
{
/** @var null|BudgetLimit */
return $budget->budgetlimits()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first();
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first()
;
}
#[Override]
#[\Override]
public function setNoteText(BudgetLimit $budgetLimit, string $text): void
{
$dbNote = $budgetLimit->notes()->first();
@@ -335,8 +347,8 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
*/
public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit
{
$budgetLimit->amount = array_key_exists('amount', $data) ? $data['amount'] : $budgetLimit->amount;
$budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id;
$budgetLimit->amount = array_key_exists('amount', $data) ? $data['amount'] : $budgetLimit->amount;
$budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id;
if (array_key_exists('start', $data)) {
$budgetLimit->start_date = $data['start']->startOfDay();
@@ -348,7 +360,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
}
// if no currency has been provided, use the user's default currency:
$currency = null;
$currency = null;
// update if relevant:
if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) {
@@ -360,7 +372,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
if (null === $currency) {
$currency = $budgetLimit->transactionCurrency ?? app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
}
$currency->enabled = true;
$currency->enabled = true;
$currency->save();
$budgetLimit->transaction_currency_id = $currency->id;
@@ -377,26 +389,29 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
{
// count the limits:
$limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->count('budget_limits.*');
$limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->count('budget_limits.*')
;
app('log')->debug(sprintf('Found %d budget limits.', $limits));
// there might be a budget limit for these dates:
/** @var null|BudgetLimit $limit */
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->first(['budget_limits.*']);
$limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->first(['budget_limits.*'])
;
// if more than 1 limit found, delete the others:
if ($limits > 1 && null !== $limit) {
app('log')->debug(sprintf('Found more than 1, delete all except #%d', $limit->id));
$budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->where('budget_limits.id', '!=', $limit->id)->delete();
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->where('budget_limits.id', '!=', $limit->id)->delete()
;
}
// delete if amount is zero.
@@ -418,7 +433,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
}
app('log')->debug('No existing budget limit, create a new one');
// or create one and return it.
$limit = new BudgetLimit();
$limit = new BudgetLimit();
$limit->budget()->associate($budget);
$limit->start_date = $start->startOfDay();
$limit->start_date_tz = $start->format('e');

View File

@@ -63,7 +63,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$search->whereLike('name', sprintf('%%%s', $query));
}
$search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true);
->orderBy('name', 'ASC')->where('active', true)
;
return $search->take($limit)->get();
}
@@ -75,7 +76,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$search->whereLike('name', sprintf('%s%%', $query));
}
$search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true);
->orderBy('name', 'ASC')->where('active', true)
;
return $search->take($limit)->get();
}
@@ -83,7 +85,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function budgetedInPeriod(Carbon $start, Carbon $end): array
{
app('log')->debug(sprintf('Now in budgetedInPeriod("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d')));
$return = [];
$return = [];
/** @var BudgetLimitRepository $limitRepository */
$limitRepository = app(BudgetLimitRepository::class);
@@ -100,9 +102,9 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
app('log')->debug(sprintf('Budget limit #%d', $limit->id));
$currency = $limit->transactionCurrency;
$rate = $converter->getCurrencyRate($currency, $defaultCurrency, $end);
$currencyCode = $currency->code;
$currency = $limit->transactionCurrency;
$rate = $converter->getCurrencyRate($currency, $defaultCurrency, $end);
$currencyCode = $currency->code;
$return[$currencyCode] ??= [
'currency_id' => (string) $currency->id,
'currency_name' => $currency->name,
@@ -157,9 +159,10 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getActiveBudgets(): Collection
{
return $this->user->budgets()->where('active', true)
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get();
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get()
;
}
/**
@@ -199,19 +202,19 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function budgetedInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array
{
app('log')->debug(sprintf('Now in budgetedInPeriod(#%d, "%s", "%s")', $budget->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
$return = [];
$return = [];
/** @var BudgetLimitRepository $limitRepository */
$limitRepository = app(BudgetLimitRepository::class);
$limitRepository->setUser($this->user);
app('log')->debug(sprintf('Budget #%d: "%s"', $budget->id, $budget->name));
$limits = $limitRepository->getBudgetLimits($budget, $start, $end);
$limits = $limitRepository->getBudgetLimits($budget, $start, $end);
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
app('log')->debug(sprintf('Budget limit #%d', $limit->id));
$currency = $limit->transactionCurrency;
$currency = $limit->transactionCurrency;
$return[$currency->id] ??= [
'id' => (string) $currency->id,
'name' => $currency->name,
@@ -280,7 +283,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
{
app('log')->debug('Now in update()');
$oldName = $budget->name;
$oldName = $budget->name;
if (array_key_exists('name', $data)) {
$budget->name = $data['name'];
$this->updateRuleActions($oldName, $budget->name);
@@ -295,7 +298,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$budget->save();
// update or create auto-budget:
$autoBudget = $this->getAutoBudget($budget);
$autoBudget = $this->getAutoBudget($budget);
// first things first: delete when no longer required:
$autoBudgetType = array_key_exists('auto_budget_type', $data) ? $data['auto_budget_type'] : null;
@@ -321,10 +324,11 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
{
$types = ['set_budget'];
$actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id')
->where('rules.user_id', $this->user->id)
->whereIn('rule_actions.action_type', $types)
->where('rule_actions.action_value', $oldName)
->get(['rule_actions.*']);
->where('rules.user_id', $this->user->id)
->whereIn('rule_actions.action_type', $types)
->where('rule_actions.action_value', $oldName)
->get(['rule_actions.*'])
;
app('log')->debug(sprintf('Found %d actions to update.', $actions->count()));
/** @var RuleAction $action */
@@ -339,10 +343,11 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
{
$types = ['budget_is'];
$triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rules.user_id', $this->user->id)
->whereIn('rule_triggers.trigger_type', $types)
->where('rule_triggers.trigger_value', $oldName)
->get(['rule_triggers.*']);
->where('rules.user_id', $this->user->id)
->whereIn('rule_triggers.trigger_type', $types)
->where('rule_triggers.trigger_value', $oldName)
->get(['rule_triggers.*'])
;
app('log')->debug(sprintf('Found %d triggers to update.', $triggers->count()));
/** @var RuleTrigger $trigger */
@@ -386,7 +391,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$autoBudget = $this->getAutoBudget($budget);
// grab default currency:
$currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
$currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
if (null === $autoBudget) {
// at this point it's a blind assumption auto_budget_type is 1 or 2.
@@ -466,7 +471,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getBudgets(): Collection
{
return $this->user->budgets()->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->get();
->orderBy('name', 'ASC')->get()
;
}
public function destroyAutoBudget(Budget $budget): void
@@ -524,7 +530,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getAttachments(Budget $budget): Collection
{
$set = $budget->attachments()->get();
$set = $budget->attachments()->get();
$disk = Storage::disk('upload');
@@ -550,8 +556,9 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getInactiveBudgets(): Collection
{
return $this->user->budgets()
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', 0)->get();
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', 0)->get()
;
}
public function getNoteText(Budget $budget): ?string
@@ -571,7 +578,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$search->whereLike('name', sprintf('%%%s%%', $query));
}
$search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true);
->orderBy('name', 'ASC')->where('active', true)
;
return $search->take($limit)->get();
}
@@ -591,8 +599,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// exclude specific liabilities
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
/** @var Account $account */
foreach ($subset as $account) {
@@ -603,19 +611,20 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// start collecting:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)
->setRange($start, $end)
->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
->setBudgets($this->getActiveBudgets());
->setRange($start, $end)
->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
->setBudgets($this->getActiveBudgets())
;
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'id' => (string) $currencyId,
'name' => $journal['currency_name'],
'symbol' => $journal['currency_symbol'],
@@ -626,9 +635,9 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
// also do foreign amount:
$foreignId = (int) $journal['foreign_currency_id'];
$foreignId = (int) $journal['foreign_currency_id'];
if (0 !== $foreignId) {
$array[$foreignId] ??= [
$array[$foreignId] ??= [
'id' => (string) $foreignId,
'name' => $journal['foreign_currency_name'],
'symbol' => $journal['foreign_currency_symbol'],
@@ -652,8 +661,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// exclude specific liabilities
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
/** @var Account $account */
foreach ($subset as $account) {
@@ -664,19 +673,20 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// start collecting:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)
->setRange($start, $end)
->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
->setBudget($budget);
->setRange($start, $end)
->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
->setBudget($budget)
;
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'id' => (string) $currencyId,
'name' => $journal['currency_name'],
'symbol' => $journal['currency_symbol'],
@@ -687,9 +697,9 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
// also do foreign amount:
$foreignId = (int) $journal['foreign_currency_id'];
$foreignId = (int) $journal['foreign_currency_id'];
if (0 !== $foreignId) {
$array[$foreignId] ??= [
$array[$foreignId] ??= [
'id' => (string) $foreignId,
'name' => $journal['foreign_currency_name'],
'symbol' => $journal['foreign_currency_symbol'],
@@ -711,7 +721,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
*/
public function store(array $data): Budget
{
$order = $this->getMaxOrder();
$order = $this->getMaxOrder();
try {
$newBudget = Budget::create(
@@ -738,7 +748,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
if (!array_key_exists('auto_budget_type', $data) || !array_key_exists('auto_budget_amount', $data) || !array_key_exists('auto_budget_period', $data)) {
return $newBudget;
}
$type = $data['auto_budget_type'];
$type = $data['auto_budget_type'];
if ('none' === $type) {
return $newBudget;
}
@@ -757,8 +767,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
}
/** @var CurrencyRepositoryInterface $repos */
$repos = app(CurrencyRepositoryInterface::class);
$currency = null;
$repos = app(CurrencyRepositoryInterface::class);
$currency = null;
if (array_key_exists('currency_id', $data)) {
$currency = $repos->find((int) $data['currency_id']);
}
@@ -769,7 +779,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
}
$autoBudget = new AutoBudget();
$autoBudget = new AutoBudget();
$autoBudget->budget()->associate($newBudget);
$autoBudget->transaction_currency_id = $currency->id;
$autoBudget->auto_budget_type = $type;
@@ -778,11 +788,11 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$autoBudget->save();
// create initial budget limit.
$today = today(config('app.timezone'));
$start = app('navigation')->startOfPeriod($today, $autoBudget->period);
$end = app('navigation')->endOfPeriod($start, $autoBudget->period);
$today = today(config('app.timezone'));
$start = app('navigation')->startOfPeriod($today, $autoBudget->period);
$end = app('navigation')->endOfPeriod($start, $autoBudget->period);
$limitRepos = app(BudgetLimitRepositoryInterface::class);
$limitRepos = app(BudgetLimitRepositoryInterface::class);
$limitRepos->setUser($this->user);
$limitRepos->store(
[

View File

@@ -42,7 +42,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface BudgetRepositoryInterface
{

View File

@@ -45,17 +45,17 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface, UserGroupInterf
$carbonFormat = app('navigation')->preferredCarbonFormat($start, $end);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$collector->withoutBudget();
$journals = $collector->getExtractedJournals();
$data = [];
$journals = $collector->getExtractedJournals();
$data = [];
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$currencyId = (int) $journal['currency_id'];
$data[$currencyId] ??= [
'id' => 0,
@@ -68,7 +68,7 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface, UserGroupInterf
'currency_decimal_places' => $journal['currency_decimal_places'],
'entries' => [],
];
$date = $journal['date']->format($carbonFormat);
$date = $journal['date']->format($carbonFormat);
if (!array_key_exists($date, $data[$currencyId]['entries'])) {
$data[$currencyId]['entries'][$date] = '0';
@@ -82,7 +82,7 @@ class NoBudgetRepository implements NoBudgetRepositoryInterface, UserGroupInterf
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?TransactionCurrency $currency = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
if (null !== $accounts && $accounts->count() > 0) {

View File

@@ -41,7 +41,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface NoBudgetRepositoryInterface
{

View File

@@ -62,7 +62,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
++$count;
app('log')->debug(sprintf('Found %d budget limits. Per day is %s, total is %s', $count, $perDay, $total));
}
$avg = $total;
$avg = $total;
if ($count > 0) {
$avg = bcdiv($total, (string) $count);
}
@@ -84,21 +84,21 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// get all transactions:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts($accounts)->setRange($start, $end);
$collector->setBudgets($budgets);
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
// loop transactions:
/** @var array $journal */
foreach ($journals as $journal) {
// prep data array for currency:
$budgetId = (int) $journal['budget_id'];
$budgetName = $journal['budget_name'];
$currencyId = (int) $journal['currency_id'];
$key = sprintf('%d-%d', $budgetId, $currencyId);
$budgetId = (int) $journal['budget_id'];
$budgetName = $journal['budget_name'];
$currencyId = (int) $journal['currency_id'];
$key = sprintf('%d-%d', $budgetId, $currencyId);
$data[$key] ??= [
$data[$key] ??= [
'id' => $budgetId,
'name' => sprintf('%s (%s)', $budgetName, $journal['currency_name']),
'sum' => '0',
@@ -136,13 +136,13 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setBudgets($this->getBudgets());
}
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$budgetId = (int) $journal['budget_id'];
$budgetName = (string) $journal['budget_name'];
$currencyId = (int) $journal['currency_id'];
$budgetId = (int) $journal['budget_id'];
$budgetName = (string) $journal['budget_name'];
// catch "no category" entries.
if (0 === $budgetId) {
@@ -150,7 +150,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
// info about the currency:
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'budgets' => [],
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
@@ -202,8 +202,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
?Collection $accounts = null,
?Collection $budgets = null,
?TransactionCurrency $currency = null
): array
{
): array {
Log::debug(sprintf('Start of %s.', __METHOD__));
// this collector excludes all transfers TO liabilities (which are also withdrawals)
// because those expenses only become expenses once they move from the liability to the friend.
@@ -211,8 +210,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection();
/** @var Account $account */
foreach ($subset as $account) {
@@ -222,11 +221,12 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)
->setRange($start, $end)
->setRange($start, $end)
// ->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
;
if (null !== $accounts) {
$collector->setAccounts($accounts);
@@ -239,7 +239,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setCurrency($currency);
}
$collector->setBudgets($budgets);
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
// same but for transactions in the foreign currency:
if (null !== $currency) {

View File

@@ -42,7 +42,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface OperationsRepositoryInterface
{

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Category;
use Carbon\Carbon;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\CategoryFactory;
use FireflyIII\Models\Attachment;
@@ -151,7 +150,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
public function store(array $data): Category
{
/** @var CategoryFactory $factory */
$factory = app(CategoryFactory::class);
$factory = app(CategoryFactory::class);
$factory->setUser($this->user);
$category = $factory->findOrCreate(null, $data['name']);
@@ -177,7 +176,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
public function updateNotes(Category $category, string $notes): void
{
$dbNote = $category->notes()->first();
$dbNote = $category->notes()->first();
if (null === $dbNote) {
$dbNote = new Note();
$dbNote->noteable()->associate($category);
@@ -223,9 +222,10 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
private function getFirstTransactionDate(Category $category): ?Carbon
{
// check transactions:
$query = $category->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->orderBy('transaction_journals.date', 'ASC');
$query = $category->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->orderBy('transaction_journals.date', 'ASC')
;
$lastTransaction = $query->first(['transaction_journals.*']);
if (null !== $lastTransaction) {
@@ -237,7 +237,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
public function getAttachments(Category $category): Collection
{
$set = $category->attachments()->get();
$set = $category->attachments()->get();
$disk = Storage::disk('upload');
@@ -271,7 +271,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
}
/**
* @throws Exception
* @throws \Exception
*/
public function lastUseDate(Category $category, Collection $accounts): ?Carbon
{
@@ -297,7 +297,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
private function getLastJournalDate(Category $category, Collection $accounts): ?Carbon
{
$query = $category->transactionJournals()->orderBy('date', 'DESC');
$query = $category->transactionJournals()->orderBy('date', 'DESC');
if ($accounts->count() > 0) {
$query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id');
@@ -314,14 +314,15 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
}
/**
* @throws Exception
* @throws \Exception
*/
private function getLastTransactionDate(Category $category, Collection $accounts): ?Carbon
{
// check transactions:
$query = $category->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->orderBy('transaction_journals.date', 'DESC');
$query = $category->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->orderBy('transaction_journals.date', 'DESC')
;
if ($accounts->count() > 0) {
// filter journals:
$query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray());
@@ -346,7 +347,7 @@ class CategoryRepository implements CategoryRepositoryInterface, UserGroupInterf
}
/**
* @throws Exception
* @throws \Exception
*/
public function update(Category $category, array $data): Category
{

View File

@@ -41,7 +41,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface CategoryRepositoryInterface
{

View File

@@ -52,12 +52,12 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
'categories' => [],
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
@@ -74,12 +74,12 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
// add journal to array:
// only a subset of the fields.
$journalId = (int) $journal['transaction_journal_id'];
$journalId = (int) $journal['transaction_journal_id'];
$array[$currencyId]['categories'][0]['transaction_journals'][$journalId]
= [
'amount' => app('steam')->negative($journal['amount']),
'date' => $journal['date'],
];
= [
'amount' => app('steam')->negative($journal['amount']),
'date' => $journal['date'],
];
}
return $array;
@@ -98,12 +98,12 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
'categories' => [],
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
@@ -120,12 +120,12 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
];
// add journal to array:
// only a subset of the fields.
$journalId = (int) $journal['transaction_journal_id'];
$journalId = (int) $journal['transaction_journal_id'];
$array[$currencyId]['categories'][0]['transaction_journals'][$journalId]
= [
'amount' => app('steam')->positive($journal['amount']),
'date' => $journal['date'],
];
= [
'amount' => app('steam')->positive($journal['amount']),
'date' => $journal['date'],
];
}
return $array;
@@ -137,7 +137,7 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value])->withoutCategory();
if (null !== $accounts && $accounts->count() > 0) {
@@ -161,12 +161,12 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
@@ -189,12 +189,12 @@ class NoCategoryRepository implements NoCategoryRepositoryInterface, UserGroupIn
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],

View File

@@ -40,7 +40,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface NoCategoryRepositoryInterface
{

View File

@@ -63,13 +63,13 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setCategories($this->getCategories());
}
$collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$categoryId = (int) $journal['category_id'];
$categoryName = (string) $journal['category_name'];
$currencyId = (int) $journal['currency_id'];
$categoryId = (int) $journal['category_id'];
$categoryName = (string) $journal['category_name'];
// catch "no category" entries.
if (0 === $categoryId) {
@@ -77,7 +77,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
// info about the currency:
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'categories' => [],
'currency_id' => (string) $currencyId,
'currency_name' => $journal['currency_name'],
@@ -140,13 +140,13 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setCategories($this->getCategories());
}
$collector->withCategoryInformation()->withAccountInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$categoryId = (int) $journal['category_id'];
$categoryName = (string) $journal['category_name'];
$currencyId = (int) $journal['currency_id'];
$categoryId = (int) $journal['category_id'];
$categoryName = (string) $journal['category_name'];
// catch "no category" entries.
if (0 === $categoryId) {
@@ -154,7 +154,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
// info about the currency:
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'categories' => [],
'currency_id' => (string) $currencyId,
'currency_name' => $journal['currency_name'],
@@ -193,7 +193,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::TRANSFER->value])
->setDestinationAccounts($accounts)->excludeSourceAccounts($accounts);
->setDestinationAccounts($accounts)->excludeSourceAccounts($accounts)
;
if (null !== $categories && $categories->count() > 0) {
$collector->setCategories($categories);
}
@@ -201,13 +202,13 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setCategories($this->getCategories());
}
$collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$categoryId = (int) $journal['category_id'];
$categoryName = (string) $journal['category_name'];
$currencyId = (int) $journal['currency_id'];
$categoryId = (int) $journal['category_id'];
$categoryName = (string) $journal['category_name'];
// catch "no category" entries.
if (0 === $categoryId) {
@@ -215,7 +216,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
// info about the currency:
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'categories' => [],
'currency_id' => (string) $currencyId,
'currency_name' => $journal['currency_name'],
@@ -255,7 +256,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::TRANSFER->value])
->setSourceAccounts($accounts)->excludeDestinationAccounts($accounts);
->setSourceAccounts($accounts)->excludeDestinationAccounts($accounts)
;
if (null !== $categories && $categories->count() > 0) {
$collector->setCategories($categories);
}
@@ -263,13 +265,13 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$collector->setCategories($this->getCategories());
}
$collector->withCategoryInformation()->withAccountInformation()->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$categoryId = (int) $journal['category_id'];
$categoryName = (string) $journal['category_name'];
$currencyId = (int) $journal['currency_id'];
$categoryId = (int) $journal['category_id'];
$categoryName = (string) $journal['category_name'];
// catch "no category" entries.
if (0 === $categoryId) {
@@ -277,7 +279,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
}
// info about the currency:
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'categories' => [],
'currency_id' => (string) $currencyId,
'currency_name' => $journal['currency_name'],
@@ -318,7 +320,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
if (null !== $accounts && $accounts->count() > 0) {
@@ -341,9 +343,10 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
public function sumIncome(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $categories = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)
->setTypes([TransactionTypeEnum::DEPOSIT->value]);
->setTypes([TransactionTypeEnum::DEPOSIT->value])
;
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
@@ -359,12 +362,12 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
foreach ($journals as $journal) {
// Almost the same as in \FireflyIII\Repositories\Budget\OperationsRepository::sumExpenses
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$amount = '0';
$currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
if ($convertToNative) {
$amount = Amount::getAmountFromJournal($journal);
if ($default->id !== (int) $journal['currency_id'] && $default->id !== (int) $journal['foreign_currency_id']) {
@@ -389,7 +392,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$amount = $journal['amount'];
}
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => (string) $currencyId,
'currency_name' => $currencyName,
@@ -411,7 +414,8 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)
->setTypes([TransactionTypeEnum::TRANSFER->value]);
->setTypes([TransactionTypeEnum::TRANSFER->value])
;
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
@@ -420,12 +424,12 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$categories = $this->getCategories();
}
$collector->setCategories($categories);
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => (string) $currencyId,
'currency_name' => $journal['currency_name'],

View File

@@ -40,7 +40,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface OperationsRepositoryInterface
{

View File

@@ -42,7 +42,6 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Override;
/**
* Class CurrencyRepository.
@@ -67,7 +66,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
public function currencyInUseAt(TransactionCurrency $currency): ?string
{
app('log')->debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code));
$countJournals = $this->countJournals($currency);
$countJournals = $this->countJournals($currency);
if ($countJournals > 0) {
app('log')->info(sprintf('Count journals is %d, return true.', $countJournals));
@@ -82,7 +81,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is being used in accounts:
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((string) $currency->id))->count();
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((string) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -90,7 +89,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// second search using integer check.
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((int) $currency->id))->count();
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((int) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -98,7 +97,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is being used in bills:
$bills = Bill::where('transaction_currency_id', $currency->id)->count();
$bills = Bill::where('transaction_currency_id', $currency->id)->count();
if ($bills > 0) {
app('log')->info(sprintf('Used in %d bills as currency, return true. ', $bills));
@@ -116,9 +115,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is being used in accounts (as integer)
$meta = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id')
->whereNull('accounts.deleted_at')
->where('account_meta.name', 'currency_id')->where('account_meta.data', json_encode($currency->id))->count();
$meta = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id')
->whereNull('accounts.deleted_at')
->where('account_meta.name', 'currency_id')->where('account_meta.data', json_encode($currency->id))->count()
;
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -134,7 +134,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is being used in budget limits
$budgetLimit = BudgetLimit::where('transaction_currency_id', $currency->id)->count();
$budgetLimit = BudgetLimit::where('transaction_currency_id', $currency->id)->count();
if ($budgetLimit > 0) {
app('log')->info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit));
@@ -142,7 +142,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is the default currency for the user or the system
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
app('log')->info('Is the default currency of the user, return true.');
@@ -150,7 +150,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
}
// is the default currency for the user or the system
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
app('log')->info('Is the default currency of the user group, return true.');
@@ -275,7 +275,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
return $result;
}
#[Override]
#[\Override]
public function find(int $currencyId): ?TransactionCurrency
{
return TransactionCurrency::find($currencyId);
@@ -320,9 +320,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
/** @var null|CurrencyExchangeRate $rate */
$rate = $this->user->currencyExchangeRates()
->where('from_currency_id', $fromCurrency->id)
->where('to_currency_id', $toCurrency->id)
->where('date', $date->format('Y-m-d'))->first();
->where('from_currency_id', $fromCurrency->id)
->where('to_currency_id', $toCurrency->id)
->where('date', $date->format('Y-m-d'))->first()
;
if (null !== $rate) {
app('log')->debug(sprintf('Found cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d')));

View File

@@ -42,7 +42,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface CurrencyRepositoryInterface
{

View File

@@ -1,4 +1,5 @@
<?php
/*
* ExchangeRateRepository.php
* Copyright (c) 2025 james@firefly-iii.org.
@@ -30,57 +31,60 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Override;
class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGroupInterface
{
use UserGroupTrait;
#[Override]
#[\Override]
public function deleteRate(CurrencyExchangeRate $rate): void
{
$this->userGroup->currencyExchangeRates()->where('id', $rate->id)->delete();
}
#[Override]
#[\Override]
public function getAll(): Collection
{
return $this->userGroup->currencyExchangeRates()->orderBy('date', 'ASC')->get();
}
#[Override]
#[\Override]
public function getRates(TransactionCurrency $from, TransactionCurrency $to): Collection
{
// orderBy('date', 'DESC')->toRawSql();
return
$this->userGroup->currencyExchangeRates()
->where(function (Builder $q1) use ($from, $to): void {
$q1->where(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id);
})->orWhere(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $to->id)
->where('to_currency_id', $from->id);
});
})
->orderBy('date', 'DESC')
->get(['currency_exchange_rates.*']);
->where(function (Builder $q1) use ($from, $to): void {
$q1->where(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
;
})->orWhere(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $to->id)
->where('to_currency_id', $from->id)
;
});
})
->orderBy('date', 'DESC')
->get(['currency_exchange_rates.*'])
;
}
#[Override]
#[\Override]
public function getSpecificRateOnDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): ?CurrencyExchangeRate
{
/** @var null|CurrencyExchangeRate */
return
$this->userGroup->currencyExchangeRates()
->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
->where('date', $date->format('Y-m-d'))
->first();
->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
->where('date', $date->format('Y-m-d'))
->first()
;
}
#[Override]
#[\Override]
public function storeExchangeRate(TransactionCurrency $from, TransactionCurrency $to, string $rate, Carbon $date): CurrencyExchangeRate
{
$object = new CurrencyExchangeRate();
@@ -96,7 +100,7 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGro
return $object;
}
#[Override]
#[\Override]
public function updateExchangeRate(CurrencyExchangeRate $object, string $rate, ?Carbon $date = null): CurrencyExchangeRate
{
$object->rate = $rate;
@@ -107,5 +111,4 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGro
return $object;
}
}

View File

@@ -1,4 +1,5 @@
<?php
/*
* ExchangeRateRepositoryInterface.php
* Copyright (c) 2025 james@firefly-iii.org.
@@ -32,7 +33,6 @@ use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
/**
* Interface ExchangeRateRepositoryInterface
*

View File

@@ -47,9 +47,10 @@ class JournalAPIRepository implements JournalAPIRepositoryInterface, UserGroupIn
public function findTransaction(int $transactionId): ?Transaction
{
return Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_id', $this->user->id)
->where('transactions.id', $transactionId)
->first(['transactions.*']);
->where('transaction_journals.user_id', $this->user->id)
->where('transactions.id', $transactionId)
->first(['transactions.*'])
;
}
/**
@@ -59,7 +60,7 @@ class JournalAPIRepository implements JournalAPIRepositoryInterface, UserGroupIn
*/
public function getAttachments(TransactionJournal $journal): Collection
{
$set = $journal->attachments;
$set = $journal->attachments;
$disk = Storage::disk('upload');

View File

@@ -41,7 +41,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface JournalAPIRepositoryInterface
{

View File

@@ -32,7 +32,6 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use stdClass;
/**
* Class JournalCLIRepository
@@ -47,9 +46,10 @@ class JournalCLIRepository implements JournalCLIRepositoryInterface, UserGroupIn
public function getAllJournals(array $types): Collection
{
return TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->whereIn('transaction_types.type', $types)
->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account'])
->get(['transaction_journals.*']);
->whereIn('transaction_types.type', $types)
->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account'])
->get(['transaction_journals.*'])
;
}
/**
@@ -57,7 +57,7 @@ class JournalCLIRepository implements JournalCLIRepositoryInterface, UserGroupIn
*/
public function getJournalBudgetId(TransactionJournal $journal): int
{
$budget = $journal->budgets()->first();
$budget = $journal->budgets()->first();
if (null !== $budget) {
return $budget->id;
}
@@ -77,7 +77,7 @@ class JournalCLIRepository implements JournalCLIRepositoryInterface, UserGroupIn
*/
public function getJournalCategoryId(TransactionJournal $journal): int
{
$category = $journal->categories()->first();
$category = $journal->categories()->first();
if (null !== $category) {
return $category->id;
}
@@ -129,7 +129,7 @@ class JournalCLIRepository implements JournalCLIRepositoryInterface, UserGroupIn
*/
public function getMetaField(TransactionJournal $journal, string $field): ?string
{
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty('journal-meta-updated');
$cache->addProperty($journal->id);
$cache->addProperty($field);
@@ -138,12 +138,12 @@ class JournalCLIRepository implements JournalCLIRepositoryInterface, UserGroupIn
return $cache->get();
}
$entry = $journal->transactionJournalMeta()->where('name', $field)->first();
$entry = $journal->transactionJournalMeta()->where('name', $field)->first();
if (null === $entry) {
return null;
}
$value = $entry->data;
$value = $entry->data;
if (is_array($value)) {
$return = implode(',', $value);
@@ -179,11 +179,12 @@ class JournalCLIRepository implements JournalCLIRepositoryInterface, UserGroupIn
public function getSplitJournals(): Collection
{
$query = TransactionJournal::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->groupBy('transaction_journals.id');
->groupBy('transaction_journals.id')
;
$result = $query->get(['transaction_journals.id as id', DB::raw('count(transactions.id) as transaction_count')]); // @phpstan-ignore-line
$journalIds = [];
/** @var stdClass $row */
/** @var \stdClass $row */
foreach ($result as $row) {
if ((int) $row->transaction_count > 2) {
$journalIds[] = (int) $row->id;
@@ -192,7 +193,8 @@ class JournalCLIRepository implements JournalCLIRepositoryInterface, UserGroupIn
$journalIds = array_unique($journalIds);
return TransactionJournal::with(['transactions'])
->whereIn('id', $journalIds)->get();
->whereIn('id', $journalIds)->get()
;
}
/**

View File

@@ -67,7 +67,8 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
->transactionJournals()
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->whereIn('transaction_types.type', $types)
->get(['transaction_journals.*']);
->get(['transaction_journals.*'])
;
}
/**
@@ -101,7 +102,7 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
*/
public function getJournalTotal(TransactionJournal $journal): string
{
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($journal->id);
$cache->addProperty('amount-positive');
if ($cache->has()) {
@@ -150,7 +151,8 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
return new Carbon($cache->get());
}
$entry = TransactionJournalMeta::where('transaction_journal_id', $journalId)
->where('name', $field)->first();
->where('name', $field)->first()
;
if (null === $entry) {
return null;
}
@@ -193,7 +195,8 @@ class JournalRepository implements JournalRepositoryInterface, UserGroupInterfac
public function searchJournalDescriptions(string $search, int $limit): Collection
{
$query = $this->user->transactionJournals()
->orderBy('date', 'DESC');
->orderBy('date', 'DESC')
;
if ('' !== $search) {
$query->whereLike('description', sprintf('%%%s%%', $search));
}

View File

@@ -44,7 +44,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface JournalRepositoryInterface
{

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\LinkType;
use Exception;
use FireflyIII\Events\DestroyedTransactionLink;
use FireflyIII\Models\LinkType;
use FireflyIII\Models\Note;
@@ -72,7 +71,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
}
/**
* @throws Exception
* @throws \Exception
*/
public function destroyLink(TransactionJournalLink $link): bool
{
@@ -117,12 +116,13 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
public function getJournalLinks(?LinkType $linkType = null): Collection
{
$query = TransactionJournalLink::with(['source', 'destination'])
->leftJoin('transaction_journals as source_journals', 'journal_links.source_id', '=', 'source_journals.id')
->leftJoin('transaction_journals as dest_journals', 'journal_links.destination_id', '=', 'dest_journals.id')
->where('source_journals.user_id', $this->user->id)
->where('dest_journals.user_id', $this->user->id)
->whereNull('source_journals.deleted_at')
->whereNull('dest_journals.deleted_at');
->leftJoin('transaction_journals as source_journals', 'journal_links.source_id', '=', 'source_journals.id')
->leftJoin('transaction_journals as dest_journals', 'journal_links.destination_id', '=', 'dest_journals.id')
->where('source_journals.user_id', $this->user->id)
->where('dest_journals.user_id', $this->user->id)
->whereNull('source_journals.deleted_at')
->whereNull('dest_journals.deleted_at')
;
if (null !== $linkType) {
$query->where('journal_links.link_type_id', $linkType->id);
@@ -172,7 +172,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
/**
* Store link between two journals.
*
* @throws Exception
* @throws \Exception
*/
public function storeLink(array $information, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink
{
@@ -192,7 +192,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
return $existing;
}
$link = new TransactionJournalLink();
$link = new TransactionJournalLink();
$link->linkType()->associate($linkType);
if ('inward' === $information['direction']) {
app('log')->debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->inward, $inward->id, $outward->id));
@@ -233,12 +233,13 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
public function findSpecificLink(LinkType $linkType, TransactionJournal $inward, TransactionJournal $outward): ?TransactionJournalLink
{
return TransactionJournalLink::where('link_type_id', $linkType->id)
->where('source_id', $inward->id)
->where('destination_id', $outward->id)->first();
->where('source_id', $inward->id)
->where('destination_id', $outward->id)->first()
;
}
/**
* @throws Exception
* @throws \Exception
*/
private function setNoteText(TransactionJournalLink $link, string $text): void
{
@@ -280,7 +281,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
/**
* Update an existing transaction journal link.
*
* @throws Exception
* @throws \Exception
*/
public function updateLink(TransactionJournalLink $journalLink, array $data): TransactionJournalLink
{
@@ -296,7 +297,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface, UserGroupInterf
$journalLink->refresh();
}
$journalLink->link_type_id = null === $data['link_type_id'] ? $journalLink->link_type_id : $data['link_type_id'];
$journalLink->link_type_id = null === $data['link_type_id'] ? $journalLink->link_type_id : $data['link_type_id'];
$journalLink->save();
if (array_key_exists('notes', $data) && null !== $data['notes']) {
$this->setNoteText($journalLink, $data['notes']);

View File

@@ -41,7 +41,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface LinkTypeRepositoryInterface
{

View File

@@ -53,9 +53,10 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface, UserGroup
public function get(): Collection
{
return $this->user->objectGroups()
->with(['piggyBanks', 'bills'])
->orderBy('order', 'ASC')
->orderBy('title', 'ASC')->get();
->with(['piggyBanks', 'bills'])
->orderBy('order', 'ASC')
->orderBy('title', 'ASC')->get()
;
}
public function deleteEmpty(): void
@@ -148,16 +149,18 @@ class ObjectGroupRepository implements ObjectGroupRepositoryInterface, UserGroup
if ($newOrder > $oldOrder) {
$this->user->objectGroups()->where('object_groups.order', '<=', $newOrder)->where('object_groups.order', '>', $oldOrder)
->where('object_groups.id', '!=', $objectGroup->id)
->decrement('object_groups.order');
->where('object_groups.id', '!=', $objectGroup->id)
->decrement('object_groups.order')
;
$objectGroup->order = $newOrder;
$objectGroup->save();
}
if ($newOrder < $oldOrder) {
$this->user->objectGroups()->where('object_groups.order', '>=', $newOrder)->where('object_groups.order', '<', $oldOrder)
->where('object_groups.id', '!=', $objectGroup->id)
->increment('object_groups.order');
->where('object_groups.id', '!=', $objectGroup->id)
->increment('object_groups.order')
;
$objectGroup->order = $newOrder;
$objectGroup->save();

View File

@@ -40,7 +40,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface ObjectGroupRepositoryInterface
{

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PiggyBank;
use Exception;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\PiggyBankFactory;
@@ -69,9 +68,9 @@ trait ModifiesPiggyBanks
$pivot->native_current_amount = null;
// also update native_current_amount.
$userCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
$userCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
if ($userCurrency->id !== $piggyBank->transaction_currency_id) {
$converter = new ExchangeRateConverter();
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount);
}
@@ -92,9 +91,9 @@ trait ModifiesPiggyBanks
$pivot->native_current_amount = null;
// also update native_current_amount.
$userCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
$userCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
if ($userCurrency->id !== $piggyBank->transaction_currency_id) {
$converter = new ExchangeRateConverter();
$converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true);
$pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount);
}
@@ -126,8 +125,8 @@ trait ModifiesPiggyBanks
Log::debug(sprintf('Maximum amount: %s', $maxAmount));
}
$compare = bccomp($amount, $maxAmount);
$result = $compare <= 0;
$compare = bccomp($amount, $maxAmount);
$result = $compare <= 0;
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
@@ -142,7 +141,7 @@ trait ModifiesPiggyBanks
}
/**
* @throws Exception
* @throws \Exception
*/
public function destroy(PiggyBank $piggyBank): bool
{
@@ -161,11 +160,11 @@ trait ModifiesPiggyBanks
public function setCurrentAmount(PiggyBank $piggyBank, string $amount): PiggyBank
{
$repetition = $this->getRepetition($piggyBank);
$repetition = $this->getRepetition($piggyBank);
if (null === $repetition) {
return $piggyBank;
}
$max = $piggyBank->target_amount;
$max = $piggyBank->target_amount;
if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) {
$amount = $max;
}
@@ -208,14 +207,14 @@ trait ModifiesPiggyBanks
public function update(PiggyBank $piggyBank, array $data): PiggyBank
{
$piggyBank = $this->updateProperties($piggyBank, $data);
$piggyBank = $this->updateProperties($piggyBank, $data);
if (array_key_exists('notes', $data)) {
$this->updateNote($piggyBank, (string) $data['notes']);
}
// update the order of the piggy bank:
$oldOrder = $piggyBank->order;
$newOrder = (int) ($data['order'] ?? $oldOrder);
$oldOrder = $piggyBank->order;
$newOrder = (int) ($data['order'] ?? $oldOrder);
if ($oldOrder !== $newOrder) {
$this->setOrder($piggyBank, $newOrder);
}
@@ -308,7 +307,7 @@ trait ModifiesPiggyBanks
return;
}
$dbNote = $piggyBank->notes()->first();
$dbNote = $piggyBank->notes()->first();
if (null === $dbNote) {
$dbNote = new Note();
$dbNote->noteable()->associate($piggyBank);
@@ -319,15 +318,16 @@ trait ModifiesPiggyBanks
public function setOrder(PiggyBank $piggyBank, int $newOrder): bool
{
$oldOrder = $piggyBank->order;
$oldOrder = $piggyBank->order;
// Log::debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
if ($newOrder > $oldOrder) {
PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder)
->where('piggy_banks.id', '!=', $piggyBank->id)
->distinct()->decrement('piggy_banks.order');
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder)
->where('piggy_banks.id', '!=', $piggyBank->id)
->distinct()->decrement('piggy_banks.order')
;
$piggyBank->order = $newOrder;
Log::debug(sprintf('[1] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
@@ -336,11 +336,12 @@ trait ModifiesPiggyBanks
return true;
}
PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder)
->where('piggy_banks.id', '!=', $piggyBank->id)
->distinct()->increment('piggy_banks.order');
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder)
->where('piggy_banks.id', '!=', $piggyBank->id)
->distinct()->increment('piggy_banks.order')
;
$piggyBank->order = $newOrder;
Log::debug(sprintf('[2] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
@@ -361,7 +362,7 @@ trait ModifiesPiggyBanks
}
// if this account contains less than the amount, remove the current amount, update the amount and continue.
$this->removeAmount($piggyBank, $account, $current);
$amount = bcsub($amount, $current);
$amount = bcsub($amount, $current);
}
}
}

View File

@@ -41,7 +41,6 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Override;
/**
* Class PiggyBankRepository.
@@ -57,9 +56,10 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
Log::channel('audit')->info('Delete all piggy banks through destroyAll');
PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->delete();
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->delete()
;
}
public function findPiggyBank(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank
@@ -90,9 +90,10 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
public function find(int $piggyBankId): ?PiggyBank
{
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']);
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*'])
;
}
/**
@@ -101,14 +102,15 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
public function findByName(string $name): ?PiggyBank
{
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.name', $name)->first(['piggy_banks.*']);
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->where('piggy_banks.name', $name)->first(['piggy_banks.*'])
;
}
public function getAttachments(PiggyBank $piggyBank): Collection
{
$set = $piggyBank->attachments()->get();
$set = $piggyBank->attachments()->get();
$disk = Storage::disk('upload');
@@ -155,15 +157,15 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
{
app('log')->debug(sprintf('Now in getExactAmount(%d, %d)', $piggyBank->id, $journal->id));
$operator = null;
$currency = null;
$operator = null;
$currency = null;
/** @var JournalRepositoryInterface $journalRepost */
$journalRepost = app(JournalRepositoryInterface::class);
$journalRepost = app(JournalRepositoryInterface::class);
$journalRepost->setUser($this->user);
/** @var AccountRepositoryInterface $accountRepos */
$accountRepos = app(AccountRepositoryInterface::class);
$accountRepos = app(AccountRepositoryInterface::class);
$accountRepos->setUser($this->user);
$defaultCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
@@ -171,11 +173,11 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
app('log')->debug(sprintf('Piggy bank #%d currency is %s', $piggyBank->id, $piggyBank->transactionCurrency->code));
/** @var Transaction $source */
$source = $journal->transactions()->with(['account'])->where('amount', '<', 0)->first();
$source = $journal->transactions()->with(['account'])->where('amount', '<', 0)->first();
/** @var Transaction $destination */
$destination = $journal->transactions()->with(['account'])->where('amount', '>', 0)->first();
$hits = 0;
$destination = $journal->transactions()->with(['account'])->where('amount', '>', 0)->first();
$hits = 0;
foreach ($piggyBank->accounts as $account) {
// matches source, which means amount will be removed from piggy:
@@ -207,7 +209,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
}
// currency of the account + the piggy bank currency are almost the same.
// which amount from the transaction matches?
$amount = null;
$amount = null;
if ((int) $source->transaction_currency_id === $currency->id) {
app('log')->debug('Use normal amount');
$amount = app('steam')->{$operator}($source->amount); // @phpstan-ignore-line
@@ -223,9 +225,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
}
app('log')->debug(sprintf('The currency is %s and the amount is %s', $currency->code, $amount));
$currentAmount = $this->getCurrentAmount($piggyBank);
$room = bcsub($piggyBank->target_amount, $currentAmount);
$compare = bcmul($currentAmount, '-1');
$currentAmount = $this->getCurrentAmount($piggyBank);
$room = bcsub($piggyBank->target_amount, $currentAmount);
$compare = bcmul($currentAmount, '-1');
if (0 === bccomp($piggyBank->target_amount, '0')) {
// amount is zero? then the "room" is positive amount of we wish to add or remove.
@@ -306,20 +308,23 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
public function getPiggyBanks(): Collection
{
$query = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id');
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
;
if (null === $this->user) {
$query->where('accounts.user_group_id', $this->userGroup->id);
}
if (null !== $this->user) {
$query->where('accounts.user_id', $this->user->id);
}
return $query
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->distinct()->get(['piggy_banks.*']);
->orderBy('piggy_banks.order', 'ASC')->distinct()->get(['piggy_banks.*'])
;
}
public function getRepetition(PiggyBank $piggyBank, bool $overrule = false): ?PiggyBankRepetition
@@ -385,23 +390,24 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
return $balance;
}
#[Override]
#[\Override]
public function purgeAll(): void
{
PiggyBank::withTrashed()
->whereNotNull('piggy_banks.deleted_at')
->leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->delete();
->whereNotNull('piggy_banks.deleted_at')
->leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->delete()
;
}
#[Override]
#[\Override]
public function resetOrder(): void
{
$factory = new PiggyBankFactory();
@@ -412,19 +418,21 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
public function searchPiggyBank(string $query, int $limit): Collection
{
$search = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->distinct();
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', $this->user->id)
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->distinct()
;
if ('' !== $query) {
$search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query));
}
$search->orderBy('piggy_banks.order', 'ASC')
->orderBy('piggy_banks.name', 'ASC');
->orderBy('piggy_banks.name', 'ASC')
;
return $search->take($limit)->get(['piggy_banks.*']);
}

View File

@@ -44,7 +44,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface PiggyBankRepositoryInterface
{

View File

@@ -67,9 +67,9 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
// if not, loop set and try to read the recurrence_date. If it matches start or end, return it as well.
$set
= TransactionJournalMeta::where(static function (Builder $q1) use ($recurrence): void {
$q1->where('name', 'recurrence_id');
$q1->where('data', json_encode((string) $recurrence->id));
})->get(['journal_meta.transaction_journal_id']);
$q1->where('name', 'recurrence_id');
$q1->where('data', json_encode((string) $recurrence->id));
})->get(['journal_meta.transaction_journal_id']);
// there are X journals made for this recurrence. Any of them meant for today?
foreach ($set as $journalMeta) {
@@ -79,8 +79,9 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
$q2->where('name', 'recurrence_date');
$q2->where('data', json_encode($string));
})
->where('transaction_journal_id', $journalMeta->transaction_journal_id)
->count();
->where('transaction_journal_id', $journalMeta->transaction_journal_id)
->count()
;
if ($count > 0) {
app('log')->debug(sprintf('Looks like journal #%d was already created', $journalMeta->transaction_journal_id));
@@ -97,11 +98,12 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
public function get(): Collection
{
return $this->user->recurrences()
->with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions'])
->orderBy('active', 'DESC')
->orderBy('transaction_type_id', 'ASC')
->orderBy('title', 'ASC')
->get();
->with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions'])
->orderBy('active', 'DESC')
->orderBy('transaction_type_id', 'ASC')
->orderBy('title', 'ASC')
->get()
;
}
/**
@@ -127,9 +129,10 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
{
// grab ALL recurring transactions:
return Recurrence::with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions'])
->orderBy('active', 'DESC')
->orderBy('title', 'ASC')
->get();
->orderBy('active', 'DESC')
->orderBy('title', 'ASC')
->get()
;
}
public function getBillId(RecurrenceTransaction $recTransaction): ?int
@@ -204,10 +207,11 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
{
Log::debug(sprintf('Now in getJournalCount(#%d, "%s", "%s")', $recurrence->id, $start?->format('Y-m-d H:i:s'), $end?->format('Y-m-d H:i:s')));
$query = TransactionJournal::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id')
->where('transaction_journals.user_id', $recurrence->user_id)
->whereNull('transaction_journals.deleted_at')
->where('journal_meta.name', 'recurrence_id')
->where('journal_meta.data', '"' . $recurrence->id . '"');
->where('transaction_journals.user_id', $recurrence->user_id)
->whereNull('transaction_journals.deleted_at')
->where('journal_meta.name', 'recurrence_id')
->where('journal_meta.data', '"'.$recurrence->id.'"')
;
if (null !== $start) {
$query->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00'));
}
@@ -226,10 +230,11 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
public function getJournalIds(Recurrence $recurrence): array
{
return TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->where('transaction_journals.user_id', $this->user->id)
->where('journal_meta.name', '=', 'recurrence_id')
->where('journal_meta.data', '=', json_encode((string) $recurrence->id))
->get(['journal_meta.transaction_journal_id'])->pluck('transaction_journal_id')->toArray();
->where('transaction_journals.user_id', $this->user->id)
->where('journal_meta.name', '=', 'recurrence_id')
->where('journal_meta.data', '=', json_encode((string) $recurrence->id))
->get(['journal_meta.transaction_journal_id'])->pluck('transaction_journal_id')->toArray()
;
}
/**
@@ -277,22 +282,24 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
public function getTransactionPaginator(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator
{
$journalMeta = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->where('name', 'recurrence_id')
->where('data', json_encode((string) $recurrence->id))
->get()->pluck('transaction_journal_id')->toArray();
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->where('name', 'recurrence_id')
->where('data', json_encode((string) $recurrence->id))
->get()->pluck('transaction_journal_id')->toArray()
;
$search = [];
foreach ($journalMeta as $journalId) {
$search[] = (int) $journalId;
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($recurrence->user);
$collector->withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page)
->withAccountInformation();
->withAccountInformation()
;
$collector->setJournalIds($search);
return $collector->getPaginatedGroups();
@@ -301,11 +308,12 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
public function getTransactions(Recurrence $recurrence): Collection
{
$journalMeta = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->where('name', 'recurrence_id')
->where('data', json_encode((string) $recurrence->id))
->get()->pluck('transaction_journal_id')->toArray();
->whereNull('transaction_journals.deleted_at')
->where('transaction_journals.user_id', $this->user->id)
->where('name', 'recurrence_id')
->where('data', json_encode((string) $recurrence->id))
->get()->pluck('transaction_journal_id')->toArray()
;
$search = [];
foreach ($journalMeta as $journalId) {
@@ -316,7 +324,7 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
}
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($recurrence->user);
$collector->withCategoryInformation()->withBudgetInformation()->withAccountInformation();
@@ -459,21 +467,21 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
);
}
if ('ndom' === $repetition->repetition_type) {
$parts = explode(',', $repetition->repetition_moment);
$parts = explode(',', $repetition->repetition_moment);
// first part is number of week, second is weekday.
$dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language);
return (string) trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
}
if ('yearly' === $repetition->repetition_type) {
$today = today(config('app.timezone'))->endOfYear();
$repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment);
$today = today(config('app.timezone'))->endOfYear();
$repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment);
if (null === $repDate) {
$repDate = clone $today;
}
$diffInYears = (int) $today->diffInYears($repDate, true);
$repDate->addYears($diffInYears); // technically not necessary.
$string = $repDate->isoFormat((string) trans('config.month_and_day_no_year_js'));
$string = $repDate->isoFormat((string) trans('config.month_and_day_no_year_js'));
return (string) trans('firefly.recurring_yearly', ['date' => $string], $language);
}
@@ -488,7 +496,8 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
$search->whereLike('recurrences.title', sprintf('%%%s%%', $query));
}
$search
->orderBy('recurrences.title', 'ASC');
->orderBy('recurrences.title', 'ASC')
;
return $search->take($limit)->get(['id', 'title', 'description']);
}
@@ -534,7 +543,7 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte
$occurrences = [];
$mutator = clone $start;
$mutator->startOfDay();
$skipMod = $repetition->repetition_skip + 1;
$skipMod = $repetition->repetition_skip + 1;
app('log')->debug(sprintf('Calculating occurrences for rep type "%s"', $repetition->repetition_type));
app('log')->debug(sprintf('Mutator is now: %s', $mutator->format('Y-m-d')));

View File

@@ -45,7 +45,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface RecurringRepositoryInterface
{

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Rule;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction;
@@ -43,7 +42,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
use UserGroupTrait;
/**
* @throws Exception
* @throws \Exception
*/
public function destroy(Rule $rule): bool
{
@@ -112,7 +111,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
{
$count = $rule->ruleTriggers()->count();
if (0 === $count) {
throw new FireflyException('Rules should have more than zero triggers, rule #' . $rule->id . ' has none!');
throw new FireflyException('Rules should have more than zero triggers, rule #'.$rule->id.' has none!');
}
return $rule->ruleTriggers()->where('trigger_type', 'user_action')->first()->trigger_value;
@@ -142,7 +141,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
if ('user_action' === $trigger->trigger_type) {
continue;
}
$triggerType = $trigger->trigger_type;
$triggerType = $trigger->trigger_type;
if (str_starts_with($trigger->trigger_type, '-')) {
$triggerType = substr($trigger->trigger_type, 1);
}
@@ -161,13 +160,14 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
public function getStoreRules(): Collection
{
$collection = $this->user->rules()
->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
->where('rules.active', true)
->where('rule_groups.active', true)
->orderBy('rule_groups.order', 'ASC')
->orderBy('rules.order', 'ASC')
->orderBy('rules.id', 'ASC')
->with(['ruleGroup', 'ruleTriggers'])->get(['rules.*']);
->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
->where('rules.active', true)
->where('rule_groups.active', true)
->orderBy('rule_groups.order', 'ASC')
->orderBy('rules.order', 'ASC')
->orderBy('rules.id', 'ASC')
->with(['ruleGroup', 'ruleTriggers'])->get(['rules.*'])
;
$filtered = new Collection();
/** @var Rule $rule */
@@ -186,13 +186,14 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
public function getUpdateRules(): Collection
{
$collection = $this->user->rules()
->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
->where('rules.active', true)
->where('rule_groups.active', true)
->orderBy('rule_groups.order', 'ASC')
->orderBy('rules.order', 'ASC')
->orderBy('rules.id', 'ASC')
->with(['ruleGroup', 'ruleTriggers'])->get();
->leftJoin('rule_groups', 'rule_groups.id', '=', 'rules.rule_group_id')
->where('rules.active', true)
->where('rule_groups.active', true)
->orderBy('rule_groups.order', 'ASC')
->orderBy('rules.order', 'ASC')
->orderBy('rules.id', 'ASC')
->with(['ruleGroup', 'ruleTriggers'])->get()
;
$filtered = new Collection();
/** @var Rule $rule */
@@ -215,7 +216,8 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
$search->whereLike('rules.title', sprintf('%%%s%%', $query));
}
$search->orderBy('rules.order', 'ASC')
->orderBy('rules.title', 'ASC');
->orderBy('rules.title', 'ASC')
;
return $search->take($limit)->get(['id', 'title', 'description']);
}
@@ -225,7 +227,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
*/
public function store(array $data): Rule
{
$ruleGroup = null;
$ruleGroup = null;
if (array_key_exists('rule_group_id', $data)) {
$ruleGroup = $this->user->ruleGroups()->find($data['rule_group_id']);
}
@@ -239,7 +241,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
/** @var RuleGroup $ruleGroup */
// start by creating a new rule:
$rule = new Rule();
$rule = new Rule();
$rule->user()->associate($this->user);
$rule->userGroup()->associate($this->user->userGroup);
@@ -283,7 +285,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
private function setRuleTrigger(string $moment, Rule $rule): void
{
/** @var null|RuleTrigger $trigger */
$trigger = $rule->ruleTriggers()->where('trigger_type', 'user_action')->first();
$trigger = $rule->ruleTriggers()->where('trigger_type', 'user_action')->first();
if (null !== $trigger) {
$trigger->trigger_value = $moment;
$trigger->save();
@@ -311,19 +313,20 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
public function setOrder(Rule $rule, int $newOrder): void
{
$oldOrder = $rule->order;
$groupId = $rule->rule_group_id;
$maxOrder = $this->maxOrder($rule->ruleGroup);
$newOrder = $newOrder > $maxOrder ? $maxOrder + 1 : $newOrder;
$oldOrder = $rule->order;
$groupId = $rule->rule_group_id;
$maxOrder = $this->maxOrder($rule->ruleGroup);
$newOrder = $newOrder > $maxOrder ? $maxOrder + 1 : $newOrder;
app('log')->debug(sprintf('New order will be %d', $newOrder));
if ($newOrder > $oldOrder) {
$this->user->rules()
->where('rules.rule_group_id', $groupId)
->where('rules.order', '<=', $newOrder)
->where('rules.order', '>', $oldOrder)
->where('rules.id', '!=', $rule->id)
->decrement('rules.order');
->where('rules.rule_group_id', $groupId)
->where('rules.order', '<=', $newOrder)
->where('rules.order', '>', $oldOrder)
->where('rules.id', '!=', $rule->id)
->decrement('rules.order')
;
$rule->order = $newOrder;
app('log')->debug(sprintf('Order of rule #%d ("%s") is now %d', $rule->id, $rule->title, $newOrder));
$rule->save();
@@ -332,11 +335,12 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
}
$this->user->rules()
->where('rules.rule_group_id', $groupId)
->where('rules.order', '>=', $newOrder)
->where('rules.order', '<', $oldOrder)
->where('rules.id', '!=', $rule->id)
->increment('rules.order');
->where('rules.rule_group_id', $groupId)
->where('rules.order', '>=', $newOrder)
->where('rules.order', '<', $oldOrder)
->where('rules.id', '!=', $rule->id)
->increment('rules.order')
;
$rule->order = $newOrder;
app('log')->debug(sprintf('Order of rule #%d ("%s") is now %d', $rule->id, $rule->title, $newOrder));
$rule->save();
@@ -361,7 +365,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
// empty the value in case the rule needs no context
// TODO create a helper to automatically return these.
$needTrue = [
$needTrue = [
'reconciled',
'has_attachments',
'has_any_category',
@@ -388,7 +392,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
$value = '';
}
$triggerValues = [
$triggerValues = [
'action' => $type,
'value' => $value,
'stop_processing' => $stopProcessing,
@@ -402,7 +406,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
public function storeTrigger(Rule $rule, array $values): RuleTrigger
{
$ruleTrigger = new RuleTrigger();
$ruleTrigger = new RuleTrigger();
$ruleTrigger->rule()->associate($rule);
$ruleTrigger->order = $values['order'];
$ruleTrigger->active = $values['active'];
@@ -435,7 +439,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
public function storeAction(Rule $rule, array $values): RuleAction
{
$ruleAction = new RuleAction();
$ruleAction = new RuleAction();
$ruleAction->rule()->associate($rule);
$ruleAction->order = $values['order'];
$ruleAction->active = $values['active'];
@@ -465,7 +469,7 @@ class RuleRepository implements RuleRepositoryInterface, UserGroupInterface
}
$rule->save();
$rule->refresh();
$group = $rule->ruleGroup;
$group = $rule->ruleGroup;
// update the order:
$this->resetRuleOrder($group);
if (array_key_exists('order', $data)) {

View File

@@ -42,7 +42,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface RuleRepositoryInterface
{

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\RuleGroup;
use Exception;
use FireflyIII\Models\Rule;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleGroup;
@@ -48,7 +47,8 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('title', 'ASC')
->get(['rule_groups.id']);
->get(['rule_groups.id'])
;
$index = 1;
/** @var RuleGroup $ruleGroup */
@@ -72,7 +72,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
}
/**
* @throws Exception
* @throws \Exception
*/
public function destroy(RuleGroup $ruleGroup, ?RuleGroup $moveTo): bool
{
@@ -105,7 +105,8 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
->whereNull('deleted_at')
->orderBy('order', 'ASC')
->orderBy('title', 'DESC')
->get();
->get()
;
$count = 1;
/** @var RuleGroup $entry */
@@ -127,10 +128,11 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
public function resetRuleOrder(RuleGroup $ruleGroup): bool
{
$set = $ruleGroup->rules()
->orderBy('order', 'ASC')
->orderBy('title', 'DESC')
->orderBy('updated_at', 'DESC')
->get(['rules.*']);
->orderBy('order', 'ASC')
->orderBy('title', 'DESC')
->orderBy('updated_at', 'DESC')
->get(['rules.*'])
;
$count = 1;
/** @var Rule $entry */
@@ -152,10 +154,11 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
private function resetRuleActionOrder(Rule $rule): void
{
$actions = $rule->ruleActions()
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('action_type', 'ASC')
->get();
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('action_type', 'ASC')
->get()
;
$index = 1;
/** @var RuleAction $action */
@@ -172,10 +175,11 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
private function resetRuleTriggerOrder(Rule $rule): void
{
$triggers = $rule->ruleTriggers()
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('trigger_type', 'ASC')
->get();
->orderBy('order', 'ASC')
->orderBy('active', 'DESC')
->orderBy('trigger_type', 'ASC')
->get()
;
$index = 1;
/** @var RuleTrigger $trigger */
@@ -222,47 +226,51 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
public function getActiveRules(RuleGroup $group): Collection
{
return $group->rules()
->where('rules.active', true)
->get(['rules.*']);
->where('rules.active', true)
->get(['rules.*'])
;
}
public function getActiveStoreRules(RuleGroup $group): Collection
{
return $group->rules()
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
->where('rule_triggers.trigger_value', 'store-journal')
->where('rules.active', true)
->get(['rules.*']);
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
->where('rule_triggers.trigger_value', 'store-journal')
->where('rules.active', true)
->get(['rules.*'])
;
}
public function getActiveUpdateRules(RuleGroup $group): Collection
{
return $group->rules()
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
->where('rule_triggers.trigger_value', 'update-journal')
->where('rules.active', true)
->get(['rules.*']);
->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rule_triggers.trigger_type', 'user_action')
->where('rule_triggers.trigger_value', 'update-journal')
->where('rules.active', true)
->get(['rules.*'])
;
}
public function getAllRuleGroupsWithRules(?string $filter): Collection
{
$groups = $this->user->ruleGroups()
->orderBy('order', 'ASC')
->with(
[ // @phpstan-ignore-line
'rules' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
'rules.ruleTriggers' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
'rules.ruleActions' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
]
)->get();
->orderBy('order', 'ASC')
->with(
[ // @phpstan-ignore-line
'rules' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
'rules.ruleTriggers' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
'rules.ruleActions' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
]
)->get()
;
if (null === $filter) {
return $groups;
}
@@ -303,21 +311,22 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
public function getRuleGroupsWithRules(?string $filter): Collection
{
$groups = $this->user->ruleGroups()
->orderBy('order', 'ASC')
->where('active', true)
->with(
[ // @phpstan-ignore-line
'rules' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
'rules.ruleTriggers' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
'rules.ruleActions' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
]
)->get();
->orderBy('order', 'ASC')
->where('active', true)
->with(
[ // @phpstan-ignore-line
'rules' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
'rules.ruleTriggers' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
'rules.ruleActions' => static function (HasMany $query): void {
$query->orderBy('order', 'ASC');
},
]
)->get()
;
if (null === $filter) {
return $groups;
}
@@ -351,7 +360,8 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
public function getRules(RuleGroup $group): Collection
{
return $group->rules()
->get(['rules.*']);
->get(['rules.*'])
;
}
public function maxOrder(): int
@@ -366,7 +376,8 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
$search->whereLike('rule_groups.title', sprintf('%%%s%%', $query));
}
$search->orderBy('rule_groups.order', 'ASC')
->orderBy('rule_groups.title', 'ASC');
->orderBy('rule_groups.title', 'ASC')
;
return $search->take($limit)->get(['id', 'title', 'description']);
}
@@ -394,12 +405,13 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
public function setOrder(RuleGroup $ruleGroup, int $newOrder): void
{
$oldOrder = $ruleGroup->order;
$oldOrder = $ruleGroup->order;
if ($newOrder > $oldOrder) {
$this->user->ruleGroups()->where('rule_groups.order', '<=', $newOrder)->where('rule_groups.order', '>', $oldOrder)
->where('rule_groups.id', '!=', $ruleGroup->id)
->decrement('order');
->where('rule_groups.id', '!=', $ruleGroup->id)
->decrement('order')
;
$ruleGroup->order = $newOrder;
app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
$ruleGroup->save();
@@ -408,8 +420,9 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte
}
$this->user->ruleGroups()->where('rule_groups.order', '>=', $newOrder)->where('rule_groups.order', '<', $oldOrder)
->where('rule_groups.id', '!=', $ruleGroup->id)
->increment('order');
->where('rule_groups.id', '!=', $ruleGroup->id)
->increment('order')
;
$ruleGroup->order = $newOrder;
app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder));
$ruleGroup->save();

View File

@@ -39,7 +39,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface RuleGroupRepositoryInterface
{

View File

@@ -47,9 +47,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $tags = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$tagIds = [];
$tagIds = [];
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
@@ -66,7 +66,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$array = [];
$listedJournals = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
'tags' => [],
'currency_id' => $currencyId,
@@ -78,9 +78,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// may have multiple tags:
foreach ($journal['tags'] as $tag) {
$tagId = (int) $tag['id'];
$tagName = (string) $tag['name'];
$journalId = (int) $journal['transaction_journal_id'];
$tagId = (int) $tag['id'];
$tagName = (string) $tag['name'];
$journalId = (int) $journal['transaction_journal_id'];
if (!in_array($tagId, $tagIds, true)) {
continue;
}
@@ -89,7 +89,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
if (in_array($journalId, $listedJournals, true)) {
continue;
}
$listedJournals[] = $journalId;
$listedJournals[] = $journalId;
$array[$currencyId]['tags'][$tagId] ??= [
'id' => $tagId,
'name' => $tagName,
@@ -130,9 +130,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
public function listIncome(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $tags = null): array
{
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$tagIds = [];
$tagIds = [];
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
@@ -150,7 +150,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$listedJournals = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [
'tags' => [],
'currency_id' => $currencyId,
@@ -162,9 +162,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// may have multiple tags:
foreach ($journal['tags'] as $tag) {
$tagId = (int) $tag['id'];
$tagName = (string) $tag['name'];
$journalId = (int) $journal['transaction_journal_id'];
$tagId = (int) $tag['id'];
$tagName = (string) $tag['name'];
$journalId = (int) $journal['transaction_journal_id'];
if (!in_array($tagId, $tagIds, true)) {
continue;
@@ -173,9 +173,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
if (in_array($journalId, $listedJournals, true)) {
continue;
}
$listedJournals[] = $journalId;
$listedJournals[] = $journalId;
$array[$currencyId]['tags'][$tagId] ??= [
$array[$currencyId]['tags'][$tagId] ??= [
'id' => $tagId,
'name' => $tagName,
'transaction_journals' => [],

View File

@@ -40,7 +40,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface OperationsRepositoryInterface
{

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Tag;
use Carbon\Carbon;
use Exception;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Factory\TagFactory;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -52,7 +51,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
}
/**
* @throws Exception
* @throws \Exception
*/
public function destroy(Tag $tag): bool
{
@@ -120,7 +119,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
return $set->each(
static function (Attachment $attachment) use ($disk): void { // @phpstan-ignore-line
/** @var null|Note $note */
$note = $attachment->notes()->first();
$note = $attachment->notes()->first();
// only used in v1 view of tags
$attachment->file_exists = $disk->exists($attachment->fileName());
$attachment->notes_text = null === $note ? '' : $note->text;
@@ -131,7 +130,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
public function getTagsInYear(?int $year): array
{
// get all tags in the year (if present):
$tagQuery = $this->user->tags()->with(['locations', 'attachments'])->orderBy('tags.tag');
$tagQuery = $this->user->tags()->with(['locations', 'attachments'])->orderBy('tags.tag');
// add date range (or not):
if (null === $year) {
@@ -141,7 +140,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
if (null !== $year) {
app('log')->debug(sprintf('Get tags with year %s.', $year));
$tagQuery->where('tags.date', '>=', $year . '-01-01 00:00:00')->where('tags.date', '<=', $year . '-12-31 23:59:59');
$tagQuery->where('tags.date', '>=', $year.'-01-01 00:00:00')->where('tags.date', '<=', $year.'-12-31 23:59:59');
}
$collection = $tagQuery->get();
$return = [];
@@ -236,13 +235,13 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
}
$collector->setTag($tag)->withAccountInformation();
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
$sums = [];
$sums = [];
/** @var array $journal */
foreach ($journals as $journal) {
$found = false;
$found = false;
/** @var array $localTag */
foreach ($journal['tags'] as $localTag) {
@@ -253,7 +252,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
if (false === $found) {
continue;
}
$currencyId = (int) $journal['currency_id'];
$currencyId = (int) $journal['currency_id'];
$sums[$currencyId] ??= [
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
@@ -267,14 +266,14 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
];
// add amount to correct type:
$amount = app('steam')->positive((string) $journal['amount']);
$type = $journal['transaction_type_type'];
$amount = app('steam')->positive((string) $journal['amount']);
$type = $journal['transaction_type_type'];
if (TransactionTypeEnum::WITHDRAWAL->value === $type) {
$amount = bcmul($amount, '-1');
}
$sums[$currencyId][$type] = bcadd($sums[$currencyId][$type], $amount);
$foreignCurrencyId = $journal['foreign_currency_id'];
$foreignCurrencyId = $journal['foreign_currency_id'];
if (null !== $foreignCurrencyId && 0 !== $foreignCurrencyId) {
$sums[$foreignCurrencyId] ??= [
'currency_id' => $foreignCurrencyId,
@@ -288,7 +287,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
TransactionTypeEnum::OPENING_BALANCE->value => '0',
];
// add foreign amount to correct type:
$amount = app('steam')->positive((string) $journal['foreign_amount']);
$amount = app('steam')->positive((string) $journal['foreign_amount']);
if (TransactionTypeEnum::WITHDRAWAL->value === $type) {
$amount = bcmul($amount, '-1');
}
@@ -353,7 +352,7 @@ class TagRepository implements TagRepositoryInterface, UserGroupInterface
// otherwise, update or create.
if (!(null === $data['latitude'] && null === $data['longitude'] && null === $data['zoom_level'])) {
$location = $this->getLocation($tag);
$location = $this->getLocation($tag);
if (null === $location) {
$location = new Location();
$location->locatable()->associate($tag);

View File

@@ -41,7 +41,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface TagRepositoryInterface
{

View File

@@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\TransactionGroup;
use Carbon\Carbon;
use Exception;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
@@ -141,21 +140,22 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
{
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($this->user);
$journals = $group->transactionJournals->pluck('id')->toArray();
$set = Attachment::whereIn('attachable_id', $journals)
->where('attachable_type', TransactionJournal::class)
->where('uploaded', true)
->whereNull('deleted_at')->get();
$journals = $group->transactionJournals->pluck('id')->toArray();
$set = Attachment::whereIn('attachable_id', $journals)
->where('attachable_type', TransactionJournal::class)
->where('uploaded', true)
->whereNull('deleted_at')->get()
;
$result = [];
$result = [];
/** @var Attachment $attachment */
foreach ($set as $attachment) {
$journalId = $attachment->attachable_id;
$result[$journalId] ??= [];
$current = $attachment->toArray();
$current['file_exists'] = true;
$current['notes'] = $repository->getNoteText($attachment);
$journalId = $attachment->attachable_id;
$result[$journalId] ??= [];
$current = $attachment->toArray();
$current['file_exists'] = true;
$current['notes'] = $repository->getNoteText($attachment);
// already determined that this attachable is a TransactionJournal.
$current['journal_title'] = $attachment->attachable->description;
$result[$journalId][] = $current;
@@ -171,8 +171,9 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
{
/** @var null|Note $note */
$note = Note::where('noteable_id', $journalId)
->where('noteable_type', TransactionJournal::class)
->first();
->where('noteable_type', TransactionJournal::class)
->first()
;
if (null === $note) {
return null;
}
@@ -193,13 +194,14 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
$q->orWhereIn('destination_id', $journals);
}
)
->with(['source', 'destination', 'source.transactions'])
->leftJoin('link_types', 'link_types.id', '=', 'journal_links.link_type_id')
->get(['journal_links.*', 'link_types.inward', 'link_types.outward', 'link_types.editable']);
->with(['source', 'destination', 'source.transactions'])
->leftJoin('link_types', 'link_types.id', '=', 'journal_links.link_type_id')
->get(['journal_links.*', 'link_types.inward', 'link_types.outward', 'link_types.editable'])
;
/** @var TransactionJournalLink $entry */
foreach ($set as $entry) {
$journalId = in_array($entry->source_id, $journals, true) ? $entry->source_id : $entry->destination_id;
$journalId = in_array($entry->source_id, $journals, true) ? $entry->source_id : $entry->destination_id;
$return[$journalId] ??= [];
// phpstan: the editable field is provided by the query.
@@ -263,10 +265,10 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
if (0 === bccomp('0', $transaction->foreign_amount)) {
return '';
}
$currency = $transaction->foreignCurrency;
$type = $journal->transactionType->type;
$amount = app('steam')->positive($transaction->foreign_amount);
$return = '';
$currency = $transaction->foreignCurrency;
$type = $journal->transactionType->type;
$amount = app('steam')->positive($transaction->foreign_amount);
$return = '';
if (TransactionTypeEnum::WITHDRAWAL->value === $type) {
$return = app('amount')->formatAnything($currency, app('steam')->negative($amount));
}
@@ -289,15 +291,16 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
/**
* Return object with all found meta field things as Carbon objects.
*
* @throws Exception
* @throws \Exception
*/
public function getMetaDateFields(int $journalId, array $fields): NullArrayObject
{
$query = DB::table('journal_meta')
->where('transaction_journal_id', $journalId)
->whereIn('name', $fields)
->whereNull('deleted_at')
->get(['name', 'data']);
->where('transaction_journal_id', $journalId)
->whereIn('name', $fields)
->whereNull('deleted_at')
->get(['name', 'data'])
;
$return = [];
foreach ($query as $row) {
@@ -313,10 +316,11 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
public function getMetaFields(int $journalId, array $fields): NullArrayObject
{
$query = DB::table('journal_meta')
->where('transaction_journal_id', $journalId)
->whereIn('name', $fields)
->whereNull('deleted_at')
->get(['name', 'data']);
->where('transaction_journal_id', $journalId)
->whereIn('name', $fields)
->whereNull('deleted_at')
->get(['name', 'data'])
;
$return = [];
foreach ($query as $row) {
@@ -337,8 +341,9 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
$journals = $group->transactionJournals->pluck('id')->toArray();
$currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
$data = PiggyBankEvent::whereIn('transaction_journal_id', $journals)
->with('piggyBank', 'piggyBank.account')
->get(['piggy_bank_events.*']);
->with('piggyBank', 'piggyBank.account')
->get(['piggy_bank_events.*'])
;
/** @var PiggyBankEvent $row */
foreach ($data as $row) {
@@ -346,13 +351,14 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
continue;
}
// get currency preference.
$currencyPreference = AccountMeta::where('account_id', $row->piggyBank->account_id)
->where('name', 'currency_id')
->first();
$currencyPreference = AccountMeta::where('account_id', $row->piggyBank->account_id)
->where('name', 'currency_id')
->first()
;
if (null !== $currencyPreference) {
$currency = TransactionCurrency::where('id', $currencyPreference->data)->first();
}
$journalId = $row->transaction_journal_id;
$journalId = $row->transaction_journal_id;
$return[$journalId] ??= [];
$return[$journalId][] = [
@@ -379,10 +385,11 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface,
public function getTags(int $journalId): array
{
$result = DB::table('tag_transaction_journal')
->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id')
->where('tag_transaction_journal.transaction_journal_id', $journalId)
->orderBy('tags.tag', 'ASC')
->get(['tags.tag']);
->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id')
->where('tag_transaction_journal.transaction_journal_id', $journalId)
->orderBy('tags.tag', 'ASC')
->get(['tags.tag'])
;
return $result->pluck('tag')->toArray();
}

View File

@@ -44,7 +44,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface TransactionGroupRepositoryInterface
{

View File

@@ -42,7 +42,7 @@ class TransactionTypeRepository implements TransactionTypeRepositoryInterface
return $type;
}
$typeString ??= TransactionTypeEnum::WITHDRAWAL->value;
$search = $this->findByType($typeString);
$search = $this->findByType($typeString);
if (null === $search) {
$search = $this->findByType(TransactionTypeEnum::WITHDRAWAL->value);
}

View File

@@ -40,7 +40,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface TransactionTypeRepositoryInterface
{

View File

@@ -23,7 +23,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\User;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\GroupMembership;
@@ -35,7 +34,6 @@ use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Override;
/**
* Class UserRepository.
@@ -46,17 +44,17 @@ class UserRepository implements UserRepositoryInterface
* This updates the users email address and records some things so it can be confirmed or undone later.
* The user is blocked until the change is confirmed.
*
* @throws Exception
* @throws \Exception
*
* @see updateEmail
*/
public function changeEmail(User $user, string $newEmail): bool
{
$oldEmail = $user->email;
$oldEmail = $user->email;
// save old email as pref
app('preferences')->setForUser($user, 'previous_email_latest', $oldEmail);
app('preferences')->setForUser($user, 'previous_email_' . date('Y-m-d-H-i-s'), $oldEmail);
app('preferences')->setForUser($user, 'previous_email_'.date('Y-m-d-H-i-s'), $oldEmail);
// set undo and confirm token:
app('preferences')->setForUser($user, 'email_change_undo_token', bin2hex(random_bytes(16)));
@@ -101,7 +99,7 @@ class UserRepository implements UserRepositoryInterface
}
/**
* @throws Exception
* @throws \Exception
*/
public function destroy(User $user): bool
{
@@ -173,7 +171,7 @@ class UserRepository implements UserRepositoryInterface
public function getRolesInGroup(User $user, int $groupId): array
{
/** @var null|UserGroup $group */
$group = UserGroup::find($groupId);
$group = UserGroup::find($groupId);
if (null === $group) {
throw new FireflyException(sprintf('Could not find group #%d', $groupId));
}
@@ -199,7 +197,7 @@ class UserRepository implements UserRepositoryInterface
*/
public function getUserData(User $user): array
{
$return = [];
$return = [];
// two factor:
$return['has_2fa'] = null !== $user->mfa_secret;
@@ -215,11 +213,12 @@ class UserRepository implements UserRepositoryInterface
$return['categories'] = $user->categories()->count();
$return['budgets'] = $user->budgets()->count();
$return['budgets_with_limits'] = BudgetLimit::distinct()
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->where('amount', '>', 0)
->whereNull('budgets.deleted_at')
->where('budgets.user_id', $user->id)
->count('budget_limits.budget_id');
->leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->where('amount', '>', 0)
->whereNull('budgets.deleted_at')
->where('budgets.user_id', $user->id)
->count('budget_limits.budget_id')
;
$return['rule_groups'] = $user->ruleGroups()->count();
$return['rules'] = $user->rules()->count();
$return['tags'] = $user->tags()->count();
@@ -227,7 +226,7 @@ class UserRepository implements UserRepositoryInterface
return $return;
}
public function hasRole(null | Authenticatable | User $user, string $role): bool
public function hasRole(null|Authenticatable|User $user, string $role): bool
{
if (null === $user) {
return false;
@@ -244,7 +243,7 @@ class UserRepository implements UserRepositoryInterface
return false;
}
#[Override]
#[\Override]
public function getUserGroups(User $user): Collection
{
$memberships = $user->groupMemberships()->get();
@@ -256,7 +255,7 @@ class UserRepository implements UserRepositoryInterface
/** @var null|UserGroup $group */
$group = $membership->userGroup()->first();
if (null !== $group) {
$groupId = $group->id;
$groupId = $group->id;
if (in_array($groupId, array_keys($set), true)) {
continue;
}
@@ -268,14 +267,14 @@ class UserRepository implements UserRepositoryInterface
return $collection;
}
public function inviteUser(null | Authenticatable | User $user, string $email): InvitedUser
public function inviteUser(null|Authenticatable|User $user, string $email): InvitedUser
{
if (!$user instanceof User) {
throw new FireflyException('User is not a User object.');
}
$now = today(config('app.timezone'));
$now = today(config('app.timezone'));
$now->addDays(2);
$invitee = new InvitedUser();
$invitee = new InvitedUser();
$invitee->user()->associate($user);
$invitee->invite_code = Str::random(64);
$invitee->email = $email;
@@ -386,11 +385,11 @@ class UserRepository implements UserRepositoryInterface
if ('' === $newEmail) {
return true;
}
$oldEmail = $user->email;
$oldEmail = $user->email;
// save old email as pref
app('preferences')->setForUser($user, 'admin_previous_email_latest', $oldEmail);
app('preferences')->setForUser($user, 'admin_previous_email_' . date('Y-m-d-H-i-s'), $oldEmail);
app('preferences')->setForUser($user, 'admin_previous_email_'.date('Y-m-d-H-i-s'), $oldEmail);
$user->email = $newEmail;
$user->save();

View File

@@ -40,7 +40,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface UserRepositoryInterface
{
@@ -106,9 +105,9 @@ interface UserRepositoryInterface
public function getUserGroups(User $user): Collection;
public function hasRole(null | Authenticatable | User $user, string $role): bool;
public function hasRole(null|Authenticatable|User $user, string $role): bool;
public function inviteUser(null | Authenticatable | User $user, string $email): InvitedUser;
public function inviteUser(null|Authenticatable|User $user, string $email): InvitedUser;
public function redeemCode(string $code): void;

View File

@@ -35,8 +35,6 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Override;
use ValueError;
/**
* Class UserGroupRepository
@@ -53,7 +51,7 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
/** @var GroupMembership $membership */
foreach ($memberships as $membership) {
/** @var null|User $user */
$user = $membership->user()->first();
$user = $membership->user()->first();
if (null === $user) {
continue;
}
@@ -82,8 +80,8 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
// all users are now moved away from user group.
// time to DESTROY all objects.
// we have to do this one by one to trigger the necessary observers :(
$objects = ['availableBudgets', 'bills', 'budgets', 'categories', 'currencyExchangeRates', 'objectGroups',
'recurrences', 'rules', 'ruleGroups', 'tags', 'transactionGroups', 'transactionJournals', 'piggyBanks', 'accounts', 'webhooks',
$objects = ['availableBudgets', 'bills', 'budgets', 'categories', 'currencyExchangeRates', 'objectGroups',
'recurrences', 'rules', 'ruleGroups', 'tags', 'transactionGroups', 'transactionJournals', 'piggyBanks', 'accounts', 'webhooks',
];
foreach ($objects as $object) {
foreach ($userGroup->{$object}()->get() as $item) { // @phpstan-ignore-line
@@ -110,7 +108,7 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
/** @var null|UserGroup $group */
$group = $membership->userGroup()->first();
if (null !== $group) {
$groupId = $group->id;
$groupId = $group->id;
if (in_array($groupId, array_keys($set), true)) {
continue;
}
@@ -135,14 +133,14 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
while ($exists && $loop < 10) {
$existingGroup = $this->findByName($groupName);
if (null === $existingGroup) {
$exists = false;
$exists = false;
/** @var null|UserGroup $existingGroup */
$existingGroup = $this->store(['user' => $user, 'title' => $groupName]);
}
if (null !== $existingGroup) {
// group already exists
$groupName = sprintf('%s-%s', $user->email, substr(sha1(rand(1000, 9999) . microtime()), 0, 4));
$groupName = sprintf('%s-%s', $user->email, substr(sha1(rand(1000, 9999).microtime()), 0, 4));
}
++$loop;
}
@@ -163,7 +161,7 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
$data['user'] = $this->user;
/** @var UserGroupFactory $factory */
$factory = app(UserGroupFactory::class);
$factory = app(UserGroupFactory::class);
return $factory->create($data);
}
@@ -178,13 +176,13 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
return UserGroup::all();
}
#[Override]
#[\Override]
public function getById(int $id): ?UserGroup
{
return UserGroup::find($id);
}
#[Override]
#[\Override]
public function getMembershipsFromGroupId(int $groupId): Collection
{
return $this->user->groupMemberships()->where('user_group_id', $groupId)->get();
@@ -194,10 +192,10 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
{
$userGroup->title = $data['title'];
$userGroup->save();
$currency = null;
$currency = null;
/** @var CurrencyRepositoryInterface $repository */
$repository = app(CurrencyRepositoryInterface::class);
$repository = app(CurrencyRepositoryInterface::class);
if (array_key_exists('native_currency_code', $data)) {
$repository->setUser($this->user);
@@ -223,11 +221,11 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
*/
public function updateMembership(UserGroup $userGroup, array $data): UserGroup
{
$owner = UserRole::whereTitle(UserRoleEnum::OWNER)->first();
$owner = UserRole::whereTitle(UserRoleEnum::OWNER)->first();
app('log')->debug('in update membership');
/** @var null|User $user */
$user = null;
$user = null;
if (array_key_exists('id', $data)) {
/** @var null|User $user */
$user = User::find($data['id']);
@@ -266,8 +264,9 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
if ($membershipCount > 1) {
// group has multiple members. How many are owner, except the user we're editing now?
$ownerCount = $userGroup->groupMemberships()
->where('user_role_id', $owner->id)
->where('user_id', '!=', $user->id)->count();
->where('user_role_id', $owner->id)
->where('user_id', '!=', $user->id)->count()
;
// if there are no other owners and the current users does not get or keep the owner role, refuse.
if (
0 === $ownerCount
@@ -287,7 +286,7 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
foreach ($rolesSimplified as $role) {
try {
$enum = UserRoleEnum::from($role);
} catch (ValueError $e) {
} catch (\ValueError $e) {
// TODO error message
continue;
}
@@ -314,7 +313,7 @@ class UserGroupRepository implements UserGroupRepositoryInterface, UserGroupInte
return $roles;
}
#[Override]
#[\Override]
public function useUserGroup(UserGroup $userGroup): void
{
$this->user->user_group_id = $userGroup->id;

View File

@@ -39,7 +39,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface UserGroupRepositoryInterface
{

View File

@@ -37,8 +37,6 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Override;
use stdClass;
/**
* Class AccountRepository
@@ -49,7 +47,7 @@ class AccountRepository implements AccountRepositoryInterface
{
use UserGroupTrait;
#[Override]
#[\Override]
public function countAccounts(array $types): int
{
$query = $this->userGroup->accounts();
@@ -72,7 +70,8 @@ class AccountRepository implements AccountRepositoryInterface
$q1->where('account_meta.name', '=', 'account_number');
$q1->where('account_meta.data', '=', $json);
}
);
)
;
if (0 !== count($types)) {
$dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
@@ -99,7 +98,7 @@ class AccountRepository implements AccountRepositoryInterface
public function findByName(string $name, array $types): ?Account
{
$query = $this->userGroup->accounts();
$query = $this->userGroup->accounts();
if (0 !== count($types)) {
$query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id');
@@ -121,7 +120,7 @@ class AccountRepository implements AccountRepositoryInterface
return $account;
}
#[Override]
#[\Override]
public function getAccountBalances(Account $account): Collection
{
return $account->accountBalances;
@@ -129,8 +128,8 @@ class AccountRepository implements AccountRepositoryInterface
public function getAccountCurrency(Account $account): ?TransactionCurrency
{
$type = $account->accountType->type;
$list = config('firefly.valid_currency_account_types');
$type = $account->accountType->type;
$list = config('firefly.valid_currency_account_types');
// return null if not in this list.
if (!in_array($type, $list, true)) {
@@ -175,12 +174,13 @@ class AccountRepository implements AccountRepositoryInterface
return $account;
}
#[Override]
#[\Override]
public function getAccountTypes(Collection $accounts): Collection
{
return AccountType::leftJoin('accounts', 'accounts.account_type_id', '=', 'account_types.id')
->whereIn('accounts.id', $accounts->pluck('id')->toArray())
->get(['accounts.id', 'account_types.type']);
->whereIn('accounts.id', $accounts->pluck('id')->toArray())
->get(['accounts.id', 'account_types.type'])
;
}
public function getAccountsById(array $accountIds): Collection
@@ -197,7 +197,7 @@ class AccountRepository implements AccountRepositoryInterface
return $query->get(['accounts.*']);
}
#[Override]
#[\Override]
public function getAccountsInOrder(array $types, array $sort, int $startRow, int $endRow): Collection
{
$query = $this->userGroup->accounts();
@@ -237,17 +237,17 @@ class AccountRepository implements AccountRepositoryInterface
return $query->get(['accounts.*']);
}
#[Override]
#[\Override]
public function getLastActivity(Collection $accounts): array
{
return Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->groupBy('transactions.account_id')
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line
;
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->groupBy('transactions.account_id')
->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) as date_max')])->toArray() // @phpstan-ignore-line
;
}
#[Override]
#[\Override]
public function getMetaValues(Collection $accounts, array $fields): Collection
{
$query = AccountMeta::whereIn('account_id', $accounts->pluck('id')->toArray());
@@ -258,22 +258,23 @@ class AccountRepository implements AccountRepositoryInterface
return $query->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data']);
}
#[Override]
#[\Override]
public function getObjectGroups(Collection $accounts): array
{
$groupIds = [];
$return = [];
$set = DB::table('object_groupables')->where('object_groupable_type', Account::class)
->whereIn('object_groupable_id', $accounts->pluck('id')->toArray())->get();
->whereIn('object_groupable_id', $accounts->pluck('id')->toArray())->get()
;
/** @var stdClass $row */
/** @var \stdClass $row */
foreach ($set as $row) {
$groupIds[] = $row->object_group_id;
}
$groupIds = array_unique($groupIds);
$groups = ObjectGroup::whereIn('id', $groupIds)->get();
/** @var stdClass $row */
/** @var \stdClass $row */
foreach ($set as $row) {
if (!array_key_exists($row->object_groupable_id, $return)) {
/** @var null|ObjectGroup $group */
@@ -311,17 +312,18 @@ class AccountRepository implements AccountRepositoryInterface
}
}
// reset the rest to zero.
$all = [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::MORTGAGE->value];
$all = [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::MORTGAGE->value];
$this->user->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->whereNotIn('account_types.type', $all)
->update(['order' => 0]);
->whereNotIn('account_types.type', $all)
->update(['order' => 0])
;
}
public function getAccountsByType(array $types, ?array $sort = [], ?array $filters = []): Collection
{
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
$res = array_intersect([AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value], $types);
$query = $this->userGroup->accounts();
$sortable = ['name', 'active']; // TODO yes this is a duplicate array.
$res = array_intersect([AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value], $types);
$query = $this->userGroup->accounts();
if (0 !== count($types)) {
$query->accountTypeIn($types);
}
@@ -368,7 +370,7 @@ class AccountRepository implements AccountRepositoryInterface
return $query->get(['accounts.*']);
}
#[Override]
#[\Override]
public function update(Account $account, array $data): Account
{
/** @var AccountUpdateService $service */
@@ -381,12 +383,13 @@ class AccountRepository implements AccountRepositoryInterface
{
// search by group, not by user
$dbQuery = $this->userGroup->accounts()
->where('active', true)
->orderBy('accounts.updated_at', 'ASC')
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType']);
->where('active', true)
->orderBy('accounts.updated_at', 'ASC')
->orderBy('accounts.order', 'ASC')
->orderBy('accounts.account_type_id', 'ASC')
->orderBy('accounts.name', 'ASC')
->with(['accountType'])
;
// split query on spaces just in case:
if ('' !== trim($query)) {

View File

@@ -73,7 +73,6 @@ interface AccountRepositoryInterface
public function getObjectGroups(Collection $accounts): array;
/**
* Reset order types of the mentioned accounts.
*/

View File

@@ -62,8 +62,9 @@ class BillRepository implements BillRepositoryInterface
public function getBills(): Collection
{
return $this->userGroup->bills()
->orderBy('bills.name', 'ASC')
->get(['bills.*']);
->orderBy('bills.name', 'ASC')
->get(['bills.*'])
;
}
public function sumPaidInRange(Carbon $start, Carbon $end): array
@@ -101,13 +102,13 @@ class BillRepository implements BillRepositoryInterface
/** @var null|Transaction $sourceTransaction */
$sourceTransaction = $transactionJournal->transactions()->where('amount', '<', 0)->first();
if (null !== $sourceTransaction) {
$amount = $sourceTransaction->amount;
$amount = $sourceTransaction->amount;
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead!
$amount = (string) $sourceTransaction->foreign_amount;
}
// convert to native currency
$nativeAmount = $amount;
$nativeAmount = $amount;
if ($currencyId !== $default->id) {
// get rate and convert.
$nativeAmount = $converter->convert($currency, $default, $transactionJournal->date, $amount);
@@ -129,9 +130,10 @@ class BillRepository implements BillRepositoryInterface
public function getActiveBills(): Collection
{
return $this->userGroup->bills()
->where('active', true)
->orderBy('bills.name', 'ASC')
->get(['bills.*']);
->where('active', true)
->orderBy('bills.name', 'ASC')
->get(['bills.*'])
;
}
public function sumUnpaidInRange(Carbon $start, Carbon $end): array
@@ -153,7 +155,7 @@ class BillRepository implements BillRepositoryInterface
$currencyId = $bill->transaction_currency_id;
$average = bcdiv(bcadd($bill->amount_max, $bill->amount_min), '2');
$nativeAverage = $converter->convert($currency, $default, $start, $average);
$return[$currencyId] ??= [
$return[$currencyId] ??= [
'currency_id' => (string) $currency->id,
'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol,
@@ -200,7 +202,7 @@ class BillRepository implements BillRepositoryInterface
// app('log')->debug(sprintf('Currentstart (%s) has become %s.', $currentStart->format('Y-m-d'), $nextExpectedMatch->format('Y-m-d')));
$currentStart = clone $nextExpectedMatch;
$currentStart = clone $nextExpectedMatch;
}
return $set;

View File

@@ -46,13 +46,14 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
$converter = new ExchangeRateConverter();
$default = app('amount')->getNativeCurrency();
$availableBudgets = $this->userGroup->availableBudgets()
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->get();
->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->get()
;
/** @var AvailableBudget $availableBudget */
foreach ($availableBudgets as $availableBudget) {
$currencyId = $availableBudget->transaction_currency_id;
$return[$currencyId] ??= [
$return[$currencyId] ??= [
'currency_id' => $currencyId,
'currency_code' => $availableBudget->transactionCurrency->code,
'currency_symbol' => $availableBudget->transactionCurrency->symbol,

View File

@@ -39,16 +39,18 @@ class BudgetRepository implements BudgetRepositoryInterface
public function getActiveBudgets(): Collection
{
return $this->userGroup->budgets()->where('active', true)
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get();
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get()
;
}
public function getBudgets(): Collection
{
return $this->userGroup->budgets()
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get();
->orderBy('order', 'ASC')
->orderBy('name', 'ASC')
->get()
;
}
}

View File

@@ -59,13 +59,13 @@ class OperationsRepository implements OperationsRepositoryInterface
$collector->setBudgets($this->getBudgets());
}
$collector->withBudgetInformation()->withAccountInformation()->withCategoryInformation();
$journals = $collector->getExtractedJournals();
$array = [];
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$budgetId = (int) $journal['budget_id'];
$budgetName = (string) $journal['budget_name'];
$currencyId = (int) $journal['currency_id'];
$budgetId = (int) $journal['budget_id'];
$budgetName = (string) $journal['budget_name'];
// catch "no budget" entries.
if (0 === $budgetId) {
@@ -73,7 +73,7 @@ class OperationsRepository implements OperationsRepositoryInterface
}
// info about the currency:
$array[$currencyId] ??= [
$array[$currencyId] ??= [
'budgets' => [],
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
@@ -91,8 +91,8 @@ class OperationsRepository implements OperationsRepositoryInterface
// add journal to array:
// only a subset of the fields.
$journalId = (int) $journal['transaction_journal_id'];
$final = [
$journalId = (int) $journal['transaction_journal_id'];
$final = [
'amount' => app('steam')->negative($journal['amount']),
'currency_id' => $journal['currency_id'],
'foreign_amount' => null,

View File

@@ -66,7 +66,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
public function currencyInUseAt(TransactionCurrency $currency): ?string
{
app('log')->debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code));
$countJournals = $this->countJournals($currency);
$countJournals = $this->countJournals($currency);
if ($countJournals > 0) {
app('log')->info(sprintf('Count journals is %d, return true.', $countJournals));
@@ -81,7 +81,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// is being used in accounts:
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((string) $currency->id))->count();
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((string) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -89,7 +89,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// second search using integer check.
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((int) $currency->id))->count();
$meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((int) $currency->id))->count();
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -97,7 +97,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// is being used in bills:
$bills = Bill::where('transaction_currency_id', $currency->id)->count();
$bills = Bill::where('transaction_currency_id', $currency->id)->count();
if ($bills > 0) {
app('log')->info(sprintf('Used in %d bills as currency, return true. ', $bills));
@@ -115,9 +115,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// is being used in accounts (as integer)
$meta = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id')
->whereNull('accounts.deleted_at')
->where('account_meta.name', 'currency_id')->where('account_meta.data', json_encode($currency->id))->count();
$meta = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id')
->whereNull('accounts.deleted_at')
->where('account_meta.name', 'currency_id')->where('account_meta.data', json_encode($currency->id))->count()
;
if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta));
@@ -133,7 +134,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// is being used in budget limits
$budgetLimit = BudgetLimit::where('transaction_currency_id', $currency->id)->count();
$budgetLimit = BudgetLimit::where('transaction_currency_id', $currency->id)->count();
if ($budgetLimit > 0) {
app('log')->info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit));
@@ -141,7 +142,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// is the default currency for the user or the system
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
app('log')->info('Is the default currency of the user, return true.');
@@ -149,7 +150,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
}
// is the default currency for the user or the system
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
$count = $this->userGroup->currencies()->where('transaction_currencies.id', $currency->id)->wherePivot('group_default', 1)->count();
if ($count > 0) {
app('log')->info('Is the default currency of the user group, return true.');

View File

@@ -30,7 +30,6 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Override;
/**
* Class ExchangeRateRepository
@@ -41,51 +40,55 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface
{
use UserGroupTrait;
#[Override]
#[\Override]
public function deleteRate(CurrencyExchangeRate $rate): void
{
$this->userGroup->currencyExchangeRates()->where('id', $rate->id)->delete();
}
#[Override]
#[\Override]
public function getAll(): Collection
{
return $this->userGroup->currencyExchangeRates()->orderBy('date', 'ASC')->get();
}
#[Override]
#[\Override]
public function getRates(TransactionCurrency $from, TransactionCurrency $to): Collection
{
// orderBy('date', 'DESC')->toRawSql();
return
$this->userGroup->currencyExchangeRates()
->where(function (Builder $q1) use ($from, $to): void {
$q1->where(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id);
})->orWhere(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $to->id)
->where('to_currency_id', $from->id);
});
})
->orderBy('date', 'DESC')
->get(['currency_exchange_rates.*']);
->where(function (Builder $q1) use ($from, $to): void {
$q1->where(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
;
})->orWhere(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $to->id)
->where('to_currency_id', $from->id)
;
});
})
->orderBy('date', 'DESC')
->get(['currency_exchange_rates.*'])
;
}
#[Override]
#[\Override]
public function getSpecificRateOnDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): ?CurrencyExchangeRate
{
/** @var null|CurrencyExchangeRate */
return
$this->userGroup->currencyExchangeRates()
->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
->where('date', $date->format('Y-m-d'))
->first();
->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
->where('date', $date->format('Y-m-d'))
->first()
;
}
#[Override]
#[\Override]
public function storeExchangeRate(TransactionCurrency $from, TransactionCurrency $to, string $rate, Carbon $date): CurrencyExchangeRate
{
$object = new CurrencyExchangeRate();
@@ -101,7 +104,7 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface
return $object;
}
#[Override]
#[\Override]
public function updateExchangeRate(CurrencyExchangeRate $object, string $rate, ?Carbon $date = null): CurrencyExchangeRate
{
$object->rate = $rate;

View File

@@ -40,7 +40,8 @@ class JournalRepository implements JournalRepositoryInterface
public function searchJournalDescriptions(array $query, int $limit): Collection
{
$search = $this->userGroup->transactionJournals()
->orderBy('date', 'DESC');
->orderBy('date', 'DESC')
;
if (count($query) > 0) {
// split query on spaces just in case:
$search->where(function (EloquentBuilder $q) use ($query): void {

View File

@@ -40,13 +40,14 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function getPiggyBanks(): Collection
{
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_group_id', $this->userGroup->id)
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->distinct()->get(['piggy_banks.*']);
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_group_id', $this->userGroup->id)
->with(
[
'objectGroups',
]
)
->orderBy('piggy_banks.order', 'ASC')->distinct()->get(['piggy_banks.*'])
;
}
}

View File

@@ -67,21 +67,23 @@ class WebhookRepository implements WebhookRepositoryInterface, UserGroupInterfac
public function getMessages(Webhook $webhook): Collection
{
return $webhook->webhookMessages()
->orderBy('created_at', 'DESC')
->get(['webhook_messages.*']);
->orderBy('created_at', 'DESC')
->get(['webhook_messages.*'])
;
}
public function getReadyMessages(Webhook $webhook): Collection
{
return $webhook->webhookMessages()
->where('webhook_messages.sent', 0)
->where('webhook_messages.errored', 0)
->get(['webhook_messages.*'])
->filter(
static function (WebhookMessage $message) { // @phpstan-ignore-line
return $message->webhookAttempts()->count() <= 2;
}
)->splice(0, 3);
->where('webhook_messages.sent', 0)
->where('webhook_messages.errored', 0)
->get(['webhook_messages.*'])
->filter(
static function (WebhookMessage $message) { // @phpstan-ignore-line
return $message->webhookAttempts()->count() <= 2;
}
)->splice(0, 3)
;
}
public function store(array $data): Webhook

View File

@@ -42,7 +42,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId)
*
*/
interface WebhookRepositoryInterface
{

View File

@@ -31,6 +31,7 @@ use Illuminate\Support\Facades\Log;
class IsValidZeroOrMoreAmount implements ValidationRule
{
use ValidatesAmountsTrait;
private bool $nullable = false;
public function __construct(bool $nullable = false)
@@ -38,9 +39,6 @@ class IsValidZeroOrMoreAmount implements ValidationRule
$this->nullable = $nullable;
}
use ValidatesAmountsTrait;
/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/

View File

@@ -1,4 +1,5 @@
<?php
/*
* Timer.php
* Copyright (c) 2025 james@firefly-iii.org.
@@ -42,5 +43,4 @@ class Timer
unset(self::$times[$title]);
Log::debug(sprintf('Timer "%s" took %f seconds', $title, $diff));
}
}

View File

@@ -36,7 +36,6 @@ use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Debug\Timer;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Trait PeriodOverview.
@@ -82,10 +81,10 @@ trait PeriodOverview
Timer::start('account-period-total');
$this->accountRepository = app(AccountRepositoryInterface::class);
$range = app('navigation')->getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
// properties for cache
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('account-show-period-entries');
@@ -95,38 +94,40 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
// run a custom query because doing this with the collector is MEGA slow.
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
// loop dates
foreach ($dates as $currentDate) {
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $transferredAway] = $this->filterTransfers('away',$transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $transferredIn] = $this->filterTransfers('in',$transactions, $currentDate['start'], $currentDate['end']);
$title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']);
[$transactions, $spent] = $this->filterTransactionsByType(TransactionTypeEnum::WITHDRAWAL, $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $earned] = $this->filterTransactionsByType(TransactionTypeEnum::DEPOSIT, $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $transferredAway] = $this->filterTransfers('away', $transactions, $currentDate['start'], $currentDate['end']);
[$transactions, $transferredIn] = $this->filterTransfers('in', $transactions, $currentDate['start'], $currentDate['end']);
$entries[]
= [
'title' => $title,
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred_away' => $this->groupByCurrency($transferredAway),
'transferred_in' => $this->groupByCurrency($transferredIn),
];
= [
'title' => $title,
'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred_away' => $this->groupByCurrency($transferredAway),
'transferred_in' => $this->groupByCurrency($transferredIn),
];
}
$cache->store($entries);
Timer::stop('account-period-total');
return $entries;
}
private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array
private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array
{
$result = [];
/**
* @var int $index
* @var array $item
@@ -134,29 +135,31 @@ trait PeriodOverview
foreach ($transactions as $index => $item) {
$date = Carbon::parse($item['date']);
if ($date >= $start && $date <= $end) {
if ($direction === 'away' && bccomp($item['amount'], '0') === -1) {
if ('away' === $direction && -1 === bccomp($item['amount'], '0')) {
$result[] = $item;
unset($transactions[$index]);
}
if ($direction === 'in' && bccomp($item['amount'], '0') === 1) {
if ('in' === $direction && 1 === bccomp($item['amount'], '0')) {
$result[] = $item;
unset($transactions[$index]);
}
}
}
return [$transactions, $result];
}
private function filterTransactionsByType(TransactionTypeEnum $type, array $transactions, Carbon $start, Carbon $end): array
{
$result = [];
/**
* @var int $index
* @var array $item
*/
foreach ($transactions as $index => $item) {
$date = Carbon::parse($item['date']);
if($item['type'] === $type->value && $date >= $start && $date <= $end) {
if ($item['type'] === $type->value && $date >= $start && $date <= $end) {
$result[] = $item;
unset($transactions[$index]);
}
@@ -222,13 +225,13 @@ trait PeriodOverview
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$foreignCurrencyId = $journal['foreign_currency_id'];
$amount = $journal['amount'] ?? '0';
$currencyId = (int) $journal['currency_id'];
$currencyCode = $journal['currency_code'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyDecimalPlaces = $journal['currency_decimal_places'];
$foreignCurrencyId = $journal['foreign_currency_id'];
$amount = $journal['amount'] ?? '0';
if ($this->convertToNative && $currencyId !== $this->defaultCurrency->id && $foreignCurrencyId !== $this->defaultCurrency->id) {
$amount = $journal['native_amount'] ?? '0';
@@ -271,11 +274,11 @@ trait PeriodOverview
*/
protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array
{
$range = app('navigation')->getViewRange(true);
$range = app('navigation')->getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
// properties for entries with their amounts.
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($range);
@@ -287,32 +290,32 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
// collect all expenses in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setCategory($category);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$earnedSet = $collector->getExtractedJournals();
$earnedSet = $collector->getExtractedJournals();
// collect all income in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setCategory($category);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$spentSet = $collector->getExtractedJournals();
$spentSet = $collector->getExtractedJournals();
// collect all transfers in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setCategory($category);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
$transferSet = $collector->getExtractedJournals();
$transferSet = $collector->getExtractedJournals();
foreach ($dates as $currentDate) {
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
$earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
@@ -320,17 +323,17 @@ trait PeriodOverview
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'transactions' => 0,
'title' => $title,
'route' => route(
'categories.show',
[$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
'transactions' => 0,
'title' => $title,
'route' => route(
'categories.show',
[$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
}
$cache->store($entries);
@@ -346,11 +349,11 @@ trait PeriodOverview
*/
protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array
{
$range = app('navigation')->getViewRange(true);
$range = app('navigation')->getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($this->convertToNative);
@@ -361,28 +364,28 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
// get all expenses without a budget.
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$journals = $collector->getExtractedJournals();
$journals = $collector->getExtractedJournals();
foreach ($dates as $currentDate) {
$set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']);
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'title' => $title,
'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($set),
'spent' => $this->groupByCurrency($set),
'earned' => [],
'transferred_away' => [],
'transferred_in' => [],
];
'title' => $title,
'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($set),
'spent' => $this->groupByCurrency($set),
'earned' => [],
'transferred_away' => [],
'transferred_in' => [],
];
}
$cache->store($entries);
@@ -399,38 +402,38 @@ trait PeriodOverview
protected function getNoCategoryPeriodOverview(Carbon $theDate): array
{
app('log')->debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
$range = app('navigation')->getViewRange(true);
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon() : $first->date;
$end = clone $theDate;
$end = app('navigation')->endOfPeriod($end, $range);
$range = app('navigation')->getViewRange(true);
$first = $this->journalRepos->firstNull();
$start = null === $first ? new Carbon() : $first->date;
$end = clone $theDate;
$end = app('navigation')->endOfPeriod($end, $range);
app('log')->debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
app('log')->debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
// properties for cache
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
// collect all expenses in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$earnedSet = $collector->getExtractedJournals();
$earnedSet = $collector->getExtractedJournals();
// collect all income in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$spentSet = $collector->getExtractedJournals();
$spentSet = $collector->getExtractedJournals();
// collect all transfers in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->withoutCategory();
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
@@ -444,13 +447,13 @@ trait PeriodOverview
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'title' => $title,
'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
'title' => $title,
'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
}
app('log')->debug('End of loops');
@@ -464,11 +467,11 @@ trait PeriodOverview
*/
protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags.
{
$range = app('navigation')->getViewRange(true);
$range = app('navigation')->getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
// properties for cache
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('tag-period-entries');
@@ -478,37 +481,37 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
// collect all expenses in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTag($tag);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$earnedSet = $collector->getExtractedJournals();
$earnedSet = $collector->getExtractedJournals();
// collect all income in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTag($tag);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$spentSet = $collector->getExtractedJournals();
$spentSet = $collector->getExtractedJournals();
// collect all transfers in this period:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTag($tag);
$collector->setRange($start, $end);
$collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
$transferSet = $collector->getExtractedJournals();
$transferSet = $collector->getExtractedJournals();
// filer all of them:
$earnedSet = $this->filterJournalsByTag($earnedSet, $tag);
$spentSet = $this->filterJournalsByTag($spentSet, $tag);
$transferSet = $this->filterJournalsByTag($transferSet, $tag);
$earnedSet = $this->filterJournalsByTag($earnedSet, $tag);
$spentSet = $this->filterJournalsByTag($spentSet, $tag);
$transferSet = $this->filterJournalsByTag($transferSet, $tag);
foreach ($dates as $currentDate) {
$spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
@@ -517,17 +520,17 @@ trait PeriodOverview
$title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']);
$entries[]
= [
'transactions' => 0,
'title' => $title,
'route' => route(
'tags.show',
[$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
'transactions' => 0,
'title' => $title,
'route' => route(
'tags.show',
[$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
}
return $entries;
@@ -537,7 +540,7 @@ trait PeriodOverview
{
$return = [];
foreach ($set as $entry) {
$found = false;
$found = false;
/** @var array $localTag */
foreach ($entry['tags'] as $localTag) {
@@ -559,12 +562,12 @@ trait PeriodOverview
*/
protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array
{
$range = app('navigation')->getViewRange(true);
$types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType));
$range = app('navigation')->getViewRange(true);
$types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType));
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
// properties for cache
$cache = new CacheProperties();
$cache = new CacheProperties();
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('transactions-period-entries');
@@ -574,13 +577,13 @@ trait PeriodOverview
}
/** @var array $dates */
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
$dates = app('navigation')->blockPeriods($start, $end, $range);
$entries = [];
// collect all journals in this period (regardless of type)
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector->setTypes($types)->setRange($start, $end);
$genericSet = $collector->getExtractedJournals();
$genericSet = $collector->getExtractedJournals();
foreach ($dates as $currentDate) {
$spent = [];
@@ -599,14 +602,14 @@ trait PeriodOverview
$transferred = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
}
$entries[]
= [
'title' => $title,
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
= [
'title' => $title,
'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
'total_transactions' => count($spent) + count($earned) + count($transferred),
'spent' => $this->groupByCurrency($spent),
'earned' => $this->groupByCurrency($earned),
'transferred' => $this->groupByCurrency($transferred),
];
}
return $entries;

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -49,7 +48,6 @@ use Illuminate\Support\Facades\Log;
*/
class AccountEnrichment implements EnrichmentInterface
{
private Collection $collection;
private User $user;

View File

@@ -72,7 +72,7 @@ class Preferences
public function getForUser(User $user, string $name, null|array|bool|int|string $default = null): ?Preference
{
//Log::debug(sprintf('getForUser(#%d, "%s")', $user->id, $name));
// Log::debug(sprintf('getForUser(#%d, "%s")', $user->id, $name));
// don't care about user group ID, except for some specific preferences.
$userGroupId = $this->getUserGroupId($user, $name);
$query = Preference::where('user_id', $user->id)->where('name', $name);
@@ -90,7 +90,7 @@ class Preferences
}
if (null !== $preference) {
//Log::debug(sprintf('Found preference #%d for user #%d: %s', $preference->id, $user->id, $name));
// Log::debug(sprintf('Found preference #%d for user #%d: %s', $preference->id, $user->id, $name));
return $preference;
}

View File

@@ -400,14 +400,14 @@ trait ConvertsDataTypes
if (!is_array($entry)) {
continue;
}
$amount = null;
if(array_key_exists('current_amount',$entry)) {
$amount = null;
if (array_key_exists('current_amount', $entry)) {
$amount = $this->clearString((string) ($entry['current_amount'] ?? '0'));
if(null === $entry['current_amount']) {
if (null === $entry['current_amount']) {
$amount = null;
}
}
if(!array_key_exists('current_amount',$entry)) {
if (!array_key_exists('current_amount', $entry)) {
$amount = null;
}
$return[] = [

View File

@@ -63,13 +63,13 @@ class AccountValidator
*/
public function __construct()
{
$this->createMode = false;
$this->destError = 'No error yet.';
$this->sourceError = 'No error yet.';
$this->combinations = config('firefly.source_dests');
$this->source = null;
$this->destination = null;
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->createMode = false;
$this->destError = 'No error yet.';
$this->sourceError = 'No error yet.';
$this->combinations = config('firefly.source_dests');
$this->source = null;
$this->destination = null;
$this->accountRepository = app(AccountRepositoryInterface::class);
}
public function getSource(): ?Account

View File

@@ -31,15 +31,15 @@ trait ValidatesBulkTransactionQuery
{
protected function validateTransactionQuery(Validator $validator): void
{
$data = $validator->getData();
$data = $validator->getData();
// assumption is all validation has already taken place and the query key exists.
$query =$data['query'] ?? '[]';
$json = json_decode($query, true, 8, JSON_THROW_ON_ERROR);
$query = $data['query'] ?? '[]';
$json = json_decode($query, true, 8, JSON_THROW_ON_ERROR);
if (
array_key_exists('where', $json) &&
array_key_exists('update', $json) &&
array_key_exists('account_id', $json['where']) && array_key_exists('account_id', $json['update'])
array_key_exists('where', $json)
&& array_key_exists('update', $json)
&& array_key_exists('account_id', $json['where']) && array_key_exists('account_id', $json['update'])
) {
// find both accounts, must be same type.
// already validated: belongs to this user.

16
composer.lock generated
View File

@@ -1878,16 +1878,16 @@
},
{
"name": "laravel/framework",
"version": "v11.44.1",
"version": "v11.44.2",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d"
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/0883d4175f4e2b5c299e7087ad3c74f2ce195c6d",
"reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d",
"url": "https://api.github.com/repos/laravel/framework/zipball/f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"shasum": ""
},
"require": {
@@ -2089,7 +2089,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-03-05T15:34:10+00:00"
"time": "2025-03-12T14:34:30+00:00"
},
{
"name": "laravel/passport",
@@ -12876,7 +12876,7 @@
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
@@ -12897,6 +12897,6 @@
"ext-xml": "*",
"ext-xmlwriter": "*"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"
"platform-dev": {},
"plugin-api-version": "2.6.0"
}

View File

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

View File

@@ -24,241 +24,241 @@ declare(strict_types=1);
return [
'operators' => [
'user_action' => ['alias' => false, 'needs_context' => true],
'account_id' => ['alias' => false, 'needs_context' => true],
'reconciled' => ['alias' => false, 'needs_context' => false],
'source_account_id' => ['alias' => false, 'needs_context' => true],
'destination_account_id' => ['alias' => false, 'needs_context' => true],
'transaction_type' => ['alias' => false, 'needs_context' => true],
'type' => ['alias' => true, 'alias_for' => 'transaction_type', 'needs_context' => true],
'tag_is' => ['alias' => false, 'needs_context' => true],
'tag_is_not' => ['alias' => false, 'needs_context' => true],
'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true],
'tag_contains' => ['alias' => false, 'needs_context' => true],
'tag_ends' => ['alias' => false, 'needs_context' => true],
'tag_starts' => ['alias' => false, 'needs_context' => true],
'description_is' => ['alias' => false, 'needs_context' => true],
'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true],
'description_contains' => ['alias' => false, 'needs_context' => true],
'description_ends' => ['alias' => false, 'needs_context' => true],
'description_starts' => ['alias' => false, 'needs_context' => true],
'notes_is' => ['alias' => false, 'needs_context' => true],
'notes_are' => ['alias' => true, 'alias_for' => 'notes_is', 'needs_context' => true],
'notes_contains' => ['alias' => false, 'needs_context' => true],
'notes_contain' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes_ends' => ['alias' => false, 'needs_context' => true],
'notes_end' => ['alias' => true, 'alias_for' => 'notes_ends', 'needs_context' => true],
'notes_starts' => ['alias' => false, 'needs_context' => true],
'notes_start' => ['alias' => true, 'alias_for' => 'notes_starts', 'needs_context' => true],
'source_account_is' => ['alias' => false, 'needs_context' => true],
'from_account_is' => ['alias' => true, 'alias_for' => 'source_account_is', 'needs_context' => true],
'source_account_contains' => ['alias' => false, 'needs_context' => true],
'source' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from_account_contains' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'source_account_ends' => ['alias' => false, 'needs_context' => true],
'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true],
'source_account_starts' => ['alias' => false, 'needs_context' => true],
'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', 'needs_context' => true],
'source_account_nr_is' => ['alias' => false, 'needs_context' => true],
'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', 'needs_context' => true],
'source_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'from_account_nr_contains' => ['alias' => true, 'alias_for' => 'source_account_nr_contains', 'needs_context' => true],
'source_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true],
'source_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true],
'destination_account_is' => ['alias' => false, 'needs_context' => true],
'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true],
'destination_account_contains' => ['alias' => false, 'needs_context' => true],
'destination' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to_account_contains' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'destination_account_ends' => ['alias' => false, 'needs_context' => true],
'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true],
'destination_account_starts' => ['alias' => false, 'needs_context' => true],
'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', 'needs_context' => true],
'destination_account_nr_is' => ['alias' => false, 'needs_context' => true],
'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', 'needs_context' => true],
'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'to_account_nr_contains' => ['alias' => true, 'alias_for' => 'destination_account_nr_contains', 'needs_context' => true],
'destination_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true],
'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true],
'account_is' => ['alias' => false, 'needs_context' => true],
'account_contains' => ['alias' => false, 'needs_context' => true],
'account_ends' => ['alias' => false, 'needs_context' => true],
'account_starts' => ['alias' => false, 'needs_context' => true],
'account_nr_is' => ['alias' => false, 'needs_context' => true],
'account_nr_contains' => ['alias' => false, 'needs_context' => true],
'account_nr_ends' => ['alias' => false, 'needs_context' => true],
'account_nr_starts' => ['alias' => false, 'needs_context' => true],
'category_is' => ['alias' => false, 'needs_context' => true],
'category_contains' => ['alias' => false, 'needs_context' => true],
'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true],
'category_ends' => ['alias' => false, 'needs_context' => true],
'category_starts' => ['alias' => false, 'needs_context' => true],
'budget_is' => ['alias' => false, 'needs_context' => true],
'budget_contains' => ['alias' => false, 'needs_context' => true],
'budget' => ['alias' => true, 'alias_for' => 'budget_contains', 'needs_context' => true],
'budget_ends' => ['alias' => false, 'needs_context' => true],
'budget_starts' => ['alias' => false, 'needs_context' => true],
'bill_is' => ['alias' => false, 'needs_context' => true],
'bill_contains' => ['alias' => false, 'needs_context' => true],
'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'bill_ends' => ['alias' => false, 'needs_context' => true],
'bill_starts' => ['alias' => false, 'needs_context' => true],
'subscription_is' => ['alias' => true, 'alias_for' => 'bill_is', 'needs_context' => true],
'subscription_contains' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription_ends' => ['alias' => true, 'alias_for' => 'bill_ends', 'needs_context' => true],
'subscription_starts' => ['alias' => true, 'alias_for' => 'bill_starts', 'needs_context' => true],
'external_id_is' => ['alias' => false, 'needs_context' => true],
'external_id_contains' => ['alias' => false, 'needs_context' => true],
'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true],
'external_id_ends' => ['alias' => false, 'needs_context' => true],
'external_id_starts' => ['alias' => false, 'needs_context' => true],
'internal_reference_is' => ['alias' => false, 'needs_context' => true],
'internal_reference_contains' => ['alias' => false, 'needs_context' => true],
'internal_reference' => ['alias' => true, 'alias_for' => 'internal_reference_contains', 'needs_context' => true],
'internal_reference_ends' => ['alias' => false, 'needs_context' => true],
'internal_reference_starts' => ['alias' => false, 'needs_context' => true],
'external_url_is' => ['alias' => false, 'needs_context' => true],
'external_url_contains' => ['alias' => false, 'needs_context' => true],
'external_url' => ['alias' => true, 'alias_for' => 'external_url_contains', 'needs_context' => true],
'external_url_ends' => ['alias' => false, 'needs_context' => true],
'external_url_starts' => ['alias' => false, 'needs_context' => true],
'has_attachments' => ['alias' => false, 'needs_context' => false],
'has_any_category' => ['alias' => false, 'needs_context' => false],
'has_any_budget' => ['alias' => false, 'needs_context' => false],
'has_any_bill' => ['alias' => false, 'needs_context' => false],
'user_action' => ['alias' => false, 'needs_context' => true],
'account_id' => ['alias' => false, 'needs_context' => true],
'reconciled' => ['alias' => false, 'needs_context' => false],
'source_account_id' => ['alias' => false, 'needs_context' => true],
'destination_account_id' => ['alias' => false, 'needs_context' => true],
'transaction_type' => ['alias' => false, 'needs_context' => true],
'type' => ['alias' => true, 'alias_for' => 'transaction_type', 'needs_context' => true],
'tag_is' => ['alias' => false, 'needs_context' => true],
'tag_is_not' => ['alias' => false, 'needs_context' => true],
'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true],
'tag_contains' => ['alias' => false, 'needs_context' => true],
'tag_ends' => ['alias' => false, 'needs_context' => true],
'tag_starts' => ['alias' => false, 'needs_context' => true],
'description_is' => ['alias' => false, 'needs_context' => true],
'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true],
'description_contains' => ['alias' => false, 'needs_context' => true],
'description_ends' => ['alias' => false, 'needs_context' => true],
'description_starts' => ['alias' => false, 'needs_context' => true],
'notes_is' => ['alias' => false, 'needs_context' => true],
'notes_are' => ['alias' => true, 'alias_for' => 'notes_is', 'needs_context' => true],
'notes_contains' => ['alias' => false, 'needs_context' => true],
'notes_contain' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes_ends' => ['alias' => false, 'needs_context' => true],
'notes_end' => ['alias' => true, 'alias_for' => 'notes_ends', 'needs_context' => true],
'notes_starts' => ['alias' => false, 'needs_context' => true],
'notes_start' => ['alias' => true, 'alias_for' => 'notes_starts', 'needs_context' => true],
'source_account_is' => ['alias' => false, 'needs_context' => true],
'from_account_is' => ['alias' => true, 'alias_for' => 'source_account_is', 'needs_context' => true],
'source_account_contains' => ['alias' => false, 'needs_context' => true],
'source' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from_account_contains' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'source_account_ends' => ['alias' => false, 'needs_context' => true],
'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true],
'source_account_starts' => ['alias' => false, 'needs_context' => true],
'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', 'needs_context' => true],
'source_account_nr_is' => ['alias' => false, 'needs_context' => true],
'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', 'needs_context' => true],
'source_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'from_account_nr_contains' => ['alias' => true, 'alias_for' => 'source_account_nr_contains', 'needs_context' => true],
'source_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true],
'source_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true],
'destination_account_is' => ['alias' => false, 'needs_context' => true],
'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true],
'destination_account_contains' => ['alias' => false, 'needs_context' => true],
'destination' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to_account_contains' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'destination_account_ends' => ['alias' => false, 'needs_context' => true],
'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true],
'destination_account_starts' => ['alias' => false, 'needs_context' => true],
'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', 'needs_context' => true],
'destination_account_nr_is' => ['alias' => false, 'needs_context' => true],
'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', 'needs_context' => true],
'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'to_account_nr_contains' => ['alias' => true, 'alias_for' => 'destination_account_nr_contains', 'needs_context' => true],
'destination_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true],
'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true],
'account_is' => ['alias' => false, 'needs_context' => true],
'account_contains' => ['alias' => false, 'needs_context' => true],
'account_ends' => ['alias' => false, 'needs_context' => true],
'account_starts' => ['alias' => false, 'needs_context' => true],
'account_nr_is' => ['alias' => false, 'needs_context' => true],
'account_nr_contains' => ['alias' => false, 'needs_context' => true],
'account_nr_ends' => ['alias' => false, 'needs_context' => true],
'account_nr_starts' => ['alias' => false, 'needs_context' => true],
'category_is' => ['alias' => false, 'needs_context' => true],
'category_contains' => ['alias' => false, 'needs_context' => true],
'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true],
'category_ends' => ['alias' => false, 'needs_context' => true],
'category_starts' => ['alias' => false, 'needs_context' => true],
'budget_is' => ['alias' => false, 'needs_context' => true],
'budget_contains' => ['alias' => false, 'needs_context' => true],
'budget' => ['alias' => true, 'alias_for' => 'budget_contains', 'needs_context' => true],
'budget_ends' => ['alias' => false, 'needs_context' => true],
'budget_starts' => ['alias' => false, 'needs_context' => true],
'bill_is' => ['alias' => false, 'needs_context' => true],
'bill_contains' => ['alias' => false, 'needs_context' => true],
'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'bill_ends' => ['alias' => false, 'needs_context' => true],
'bill_starts' => ['alias' => false, 'needs_context' => true],
'subscription_is' => ['alias' => true, 'alias_for' => 'bill_is', 'needs_context' => true],
'subscription_contains' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription_ends' => ['alias' => true, 'alias_for' => 'bill_ends', 'needs_context' => true],
'subscription_starts' => ['alias' => true, 'alias_for' => 'bill_starts', 'needs_context' => true],
'external_id_is' => ['alias' => false, 'needs_context' => true],
'external_id_contains' => ['alias' => false, 'needs_context' => true],
'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true],
'external_id_ends' => ['alias' => false, 'needs_context' => true],
'external_id_starts' => ['alias' => false, 'needs_context' => true],
'internal_reference_is' => ['alias' => false, 'needs_context' => true],
'internal_reference_contains' => ['alias' => false, 'needs_context' => true],
'internal_reference' => ['alias' => true, 'alias_for' => 'internal_reference_contains', 'needs_context' => true],
'internal_reference_ends' => ['alias' => false, 'needs_context' => true],
'internal_reference_starts' => ['alias' => false, 'needs_context' => true],
'external_url_is' => ['alias' => false, 'needs_context' => true],
'external_url_contains' => ['alias' => false, 'needs_context' => true],
'external_url' => ['alias' => true, 'alias_for' => 'external_url_contains', 'needs_context' => true],
'external_url_ends' => ['alias' => false, 'needs_context' => true],
'external_url_starts' => ['alias' => false, 'needs_context' => true],
'has_attachments' => ['alias' => false, 'needs_context' => false],
'has_any_category' => ['alias' => false, 'needs_context' => false],
'has_any_budget' => ['alias' => false, 'needs_context' => false],
'has_any_bill' => ['alias' => false, 'needs_context' => false],
'has_any_subscription' => ['alias' => true, 'needs_context' => false, 'alias_for' => 'has_any_bill'],
'has_any_tag' => ['alias' => false, 'needs_context' => false],
'any_notes' => ['alias' => false, 'needs_context' => false],
'has_any_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'has_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'any_external_url' => ['alias' => false, 'needs_context' => false],
'has_any_external_url' => ['alias' => true, 'alias_for' => 'any_external_url', 'needs_context' => false],
'has_no_attachments' => ['alias' => false, 'needs_context' => false],
'has_no_category' => ['alias' => false, 'needs_context' => false],
'has_no_budget' => ['alias' => false, 'needs_context' => false],
'has_no_bill' => ['alias' => false, 'needs_context' => false],
'has_any_tag' => ['alias' => false, 'needs_context' => false],
'any_notes' => ['alias' => false, 'needs_context' => false],
'has_any_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'has_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'any_external_url' => ['alias' => false, 'needs_context' => false],
'has_any_external_url' => ['alias' => true, 'alias_for' => 'any_external_url', 'needs_context' => false],
'has_no_attachments' => ['alias' => false, 'needs_context' => false],
'has_no_category' => ['alias' => false, 'needs_context' => false],
'has_no_budget' => ['alias' => false, 'needs_context' => false],
'has_no_bill' => ['alias' => false, 'needs_context' => false],
'has_no_subscription' => ['alias' => true, 'needs_context' => false, 'alias_for' => 'has_no_bill'],
'has_no_tag' => ['alias' => false, 'needs_context' => false],
'no_notes' => ['alias' => false, 'needs_context' => false],
'no_external_url' => ['alias' => false, 'needs_context' => false],
'source_is_cash' => ['alias' => false, 'needs_context' => false],
'destination_is_cash' => ['alias' => false, 'needs_context' => false],
'account_is_cash' => ['alias' => false, 'needs_context' => false],
'currency_is' => ['alias' => false, 'needs_context' => true],
'foreign_currency_is' => ['alias' => false, 'needs_context' => true],
'id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'journal_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'recurrence_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'date_on' => ['alias' => false, 'needs_context' => true],
'date' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_is' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'on' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_before' => ['alias' => false, 'needs_context' => true],
'before' => ['alias' => true, 'alias_for' => 'date_before', 'needs_context' => true],
'date_after' => ['alias' => false, 'needs_context' => true],
'after' => ['alias' => true, 'alias_for' => 'date_after', 'needs_context' => true],
'interest_date_on' => ['alias' => false, 'needs_context' => true],
'interest_date' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_is' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_before' => ['alias' => false, 'needs_context' => true],
'interest_date_after' => ['alias' => false, 'needs_context' => true],
'book_date_on' => ['alias' => false, 'needs_context' => true],
'book_date' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_is' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_before' => ['alias' => false, 'needs_context' => true],
'book_date_after' => ['alias' => false, 'needs_context' => true],
'process_date_on' => ['alias' => false, 'needs_context' => true],
'process_date' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_is' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_before' => ['alias' => false, 'needs_context' => true],
'process_date_after' => ['alias' => false, 'needs_context' => true],
'due_date_on' => ['alias' => false, 'needs_context' => true],
'due_date' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_is' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_before' => ['alias' => false, 'needs_context' => true],
'due_date_after' => ['alias' => false, 'needs_context' => true],
'payment_date_on' => ['alias' => false, 'needs_context' => true],
'payment_date' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_is' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_before' => ['alias' => false, 'needs_context' => true],
'payment_date_after' => ['alias' => false, 'needs_context' => true],
'invoice_date_on' => ['alias' => false, 'needs_context' => true],
'invoice_date' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_is' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_before' => ['alias' => false, 'needs_context' => true],
'invoice_date_after' => ['alias' => false, 'needs_context' => true],
'created_at_on' => ['alias' => false, 'needs_context' => true],
'created_at' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_is' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_before' => ['alias' => false, 'needs_context' => true],
'created_at_after' => ['alias' => false, 'needs_context' => true],
'updated_at_on' => ['alias' => false, 'needs_context' => true],
'updated_at' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_is' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_before' => ['alias' => false, 'needs_context' => true],
'updated_at_after' => ['alias' => false, 'needs_context' => true],
'created_on_on' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_on' => ['alias' => true, 'alias_for' => 'created_at', 'needs_context' => true],
'created_on_before' => ['alias' => true, 'alias_for' => 'created_at_before', 'needs_context' => true],
'created_on_after' => ['alias' => true, 'alias_for' => 'created_at_after', 'needs_context' => true],
'updated_on_on' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_on' => ['alias' => true, 'alias_for' => 'updated_at', 'needs_context' => true],
'updated_on_before' => ['alias' => true, 'alias_for' => 'updated_at_before', 'needs_context' => true],
'updated_on_after' => ['alias' => true, 'alias_for' => 'updated_at_after', 'needs_context' => true],
'amount_is' => ['alias' => false, 'needs_context' => true],
'amount' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_exactly' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_less' => ['alias' => false, 'needs_context' => true],
'amount_max' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'less' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'amount_more' => ['alias' => false, 'needs_context' => true],
'amount_min' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'more' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'foreign_amount_is' => ['alias' => false, 'needs_context' => true],
'foreign_amount' => ['alias' => true, 'alias_for' => 'foreign_amount_is', 'needs_context' => true],
'foreign_amount_less' => ['alias' => false, 'needs_context' => true],
'foreign_amount_max' => ['alias' => true, 'alias_for' => 'foreign_amount_less', 'needs_context' => true],
'foreign_amount_more' => ['alias' => false, 'needs_context' => true],
'foreign_amount_min' => ['alias' => true, 'alias_for' => 'foreign_amount_more', 'needs_context' => true],
'attachment_name_is' => ['alias' => false, 'needs_context' => true],
'attachment' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_is' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name_contains' => ['alias' => false, 'needs_context' => true],
'attachment_name_starts' => ['alias' => false, 'needs_context' => true],
'attachment_name_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes' => ['alias' => true, 'alias_for' => 'attachment_notes_are', 'needs_context' => true],
'attachment_notes_are' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contains' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contain' => ['alias' => true, 'alias_for' => 'attachment_notes_contains', 'needs_context' => true],
'attachment_notes_starts' => ['alias' => false, 'needs_context' => true],
'attachment_notes_start' => ['alias' => true, 'alias_for' => 'attachment_notes_starts', 'needs_context' => true],
'attachment_notes_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes_end' => ['alias' => true, 'alias_for' => 'attachment_notes_ends', 'needs_context' => true],
'exists' => ['alias' => false, 'needs_context' => false],
'sepa_ct_is' => ['alias' => false, 'needs_context' => true],
'no_external_id' => ['alias' => false, 'needs_context' => false],
'any_external_id' => ['alias' => false, 'needs_context' => false],
'has_no_tag' => ['alias' => false, 'needs_context' => false],
'no_notes' => ['alias' => false, 'needs_context' => false],
'no_external_url' => ['alias' => false, 'needs_context' => false],
'source_is_cash' => ['alias' => false, 'needs_context' => false],
'destination_is_cash' => ['alias' => false, 'needs_context' => false],
'account_is_cash' => ['alias' => false, 'needs_context' => false],
'currency_is' => ['alias' => false, 'needs_context' => true],
'foreign_currency_is' => ['alias' => false, 'needs_context' => true],
'id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'journal_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'recurrence_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'date_on' => ['alias' => false, 'needs_context' => true],
'date' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_is' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'on' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_before' => ['alias' => false, 'needs_context' => true],
'before' => ['alias' => true, 'alias_for' => 'date_before', 'needs_context' => true],
'date_after' => ['alias' => false, 'needs_context' => true],
'after' => ['alias' => true, 'alias_for' => 'date_after', 'needs_context' => true],
'interest_date_on' => ['alias' => false, 'needs_context' => true],
'interest_date' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_is' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_before' => ['alias' => false, 'needs_context' => true],
'interest_date_after' => ['alias' => false, 'needs_context' => true],
'book_date_on' => ['alias' => false, 'needs_context' => true],
'book_date' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_is' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_before' => ['alias' => false, 'needs_context' => true],
'book_date_after' => ['alias' => false, 'needs_context' => true],
'process_date_on' => ['alias' => false, 'needs_context' => true],
'process_date' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_is' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_before' => ['alias' => false, 'needs_context' => true],
'process_date_after' => ['alias' => false, 'needs_context' => true],
'due_date_on' => ['alias' => false, 'needs_context' => true],
'due_date' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_is' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_before' => ['alias' => false, 'needs_context' => true],
'due_date_after' => ['alias' => false, 'needs_context' => true],
'payment_date_on' => ['alias' => false, 'needs_context' => true],
'payment_date' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_is' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_before' => ['alias' => false, 'needs_context' => true],
'payment_date_after' => ['alias' => false, 'needs_context' => true],
'invoice_date_on' => ['alias' => false, 'needs_context' => true],
'invoice_date' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_is' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_before' => ['alias' => false, 'needs_context' => true],
'invoice_date_after' => ['alias' => false, 'needs_context' => true],
'created_at_on' => ['alias' => false, 'needs_context' => true],
'created_at' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_is' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_before' => ['alias' => false, 'needs_context' => true],
'created_at_after' => ['alias' => false, 'needs_context' => true],
'updated_at_on' => ['alias' => false, 'needs_context' => true],
'updated_at' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_is' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_before' => ['alias' => false, 'needs_context' => true],
'updated_at_after' => ['alias' => false, 'needs_context' => true],
'created_on_on' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_on' => ['alias' => true, 'alias_for' => 'created_at', 'needs_context' => true],
'created_on_before' => ['alias' => true, 'alias_for' => 'created_at_before', 'needs_context' => true],
'created_on_after' => ['alias' => true, 'alias_for' => 'created_at_after', 'needs_context' => true],
'updated_on_on' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_on' => ['alias' => true, 'alias_for' => 'updated_at', 'needs_context' => true],
'updated_on_before' => ['alias' => true, 'alias_for' => 'updated_at_before', 'needs_context' => true],
'updated_on_after' => ['alias' => true, 'alias_for' => 'updated_at_after', 'needs_context' => true],
'amount_is' => ['alias' => false, 'needs_context' => true],
'amount' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_exactly' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_less' => ['alias' => false, 'needs_context' => true],
'amount_max' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'less' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'amount_more' => ['alias' => false, 'needs_context' => true],
'amount_min' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'more' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'foreign_amount_is' => ['alias' => false, 'needs_context' => true],
'foreign_amount' => ['alias' => true, 'alias_for' => 'foreign_amount_is', 'needs_context' => true],
'foreign_amount_less' => ['alias' => false, 'needs_context' => true],
'foreign_amount_max' => ['alias' => true, 'alias_for' => 'foreign_amount_less', 'needs_context' => true],
'foreign_amount_more' => ['alias' => false, 'needs_context' => true],
'foreign_amount_min' => ['alias' => true, 'alias_for' => 'foreign_amount_more', 'needs_context' => true],
'attachment_name_is' => ['alias' => false, 'needs_context' => true],
'attachment' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_is' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name_contains' => ['alias' => false, 'needs_context' => true],
'attachment_name_starts' => ['alias' => false, 'needs_context' => true],
'attachment_name_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes' => ['alias' => true, 'alias_for' => 'attachment_notes_are', 'needs_context' => true],
'attachment_notes_are' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contains' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contain' => ['alias' => true, 'alias_for' => 'attachment_notes_contains', 'needs_context' => true],
'attachment_notes_starts' => ['alias' => false, 'needs_context' => true],
'attachment_notes_start' => ['alias' => true, 'alias_for' => 'attachment_notes_starts', 'needs_context' => true],
'attachment_notes_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes_end' => ['alias' => true, 'alias_for' => 'attachment_notes_ends', 'needs_context' => true],
'exists' => ['alias' => false, 'needs_context' => false],
'sepa_ct_is' => ['alias' => false, 'needs_context' => true],
'no_external_id' => ['alias' => false, 'needs_context' => false],
'any_external_id' => ['alias' => false, 'needs_context' => false],
// based on source or destination balance. Very heavy search.
'source_balance_gte' => ['alias' => false, 'needs_context' => true],
'source_balance_gt' => ['alias' => false, 'needs_context' => true],
'source_balance_lte' => ['alias' => false, 'needs_context' => true],
'source_balance_lt' => ['alias' => false, 'needs_context' => true],
'source_balance_is' => ['alias' => false, 'needs_context' => true],
'destination_balance_gte' => ['alias' => false, 'needs_context' => true],
'destination_balance_gt' => ['alias' => false, 'needs_context' => true],
'destination_balance_lte' => ['alias' => false, 'needs_context' => true],
'destination_balance_lt' => ['alias' => false, 'needs_context' => true],
'destination_balance_is' => ['alias' => false, 'needs_context' => true],
'source_balance_gte' => ['alias' => false, 'needs_context' => true],
'source_balance_gt' => ['alias' => false, 'needs_context' => true],
'source_balance_lte' => ['alias' => false, 'needs_context' => true],
'source_balance_lt' => ['alias' => false, 'needs_context' => true],
'source_balance_is' => ['alias' => false, 'needs_context' => true],
'destination_balance_gte' => ['alias' => false, 'needs_context' => true],
'destination_balance_gt' => ['alias' => false, 'needs_context' => true],
'destination_balance_lte' => ['alias' => false, 'needs_context' => true],
'destination_balance_lt' => ['alias' => false, 'needs_context' => true],
'destination_balance_is' => ['alias' => false, 'needs_context' => true],
],
/**
* Which query parser to use - 'new' or 'legacy'

566
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -80,18 +80,18 @@
"profile_oauth_create_new_client": "Vytvo\u0159it nov\u00e9ho klienta",
"profile_oauth_create_client": "Vytvo\u0159it klienta",
"profile_oauth_edit_client": "Upravit klienta",
"profile_oauth_name_help": "Something your users will recognize and trust.",
"profile_oauth_name_help": "N\u011bco \u010demu va\u0161i u\u017eivatel\u00e9 budou d\u016fv\u011b\u0159ovat.",
"profile_oauth_redirect_url": "P\u0159esm\u011brovat URL adresu",
"profile_oauth_clients_external_auth": "If you're using an external authentication provider like Authelia, OAuth Clients will not work. You can use Personal Access Tokens only.",
"profile_oauth_redirect_url_help": "Your application's authorization callback URL.",
"profile_oauth_clients_external_auth": "Pokud pro ov\u011b\u0159ov\u00e1n\u00ed pou\u017e\u00edv\u00e1te extern\u00ed slu\u017ebu, nap\u0159\u00edklad Authelia, OAuth klienti nemus\u00ed fungovat spr\u00e1vn\u011b. M\u00edsto toho m\u016f\u017eete pou\u017e\u00edt Personal Access Token.",
"profile_oauth_redirect_url_help": "Callback URL va\u0161\u00ed aplikace.",
"profile_authorized_apps": "Authorized applications",
"profile_authorized_clients": "Autorizovan\u00ed klienti",
"profile_scopes": "Scopes",
"profile_revoke": "Revoke",
"profile_personal_access_tokens": "Personal Access Tokens",
"profile_personal_access_tokens": "Personal Access Token",
"profile_personal_access_token": "Personal Access Token",
"profile_personal_access_token_explanation": "Here is your new personal access token. This is the only time it will be shown so don't lose it! You may now use this token to make API requests.",
"profile_no_personal_access_token": "You have not created any personal access tokens.",
"profile_personal_access_token_explanation": "Tohle je v\u00e1\u0161 nov\u00fd p\u0159\u00edstupov\u00fd token. Tohle je naposled kdy ho vid\u00edte, tak\u017ee ho neztra\u0165te! M\u016f\u017eete ho pou\u017e\u00edt pro vol\u00e1n\u00ed API.",
"profile_no_personal_access_token": "Je\u0161t\u011b jste nevytvo\u0159ili \u017e\u00e1dn\u00e9 p\u0159\u00edstupov\u00e9 tokeny.",
"profile_create_new_token": "Vytvo\u0159it nov\u00fd token",
"profile_create_token": "Vytvo\u0159it token",
"profile_create": "Vytvo\u0159it",
@@ -100,8 +100,8 @@
"piggy_bank": "Pokladni\u010dka",
"profile_oauth_client_secret_title": "Client Secret",
"profile_oauth_client_secret_expl": "Here is your new client secret. This is the only time it will be shown so don't lose it! You may now use this secret to make API requests.",
"profile_oauth_confidential": "Confidential",
"profile_oauth_confidential_help": "Require the client to authenticate with a secret. Confidential clients can hold credentials in a secure way without exposing them to unauthorized parties. Public applications, such as native desktop or JavaScript SPA applications, are unable to hold secrets securely.",
"profile_oauth_confidential": "Soukrom\u00e1 aplikace",
"profile_oauth_confidential_help": "Po\u017eadovat aby se klienti autorizovali. Soukrom\u00e9 aplikace mohou bezpe\u010dn\u011b pracovat s p\u0159\u00edstupov\u00fdmi \u00fadaji bez toho aby je zve\u0159ejnily. Ve\u0159ejn\u00e9 aplikace, nativn\u00ed nebo JavaScriptov\u00e9 SPA, toho schopn\u00e9 nejsou.",
"multi_account_warning_unknown": "Depending on the type of transaction you create, the source and\/or destination account of subsequent splits may be overruled by whatever is defined in the first split of the transaction.",
"multi_account_warning_withdrawal": "Zdrojov\u00fd \u00fa\u010del v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed je ovl\u00e1dan\u00fd zdrojov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.",
"multi_account_warning_deposit": "C\u00edlov\u00fd \u00fa\u010del v\u0161ech n\u00e1sleduj\u00edc\u00edch rozd\u011blen\u00ed je ovl\u00e1dan\u00fd c\u00edlov\u00fdm \u00fa\u010dtem prvn\u00edho rozd\u011blen\u00ed transakce.",
@@ -134,7 +134,7 @@
"attempt_content_title": "Pokusy webhooku",
"attempt_content_help": "To v\u0161e jsou ne\u00fasp\u011b\u0161n\u00e9 pokusy t\u00e9to zpravy webhooku o odesl\u00e1n\u00ed na nakonfigurovanou URL. Po n\u011bjak\u00e9 dob\u011b, Firefly III p\u0159estane zkou\u0161et odes\u00edlat zpr\u00e1vu.",
"no_attempts": "Nebyly nalezeny \u017e\u00e1dn\u00e9 ne\u00fasp\u011b\u0161n\u00e9 pokusy. To je dobr\u00e1 v\u011bc!",
"webhook_attempt_at": "Attempt at {moment}",
"webhook_attempt_at": "Pokus v {moment}",
"logs": "Logy",
"response": "Odpov\u011b\u010f",
"visit_webhook_url": "Nav\u0161t\u00edvit URL webhooku",

View File

@@ -153,7 +153,7 @@
"url": "URL",
"active": "Activo",
"interest_date": "Fecha de inter\u00e9s",
"administration_currency": "Native currency",
"administration_currency": "Moneda nativa",
"title": "T\u00edtulo",
"date": "Fecha",
"book_date": "Fecha de registro",

View File

@@ -18,7 +18,7 @@
"is_reconciled": "\u5df2\u6838\u9500",
"split": "\u62c6\u5206",
"single_split": "\u62c6\u5206",
"not_enough_currencies": "Not enough currencies",
"not_enough_currencies": "\u6ca1\u6709\u8db3\u591f\u7684\u8d27\u5e01",
"not_enough_currencies_enabled": "\u5982\u679c\u60a8\u53ea\u542f\u7528\u4e86\u4e00\u79cd\u8d27\u5e01\uff0c\u5c31\u4e0d\u9700\u8981\u6dfb\u52a0\u6c47\u7387\u3002",
"transaction_stored_link": "<a href=\"transactions\/show\/{ID}\">\u4ea4\u6613 #{ID} (\u201c{title}\u201d)<\/a> \u5df2\u4fdd\u5b58\u3002",
"webhook_stored_link": "<a href=\"webhooks\/show\/{ID}\">\u63a8\u9001 #{ID} (\"{title}\")<\/a> \u5df2\u4fdd\u5b58.",
@@ -153,7 +153,7 @@
"url": "\u7f51\u5740",
"active": "\u542f\u7528",
"interest_date": "\u5229\u606f\u65e5\u671f",
"administration_currency": "Native currency",
"administration_currency": "\u672c\u5730\u8d27\u5e01",
"title": "\u6807\u9898",
"date": "\u65e5\u671f",
"book_date": "\u767b\u8bb0\u65e5\u671f",
@@ -166,14 +166,14 @@
"webhook_response": "\u54cd\u5e94\u5185\u5bb9",
"webhook_trigger": "\u89e6\u53d1\u6761\u4ef6",
"webhook_delivery": "\u53d1\u9001\u683c\u5f0f",
"from_currency_to_currency": "{from} &rarr; {to}",
"to_currency_from_currency": "{to} &rarr; {from}",
"rate": "Rate"
"from_currency_to_currency": "{from}&rarr;{to}",
"to_currency_from_currency": "{to}&rarr;{from}",
"rate": "\u8bc4\u7ea7"
},
"list": {
"title": "\u6807\u9898",
"active": "\u662f\u5426\u542f\u7528\uff1f",
"native_currency": "Native currency",
"native_currency": "\u672c\u5730\u8d27\u5e01",
"trigger": "\u89e6\u53d1\u6761\u4ef6",
"response": "\u7b54\u590d",
"delivery": "\u4ea4\u4ed8",