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

View File

@@ -82,7 +82,7 @@ class UpdateRequest extends FormRequest
'accounts' => 'required', 'accounts' => 'required',
'accounts.*' => 'array|required', 'accounts.*' => 'array|required',
'accounts.*.account_id' => ['required', 'numeric', 'belongsToUser:accounts,id'], '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_id' => 'numeric|belongsToUser:object_groups,id',
'object_group_title' => ['min:1', 'max:255'], 'object_group_title' => ['min:1', 'max:255'],
'transaction_currency_id' => 'exists:transaction_currencies,id|nullable', 'transaction_currency_id' => 'exists:transaction_currencies,id|nullable',

View File

@@ -122,7 +122,8 @@ class CorrectsUnevenAmount extends Command
$journals = DB::table('transactions') $journals = DB::table('transactions')
->groupBy('transaction_journal_id') ->groupBy('transaction_journal_id')
->whereNull('deleted_at') ->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 */ /** @var \stdClass $entry */
foreach ($journals as $entry) { foreach ($journals as $entry) {

View File

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

View File

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

View File

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

View File

@@ -74,29 +74,29 @@ class NetWorth implements NetWorthInterface
return $cache->get(); return $cache->get();
} }
Log::debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d H:i:s'))); Log::debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d H:i:s')));
$default = Amount::getNativeCurrency(); $default = Amount::getNativeCurrency();
$netWorth = []; $netWorth = [];
Log::debug(sprintf('NetWorth: finalAccountsBalance("%s")', $date->format('Y-m-d H:i:s'))); 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 */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {
Log::debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name)); Log::debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name));
$currency = $this->accountRepository->getAccountCurrency($account) ?? $default; $currency = $this->accountRepository->getAccountCurrency($account) ?? $default;
$useNative = $convertToNative && $default->id !== $currency->id; $useNative = $convertToNative && $default->id !== $currency->id;
$currency = $useNative ? $default : $currency; $currency = $useNative ? $default : $currency;
$currencyCode = $currency->code; $currencyCode = $currency->code;
$balance = '0'; $balance = '0';
$nativeBalance = '0'; $nativeBalance = '0';
if (array_key_exists($account->id, $balances)) { if (array_key_exists($account->id, $balances)) {
$balance = $balances[$account->id]['balance'] ?? '0'; $balance = $balances[$account->id]['balance'] ?? '0';
$nativeBalance = $balances[$account->id]['native_balance'] ?? '0'; $nativeBalance = $balances[$account->id]['native_balance'] ?? '0';
} }
Log::debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance)); Log::debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance));
// always subtract virtual balance again. // always subtract virtual balance again.
$balance = '' !== (string) $account->virtual_balance ? bcsub($balance, $account->virtual_balance) : $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; $nativeBalance = '' !== (string) $account->native_virtual_balance ? bcsub($nativeBalance, $account->native_virtual_balance) : $nativeBalance;
$amountToUse = $useNative ? $nativeBalance : $balance; $amountToUse = $useNative ? $nativeBalance : $balance;
Log::debug(sprintf('Will use %s %s', $currencyCode, $amountToUse)); Log::debug(sprintf('Will use %s %s', $currencyCode, $amountToUse));
$netWorth[$currencyCode] ??= [ $netWorth[$currencyCode] ??= [
@@ -115,7 +115,7 @@ class NetWorth implements NetWorthInterface
return $netWorth; return $netWorth;
} }
public function setUser(null | Authenticatable | User $user): void public function setUser(null|Authenticatable|User $user): void
{ {
if (!$user instanceof User) { if (!$user instanceof User) {
return; return;
@@ -130,7 +130,7 @@ class NetWorth implements NetWorthInterface
$this->accountRepository = app(AccountRepositoryInterface::class); $this->accountRepository = app(AccountRepositoryInterface::class);
$this->accountRepository->setUserGroup($userGroup); $this->accountRepository->setUserGroup($userGroup);
$this->currencyRepos = app(CurrencyRepositoryInterface::class); $this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->currencyRepos->setUserGroup($this->userGroup); $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'))); Log::debug(sprintf('SumNetWorth: finalAccountsBalance("%s")', $date->format('Y-m-d H:i:s')));
$balances = Steam::finalAccountsBalance($accounts, $date); $balances = Steam::finalAccountsBalance($accounts, $date);
foreach ($accounts as $account) { foreach ($accounts as $account) {
$currency = $this->accountRepository->getAccountCurrency($account); $currency = $this->accountRepository->getAccountCurrency($account);
$balance = $balances[$account->id]['balance'] ?? '0'; $balance = $balances[$account->id]['balance'] ?? '0';
// always subtract virtual balance. // always subtract virtual balance.
$virtualBalance = $account->virtual_balance; $virtualBalance = $account->virtual_balance;
if ('' !== $virtualBalance) { if ('' !== $virtualBalance) {
$balance = bcsub($balance, $virtualBalance); $balance = bcsub($balance, $virtualBalance);
} }
$return[$currency->id] ??= [ $return[$currency->id] ??= [
'id' => (string) $currency->id, 'id' => (string) $currency->id,
'name' => $currency->name, 'name' => $currency->name,
'symbol' => $currency->symbol, '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) 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)) { if (!$this->isEditableAccount($account)) {
return $this->redirectAccountToAccount($account); return $this->redirectAccountToAccount($account);
@@ -130,18 +130,20 @@ class ShowController extends Controller
} }
Log::debug('Collect transactions'); Log::debug('Collect transactions');
Timer::start('collection'); Timer::start('collection');
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector $collector
->setAccounts(new Collection([$account])) ->setAccounts(new Collection([$account]))
->setLimit($pageSize) ->setLimit($pageSize)
->setPage($page) ->setPage($page)
->withAPIInformation() ->withAPIInformation()
->setRange($start, $end); ->setRange($start, $end)
;
// this search will not include transaction groups where this asset account (or liability) // this search will not include transaction groups where this asset account (or liability)
// is just part of ONE of the journals. To force this: // is just part of ONE of the journals. To force this:
$collector->setExpandGroupSearch(true); $collector->setExpandGroupSearch(true);
$groups = $collector->getPaginatedGroups(); $groups = $collector->getPaginatedGroups();
Log::debug('End collect transactions'); Log::debug('End collect transactions');
Timer::stop('collection'); Timer::stop('collection');
@@ -149,21 +151,21 @@ class ShowController extends Controller
// enrich data in arrays. // enrich data in arrays.
// enrich // enrich
// $enrichment = new TransactionGroupEnrichment(); // $enrichment = new TransactionGroupEnrichment();
// $enrichment->setUser(auth()->user()); // $enrichment->setUser(auth()->user());
// $groups->setCollection($enrichment->enrich($groups->getCollection())); // $groups->setCollection($enrichment->enrich($groups->getCollection()));
$groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')])); $groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]));
$showAll = false; $showAll = false;
// correct // correct
$now = today()->endOfDay(); $now = today()->endOfDay();
if ($now->gt($end) || $now->lt($start)) { if ($now->gt($end) || $now->lt($start)) {
$now = $end; $now = $end;
} }
Log::debug(sprintf('show: Call finalAccountBalance with date/time "%s"', $now->toIso8601String())); 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( return view(
'accounts.show', 'accounts.show',
@@ -207,7 +209,7 @@ class ShowController extends Controller
$today = today(config('app.timezone')); $today = today(config('app.timezone'));
$accountCurrency = $this->repository->getAccountCurrency($account); $accountCurrency = $this->repository->getAccountCurrency($account);
$start = $this->repository->oldestJournalDate($account) ?? today(config('app.timezone'))->startOfMonth(); $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'); $page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data; $pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency; $currency = $this->repository->getAccountCurrency($account) ?? $this->defaultCurrency;
@@ -217,20 +219,20 @@ class ShowController extends Controller
$end->endOfDay(); $end->endOfDay();
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation()->withCategoryInformation(); $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation()->withCategoryInformation();
// this search will not include transaction groups where this asset account (or liability) // this search will not include transaction groups where this asset account (or liability)
// is just part of ONE of the journals. To force this: // is just part of ONE of the journals. To force this:
$collector->setExpandGroupSearch(true); $collector->setExpandGroupSearch(true);
$groups = $collector->getPaginatedGroups(); $groups = $collector->getPaginatedGroups();
$groups->setPath(route('accounts.show.all', [$account->id])); $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')]); $chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$showAll = true; $showAll = true;
// correct // correct
Log::debug(sprintf('showAll: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); 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( return view(
'accounts.show', 'accounts.show',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -36,7 +36,6 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Override;
/** /**
* Class BudgetLimitRepository * Class BudgetLimitRepository
@@ -51,10 +50,10 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
*/ */
public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string 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. // same complex where query as below.
->where( ->where(
static function (Builder $q5) use ($start, $end): void { static function (Builder $q5) use ($start, $end): void {
$q5->where( $q5->where(
static function (Builder $q1) use ($start, $end): void { 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')); $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
} }
) )
->orWhere( ->orWhere(
static function (Builder $q3) use ($start, $end): void { 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', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
} }
); )
;
} }
) )
->orWhere( ->orWhere(
static function (Builder $q4) use ($start, $end): void { static function (Builder $q4) use ($start, $end): void {
// or start is before start AND end is after end. // 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.start_date', '<=', $start->format('Y-m-d'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d')); $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
} }
); )
;
} }
) )
->where('budget_limits.transaction_currency_id', $currency->id) ->where('budget_limits.transaction_currency_id', $currency->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where('budgets.active', true) ->where('budgets.active', true)
->where('budgets.user_id', $this->user->id); ->where('budgets.user_id', $this->user->id)
;
if (null !== $budgets && $budgets->count() > 0) { if (null !== $budgets && $budgets->count() > 0) {
$query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray()); $query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray());
} }
@@ -136,17 +138,19 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
// both are NULL: // both are NULL:
if (null === $start && null === $end) { if (null === $start && null === $end) {
return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget']) ->with(['budget'])
->where('budgets.user_id', $this->user->id) ->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->get(['budget_limits.*']); ->get(['budget_limits.*'])
;
} }
// one of the two is NULL. // one of the two is NULL.
if (null === $start xor null === $end) { if (null === $start xor null === $end) {
$query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') $query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget']) ->with(['budget'])
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where('budgets.user_id', $this->user->id); ->where('budgets.user_id', $this->user->id)
;
if (null !== $end) { if (null !== $end) {
// end date must be before $end. // end date must be before $end.
$query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00'));
@@ -161,36 +165,39 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
// neither are NULL: // neither are NULL:
return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget']) ->with(['budget'])
->where('budgets.user_id', $this->user->id) ->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where( ->where(
static function (Builder $q5) use ($start, $end): void { static function (Builder $q5) use ($start, $end): void {
$q5->where( $q5->where(
static function (Builder $q1) use ($start, $end): void { static function (Builder $q1) use ($start, $end): void {
$q1->where( $q1->where(
static function (Builder $q2) use ($start, $end): void { 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', '>=', $start->format('Y-m-d'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d')); $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
} }
) )
->orWhere( ->orWhere(
static function (Builder $q3) use ($start, $end): void { 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', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
} }
); )
} ;
) }
->orWhere( )
static function (Builder $q4) use ($start, $end): void { ->orWhere(
// or start is before start AND end is after end. static function (Builder $q4) use ($start, $end): void {
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d')); // or start is before start AND end is after end.
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d')); $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.*']); ;
}
)->get(['budget_limits.*'])
;
} }
public function getBudgetLimits(Budget $budget, ?Carbon $start = null, ?Carbon $end = null): Collection 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: // when both dates are set:
return $budget->budgetlimits() return $budget->budgetlimits()
->where( ->where(
static function (Builder $q5) use ($start, $end): void { static function (Builder $q5) use ($start, $end): void {
$q5->where( $q5->where(
static function (Builder $q1) use ($start, $end): void { static function (Builder $q1) use ($start, $end): void {
// budget limit ends within period // budget limit ends within period
$q1->where( $q1->where(
static function (Builder $q2) use ($start, $end): void { 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', '>=', $start->format('Y-m-d 00:00:00'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 23:59:59')); $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 23:59:59'));
} }
) )
// budget limit start within period // budget limit start within period
->orWhere( ->orWhere(
static function (Builder $q3) use ($start, $end): void { 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', '>=', $start->format('Y-m-d 00:00:00'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59'));
} }
); )
} ;
) }
->orWhere( )
static function (Builder $q4) use ($start, $end): void { ->orWhere(
// or start is before start AND end is after end. static function (Builder $q4) use ($start, $end): void {
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 23:59:59')); // or start is before start AND end is after end.
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00')); $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.*']); ;
}
)->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*'])
;
} }
#[Override] #[\Override]
public function getNoteText(BudgetLimit $budgetLimit): string public function getNoteText(BudgetLimit $budgetLimit): string
{ {
return (string) $budgetLimit->notes()->first()?->text; 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: // if no currency has been provided, use the user's default currency:
/** @var TransactionCurrencyFactory $factory */ /** @var TransactionCurrencyFactory $factory */
$factory = app(TransactionCurrencyFactory::class); $factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null); $currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
if (null === $currency) { if (null === $currency) {
$currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup); $currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
} }
$currency->enabled = true; $currency->enabled = true;
$currency->save(); $currency->save();
// find the budget: // find the budget:
/** @var null|Budget $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) { if (null === $budget) {
throw new FireflyException('200004: Budget does not exist.'); throw new FireflyException('200004: Budget does not exist.');
} }
// find limit with same date range and currency. // find limit with same date range and currency.
$limit = $budget->budgetlimits() $limit = $budget->budgetlimits()
->where('budget_limits.start_date', $data['start_date']->format('Y-m-d')) ->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.end_date', $data['end_date']->format('Y-m-d'))
->where('budget_limits.transaction_currency_id', $currency->id) ->where('budget_limits.transaction_currency_id', $currency->id)
->first(['budget_limits.*']); ->first(['budget_limits.*'])
;
if (null !== $limit) { if (null !== $limit) {
throw new FireflyException('200027: Budget limit already exists.'); throw new FireflyException('200027: Budget limit already exists.');
} }
app('log')->debug('No existing budget limit, create a new one'); app('log')->debug('No existing budget limit, create a new one');
// or create one and return it. // or create one and return it.
$limit = new BudgetLimit(); $limit = new BudgetLimit();
$limit->budget()->associate($budget); $limit->budget()->associate($budget);
$limit->start_date = $data['start_date']->format('Y-m-d'); $limit->start_date = $data['start_date']->format('Y-m-d');
$limit->end_date = $data['end_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->transaction_currency_id = $currency->id;
$limit->save(); $limit->save();
$noteText = (string) ($data['notes'] ?? ''); $noteText = (string) ($data['notes'] ?? '');
if ('' !== $noteText) { if ('' !== $noteText) {
$this->setNoteText($limit, $noteText); $this->setNoteText($limit, $noteText);
} }
@@ -308,12 +319,13 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
{ {
/** @var null|BudgetLimit */ /** @var null|BudgetLimit */
return $budget->budgetlimits() return $budget->budgetlimits()
->where('transaction_currency_id', $currency->id) ->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d')) ->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()
;
} }
#[Override] #[\Override]
public function setNoteText(BudgetLimit $budgetLimit, string $text): void public function setNoteText(BudgetLimit $budgetLimit, string $text): void
{ {
$dbNote = $budgetLimit->notes()->first(); $dbNote = $budgetLimit->notes()->first();
@@ -335,8 +347,8 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface, UserGroup
*/ */
public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit
{ {
$budgetLimit->amount = array_key_exists('amount', $data) ? $data['amount'] : $budgetLimit->amount; $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->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id;
if (array_key_exists('start', $data)) { if (array_key_exists('start', $data)) {
$budgetLimit->start_date = $data['start']->startOfDay(); $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: // if no currency has been provided, use the user's default currency:
$currency = null; $currency = null;
// update if relevant: // update if relevant:
if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) { 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) { if (null === $currency) {
$currency = $budgetLimit->transactionCurrency ?? app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup); $currency = $budgetLimit->transactionCurrency ?? app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
} }
$currency->enabled = true; $currency->enabled = true;
$currency->save(); $currency->save();
$budgetLimit->transaction_currency_id = $currency->id; $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 public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
{ {
// count the limits: // count the limits:
$limits = $budget->budgetlimits() $limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->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.end_date', $end->format('Y-m-d 00:00:00'))
->count('budget_limits.*'); ->count('budget_limits.*')
;
app('log')->debug(sprintf('Found %d budget limits.', $limits)); app('log')->debug(sprintf('Found %d budget limits.', $limits));
// there might be a budget limit for these dates: // there might be a budget limit for these dates:
/** @var null|BudgetLimit $limit */ /** @var null|BudgetLimit $limit */
$limit = $budget->budgetlimits() $limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->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.end_date', $end->format('Y-m-d 00:00:00'))
->first(['budget_limits.*']); ->first(['budget_limits.*'])
;
// if more than 1 limit found, delete the others: // if more than 1 limit found, delete the others:
if ($limits > 1 && null !== $limit) { if ($limits > 1 && null !== $limit) {
app('log')->debug(sprintf('Found more than 1, delete all except #%d', $limit->id)); app('log')->debug(sprintf('Found more than 1, delete all except #%d', $limit->id));
$budget->budgetlimits() $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->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.end_date', $end->format('Y-m-d 00:00:00'))
->where('budget_limits.id', '!=', $limit->id)->delete(); ->where('budget_limits.id', '!=', $limit->id)->delete()
;
} }
// delete if amount is zero. // 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'); app('log')->debug('No existing budget limit, create a new one');
// or create one and return it. // or create one and return it.
$limit = new BudgetLimit(); $limit = new BudgetLimit();
$limit->budget()->associate($budget); $limit->budget()->associate($budget);
$limit->start_date = $start->startOfDay(); $limit->start_date = $start->startOfDay();
$limit->start_date_tz = $start->format('e'); $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->whereLike('name', sprintf('%%%s', $query));
} }
$search->orderBy('order', 'ASC') $search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true); ->orderBy('name', 'ASC')->where('active', true)
;
return $search->take($limit)->get(); return $search->take($limit)->get();
} }
@@ -75,7 +76,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$search->whereLike('name', sprintf('%s%%', $query)); $search->whereLike('name', sprintf('%s%%', $query));
} }
$search->orderBy('order', 'ASC') $search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true); ->orderBy('name', 'ASC')->where('active', true)
;
return $search->take($limit)->get(); return $search->take($limit)->get();
} }
@@ -83,7 +85,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function budgetedInPeriod(Carbon $start, Carbon $end): array 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'))); app('log')->debug(sprintf('Now in budgetedInPeriod("%s", "%s")', $start->format('Y-m-d'), $end->format('Y-m-d')));
$return = []; $return = [];
/** @var BudgetLimitRepository $limitRepository */ /** @var BudgetLimitRepository $limitRepository */
$limitRepository = app(BudgetLimitRepository::class); $limitRepository = app(BudgetLimitRepository::class);
@@ -100,9 +102,9 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
/** @var BudgetLimit $limit */ /** @var BudgetLimit $limit */
foreach ($limits as $limit) { foreach ($limits as $limit) {
app('log')->debug(sprintf('Budget limit #%d', $limit->id)); app('log')->debug(sprintf('Budget limit #%d', $limit->id));
$currency = $limit->transactionCurrency; $currency = $limit->transactionCurrency;
$rate = $converter->getCurrencyRate($currency, $defaultCurrency, $end); $rate = $converter->getCurrencyRate($currency, $defaultCurrency, $end);
$currencyCode = $currency->code; $currencyCode = $currency->code;
$return[$currencyCode] ??= [ $return[$currencyCode] ??= [
'currency_id' => (string) $currency->id, 'currency_id' => (string) $currency->id,
'currency_name' => $currency->name, 'currency_name' => $currency->name,
@@ -157,9 +159,10 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getActiveBudgets(): Collection public function getActiveBudgets(): Collection
{ {
return $this->user->budgets()->where('active', true) return $this->user->budgets()->where('active', true)
->orderBy('order', 'ASC') ->orderBy('order', 'ASC')
->orderBy('name', 'ASC') ->orderBy('name', 'ASC')
->get(); ->get()
;
} }
/** /**
@@ -199,19 +202,19 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function budgetedInPeriodForBudget(Budget $budget, Carbon $start, Carbon $end): array 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'))); 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 */ /** @var BudgetLimitRepository $limitRepository */
$limitRepository = app(BudgetLimitRepository::class); $limitRepository = app(BudgetLimitRepository::class);
$limitRepository->setUser($this->user); $limitRepository->setUser($this->user);
app('log')->debug(sprintf('Budget #%d: "%s"', $budget->id, $budget->name)); 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 */ /** @var BudgetLimit $limit */
foreach ($limits as $limit) { foreach ($limits as $limit) {
app('log')->debug(sprintf('Budget limit #%d', $limit->id)); app('log')->debug(sprintf('Budget limit #%d', $limit->id));
$currency = $limit->transactionCurrency; $currency = $limit->transactionCurrency;
$return[$currency->id] ??= [ $return[$currency->id] ??= [
'id' => (string) $currency->id, 'id' => (string) $currency->id,
'name' => $currency->name, 'name' => $currency->name,
@@ -280,7 +283,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
{ {
app('log')->debug('Now in update()'); app('log')->debug('Now in update()');
$oldName = $budget->name; $oldName = $budget->name;
if (array_key_exists('name', $data)) { if (array_key_exists('name', $data)) {
$budget->name = $data['name']; $budget->name = $data['name'];
$this->updateRuleActions($oldName, $budget->name); $this->updateRuleActions($oldName, $budget->name);
@@ -295,7 +298,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$budget->save(); $budget->save();
// update or create auto-budget: // update or create auto-budget:
$autoBudget = $this->getAutoBudget($budget); $autoBudget = $this->getAutoBudget($budget);
// first things first: delete when no longer required: // first things first: delete when no longer required:
$autoBudgetType = array_key_exists('auto_budget_type', $data) ? $data['auto_budget_type'] : null; $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']; $types = ['set_budget'];
$actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id') $actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id')
->where('rules.user_id', $this->user->id) ->where('rules.user_id', $this->user->id)
->whereIn('rule_actions.action_type', $types) ->whereIn('rule_actions.action_type', $types)
->where('rule_actions.action_value', $oldName) ->where('rule_actions.action_value', $oldName)
->get(['rule_actions.*']); ->get(['rule_actions.*'])
;
app('log')->debug(sprintf('Found %d actions to update.', $actions->count())); app('log')->debug(sprintf('Found %d actions to update.', $actions->count()));
/** @var RuleAction $action */ /** @var RuleAction $action */
@@ -339,10 +343,11 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
{ {
$types = ['budget_is']; $types = ['budget_is'];
$triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id') $triggers = RuleTrigger::leftJoin('rules', 'rules.id', '=', 'rule_triggers.rule_id')
->where('rules.user_id', $this->user->id) ->where('rules.user_id', $this->user->id)
->whereIn('rule_triggers.trigger_type', $types) ->whereIn('rule_triggers.trigger_type', $types)
->where('rule_triggers.trigger_value', $oldName) ->where('rule_triggers.trigger_value', $oldName)
->get(['rule_triggers.*']); ->get(['rule_triggers.*'])
;
app('log')->debug(sprintf('Found %d triggers to update.', $triggers->count())); app('log')->debug(sprintf('Found %d triggers to update.', $triggers->count()));
/** @var RuleTrigger $trigger */ /** @var RuleTrigger $trigger */
@@ -386,7 +391,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$autoBudget = $this->getAutoBudget($budget); $autoBudget = $this->getAutoBudget($budget);
// grab default currency: // grab default currency:
$currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup); $currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
if (null === $autoBudget) { if (null === $autoBudget) {
// at this point it's a blind assumption auto_budget_type is 1 or 2. // 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 public function getBudgets(): Collection
{ {
return $this->user->budgets()->orderBy('order', 'ASC') return $this->user->budgets()->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->get(); ->orderBy('name', 'ASC')->get()
;
} }
public function destroyAutoBudget(Budget $budget): void public function destroyAutoBudget(Budget $budget): void
@@ -524,7 +530,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getAttachments(Budget $budget): Collection public function getAttachments(Budget $budget): Collection
{ {
$set = $budget->attachments()->get(); $set = $budget->attachments()->get();
$disk = Storage::disk('upload'); $disk = Storage::disk('upload');
@@ -550,8 +556,9 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
public function getInactiveBudgets(): Collection public function getInactiveBudgets(): Collection
{ {
return $this->user->budgets() return $this->user->budgets()
->orderBy('order', 'ASC') ->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', 0)->get(); ->orderBy('name', 'ASC')->where('active', 0)->get()
;
} }
public function getNoteText(Budget $budget): ?string public function getNoteText(Budget $budget): ?string
@@ -571,7 +578,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$search->whereLike('name', sprintf('%%%s%%', $query)); $search->whereLike('name', sprintf('%%%s%%', $query));
} }
$search->orderBy('order', 'ASC') $search->orderBy('order', 'ASC')
->orderBy('name', 'ASC')->where('active', true); ->orderBy('name', 'ASC')->where('active', true)
;
return $search->take($limit)->get(); return $search->take($limit)->get();
} }
@@ -591,8 +599,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// exclude specific liabilities // exclude specific liabilities
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user); $repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities')); $subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection(); $selection = new Collection();
/** @var Account $account */ /** @var Account $account */
foreach ($subset as $account) { foreach ($subset as $account) {
@@ -603,19 +611,20 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// start collecting: // start collecting:
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user) $collector->setUser($this->user)
->setRange($start, $end) ->setRange($start, $end)
->excludeDestinationAccounts($selection) ->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value]) ->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
->setBudgets($this->getActiveBudgets()); ->setBudgets($this->getActiveBudgets())
;
$journals = $collector->getExtractedJournals(); $journals = $collector->getExtractedJournals();
$array = []; $array = [];
foreach ($journals as $journal) { foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id']; $currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [ $array[$currencyId] ??= [
'id' => (string) $currencyId, 'id' => (string) $currencyId,
'name' => $journal['currency_name'], 'name' => $journal['currency_name'],
'symbol' => $journal['currency_symbol'], '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'])); $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
// also do foreign amount: // also do foreign amount:
$foreignId = (int) $journal['foreign_currency_id']; $foreignId = (int) $journal['foreign_currency_id'];
if (0 !== $foreignId) { if (0 !== $foreignId) {
$array[$foreignId] ??= [ $array[$foreignId] ??= [
'id' => (string) $foreignId, 'id' => (string) $foreignId,
'name' => $journal['foreign_currency_name'], 'name' => $journal['foreign_currency_name'],
'symbol' => $journal['foreign_currency_symbol'], 'symbol' => $journal['foreign_currency_symbol'],
@@ -652,8 +661,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// exclude specific liabilities // exclude specific liabilities
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
$repository->setUser($this->user); $repository->setUser($this->user);
$subset = $repository->getAccountsByType(config('firefly.valid_liabilities')); $subset = $repository->getAccountsByType(config('firefly.valid_liabilities'));
$selection = new Collection(); $selection = new Collection();
/** @var Account $account */ /** @var Account $account */
foreach ($subset as $account) { foreach ($subset as $account) {
@@ -664,19 +673,20 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
// start collecting: // start collecting:
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user) $collector->setUser($this->user)
->setRange($start, $end) ->setRange($start, $end)
->excludeDestinationAccounts($selection) ->excludeDestinationAccounts($selection)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value]) ->setTypes([TransactionTypeEnum::WITHDRAWAL->value])
->setBudget($budget); ->setBudget($budget)
;
$journals = $collector->getExtractedJournals(); $journals = $collector->getExtractedJournals();
$array = []; $array = [];
foreach ($journals as $journal) { foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id']; $currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [ $array[$currencyId] ??= [
'id' => (string) $currencyId, 'id' => (string) $currencyId,
'name' => $journal['currency_name'], 'name' => $journal['currency_name'],
'symbol' => $journal['currency_symbol'], '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'])); $array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
// also do foreign amount: // also do foreign amount:
$foreignId = (int) $journal['foreign_currency_id']; $foreignId = (int) $journal['foreign_currency_id'];
if (0 !== $foreignId) { if (0 !== $foreignId) {
$array[$foreignId] ??= [ $array[$foreignId] ??= [
'id' => (string) $foreignId, 'id' => (string) $foreignId,
'name' => $journal['foreign_currency_name'], 'name' => $journal['foreign_currency_name'],
'symbol' => $journal['foreign_currency_symbol'], 'symbol' => $journal['foreign_currency_symbol'],
@@ -711,7 +721,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
*/ */
public function store(array $data): Budget public function store(array $data): Budget
{ {
$order = $this->getMaxOrder(); $order = $this->getMaxOrder();
try { try {
$newBudget = Budget::create( $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)) { if (!array_key_exists('auto_budget_type', $data) || !array_key_exists('auto_budget_amount', $data) || !array_key_exists('auto_budget_period', $data)) {
return $newBudget; return $newBudget;
} }
$type = $data['auto_budget_type']; $type = $data['auto_budget_type'];
if ('none' === $type) { if ('none' === $type) {
return $newBudget; return $newBudget;
} }
@@ -757,8 +767,8 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
} }
/** @var CurrencyRepositoryInterface $repos */ /** @var CurrencyRepositoryInterface $repos */
$repos = app(CurrencyRepositoryInterface::class); $repos = app(CurrencyRepositoryInterface::class);
$currency = null; $currency = null;
if (array_key_exists('currency_id', $data)) { if (array_key_exists('currency_id', $data)) {
$currency = $repos->find((int) $data['currency_id']); $currency = $repos->find((int) $data['currency_id']);
} }
@@ -769,7 +779,7 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup); $currency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup);
} }
$autoBudget = new AutoBudget(); $autoBudget = new AutoBudget();
$autoBudget->budget()->associate($newBudget); $autoBudget->budget()->associate($newBudget);
$autoBudget->transaction_currency_id = $currency->id; $autoBudget->transaction_currency_id = $currency->id;
$autoBudget->auto_budget_type = $type; $autoBudget->auto_budget_type = $type;
@@ -778,11 +788,11 @@ class BudgetRepository implements BudgetRepositoryInterface, UserGroupInterface
$autoBudget->save(); $autoBudget->save();
// create initial budget limit. // create initial budget limit.
$today = today(config('app.timezone')); $today = today(config('app.timezone'));
$start = app('navigation')->startOfPeriod($today, $autoBudget->period); $start = app('navigation')->startOfPeriod($today, $autoBudget->period);
$end = app('navigation')->endOfPeriod($start, $autoBudget->period); $end = app('navigation')->endOfPeriod($start, $autoBudget->period);
$limitRepos = app(BudgetLimitRepositoryInterface::class); $limitRepos = app(BudgetLimitRepositoryInterface::class);
$limitRepos->setUser($this->user); $limitRepos->setUser($this->user);
$limitRepos->store( $limitRepos->store(
[ [

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,6 @@ use FireflyIII\Support\Repositories\UserGroup\UserGroupInterface;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait; use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Override;
/** /**
* Class CurrencyRepository. * Class CurrencyRepository.
@@ -67,7 +66,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
public function currencyInUseAt(TransactionCurrency $currency): ?string public function currencyInUseAt(TransactionCurrency $currency): ?string
{ {
app('log')->debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code)); 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) { if ($countJournals > 0) {
app('log')->info(sprintf('Count journals is %d, return true.', $countJournals)); 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: // 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) { if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); 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. // 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) { if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); 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: // 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) { if ($bills > 0) {
app('log')->info(sprintf('Used in %d bills as currency, return true. ', $bills)); 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) // is being used in accounts (as integer)
$meta = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id') $meta = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id')
->whereNull('accounts.deleted_at') ->whereNull('accounts.deleted_at')
->where('account_meta.name', 'currency_id')->where('account_meta.data', json_encode($currency->id))->count(); ->where('account_meta.name', 'currency_id')->where('account_meta.data', json_encode($currency->id))->count()
;
if ($meta > 0) { if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); 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 // 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) { if ($budgetLimit > 0) {
app('log')->info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit)); 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 // 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) { if ($count > 0) {
app('log')->info('Is the default currency of the user, return true.'); 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 // 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) { if ($count > 0) {
app('log')->info('Is the default currency of the user group, return true.'); app('log')->info('Is the default currency of the user group, return true.');
@@ -275,7 +275,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
return $result; return $result;
} }
#[Override] #[\Override]
public function find(int $currencyId): ?TransactionCurrency public function find(int $currencyId): ?TransactionCurrency
{ {
return TransactionCurrency::find($currencyId); return TransactionCurrency::find($currencyId);
@@ -320,9 +320,10 @@ class CurrencyRepository implements CurrencyRepositoryInterface, UserGroupInterf
/** @var null|CurrencyExchangeRate $rate */ /** @var null|CurrencyExchangeRate $rate */
$rate = $this->user->currencyExchangeRates() $rate = $this->user->currencyExchangeRates()
->where('from_currency_id', $fromCurrency->id) ->where('from_currency_id', $fromCurrency->id)
->where('to_currency_id', $toCurrency->id) ->where('to_currency_id', $toCurrency->id)
->where('date', $date->format('Y-m-d'))->first(); ->where('date', $date->format('Y-m-d'))->first()
;
if (null !== $rate) { 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'))); 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 checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user) * @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId) * @method setUserGroupById(int $userGroupId)
*
*/ */
interface CurrencyRepositoryInterface interface CurrencyRepositoryInterface
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\PiggyBank; namespace FireflyIII\Repositories\PiggyBank;
use Exception;
use FireflyIII\Events\Model\PiggyBank\ChangedAmount; use FireflyIII\Events\Model\PiggyBank\ChangedAmount;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\PiggyBankFactory; use FireflyIII\Factory\PiggyBankFactory;
@@ -69,9 +68,9 @@ trait ModifiesPiggyBanks
$pivot->native_current_amount = null; $pivot->native_current_amount = null;
// also update native_current_amount. // 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) { if ($userCurrency->id !== $piggyBank->transaction_currency_id) {
$converter = new ExchangeRateConverter(); $converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true); $converter->setIgnoreSettings(true);
$pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount); $pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount);
} }
@@ -92,9 +91,9 @@ trait ModifiesPiggyBanks
$pivot->native_current_amount = null; $pivot->native_current_amount = null;
// also update native_current_amount. // 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) { if ($userCurrency->id !== $piggyBank->transaction_currency_id) {
$converter = new ExchangeRateConverter(); $converter = new ExchangeRateConverter();
$converter->setIgnoreSettings(true); $converter->setIgnoreSettings(true);
$pivot->native_current_amount = $converter->convert($piggyBank->transactionCurrency, $userCurrency, today(), $pivot->current_amount); $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)); Log::debug(sprintf('Maximum amount: %s', $maxAmount));
} }
$compare = bccomp($amount, $maxAmount); $compare = bccomp($amount, $maxAmount);
$result = $compare <= 0; $result = $compare <= 0;
Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true))); Log::debug(sprintf('Compare <= 0? %d, so canAddAmount is %s', $compare, var_export($result, true)));
@@ -142,7 +141,7 @@ trait ModifiesPiggyBanks
} }
/** /**
* @throws Exception * @throws \Exception
*/ */
public function destroy(PiggyBank $piggyBank): bool public function destroy(PiggyBank $piggyBank): bool
{ {
@@ -161,11 +160,11 @@ trait ModifiesPiggyBanks
public function setCurrentAmount(PiggyBank $piggyBank, string $amount): PiggyBank public function setCurrentAmount(PiggyBank $piggyBank, string $amount): PiggyBank
{ {
$repetition = $this->getRepetition($piggyBank); $repetition = $this->getRepetition($piggyBank);
if (null === $repetition) { if (null === $repetition) {
return $piggyBank; return $piggyBank;
} }
$max = $piggyBank->target_amount; $max = $piggyBank->target_amount;
if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) { if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) {
$amount = $max; $amount = $max;
} }
@@ -208,14 +207,14 @@ trait ModifiesPiggyBanks
public function update(PiggyBank $piggyBank, array $data): PiggyBank public function update(PiggyBank $piggyBank, array $data): PiggyBank
{ {
$piggyBank = $this->updateProperties($piggyBank, $data); $piggyBank = $this->updateProperties($piggyBank, $data);
if (array_key_exists('notes', $data)) { if (array_key_exists('notes', $data)) {
$this->updateNote($piggyBank, (string) $data['notes']); $this->updateNote($piggyBank, (string) $data['notes']);
} }
// update the order of the piggy bank: // update the order of the piggy bank:
$oldOrder = $piggyBank->order; $oldOrder = $piggyBank->order;
$newOrder = (int) ($data['order'] ?? $oldOrder); $newOrder = (int) ($data['order'] ?? $oldOrder);
if ($oldOrder !== $newOrder) { if ($oldOrder !== $newOrder) {
$this->setOrder($piggyBank, $newOrder); $this->setOrder($piggyBank, $newOrder);
} }
@@ -308,7 +307,7 @@ trait ModifiesPiggyBanks
return; return;
} }
$dbNote = $piggyBank->notes()->first(); $dbNote = $piggyBank->notes()->first();
if (null === $dbNote) { if (null === $dbNote) {
$dbNote = new Note(); $dbNote = new Note();
$dbNote->noteable()->associate($piggyBank); $dbNote->noteable()->associate($piggyBank);
@@ -319,15 +318,16 @@ trait ModifiesPiggyBanks
public function setOrder(PiggyBank $piggyBank, int $newOrder): bool 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)); // Log::debug(sprintf('Will move piggy bank #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
if ($newOrder > $oldOrder) { if ($newOrder > $oldOrder) {
PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') 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')
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder) ->where('piggy_banks.order', '<=', $newOrder)->where('piggy_banks.order', '>', $oldOrder)
->where('piggy_banks.id', '!=', $piggyBank->id) ->where('piggy_banks.id', '!=', $piggyBank->id)
->distinct()->decrement('piggy_banks.order'); ->distinct()->decrement('piggy_banks.order')
;
$piggyBank->order = $newOrder; $piggyBank->order = $newOrder;
Log::debug(sprintf('[1] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); Log::debug(sprintf('[1] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
@@ -336,11 +336,12 @@ trait ModifiesPiggyBanks
return true; return true;
} }
PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') 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')
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder) ->where('piggy_banks.order', '>=', $newOrder)->where('piggy_banks.order', '<', $oldOrder)
->where('piggy_banks.id', '!=', $piggyBank->id) ->where('piggy_banks.id', '!=', $piggyBank->id)
->distinct()->increment('piggy_banks.order'); ->distinct()->increment('piggy_banks.order')
;
$piggyBank->order = $newOrder; $piggyBank->order = $newOrder;
Log::debug(sprintf('[2] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder)); Log::debug(sprintf('[2] Order of piggy #%d ("%s") from %d to %d', $piggyBank->id, $piggyBank->name, $oldOrder, $newOrder));
@@ -361,7 +362,7 @@ trait ModifiesPiggyBanks
} }
// if this account contains less than the amount, remove the current amount, update the amount and continue. // if this account contains less than the amount, remove the current amount, update the amount and continue.
$this->removeAmount($piggyBank, $account, $current); $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\Collection;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Override;
/** /**
* Class PiggyBankRepository. * Class PiggyBankRepository.
@@ -57,9 +56,10 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
Log::channel('audit')->info('Delete all piggy banks through destroyAll'); Log::channel('audit')->info('Delete all piggy banks through destroyAll');
PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') 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')
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->delete(); ->delete()
;
} }
public function findPiggyBank(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank public function findPiggyBank(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank
@@ -90,9 +90,10 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
public function find(int $piggyBankId): ?PiggyBank public function find(int $piggyBankId): ?PiggyBank
{ {
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') return 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')
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*']); ->where('piggy_banks.id', $piggyBankId)->first(['piggy_banks.*'])
;
} }
/** /**
@@ -101,14 +102,15 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
public function findByName(string $name): ?PiggyBank public function findByName(string $name): ?PiggyBank
{ {
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') return 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')
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->where('piggy_banks.name', $name)->first(['piggy_banks.*']); ->where('piggy_banks.name', $name)->first(['piggy_banks.*'])
;
} }
public function getAttachments(PiggyBank $piggyBank): Collection public function getAttachments(PiggyBank $piggyBank): Collection
{ {
$set = $piggyBank->attachments()->get(); $set = $piggyBank->attachments()->get();
$disk = Storage::disk('upload'); $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)); app('log')->debug(sprintf('Now in getExactAmount(%d, %d)', $piggyBank->id, $journal->id));
$operator = null; $operator = null;
$currency = null; $currency = null;
/** @var JournalRepositoryInterface $journalRepost */ /** @var JournalRepositoryInterface $journalRepost */
$journalRepost = app(JournalRepositoryInterface::class); $journalRepost = app(JournalRepositoryInterface::class);
$journalRepost->setUser($this->user); $journalRepost->setUser($this->user);
/** @var AccountRepositoryInterface $accountRepos */ /** @var AccountRepositoryInterface $accountRepos */
$accountRepos = app(AccountRepositoryInterface::class); $accountRepos = app(AccountRepositoryInterface::class);
$accountRepos->setUser($this->user); $accountRepos->setUser($this->user);
$defaultCurrency = app('amount')->getNativeCurrencyByUserGroup($this->user->userGroup); $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)); app('log')->debug(sprintf('Piggy bank #%d currency is %s', $piggyBank->id, $piggyBank->transactionCurrency->code));
/** @var Transaction $source */ /** @var Transaction $source */
$source = $journal->transactions()->with(['account'])->where('amount', '<', 0)->first(); $source = $journal->transactions()->with(['account'])->where('amount', '<', 0)->first();
/** @var Transaction $destination */ /** @var Transaction $destination */
$destination = $journal->transactions()->with(['account'])->where('amount', '>', 0)->first(); $destination = $journal->transactions()->with(['account'])->where('amount', '>', 0)->first();
$hits = 0; $hits = 0;
foreach ($piggyBank->accounts as $account) { foreach ($piggyBank->accounts as $account) {
// matches source, which means amount will be removed from piggy: // 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. // currency of the account + the piggy bank currency are almost the same.
// which amount from the transaction matches? // which amount from the transaction matches?
$amount = null; $amount = null;
if ((int) $source->transaction_currency_id === $currency->id) { if ((int) $source->transaction_currency_id === $currency->id) {
app('log')->debug('Use normal amount'); app('log')->debug('Use normal amount');
$amount = app('steam')->{$operator}($source->amount); // @phpstan-ignore-line $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)); app('log')->debug(sprintf('The currency is %s and the amount is %s', $currency->code, $amount));
$currentAmount = $this->getCurrentAmount($piggyBank); $currentAmount = $this->getCurrentAmount($piggyBank);
$room = bcsub($piggyBank->target_amount, $currentAmount); $room = bcsub($piggyBank->target_amount, $currentAmount);
$compare = bcmul($currentAmount, '-1'); $compare = bcmul($currentAmount, '-1');
if (0 === bccomp($piggyBank->target_amount, '0')) { if (0 === bccomp($piggyBank->target_amount, '0')) {
// amount is zero? then the "room" is positive amount of we wish to add or remove. // 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 public function getPiggyBanks(): Collection
{ {
$query = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') $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) { if (null === $this->user) {
$query->where('accounts.user_group_id', $this->userGroup->id); $query->where('accounts.user_group_id', $this->userGroup->id);
} }
if (null !== $this->user) { if (null !== $this->user) {
$query->where('accounts.user_id', $this->user->id); $query->where('accounts.user_id', $this->user->id);
} }
return $query return $query
->with( ->with(
[ [
'objectGroups', '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 public function getRepetition(PiggyBank $piggyBank, bool $overrule = false): ?PiggyBankRepetition
@@ -385,23 +390,24 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
return $balance; return $balance;
} }
#[Override] #[\Override]
public function purgeAll(): void public function purgeAll(): void
{ {
PiggyBank::withTrashed() PiggyBank::withTrashed()
->whereNotNull('piggy_banks.deleted_at') ->whereNotNull('piggy_banks.deleted_at')
->leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') ->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')
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->with( ->with(
[ [
'objectGroups', 'objectGroups',
] ]
) )
->delete(); ->delete()
;
} }
#[Override] #[\Override]
public function resetOrder(): void public function resetOrder(): void
{ {
$factory = new PiggyBankFactory(); $factory = new PiggyBankFactory();
@@ -412,19 +418,21 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte
public function searchPiggyBank(string $query, int $limit): Collection public function searchPiggyBank(string $query, int $limit): Collection
{ {
$search = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') $search = 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')
->where('accounts.user_id', $this->user->id) ->where('accounts.user_id', $this->user->id)
->with( ->with(
[ [
'objectGroups', 'objectGroups',
] ]
) )
->orderBy('piggy_banks.order', 'ASC')->distinct(); ->orderBy('piggy_banks.order', 'ASC')->distinct()
;
if ('' !== $query) { if ('' !== $query) {
$search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query)); $search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query));
} }
$search->orderBy('piggy_banks.order', 'ASC') $search->orderBy('piggy_banks.order', 'ASC')
->orderBy('piggy_banks.name', 'ASC'); ->orderBy('piggy_banks.name', 'ASC')
;
return $search->take($limit)->get(['piggy_banks.*']); return $search->take($limit)->get(['piggy_banks.*']);
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,7 +39,6 @@ use Illuminate\Support\Collection;
* @method checkUserGroupAccess(UserRoleEnum $role) * @method checkUserGroupAccess(UserRoleEnum $role)
* @method setUser(null|Authenticatable|User $user) * @method setUser(null|Authenticatable|User $user)
* @method setUserGroupById(int $userGroupId) * @method setUserGroupById(int $userGroupId)
*
*/ */
interface RuleGroupRepositoryInterface 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 public function listExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $tags = null): array
{ {
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]); $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
$tagIds = []; $tagIds = [];
if (null !== $accounts && $accounts->count() > 0) { if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts); $collector->setAccounts($accounts);
} }
@@ -66,7 +66,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$array = []; $array = [];
$listedJournals = []; $listedJournals = [];
foreach ($journals as $journal) { foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id']; $currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [ $array[$currencyId] ??= [
'tags' => [], 'tags' => [],
'currency_id' => $currencyId, 'currency_id' => $currencyId,
@@ -78,9 +78,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// may have multiple tags: // may have multiple tags:
foreach ($journal['tags'] as $tag) { foreach ($journal['tags'] as $tag) {
$tagId = (int) $tag['id']; $tagId = (int) $tag['id'];
$tagName = (string) $tag['name']; $tagName = (string) $tag['name'];
$journalId = (int) $journal['transaction_journal_id']; $journalId = (int) $journal['transaction_journal_id'];
if (!in_array($tagId, $tagIds, true)) { if (!in_array($tagId, $tagIds, true)) {
continue; continue;
} }
@@ -89,7 +89,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
if (in_array($journalId, $listedJournals, true)) { if (in_array($journalId, $listedJournals, true)) {
continue; continue;
} }
$listedJournals[] = $journalId; $listedJournals[] = $journalId;
$array[$currencyId]['tags'][$tagId] ??= [ $array[$currencyId]['tags'][$tagId] ??= [
'id' => $tagId, 'id' => $tagId,
'name' => $tagName, '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 public function listIncome(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $tags = null): array
{ {
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::DEPOSIT->value]); $collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionTypeEnum::DEPOSIT->value]);
$tagIds = []; $tagIds = [];
if (null !== $accounts && $accounts->count() > 0) { if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts); $collector->setAccounts($accounts);
} }
@@ -150,7 +150,7 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
$listedJournals = []; $listedJournals = [];
foreach ($journals as $journal) { foreach ($journals as $journal) {
$currencyId = (int) $journal['currency_id']; $currencyId = (int) $journal['currency_id'];
$array[$currencyId] ??= [ $array[$currencyId] ??= [
'tags' => [], 'tags' => [],
'currency_id' => $currencyId, 'currency_id' => $currencyId,
@@ -162,9 +162,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
// may have multiple tags: // may have multiple tags:
foreach ($journal['tags'] as $tag) { foreach ($journal['tags'] as $tag) {
$tagId = (int) $tag['id']; $tagId = (int) $tag['id'];
$tagName = (string) $tag['name']; $tagName = (string) $tag['name'];
$journalId = (int) $journal['transaction_journal_id']; $journalId = (int) $journal['transaction_journal_id'];
if (!in_array($tagId, $tagIds, true)) { if (!in_array($tagId, $tagIds, true)) {
continue; continue;
@@ -173,9 +173,9 @@ class OperationsRepository implements OperationsRepositoryInterface, UserGroupIn
if (in_array($journalId, $listedJournals, true)) { if (in_array($journalId, $listedJournals, true)) {
continue; continue;
} }
$listedJournals[] = $journalId; $listedJournals[] = $journalId;
$array[$currencyId]['tags'][$tagId] ??= [ $array[$currencyId]['tags'][$tagId] ??= [
'id' => $tagId, 'id' => $tagId,
'name' => $tagName, 'name' => $tagName,
'transaction_journals' => [], 'transaction_journals' => [],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
public function currencyInUseAt(TransactionCurrency $currency): ?string public function currencyInUseAt(TransactionCurrency $currency): ?string
{ {
app('log')->debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code)); 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) { if ($countJournals > 0) {
app('log')->info(sprintf('Count journals is %d, return true.', $countJournals)); app('log')->info(sprintf('Count journals is %d, return true.', $countJournals));
@@ -81,7 +81,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface
} }
// is being used in accounts: // 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) { if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); 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. // 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) { if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); 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: // 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) { if ($bills > 0) {
app('log')->info(sprintf('Used in %d bills as currency, return true. ', $bills)); 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) // is being used in accounts (as integer)
$meta = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id') $meta = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id')
->whereNull('accounts.deleted_at') ->whereNull('accounts.deleted_at')
->where('account_meta.name', 'currency_id')->where('account_meta.data', json_encode($currency->id))->count(); ->where('account_meta.name', 'currency_id')->where('account_meta.data', json_encode($currency->id))->count()
;
if ($meta > 0) { if ($meta > 0) {
app('log')->info(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); 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 // 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) { if ($budgetLimit > 0) {
app('log')->info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit)); 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 // 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) { if ($count > 0) {
app('log')->info('Is the default currency of the user, return true.'); 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 // 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) { if ($count > 0) {
app('log')->info('Is the default currency of the user group, return true.'); 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 FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Override;
/** /**
* Class ExchangeRateRepository * Class ExchangeRateRepository
@@ -41,51 +40,55 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface
{ {
use UserGroupTrait; use UserGroupTrait;
#[Override] #[\Override]
public function deleteRate(CurrencyExchangeRate $rate): void public function deleteRate(CurrencyExchangeRate $rate): void
{ {
$this->userGroup->currencyExchangeRates()->where('id', $rate->id)->delete(); $this->userGroup->currencyExchangeRates()->where('id', $rate->id)->delete();
} }
#[Override] #[\Override]
public function getAll(): Collection public function getAll(): Collection
{ {
return $this->userGroup->currencyExchangeRates()->orderBy('date', 'ASC')->get(); return $this->userGroup->currencyExchangeRates()->orderBy('date', 'ASC')->get();
} }
#[Override] #[\Override]
public function getRates(TransactionCurrency $from, TransactionCurrency $to): Collection public function getRates(TransactionCurrency $from, TransactionCurrency $to): Collection
{ {
// orderBy('date', 'DESC')->toRawSql(); // orderBy('date', 'DESC')->toRawSql();
return return
$this->userGroup->currencyExchangeRates() $this->userGroup->currencyExchangeRates()
->where(function (Builder $q1) use ($from, $to): void { ->where(function (Builder $q1) use ($from, $to): void {
$q1->where(function (Builder $q) use ($from, $to): void { $q1->where(function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $from->id) $q->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id); ->where('to_currency_id', $to->id)
})->orWhere(function (Builder $q) use ($from, $to): void { ;
$q->where('from_currency_id', $to->id) })->orWhere(function (Builder $q) use ($from, $to): void {
->where('to_currency_id', $from->id); $q->where('from_currency_id', $to->id)
}); ->where('to_currency_id', $from->id)
}) ;
->orderBy('date', 'DESC') });
->get(['currency_exchange_rates.*']); })
->orderBy('date', 'DESC')
->get(['currency_exchange_rates.*'])
;
} }
#[Override] #[\Override]
public function getSpecificRateOnDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): ?CurrencyExchangeRate public function getSpecificRateOnDate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): ?CurrencyExchangeRate
{ {
/** @var null|CurrencyExchangeRate */ /** @var null|CurrencyExchangeRate */
return return
$this->userGroup->currencyExchangeRates() $this->userGroup->currencyExchangeRates()
->where('from_currency_id', $from->id) ->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id) ->where('to_currency_id', $to->id)
->where('date', $date->format('Y-m-d')) ->where('date', $date->format('Y-m-d'))
->first(); ->first()
;
} }
#[Override] #[\Override]
public function storeExchangeRate(TransactionCurrency $from, TransactionCurrency $to, string $rate, Carbon $date): CurrencyExchangeRate public function storeExchangeRate(TransactionCurrency $from, TransactionCurrency $to, string $rate, Carbon $date): CurrencyExchangeRate
{ {
$object = new CurrencyExchangeRate(); $object = new CurrencyExchangeRate();
@@ -101,7 +104,7 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface
return $object; return $object;
} }
#[Override] #[\Override]
public function updateExchangeRate(CurrencyExchangeRate $object, string $rate, ?Carbon $date = null): CurrencyExchangeRate public function updateExchangeRate(CurrencyExchangeRate $object, string $rate, ?Carbon $date = null): CurrencyExchangeRate
{ {
$object->rate = $rate; $object->rate = $rate;

View File

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

View File

@@ -40,13 +40,14 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function getPiggyBanks(): Collection public function getPiggyBanks(): Collection
{ {
return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') return 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')
->where('accounts.user_group_id', $this->userGroup->id) ->where('accounts.user_group_id', $this->userGroup->id)
->with( ->with(
[ [
'objectGroups', 'objectGroups',
] ]
) )
->orderBy('piggy_banks.order', 'ASC')->distinct()->get(['piggy_banks.*']); ->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 public function getMessages(Webhook $webhook): Collection
{ {
return $webhook->webhookMessages() return $webhook->webhookMessages()
->orderBy('created_at', 'DESC') ->orderBy('created_at', 'DESC')
->get(['webhook_messages.*']); ->get(['webhook_messages.*'])
;
} }
public function getReadyMessages(Webhook $webhook): Collection public function getReadyMessages(Webhook $webhook): Collection
{ {
return $webhook->webhookMessages() return $webhook->webhookMessages()
->where('webhook_messages.sent', 0) ->where('webhook_messages.sent', 0)
->where('webhook_messages.errored', 0) ->where('webhook_messages.errored', 0)
->get(['webhook_messages.*']) ->get(['webhook_messages.*'])
->filter( ->filter(
static function (WebhookMessage $message) { // @phpstan-ignore-line static function (WebhookMessage $message) { // @phpstan-ignore-line
return $message->webhookAttempts()->count() <= 2; return $message->webhookAttempts()->count() <= 2;
} }
)->splice(0, 3); )->splice(0, 3)
;
} }
public function store(array $data): Webhook public function store(array $data): Webhook

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Support\JsonApi\Enrichments; namespace FireflyIII\Support\JsonApi\Enrichments;
use Carbon\Carbon;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -49,7 +48,6 @@ use Illuminate\Support\Facades\Log;
*/ */
class AccountEnrichment implements EnrichmentInterface class AccountEnrichment implements EnrichmentInterface
{ {
private Collection $collection; private Collection $collection;
private User $user; 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 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. // don't care about user group ID, except for some specific preferences.
$userGroupId = $this->getUserGroupId($user, $name); $userGroupId = $this->getUserGroupId($user, $name);
$query = Preference::where('user_id', $user->id)->where('name', $name); $query = Preference::where('user_id', $user->id)->where('name', $name);
@@ -90,7 +90,7 @@ class Preferences
} }
if (null !== $preference) { 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; return $preference;
} }

View File

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

View File

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

View File

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

16
composer.lock generated
View File

@@ -1878,16 +1878,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v11.44.1", "version": "v11.44.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d" "reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/0883d4175f4e2b5c299e7087ad3c74f2ce195c6d", "url": "https://api.github.com/repos/laravel/framework/zipball/f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"reference": "0883d4175f4e2b5c299e7087ad3c74f2ce195c6d", "reference": "f85216c82cbd38b66d67ebd20ea762cb3751a4b4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -2089,7 +2089,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "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", "name": "laravel/passport",
@@ -12876,7 +12876,7 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": [], "stability-flags": {},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {
@@ -12897,6 +12897,6 @@
"ext-xml": "*", "ext-xml": "*",
"ext-xmlwriter": "*" "ext-xmlwriter": "*"
}, },
"platform-dev": [], "platform-dev": {},
"plugin-api-version": "2.3.0" "plugin-api-version": "2.6.0"
} }

View File

@@ -81,7 +81,7 @@ return [
'running_balance_column' => env('USE_RUNNING_BALANCE', false), 'running_balance_column' => env('USE_RUNNING_BALANCE', false),
// see cer.php for exchange rates feature flag. // 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. 'api_version' => '2.1.0', // field is no longer used.
'db_version' => 25, 'db_version' => 25,

View File

@@ -24,241 +24,241 @@ declare(strict_types=1);
return [ return [
'operators' => [ 'operators' => [
'user_action' => ['alias' => false, 'needs_context' => true], 'user_action' => ['alias' => false, 'needs_context' => true],
'account_id' => ['alias' => false, 'needs_context' => true], 'account_id' => ['alias' => false, 'needs_context' => true],
'reconciled' => ['alias' => false, 'needs_context' => false], 'reconciled' => ['alias' => false, 'needs_context' => false],
'source_account_id' => ['alias' => false, 'needs_context' => true], 'source_account_id' => ['alias' => false, 'needs_context' => true],
'destination_account_id' => ['alias' => false, 'needs_context' => true], 'destination_account_id' => ['alias' => false, 'needs_context' => true],
'transaction_type' => ['alias' => false, 'needs_context' => true], 'transaction_type' => ['alias' => false, 'needs_context' => true],
'type' => ['alias' => true, 'alias_for' => 'transaction_type', 'needs_context' => true], 'type' => ['alias' => true, 'alias_for' => 'transaction_type', 'needs_context' => true],
'tag_is' => ['alias' => false, 'needs_context' => true], 'tag_is' => ['alias' => false, 'needs_context' => true],
'tag_is_not' => ['alias' => false, 'needs_context' => true], 'tag_is_not' => ['alias' => false, 'needs_context' => true],
'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true], 'tag' => ['alias' => true, 'alias_for' => 'tag_is', 'needs_context' => true],
'tag_contains' => ['alias' => false, 'needs_context' => true], 'tag_contains' => ['alias' => false, 'needs_context' => true],
'tag_ends' => ['alias' => false, 'needs_context' => true], 'tag_ends' => ['alias' => false, 'needs_context' => true],
'tag_starts' => ['alias' => false, 'needs_context' => true], 'tag_starts' => ['alias' => false, 'needs_context' => true],
'description_is' => ['alias' => false, 'needs_context' => true], 'description_is' => ['alias' => false, 'needs_context' => true],
'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true], 'description' => ['alias' => true, 'alias_for' => 'description_is', 'needs_context' => true],
'description_contains' => ['alias' => false, 'needs_context' => true], 'description_contains' => ['alias' => false, 'needs_context' => true],
'description_ends' => ['alias' => false, 'needs_context' => true], 'description_ends' => ['alias' => false, 'needs_context' => true],
'description_starts' => ['alias' => false, 'needs_context' => true], 'description_starts' => ['alias' => false, 'needs_context' => true],
'notes_is' => ['alias' => false, 'needs_context' => true], 'notes_is' => ['alias' => false, 'needs_context' => true],
'notes_are' => ['alias' => true, 'alias_for' => 'notes_is', 'needs_context' => true], 'notes_are' => ['alias' => true, 'alias_for' => 'notes_is', 'needs_context' => true],
'notes_contains' => ['alias' => false, 'needs_context' => true], 'notes_contains' => ['alias' => false, 'needs_context' => true],
'notes_contain' => ['alias' => true, 'alias_for' => 'notes_contains', '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' => ['alias' => true, 'alias_for' => 'notes_contains', 'needs_context' => true],
'notes_ends' => ['alias' => false, 'needs_context' => true], 'notes_ends' => ['alias' => false, 'needs_context' => true],
'notes_end' => ['alias' => true, 'alias_for' => 'notes_ends', 'needs_context' => true], 'notes_end' => ['alias' => true, 'alias_for' => 'notes_ends', 'needs_context' => true],
'notes_starts' => ['alias' => false, 'needs_context' => true], 'notes_starts' => ['alias' => false, 'needs_context' => true],
'notes_start' => ['alias' => true, 'alias_for' => 'notes_starts', 'needs_context' => true], 'notes_start' => ['alias' => true, 'alias_for' => 'notes_starts', 'needs_context' => true],
'source_account_is' => ['alias' => false, 'needs_context' => true], 'source_account_is' => ['alias' => false, 'needs_context' => true],
'from_account_is' => ['alias' => true, 'alias_for' => 'source_account_is', '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_account_contains' => ['alias' => false, 'needs_context' => true],
'source' => ['alias' => true, 'alias_for' => 'source_account_contains', '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' => ['alias' => true, 'alias_for' => 'source_account_contains', 'needs_context' => true],
'from_account_contains' => ['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], 'source_account_ends' => ['alias' => false, 'needs_context' => true],
'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true], 'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true],
'source_account_starts' => ['alias' => false, 'needs_context' => true], 'source_account_starts' => ['alias' => false, 'needs_context' => true],
'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', '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], 'source_account_nr_is' => ['alias' => false, 'needs_context' => true],
'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', '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], 'source_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'from_account_nr_contains' => ['alias' => true, 'alias_for' => 'source_account_nr_contains', '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], 'source_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', '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], 'source_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', '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], 'destination_account_is' => ['alias' => false, 'needs_context' => true],
'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', '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_account_contains' => ['alias' => false, 'needs_context' => true],
'destination' => ['alias' => true, 'alias_for' => 'destination_account_contains', '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' => ['alias' => true, 'alias_for' => 'destination_account_contains', 'needs_context' => true],
'to_account_contains' => ['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], 'destination_account_ends' => ['alias' => false, 'needs_context' => true],
'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true], 'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true],
'destination_account_starts' => ['alias' => false, 'needs_context' => true], 'destination_account_starts' => ['alias' => false, 'needs_context' => true],
'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', '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], 'destination_account_nr_is' => ['alias' => false, 'needs_context' => true],
'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', '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], 'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true],
'to_account_nr_contains' => ['alias' => true, 'alias_for' => 'destination_account_nr_contains', '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], 'destination_account_nr_ends' => ['alias' => false, 'needs_context' => true],
'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', '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], 'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true],
'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', '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_is' => ['alias' => false, 'needs_context' => true],
'account_contains' => ['alias' => false, 'needs_context' => true], 'account_contains' => ['alias' => false, 'needs_context' => true],
'account_ends' => ['alias' => false, 'needs_context' => true], 'account_ends' => ['alias' => false, 'needs_context' => true],
'account_starts' => ['alias' => false, 'needs_context' => true], 'account_starts' => ['alias' => false, 'needs_context' => true],
'account_nr_is' => ['alias' => false, 'needs_context' => true], 'account_nr_is' => ['alias' => false, 'needs_context' => true],
'account_nr_contains' => ['alias' => false, 'needs_context' => true], 'account_nr_contains' => ['alias' => false, 'needs_context' => true],
'account_nr_ends' => ['alias' => false, 'needs_context' => true], 'account_nr_ends' => ['alias' => false, 'needs_context' => true],
'account_nr_starts' => ['alias' => false, 'needs_context' => true], 'account_nr_starts' => ['alias' => false, 'needs_context' => true],
'category_is' => ['alias' => false, 'needs_context' => true], 'category_is' => ['alias' => false, 'needs_context' => true],
'category_contains' => ['alias' => false, 'needs_context' => true], 'category_contains' => ['alias' => false, 'needs_context' => true],
'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true], 'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true],
'category_ends' => ['alias' => false, 'needs_context' => true], 'category_ends' => ['alias' => false, 'needs_context' => true],
'category_starts' => ['alias' => false, 'needs_context' => true], 'category_starts' => ['alias' => false, 'needs_context' => true],
'budget_is' => ['alias' => false, 'needs_context' => true], 'budget_is' => ['alias' => false, 'needs_context' => true],
'budget_contains' => ['alias' => false, 'needs_context' => true], 'budget_contains' => ['alias' => false, 'needs_context' => true],
'budget' => ['alias' => true, 'alias_for' => 'budget_contains', 'needs_context' => true], 'budget' => ['alias' => true, 'alias_for' => 'budget_contains', 'needs_context' => true],
'budget_ends' => ['alias' => false, 'needs_context' => true], 'budget_ends' => ['alias' => false, 'needs_context' => true],
'budget_starts' => ['alias' => false, 'needs_context' => true], 'budget_starts' => ['alias' => false, 'needs_context' => true],
'bill_is' => ['alias' => false, 'needs_context' => true], 'bill_is' => ['alias' => false, 'needs_context' => true],
'bill_contains' => ['alias' => false, 'needs_context' => true], 'bill_contains' => ['alias' => false, 'needs_context' => true],
'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true], 'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'bill_ends' => ['alias' => false, 'needs_context' => true], 'bill_ends' => ['alias' => false, 'needs_context' => true],
'bill_starts' => ['alias' => false, 'needs_context' => true], 'bill_starts' => ['alias' => false, 'needs_context' => true],
'subscription_is' => ['alias' => true, 'alias_for' => 'bill_is', '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_contains' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true],
'subscription' => ['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_ends' => ['alias' => true, 'alias_for' => 'bill_ends', 'needs_context' => true],
'subscription_starts' => ['alias' => true, 'alias_for' => 'bill_starts', 'needs_context' => true], 'subscription_starts' => ['alias' => true, 'alias_for' => 'bill_starts', 'needs_context' => true],
'external_id_is' => ['alias' => false, 'needs_context' => true], 'external_id_is' => ['alias' => false, 'needs_context' => true],
'external_id_contains' => ['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' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true],
'external_id_ends' => ['alias' => false, 'needs_context' => true], 'external_id_ends' => ['alias' => false, 'needs_context' => true],
'external_id_starts' => ['alias' => false, 'needs_context' => true], 'external_id_starts' => ['alias' => false, 'needs_context' => true],
'internal_reference_is' => ['alias' => false, 'needs_context' => true], 'internal_reference_is' => ['alias' => false, 'needs_context' => true],
'internal_reference_contains' => ['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' => ['alias' => true, 'alias_for' => 'internal_reference_contains', 'needs_context' => true],
'internal_reference_ends' => ['alias' => false, 'needs_context' => true], 'internal_reference_ends' => ['alias' => false, 'needs_context' => true],
'internal_reference_starts' => ['alias' => false, 'needs_context' => true], 'internal_reference_starts' => ['alias' => false, 'needs_context' => true],
'external_url_is' => ['alias' => false, 'needs_context' => true], 'external_url_is' => ['alias' => false, 'needs_context' => true],
'external_url_contains' => ['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' => ['alias' => true, 'alias_for' => 'external_url_contains', 'needs_context' => true],
'external_url_ends' => ['alias' => false, 'needs_context' => true], 'external_url_ends' => ['alias' => false, 'needs_context' => true],
'external_url_starts' => ['alias' => false, 'needs_context' => true], 'external_url_starts' => ['alias' => false, 'needs_context' => true],
'has_attachments' => ['alias' => false, 'needs_context' => false], 'has_attachments' => ['alias' => false, 'needs_context' => false],
'has_any_category' => ['alias' => false, 'needs_context' => false], 'has_any_category' => ['alias' => false, 'needs_context' => false],
'has_any_budget' => ['alias' => false, 'needs_context' => false], 'has_any_budget' => ['alias' => false, 'needs_context' => false],
'has_any_bill' => ['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_subscription' => ['alias' => true, 'needs_context' => false, 'alias_for' => 'has_any_bill'],
'has_any_tag' => ['alias' => false, 'needs_context' => false], 'has_any_tag' => ['alias' => false, 'needs_context' => false],
'any_notes' => ['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_any_notes' => ['alias' => true, 'alias_for' => 'any_notes', 'needs_context' => false],
'has_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], 'any_external_url' => ['alias' => false, 'needs_context' => false],
'has_any_external_url' => ['alias' => true, 'alias_for' => 'any_external_url', '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_attachments' => ['alias' => false, 'needs_context' => false],
'has_no_category' => ['alias' => false, 'needs_context' => false], 'has_no_category' => ['alias' => false, 'needs_context' => false],
'has_no_budget' => ['alias' => false, 'needs_context' => false], 'has_no_budget' => ['alias' => false, 'needs_context' => false],
'has_no_bill' => ['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_subscription' => ['alias' => true, 'needs_context' => false, 'alias_for' => 'has_no_bill'],
'has_no_tag' => ['alias' => false, 'needs_context' => false], 'has_no_tag' => ['alias' => false, 'needs_context' => false],
'no_notes' => ['alias' => false, 'needs_context' => false], 'no_notes' => ['alias' => false, 'needs_context' => false],
'no_external_url' => ['alias' => false, 'needs_context' => false], 'no_external_url' => ['alias' => false, 'needs_context' => false],
'source_is_cash' => ['alias' => false, 'needs_context' => false], 'source_is_cash' => ['alias' => false, 'needs_context' => false],
'destination_is_cash' => ['alias' => false, 'needs_context' => false], 'destination_is_cash' => ['alias' => false, 'needs_context' => false],
'account_is_cash' => ['alias' => false, 'needs_context' => false], 'account_is_cash' => ['alias' => false, 'needs_context' => false],
'currency_is' => ['alias' => false, 'needs_context' => true], 'currency_is' => ['alias' => false, 'needs_context' => true],
'foreign_currency_is' => ['alias' => false, 'needs_context' => true], 'foreign_currency_is' => ['alias' => false, 'needs_context' => true],
'id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true], 'id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'journal_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], 'recurrence_id' => ['alias' => false, 'trigger_class' => '', 'needs_context' => true],
'date_on' => ['alias' => false, 'needs_context' => true], 'date_on' => ['alias' => false, 'needs_context' => true],
'date' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true], 'date' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_is' => ['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], 'on' => ['alias' => true, 'alias_for' => 'date_on', 'needs_context' => true],
'date_before' => ['alias' => false, 'needs_context' => true], 'date_before' => ['alias' => false, 'needs_context' => true],
'before' => ['alias' => true, 'alias_for' => 'date_before', 'needs_context' => true], 'before' => ['alias' => true, 'alias_for' => 'date_before', 'needs_context' => true],
'date_after' => ['alias' => false, 'needs_context' => true], 'date_after' => ['alias' => false, 'needs_context' => true],
'after' => ['alias' => true, 'alias_for' => 'date_after', 'needs_context' => true], 'after' => ['alias' => true, 'alias_for' => 'date_after', 'needs_context' => true],
'interest_date_on' => ['alias' => false, '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' => ['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_is' => ['alias' => true, 'alias_for' => 'interest_date_on', 'needs_context' => true],
'interest_date_before' => ['alias' => false, 'needs_context' => true], 'interest_date_before' => ['alias' => false, 'needs_context' => true],
'interest_date_after' => ['alias' => false, 'needs_context' => true], 'interest_date_after' => ['alias' => false, 'needs_context' => true],
'book_date_on' => ['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' => ['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_is' => ['alias' => true, 'alias_for' => 'book_date_on', 'needs_context' => true],
'book_date_before' => ['alias' => false, 'needs_context' => true], 'book_date_before' => ['alias' => false, 'needs_context' => true],
'book_date_after' => ['alias' => false, 'needs_context' => true], 'book_date_after' => ['alias' => false, 'needs_context' => true],
'process_date_on' => ['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' => ['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_is' => ['alias' => true, 'alias_for' => 'process_date_on', 'needs_context' => true],
'process_date_before' => ['alias' => false, 'needs_context' => true], 'process_date_before' => ['alias' => false, 'needs_context' => true],
'process_date_after' => ['alias' => false, 'needs_context' => true], 'process_date_after' => ['alias' => false, 'needs_context' => true],
'due_date_on' => ['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' => ['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_is' => ['alias' => true, 'alias_for' => 'due_date_on', 'needs_context' => true],
'due_date_before' => ['alias' => false, 'needs_context' => true], 'due_date_before' => ['alias' => false, 'needs_context' => true],
'due_date_after' => ['alias' => false, 'needs_context' => true], 'due_date_after' => ['alias' => false, 'needs_context' => true],
'payment_date_on' => ['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' => ['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_is' => ['alias' => true, 'alias_for' => 'payment_date_on', 'needs_context' => true],
'payment_date_before' => ['alias' => false, 'needs_context' => true], 'payment_date_before' => ['alias' => false, 'needs_context' => true],
'payment_date_after' => ['alias' => false, 'needs_context' => true], 'payment_date_after' => ['alias' => false, 'needs_context' => true],
'invoice_date_on' => ['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' => ['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_is' => ['alias' => true, 'alias_for' => 'invoice_date_on', 'needs_context' => true],
'invoice_date_before' => ['alias' => false, 'needs_context' => true], 'invoice_date_before' => ['alias' => false, 'needs_context' => true],
'invoice_date_after' => ['alias' => false, 'needs_context' => true], 'invoice_date_after' => ['alias' => false, 'needs_context' => true],
'created_at_on' => ['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' => ['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_is' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_at_before' => ['alias' => false, 'needs_context' => true], 'created_at_before' => ['alias' => false, 'needs_context' => true],
'created_at_after' => ['alias' => false, 'needs_context' => true], 'created_at_after' => ['alias' => false, 'needs_context' => true],
'updated_at_on' => ['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' => ['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_is' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_at_before' => ['alias' => false, 'needs_context' => true], 'updated_at_before' => ['alias' => false, 'needs_context' => true],
'updated_at_after' => ['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_on' => ['alias' => true, 'alias_for' => 'created_at_on', 'needs_context' => true],
'created_on' => ['alias' => true, 'alias_for' => 'created_at', '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_before' => ['alias' => true, 'alias_for' => 'created_at_before', 'needs_context' => true],
'created_on_after' => ['alias' => true, 'alias_for' => 'created_at_after', '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_on' => ['alias' => true, 'alias_for' => 'updated_at_on', 'needs_context' => true],
'updated_on' => ['alias' => true, 'alias_for' => 'updated_at', '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_before' => ['alias' => true, 'alias_for' => 'updated_at_before', 'needs_context' => true],
'updated_on_after' => ['alias' => true, 'alias_for' => 'updated_at_after', 'needs_context' => true], 'updated_on_after' => ['alias' => true, 'alias_for' => 'updated_at_after', 'needs_context' => true],
'amount_is' => ['alias' => false, 'needs_context' => true], 'amount_is' => ['alias' => false, 'needs_context' => true],
'amount' => ['alias' => true, 'alias_for' => 'amount_is', '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_exactly' => ['alias' => true, 'alias_for' => 'amount_is', 'needs_context' => true],
'amount_less' => ['alias' => false, 'needs_context' => true], 'amount_less' => ['alias' => false, 'needs_context' => true],
'amount_max' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true], 'amount_max' => ['alias' => true, 'alias_for' => 'amount_less', 'needs_context' => true],
'less' => ['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_more' => ['alias' => false, 'needs_context' => true],
'amount_min' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true], 'amount_min' => ['alias' => true, 'alias_for' => 'amount_more', 'needs_context' => true],
'more' => ['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_is' => ['alias' => false, 'needs_context' => true],
'foreign_amount' => ['alias' => true, 'alias_for' => 'foreign_amount_is', '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_less' => ['alias' => false, 'needs_context' => true],
'foreign_amount_max' => ['alias' => true, 'alias_for' => 'foreign_amount_less', '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_more' => ['alias' => false, 'needs_context' => true],
'foreign_amount_min' => ['alias' => true, 'alias_for' => 'foreign_amount_more', '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_name_is' => ['alias' => false, 'needs_context' => true],
'attachment' => ['alias' => true, 'alias_for' => 'attachment_name_is', '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_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' => ['alias' => true, 'alias_for' => 'attachment_name_is', 'needs_context' => true],
'attachment_name_contains' => ['alias' => false, 'needs_context' => true], 'attachment_name_contains' => ['alias' => false, 'needs_context' => true],
'attachment_name_starts' => ['alias' => false, 'needs_context' => true], 'attachment_name_starts' => ['alias' => false, 'needs_context' => true],
'attachment_name_ends' => ['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' => ['alias' => true, 'alias_for' => 'attachment_notes_are', 'needs_context' => true],
'attachment_notes_are' => ['alias' => false, 'needs_context' => true], 'attachment_notes_are' => ['alias' => false, 'needs_context' => true],
'attachment_notes_contains' => ['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_contain' => ['alias' => true, 'alias_for' => 'attachment_notes_contains', 'needs_context' => true],
'attachment_notes_starts' => ['alias' => false, '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_start' => ['alias' => true, 'alias_for' => 'attachment_notes_starts', 'needs_context' => true],
'attachment_notes_ends' => ['alias' => false, 'needs_context' => true], 'attachment_notes_ends' => ['alias' => false, 'needs_context' => true],
'attachment_notes_end' => ['alias' => true, 'alias_for' => 'attachment_notes_ends', 'needs_context' => true], 'attachment_notes_end' => ['alias' => true, 'alias_for' => 'attachment_notes_ends', 'needs_context' => true],
'exists' => ['alias' => false, 'needs_context' => false], 'exists' => ['alias' => false, 'needs_context' => false],
'sepa_ct_is' => ['alias' => false, 'needs_context' => true], 'sepa_ct_is' => ['alias' => false, 'needs_context' => true],
'no_external_id' => ['alias' => false, 'needs_context' => false], 'no_external_id' => ['alias' => false, 'needs_context' => false],
'any_external_id' => ['alias' => false, 'needs_context' => false], 'any_external_id' => ['alias' => false, 'needs_context' => false],
// based on source or destination balance. Very heavy search. // based on source or destination balance. Very heavy search.
'source_balance_gte' => ['alias' => false, 'needs_context' => true], 'source_balance_gte' => ['alias' => false, 'needs_context' => true],
'source_balance_gt' => ['alias' => false, 'needs_context' => true], 'source_balance_gt' => ['alias' => false, 'needs_context' => true],
'source_balance_lte' => ['alias' => false, 'needs_context' => true], 'source_balance_lte' => ['alias' => false, 'needs_context' => true],
'source_balance_lt' => ['alias' => false, 'needs_context' => true], 'source_balance_lt' => ['alias' => false, 'needs_context' => true],
'source_balance_is' => ['alias' => false, 'needs_context' => true], 'source_balance_is' => ['alias' => false, 'needs_context' => true],
'destination_balance_gte' => ['alias' => false, 'needs_context' => true], 'destination_balance_gte' => ['alias' => false, 'needs_context' => true],
'destination_balance_gt' => ['alias' => false, 'needs_context' => true], 'destination_balance_gt' => ['alias' => false, 'needs_context' => true],
'destination_balance_lte' => ['alias' => false, 'needs_context' => true], 'destination_balance_lte' => ['alias' => false, 'needs_context' => true],
'destination_balance_lt' => ['alias' => false, 'needs_context' => true], 'destination_balance_lt' => ['alias' => false, 'needs_context' => true],
'destination_balance_is' => ['alias' => false, 'needs_context' => true], 'destination_balance_is' => ['alias' => false, 'needs_context' => true],
], ],
/** /**
* Which query parser to use - 'new' or 'legacy' * 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_new_client": "Vytvo\u0159it nov\u00e9ho klienta",
"profile_oauth_create_client": "Vytvo\u0159it klienta", "profile_oauth_create_client": "Vytvo\u0159it klienta",
"profile_oauth_edit_client": "Upravit 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_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_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": "Your application's authorization callback URL.", "profile_oauth_redirect_url_help": "Callback URL va\u0161\u00ed aplikace.",
"profile_authorized_apps": "Authorized applications", "profile_authorized_apps": "Authorized applications",
"profile_authorized_clients": "Autorizovan\u00ed klienti", "profile_authorized_clients": "Autorizovan\u00ed klienti",
"profile_scopes": "Scopes", "profile_scopes": "Scopes",
"profile_revoke": "Revoke", "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": "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_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": "You have not created any personal access tokens.", "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_new_token": "Vytvo\u0159it nov\u00fd token",
"profile_create_token": "Vytvo\u0159it token", "profile_create_token": "Vytvo\u0159it token",
"profile_create": "Vytvo\u0159it", "profile_create": "Vytvo\u0159it",
@@ -100,8 +100,8 @@
"piggy_bank": "Pokladni\u010dka", "piggy_bank": "Pokladni\u010dka",
"profile_oauth_client_secret_title": "Client Secret", "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_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": "Soukrom\u00e1 aplikace",
"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_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_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_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.", "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_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.", "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!", "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", "logs": "Logy",
"response": "Odpov\u011b\u010f", "response": "Odpov\u011b\u010f",
"visit_webhook_url": "Nav\u0161t\u00edvit URL webhooku", "visit_webhook_url": "Nav\u0161t\u00edvit URL webhooku",

View File

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

View File

@@ -18,7 +18,7 @@
"is_reconciled": "\u5df2\u6838\u9500", "is_reconciled": "\u5df2\u6838\u9500",
"split": "\u62c6\u5206", "split": "\u62c6\u5206",
"single_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", "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", "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.", "webhook_stored_link": "<a href=\"webhooks\/show\/{ID}\">\u63a8\u9001 #{ID} (\"{title}\")<\/a> \u5df2\u4fdd\u5b58.",
@@ -153,7 +153,7 @@
"url": "\u7f51\u5740", "url": "\u7f51\u5740",
"active": "\u542f\u7528", "active": "\u542f\u7528",
"interest_date": "\u5229\u606f\u65e5\u671f", "interest_date": "\u5229\u606f\u65e5\u671f",
"administration_currency": "Native currency", "administration_currency": "\u672c\u5730\u8d27\u5e01",
"title": "\u6807\u9898", "title": "\u6807\u9898",
"date": "\u65e5\u671f", "date": "\u65e5\u671f",
"book_date": "\u767b\u8bb0\u65e5\u671f", "book_date": "\u767b\u8bb0\u65e5\u671f",
@@ -166,14 +166,14 @@
"webhook_response": "\u54cd\u5e94\u5185\u5bb9", "webhook_response": "\u54cd\u5e94\u5185\u5bb9",
"webhook_trigger": "\u89e6\u53d1\u6761\u4ef6", "webhook_trigger": "\u89e6\u53d1\u6761\u4ef6",
"webhook_delivery": "\u53d1\u9001\u683c\u5f0f", "webhook_delivery": "\u53d1\u9001\u683c\u5f0f",
"from_currency_to_currency": "{from} &rarr; {to}", "from_currency_to_currency": "{from}&rarr;{to}",
"to_currency_from_currency": "{to} &rarr; {from}", "to_currency_from_currency": "{to}&rarr;{from}",
"rate": "Rate" "rate": "\u8bc4\u7ea7"
}, },
"list": { "list": {
"title": "\u6807\u9898", "title": "\u6807\u9898",
"active": "\u662f\u5426\u542f\u7528\uff1f", "active": "\u662f\u5426\u542f\u7528\uff1f",
"native_currency": "Native currency", "native_currency": "\u672c\u5730\u8d27\u5e01",
"trigger": "\u89e6\u53d1\u6761\u4ef6", "trigger": "\u89e6\u53d1\u6761\u4ef6",
"response": "\u7b54\u590d", "response": "\u7b54\u590d",
"delivery": "\u4ea4\u4ed8", "delivery": "\u4ea4\u4ed8",