diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php
index 4b77635b4b..8c9ac76727 100644
--- a/app/Http/Controllers/Account/ShowController.php
+++ b/app/Http/Controllers/Account/ShowController.php
@@ -102,7 +102,7 @@ class ShowController extends Controller
// make sure dates are end of day and start of day:
$start->startOfDay();
- $end->endOfDay();
+ $end->endOfDay()->milli(0);
$location = $this->repository->getLocation($account);
$attachments = $this->repository->getAttachments($account);
diff --git a/app/Models/PeriodStatistic.php b/app/Models/PeriodStatistic.php
index 030735a00f..194073bc88 100644
--- a/app/Models/PeriodStatistic.php
+++ b/app/Models/PeriodStatistic.php
@@ -22,7 +22,8 @@ class PeriodStatistic extends Model
'created_at' => 'datetime',
'updated_at' => 'datetime',
'deleted_at' => 'datetime',
- 'date' => SeparateTimezoneCaster::class,
+ 'start' => SeparateTimezoneCaster::class,
+ 'end' => SeparateTimezoneCaster::class,
];
}
diff --git a/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php b/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php
index ae8914c0c0..1762329f12 100644
--- a/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php
+++ b/app/Repositories/PeriodStatistic/PeriodStatisticRepository.php
@@ -66,4 +66,9 @@ class PeriodStatisticRepository implements PeriodStatisticRepositoryInterface
return $stat;
}
+
+ public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection
+ {
+ return $model->primaryPeriodStatistics()->where('start','>=', $start)->where('end','<=', $end)->get();
+ }
}
diff --git a/app/Repositories/PeriodStatistic/PeriodStatisticRepositoryInterface.php b/app/Repositories/PeriodStatistic/PeriodStatisticRepositoryInterface.php
index 0b9a6bfbc0..d26d85d101 100644
--- a/app/Repositories/PeriodStatistic/PeriodStatisticRepositoryInterface.php
+++ b/app/Repositories/PeriodStatistic/PeriodStatisticRepositoryInterface.php
@@ -35,4 +35,6 @@ interface PeriodStatisticRepositoryInterface
public function findPeriodStatistic(Model $model, Carbon $start, Carbon $end, string $type): Collection;
public function saveStatistic(Model $model, int $currencyId, Carbon $start, Carbon $end, string $type, int $count, string $amount): PeriodStatistic;
+
+ public function allInRangeForModel(Model $model, Carbon $start, Carbon $end): Collection;
}
diff --git a/app/Support/Amount.php b/app/Support/Amount.php
index c3d82f0caf..4b59c3a4d9 100644
--- a/app/Support/Amount.php
+++ b/app/Support/Amount.php
@@ -41,280 +41,6 @@ use NumberFormatter;
*/
class Amount
{
- /**
- * This method will properly format the given number, in color or "black and white",
- * as a currency, given two things: the currency required and the current locale.
- *
- * @throws FireflyException
- */
- public function formatAnything(TransactionCurrency $format, string $amount, ?bool $coloured = null): string
- {
- return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
- }
-
- /**
- * This method will properly format the given number, in color or "black and white",
- * as a currency, given two things: the currency required and the current locale.
- *
- * @throws FireflyException
- */
- public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string
- {
- $locale = Steam::getLocale();
- $rounded = Steam::bcround($amount, $decimalPlaces);
- $coloured ??= true;
-
- $fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
- $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
- $fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
- $fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
- $result = (string)$fmt->format((float)$rounded); // intentional float
-
- if (true === $coloured) {
- if (1 === bccomp($rounded, '0')) {
- return sprintf('%s', $result);
- }
- if (-1 === bccomp($rounded, '0')) {
- return sprintf('%s', $result);
- }
-
- return sprintf('%s', $result);
- }
-
- return $result;
- }
-
- public function formatByCurrencyId(int $currencyId, string $amount, ?bool $coloured = null): string
- {
- $format = $this->getTransactionCurrencyById($currencyId);
-
- return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
- }
-
- public function getAllCurrencies(): Collection
- {
- return TransactionCurrency::orderBy('code', 'ASC')->get();
- }
-
- /**
- * Experimental function to see if we can quickly and quietly get the amount from a journal.
- * This depends on the user's default currency and the wish to have it converted.
- */
- public function getAmountFromJournal(array $journal): string
- {
- $convertToPrimary = $this->convertToPrimary();
- $currency = $this->getPrimaryCurrency();
- $field = $convertToPrimary && $currency->id !== $journal['currency_id'] ? 'pc_amount' : 'amount';
- $amount = $journal[$field] ?? '0';
- // Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
- // fallback, the transaction has a foreign amount in $currency.
- if ($convertToPrimary && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) {
- $amount = $journal['foreign_amount'];
- // Log::debug(sprintf('Overruled, amount is now %s', $amount));
- }
-
- return (string)$amount;
- }
-
- public function getTransactionCurrencyById(int $currencyId): TransactionCurrency
- {
- $instance = PreferencesSingleton::getInstance();
- $key = sprintf('transaction_currency_%d', $currencyId);
-
- /** @var null|TransactionCurrency $pref */
- $pref = $instance->getPreference($key);
- if (null !== $pref) {
- return $pref;
- }
- $currency = TransactionCurrency::find($currencyId);
- if (null === $currency) {
- $message = sprintf('Could not find a transaction currency with ID #%d in %s', $currencyId, __METHOD__);
- Log::error($message);
-
- throw new FireflyException($message);
- }
- $instance->setPreference($key, $currency);
-
- return $currency;
- }
-
- public function getTransactionCurrencyByCode(string $code): TransactionCurrency
- {
- $instance = PreferencesSingleton::getInstance();
- $key = sprintf('transaction_currency_%s', $code);
-
- /** @var null|TransactionCurrency $pref */
- $pref = $instance->getPreference($key);
- if (null !== $pref) {
- return $pref;
- }
- $currency = TransactionCurrency::whereCode($code)->first();
- if (null === $currency) {
- $message = sprintf('Could not find a transaction currency with code "%s" in %s', $code, __METHOD__);
- Log::error($message);
-
- throw new FireflyException($message);
- }
- $instance->setPreference($key, $currency);
-
- return $currency;
- }
-
- public function convertToPrimary(?User $user = null): bool
- {
- $instance = PreferencesSingleton::getInstance();
- if (!$user instanceof User) {
- $pref = $instance->getPreference('convert_to_primary_no_user');
- if (null === $pref) {
- $res = true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
- $instance->setPreference('convert_to_primary_no_user', $res);
-
- return $res;
- }
-
- return $pref;
- }
- $key = sprintf('convert_to_primary_%d', $user->id);
- $pref = $instance->getPreference($key);
- if (null === $pref) {
- $res = true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
- $instance->setPreference($key, $res);
-
- return $res;
- }
-
- return $pref;
- }
-
- public function getPrimaryCurrency(): TransactionCurrency
- {
- if (auth()->check()) {
- /** @var User $user */
- $user = auth()->user();
- if (null !== $user->userGroup) {
- return $this->getPrimaryCurrencyByUserGroup($user->userGroup);
- }
- }
-
- return $this->getSystemCurrency();
- }
-
- public function getPrimaryCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency
- {
- $cache = new CacheProperties();
- $cache->addProperty('getPrimaryCurrencyByGroup');
- $cache->addProperty($userGroup->id);
- if ($cache->has()) {
- return $cache->get();
- }
-
- /** @var null|TransactionCurrency $primary */
- $primary = $userGroup->currencies()->where('group_default', true)->first();
- if (null === $primary) {
- $primary = $this->getSystemCurrency();
- // could be the user group has no default right now.
- $userGroup->currencies()->sync([$primary->id => ['group_default' => true]]);
- }
- $cache->store($primary);
-
- return $primary;
- }
-
- public function getSystemCurrency(): TransactionCurrency
- {
- return TransactionCurrency::whereNull('deleted_at')->where('code', 'EUR')->first();
- }
-
- /**
- * Experimental function to see if we can quickly and quietly get the amount from a journal.
- * This depends on the user's default currency and the wish to have it converted.
- */
- public function getAmountFromJournalObject(TransactionJournal $journal): string
- {
- $convertToPrimary = $this->convertToPrimary();
- $currency = $this->getPrimaryCurrency();
- $field = $convertToPrimary && $currency->id !== $journal->transaction_currency_id ? 'pc_amount' : 'amount';
-
- /** @var null|Transaction $sourceTransaction */
- $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
- if (null === $sourceTransaction) {
- return '0';
- }
- $amount = $sourceTransaction->{$field} ?? '0';
- if ((int)$sourceTransaction->foreign_currency_id === $currency->id) {
- // use foreign amount instead!
- $amount = (string)$sourceTransaction->foreign_amount; // hard coded to be foreign amount.
- }
-
- return $amount;
- }
-
- public function getCurrencies(): Collection
- {
- /** @var User $user */
- $user = auth()->user();
-
- return $user->currencies()->orderBy('code', 'ASC')->get();
- }
-
- /**
- * This method returns the correct format rules required by accounting.js,
- * the library used to format amounts in charts.
- *
- * Used only in one place.
- *
- * @throws FireflyException
- */
- public function getJsConfig(): array
- {
- $config = $this->getLocaleInfo();
- $negative = self::getAmountJsConfig($config['n_sep_by_space'], $config['n_sign_posn'], $config['negative_sign'], $config['n_cs_precedes']);
- $positive = self::getAmountJsConfig($config['p_sep_by_space'], $config['p_sign_posn'], $config['positive_sign'], $config['p_cs_precedes']);
-
- return [
- 'mon_decimal_point' => $config['mon_decimal_point'],
- 'mon_thousands_sep' => $config['mon_thousands_sep'],
- 'format' => [
- 'pos' => $positive,
- 'neg' => $negative,
- 'zero' => $positive,
- ],
- ];
- }
-
- /**
- * @throws FireflyException
- */
- private function getLocaleInfo(): array
- {
- // get config from preference, not from translation:
- $locale = Steam::getLocale();
- $array = Steam::getLocaleArray($locale);
-
- setlocale(LC_MONETARY, $array);
- $info = localeconv();
-
- // correct variables
- $info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
- $info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
-
- $info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
- $info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
-
- $fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
-
- $info['mon_decimal_point'] = $fmt->getSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL);
- $info['mon_thousands_sep'] = $fmt->getSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL);
-
- return $info;
- }
-
- private function getLocaleField(array $info, string $field): bool
- {
- return (is_bool($info[$field]) && true === $info[$field])
- || (is_int($info[$field]) && 1 === $info[$field]);
- }
-
/**
* bool $sepBySpace is $localeconv['n_sep_by_space']
* int $signPosn = $localeconv['n_sign_posn']
@@ -333,11 +59,11 @@ class Amount
// there are five possible positions for the "+" or "-" sign (if it is even used)
// pos_a and pos_e could be the ( and ) symbol.
- $posA = ''; // before everything
- $posB = ''; // before currency symbol
- $posC = ''; // after currency symbol
- $posD = ''; // before amount
- $posE = ''; // after everything
+ $posA = ''; // before everything
+ $posB = ''; // before currency symbol
+ $posC = ''; // after currency symbol
+ $posD = ''; // before amount
+ $posE = ''; // after everything
// format would be (currency before amount)
// AB%sC_D%vE
@@ -379,9 +105,283 @@ class Amount
}
if ($csPrecedes) {
- return $posA.$posB.'%s'.$posC.$space.$posD.'%v'.$posE;
+ return $posA . $posB . '%s' . $posC . $space . $posD . '%v' . $posE;
}
- return $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE;
+ return $posA . $posD . '%v' . $space . $posB . '%s' . $posC . $posE;
+ }
+
+ public function convertToPrimary(?User $user = null): bool
+ {
+ $instance = PreferencesSingleton::getInstance();
+ if (!$user instanceof User) {
+ $pref = $instance->getPreference('convert_to_primary_no_user');
+ if (null === $pref) {
+ $res = true === Preferences::get('convert_to_primary', false)->data && true === config('cer.enabled');
+ $instance->setPreference('convert_to_primary_no_user', $res);
+
+ return $res;
+ }
+
+ return $pref;
+ }
+ $key = sprintf('convert_to_primary_%d', $user->id);
+ $pref = $instance->getPreference($key);
+ if (null === $pref) {
+ $res = true === Preferences::getForUser($user, 'convert_to_primary', false)->data && true === config('cer.enabled');
+ $instance->setPreference($key, $res);
+
+ return $res;
+ }
+
+ return $pref;
+ }
+
+ /**
+ * This method will properly format the given number, in color or "black and white",
+ * as a currency, given two things: the currency required and the current locale.
+ *
+ * @throws FireflyException
+ */
+ public function formatAnything(TransactionCurrency $format, string $amount, ?bool $coloured = null): string
+ {
+ return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
+ }
+
+ public function formatByCurrencyId(int $currencyId, string $amount, ?bool $coloured = null): string
+ {
+ $format = $this->getTransactionCurrencyById($currencyId);
+
+ return $this->formatFlat($format->symbol, $format->decimal_places, $amount, $coloured);
+ }
+
+ /**
+ * This method will properly format the given number, in color or "black and white",
+ * as a currency, given two things: the currency required and the current locale.
+ *
+ * @throws FireflyException
+ */
+ public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string
+ {
+ $locale = Steam::getLocale();
+ $rounded = Steam::bcround($amount, $decimalPlaces);
+ $coloured ??= true;
+
+ $fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
+ $fmt->setSymbol(NumberFormatter::CURRENCY_SYMBOL, $symbol);
+ $fmt->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimalPlaces);
+ $fmt->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimalPlaces);
+ $result = (string)$fmt->format((float)$rounded); // intentional float
+
+ if (true === $coloured) {
+ if (1 === bccomp($rounded, '0')) {
+ return sprintf('%s', $result);
+ }
+ if (-1 === bccomp($rounded, '0')) {
+ return sprintf('%s', $result);
+ }
+
+ return sprintf('%s', $result);
+ }
+
+ return $result;
+ }
+
+ public function getAllCurrencies(): Collection
+ {
+ return TransactionCurrency::orderBy('code', 'ASC')->get();
+ }
+
+ /**
+ * Experimental function to see if we can quickly and quietly get the amount from a journal.
+ * This depends on the user's default currency and the wish to have it converted.
+ */
+ public function getAmountFromJournal(array $journal): string
+ {
+ $convertToPrimary = $this->convertToPrimary();
+ $currency = $this->getPrimaryCurrency();
+ $field = $convertToPrimary && $currency->id !== $journal['currency_id'] ? 'pc_amount' : 'amount';
+ $amount = $journal[$field] ?? '0';
+ // Log::debug(sprintf('Field is %s, amount is %s', $field, $amount));
+ // fallback, the transaction has a foreign amount in $currency.
+ if ($convertToPrimary && null !== $journal['foreign_amount'] && $currency->id === (int)$journal['foreign_currency_id']) {
+ $amount = $journal['foreign_amount'];
+ // Log::debug(sprintf('Overruled, amount is now %s', $amount));
+ }
+
+ return (string)$amount;
+ }
+
+ /**
+ * Experimental function to see if we can quickly and quietly get the amount from a journal.
+ * This depends on the user's default currency and the wish to have it converted.
+ */
+ public function getAmountFromJournalObject(TransactionJournal $journal): string
+ {
+ $convertToPrimary = $this->convertToPrimary();
+ $currency = $this->getPrimaryCurrency();
+ $field = $convertToPrimary && $currency->id !== $journal->transaction_currency_id ? 'pc_amount' : 'amount';
+
+ /** @var null|Transaction $sourceTransaction */
+ $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
+ if (null === $sourceTransaction) {
+ return '0';
+ }
+ $amount = $sourceTransaction->{$field} ?? '0';
+ if ((int)$sourceTransaction->foreign_currency_id === $currency->id) {
+ // use foreign amount instead!
+ $amount = (string)$sourceTransaction->foreign_amount; // hard coded to be foreign amount.
+ }
+
+ return $amount;
+ }
+
+ public function getCurrencies(): Collection
+ {
+ /** @var User $user */
+ $user = auth()->user();
+
+ return $user->currencies()->orderBy('code', 'ASC')->get();
+ }
+
+ /**
+ * This method returns the correct format rules required by accounting.js,
+ * the library used to format amounts in charts.
+ *
+ * Used only in one place.
+ *
+ * @throws FireflyException
+ */
+ public function getJsConfig(): array
+ {
+ $config = $this->getLocaleInfo();
+ $negative = self::getAmountJsConfig($config['n_sep_by_space'], $config['n_sign_posn'], $config['negative_sign'], $config['n_cs_precedes']);
+ $positive = self::getAmountJsConfig($config['p_sep_by_space'], $config['p_sign_posn'], $config['positive_sign'], $config['p_cs_precedes']);
+
+ return [
+ 'mon_decimal_point' => $config['mon_decimal_point'],
+ 'mon_thousands_sep' => $config['mon_thousands_sep'],
+ 'format' => [
+ 'pos' => $positive,
+ 'neg' => $negative,
+ 'zero' => $positive,
+ ],
+ ];
+ }
+
+ public function getPrimaryCurrency(): TransactionCurrency
+ {
+ if (auth()->check()) {
+ /** @var User $user */
+ $user = auth()->user();
+ if (null !== $user->userGroup) {
+ return $this->getPrimaryCurrencyByUserGroup($user->userGroup);
+ }
+ }
+
+ return $this->getSystemCurrency();
+ }
+
+ public function getPrimaryCurrencyByUserGroup(UserGroup $userGroup): TransactionCurrency
+ {
+ $cache = new CacheProperties();
+ $cache->addProperty('getPrimaryCurrencyByGroup');
+ $cache->addProperty($userGroup->id);
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ /** @var null|TransactionCurrency $primary */
+ $primary = $userGroup->currencies()->where('group_default', true)->first();
+ if (null === $primary) {
+ $primary = $this->getSystemCurrency();
+ // could be the user group has no default right now.
+ $userGroup->currencies()->sync([$primary->id => ['group_default' => true]]);
+ }
+ $cache->store($primary);
+
+ return $primary;
+ }
+
+ public function getSystemCurrency(): TransactionCurrency
+ {
+ return TransactionCurrency::whereNull('deleted_at')->where('code', 'EUR')->first();
+ }
+
+ public function getTransactionCurrencyByCode(string $code): TransactionCurrency
+ {
+ $instance = PreferencesSingleton::getInstance();
+ $key = sprintf('transaction_currency_%s', $code);
+
+ /** @var null|TransactionCurrency $pref */
+ $pref = $instance->getPreference($key);
+ if (null !== $pref) {
+ return $pref;
+ }
+ $currency = TransactionCurrency::whereCode($code)->first();
+ if (null === $currency) {
+ $message = sprintf('Could not find a transaction currency with code "%s" in %s', $code, __METHOD__);
+ Log::error($message);
+
+ throw new FireflyException($message);
+ }
+ $instance->setPreference($key, $currency);
+
+ return $currency;
+ }
+
+ public function getTransactionCurrencyById(int $currencyId): TransactionCurrency
+ {
+ $instance = PreferencesSingleton::getInstance();
+ $key = sprintf('transaction_currency_%d', $currencyId);
+
+ /** @var null|TransactionCurrency $pref */
+ $pref = $instance->getPreference($key);
+ if (null !== $pref) {
+ return $pref;
+ }
+ $currency = TransactionCurrency::find($currencyId);
+ if (null === $currency) {
+ $message = sprintf('Could not find a transaction currency with ID #%d in %s', $currencyId, __METHOD__);
+ Log::error($message);
+
+ throw new FireflyException($message);
+ }
+ $instance->setPreference($key, $currency);
+
+ return $currency;
+ }
+
+ private function getLocaleField(array $info, string $field): bool
+ {
+ return (is_bool($info[$field]) && true === $info[$field])
+ || (is_int($info[$field]) && 1 === $info[$field]);
+ }
+
+ /**
+ * @throws FireflyException
+ */
+ private function getLocaleInfo(): array
+ {
+ // get config from preference, not from translation:
+ $locale = Steam::getLocale();
+ $array = Steam::getLocaleArray($locale);
+
+ setlocale(LC_MONETARY, $array);
+ $info = localeconv();
+
+ // correct variables
+ $info['n_cs_precedes'] = $this->getLocaleField($info, 'n_cs_precedes');
+ $info['p_cs_precedes'] = $this->getLocaleField($info, 'p_cs_precedes');
+
+ $info['n_sep_by_space'] = $this->getLocaleField($info, 'n_sep_by_space');
+ $info['p_sep_by_space'] = $this->getLocaleField($info, 'p_sep_by_space');
+
+ $fmt = new NumberFormatter($locale, NumberFormatter::CURRENCY);
+
+ $info['mon_decimal_point'] = $fmt->getSymbol(NumberFormatter::MONETARY_SEPARATOR_SYMBOL);
+ $info['mon_thousands_sep'] = $fmt->getSymbol(NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL);
+
+ return $info;
}
}
diff --git a/app/Support/Authentication/RemoteUserGuard.php b/app/Support/Authentication/RemoteUserGuard.php
index c2e534184b..353765af94 100644
--- a/app/Support/Authentication/RemoteUserGuard.php
+++ b/app/Support/Authentication/RemoteUserGuard.php
@@ -48,7 +48,7 @@ class RemoteUserGuard implements Guard
public function __construct(protected UserProvider $provider, Application $app)
{
/** @var null|Request $request */
- $request = $app->get('request');
+ $request = $app->get('request');
Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri()));
$this->application = $app;
$this->user = null;
@@ -63,8 +63,8 @@ class RemoteUserGuard implements Guard
return;
}
// Get the user identifier from $_SERVER or apache filtered headers
- $header = config('auth.guard_header', 'REMOTE_USER');
- $userID = request()->server($header) ?? null;
+ $header = config('auth.guard_header', 'REMOTE_USER');
+ $userID = request()->server($header) ?? null;
if (function_exists('apache_request_headers')) {
Log::debug('Use apache_request_headers to find user ID.');
@@ -83,10 +83,10 @@ class RemoteUserGuard implements Guard
$retrievedUser = $this->provider->retrieveById($userID);
// store email address if present in header and not already set.
- $header = config('auth.guard_email');
+ $header = config('auth.guard_email');
if (null !== $header) {
- $emailAddress = (string) (request()->server($header) ?? apache_request_headers()[$header] ?? null);
+ $emailAddress = (string)(request()->server($header) ?? apache_request_headers()[$header] ?? null);
$preference = Preferences::getForUser($retrievedUser, 'remote_guard_alt_email');
if ('' !== $emailAddress && null === $preference && $emailAddress !== $userID) {
@@ -99,7 +99,14 @@ class RemoteUserGuard implements Guard
}
Log::debug(sprintf('Result of getting user from provider: %s', $retrievedUser->email));
- $this->user = $retrievedUser;
+ $this->user = $retrievedUser;
+ }
+
+ public function check(): bool
+ {
+ Log::debug(sprintf('Now at %s', __METHOD__));
+
+ return $this->user() instanceof User;
}
public function guest(): bool
@@ -109,11 +116,32 @@ class RemoteUserGuard implements Guard
return !$this->check();
}
- public function check(): bool
+ public function hasUser(): bool
{
Log::debug(sprintf('Now at %s', __METHOD__));
- return $this->user() instanceof User;
+ throw new FireflyException('Did not implement RemoteUserGuard::hasUser()');
+ }
+
+ /**
+ * @SuppressWarnings("PHPMD.ShortMethodName")
+ */
+ public function id(): int | string | null
+ {
+ Log::debug(sprintf('Now at %s', __METHOD__));
+
+ return $this->user?->id;
+ }
+
+ public function setUser(Authenticatable | User | null $user): void // @phpstan-ignore-line
+ {
+ Log::debug(sprintf('Now at %s', __METHOD__));
+ if ($user instanceof User) {
+ $this->user = $user;
+
+ return;
+ }
+ Log::error(sprintf('Did not set user at %s', __METHOD__));
}
public function user(): ?User
@@ -129,34 +157,6 @@ class RemoteUserGuard implements Guard
return $user;
}
- public function hasUser(): bool
- {
- Log::debug(sprintf('Now at %s', __METHOD__));
-
- throw new FireflyException('Did not implement RemoteUserGuard::hasUser()');
- }
-
- /**
- * @SuppressWarnings("PHPMD.ShortMethodName")
- */
- public function id(): int|string|null
- {
- Log::debug(sprintf('Now at %s', __METHOD__));
-
- return $this->user?->id;
- }
-
- public function setUser(Authenticatable|User|null $user): void // @phpstan-ignore-line
- {
- Log::debug(sprintf('Now at %s', __METHOD__));
- if ($user instanceof User) {
- $this->user = $user;
-
- return;
- }
- Log::error(sprintf('Did not set user at %s', __METHOD__));
- }
-
/**
* @throws FireflyException
*
diff --git a/app/Support/Balance.php b/app/Support/Balance.php
index 6b97a04628..f9d684c5ef 100644
--- a/app/Support/Balance.php
+++ b/app/Support/Balance.php
@@ -48,19 +48,18 @@ class Balance
return $cache->get();
}
- $query = Transaction::whereIn('transactions.account_id', $accounts->pluck('id')->toArray())
- ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->orderBy('transaction_journals.date', 'desc')
- ->orderBy('transaction_journals.order', 'asc')
- ->orderBy('transaction_journals.description', 'desc')
- ->orderBy('transactions.amount', 'desc')
- ->where('transaction_journals.date', '<=', $date)
- ;
+ $query = Transaction::whereIn('transactions.account_id', $accounts->pluck('id')->toArray())
+ ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->orderBy('transaction_journals.date', 'desc')
+ ->orderBy('transaction_journals.order', 'asc')
+ ->orderBy('transaction_journals.description', 'desc')
+ ->orderBy('transactions.amount', 'desc')
+ ->where('transaction_journals.date', '<=', $date);
- $result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.balance_after']);
+ $result = $query->get(['transactions.account_id', 'transactions.transaction_currency_id', 'transactions.balance_after']);
foreach ($result as $entry) {
- $accountId = (int) $entry->account_id;
- $currencyId = (int) $entry->transaction_currency_id;
+ $accountId = (int)$entry->account_id;
+ $currencyId = (int)$entry->transaction_currency_id;
$currencies[$currencyId] ??= Amount::getTransactionCurrencyById($currencyId);
$return[$accountId] ??= [];
if (array_key_exists($currencyId, $return[$accountId])) {
diff --git a/app/Support/Binder/AccountList.php b/app/Support/Binder/AccountList.php
index 314a0c025f..3d6c48728e 100644
--- a/app/Support/Binder/AccountList.php
+++ b/app/Support/Binder/AccountList.php
@@ -43,23 +43,21 @@ class AccountList implements BinderInterface
if ('allAssetAccounts' === $value) {
/** @var Collection $collection */
$collection = auth()->user()->accounts()
- ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
- ->whereIn('account_types.type', [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value])
- ->orderBy('accounts.name', 'ASC')
- ->get(['accounts.*'])
- ;
+ ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
+ ->whereIn('account_types.type', [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value])
+ ->orderBy('accounts.name', 'ASC')
+ ->get(['accounts.*']);
}
if ('allAssetAccounts' !== $value) {
- $incoming = array_map('\intval', explode(',', $value));
- $list = array_merge(array_unique($incoming), [0]);
+ $incoming = array_map('\intval', explode(',', $value));
+ $list = array_merge(array_unique($incoming), [0]);
/** @var Collection $collection */
$collection = auth()->user()->accounts()
- ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
- ->whereIn('accounts.id', $list)
- ->orderBy('accounts.name', 'ASC')
- ->get(['accounts.*'])
- ;
+ ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
+ ->whereIn('accounts.id', $list)
+ ->orderBy('accounts.name', 'ASC')
+ ->get(['accounts.*']);
}
if ($collection->count() > 0) {
diff --git a/app/Support/Binder/BudgetList.php b/app/Support/Binder/BudgetList.php
index 6526ebd38a..917885a7d0 100644
--- a/app/Support/Binder/BudgetList.php
+++ b/app/Support/Binder/BudgetList.php
@@ -41,13 +41,12 @@ class BudgetList implements BinderInterface
if (auth()->check()) {
if ('allBudgets' === $value) {
return auth()->user()->budgets()->where('active', true)
- ->orderBy('order', 'ASC')
- ->orderBy('name', 'ASC')
- ->get()
- ;
+ ->orderBy('order', 'ASC')
+ ->orderBy('name', 'ASC')
+ ->get();
}
- $list = array_unique(array_map('\intval', explode(',', $value)));
+ $list = array_unique(array_map('\intval', explode(',', $value)));
if (0 === count($list)) { // @phpstan-ignore-line
app('log')->warning('Budget list count is zero, return 404.');
@@ -57,10 +56,9 @@ class BudgetList implements BinderInterface
/** @var Collection $collection */
$collection = auth()->user()->budgets()
- ->where('active', true)
- ->whereIn('id', $list)
- ->get()
- ;
+ ->where('active', true)
+ ->whereIn('id', $list)
+ ->get();
// add empty budget if applicable.
if (in_array(0, $list, true)) {
diff --git a/app/Support/Binder/CategoryList.php b/app/Support/Binder/CategoryList.php
index 1275481fa3..cde58f228f 100644
--- a/app/Support/Binder/CategoryList.php
+++ b/app/Support/Binder/CategoryList.php
@@ -41,21 +41,19 @@ class CategoryList implements BinderInterface
if (auth()->check()) {
if ('allCategories' === $value) {
return auth()->user()->categories()
- ->orderBy('name', 'ASC')
- ->get()
- ;
+ ->orderBy('name', 'ASC')
+ ->get();
}
- $list = array_unique(array_map('\intval', explode(',', $value)));
+ $list = array_unique(array_map('\intval', explode(',', $value)));
if (0 === count($list)) { // @phpstan-ignore-line
throw new NotFoundHttpException();
}
/** @var Collection $collection */
$collection = auth()->user()->categories()
- ->whereIn('id', $list)
- ->get()
- ;
+ ->whereIn('id', $list)
+ ->get();
// add empty category if applicable.
if (in_array(0, $list, true)) {
diff --git a/app/Support/Binder/Date.php b/app/Support/Binder/Date.php
index 99c0ce4c17..4dcfb314c8 100644
--- a/app/Support/Binder/Date.php
+++ b/app/Support/Binder/Date.php
@@ -43,16 +43,16 @@ class Date implements BinderInterface
/** @var FiscalHelperInterface $fiscalHelper */
$fiscalHelper = app(FiscalHelperInterface::class);
- $magicWords = [
- 'currentMonthStart' => today(config('app.timezone'))->startOfMonth(),
- 'currentMonthEnd' => today(config('app.timezone'))->endOfMonth(),
- 'currentYearStart' => today(config('app.timezone'))->startOfYear(),
- 'currentYearEnd' => today(config('app.timezone'))->endOfYear(),
+ $magicWords = [
+ 'currentMonthStart' => today(config('app.timezone'))->startOfMonth(),
+ 'currentMonthEnd' => today(config('app.timezone'))->endOfMonth(),
+ 'currentYearStart' => today(config('app.timezone'))->startOfYear(),
+ 'currentYearEnd' => today(config('app.timezone'))->endOfYear(),
- 'previousMonthStart' => today(config('app.timezone'))->startOfMonth()->subDay()->startOfMonth(),
- 'previousMonthEnd' => today(config('app.timezone'))->startOfMonth()->subDay()->endOfMonth(),
- 'previousYearStart' => today(config('app.timezone'))->startOfYear()->subDay()->startOfYear(),
- 'previousYearEnd' => today(config('app.timezone'))->startOfYear()->subDay()->endOfYear(),
+ 'previousMonthStart' => today(config('app.timezone'))->startOfMonth()->subDay()->startOfMonth(),
+ 'previousMonthEnd' => today(config('app.timezone'))->startOfMonth()->subDay()->endOfMonth(),
+ 'previousYearStart' => today(config('app.timezone'))->startOfYear()->subDay()->startOfYear(),
+ 'previousYearEnd' => today(config('app.timezone'))->startOfYear()->subDay()->endOfYear(),
'currentFiscalYearStart' => $fiscalHelper->startOfFiscalYear(today(config('app.timezone'))),
'currentFiscalYearEnd' => $fiscalHelper->endOfFiscalYear(today(config('app.timezone'))),
@@ -68,7 +68,7 @@ class Date implements BinderInterface
try {
$result = new Carbon($value);
- } catch (InvalidDateException|InvalidFormatException $e) { // @phpstan-ignore-line
+ } catch (InvalidDateException | InvalidFormatException $e) { // @phpstan-ignore-line
$message = sprintf('Could not parse date "%s" for user #%d: %s', $value, auth()->user()->id, $e->getMessage());
app('log')->error($message);
diff --git a/app/Support/Binder/JournalList.php b/app/Support/Binder/JournalList.php
index 5eadcc587a..217dd565ed 100644
--- a/app/Support/Binder/JournalList.php
+++ b/app/Support/Binder/JournalList.php
@@ -39,7 +39,7 @@ class JournalList implements BinderInterface
public static function routeBinder(string $value, Route $route): array
{
if (auth()->check()) {
- $list = self::parseList($value);
+ $list = self::parseList($value);
// get the journals by using the collector.
/** @var GroupCollectorInterface $collector */
@@ -47,7 +47,7 @@ class JournalList implements BinderInterface
$collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::TRANSFER->value, TransactionTypeEnum::RECONCILIATION->value]);
$collector->withCategoryInformation()->withBudgetInformation()->withTagInformation()->withAccountInformation();
$collector->setJournalIds($list);
- $result = $collector->getExtractedJournals();
+ $result = $collector->getExtractedJournals();
if (0 === count($result)) {
throw new NotFoundHttpException();
}
diff --git a/app/Support/Binder/TagList.php b/app/Support/Binder/TagList.php
index 3dd4835f54..685087da75 100644
--- a/app/Support/Binder/TagList.php
+++ b/app/Support/Binder/TagList.php
@@ -43,11 +43,10 @@ class TagList implements BinderInterface
if (auth()->check()) {
if ('allTags' === $value) {
return auth()->user()->tags()
- ->orderBy('tag', 'ASC')
- ->get()
- ;
+ ->orderBy('tag', 'ASC')
+ ->get();
}
- $list = array_unique(array_map('\strtolower', explode(',', $value)));
+ $list = array_unique(array_map('\strtolower', explode(',', $value)));
app('log')->debug('List of tags is', $list);
if (0 === count($list)) { // @phpstan-ignore-line
@@ -59,7 +58,7 @@ class TagList implements BinderInterface
/** @var TagRepositoryInterface $repository */
$repository = app(TagRepositoryInterface::class);
$repository->setUser(auth()->user());
- $allTags = $repository->get();
+ $allTags = $repository->get();
$collection = $allTags->filter(
static function (Tag $tag) use ($list) {
@@ -68,7 +67,7 @@ class TagList implements BinderInterface
return true;
}
- if (in_array((string) $tag->id, $list, true)) {
+ if (in_array((string)$tag->id, $list, true)) {
Log::debug(sprintf('TagList: (id) found tag #%d ("%s") in list.', $tag->id, $tag->tag));
return true;
diff --git a/app/Support/Binder/TagOrId.php b/app/Support/Binder/TagOrId.php
index bc511e5018..ad3a866e1a 100644
--- a/app/Support/Binder/TagOrId.php
+++ b/app/Support/Binder/TagOrId.php
@@ -40,9 +40,9 @@ class TagOrId implements BinderInterface
$repository = app(TagRepositoryInterface::class);
$repository->setUser(auth()->user());
- $result = $repository->findByTag($value);
+ $result = $repository->findByTag($value);
if (null === $result) {
- $result = $repository->find((int) $value);
+ $result = $repository->find((int)$value);
}
if (null !== $result) {
return $result;
diff --git a/app/Support/Binder/UserGroupAccount.php b/app/Support/Binder/UserGroupAccount.php
index 12d7eff4a2..47a7af5541 100644
--- a/app/Support/Binder/UserGroupAccount.php
+++ b/app/Support/Binder/UserGroupAccount.php
@@ -41,10 +41,9 @@ class UserGroupAccount implements BinderInterface
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
- $account = Account::where('id', (int) $value)
- ->where('user_group_id', $user->user_group_id)
- ->first()
- ;
+ $account = Account::where('id', (int)$value)
+ ->where('user_group_id', $user->user_group_id)
+ ->first();
if (null !== $account) {
return $account;
}
diff --git a/app/Support/Binder/UserGroupBill.php b/app/Support/Binder/UserGroupBill.php
index 551846d693..05eff73b6e 100644
--- a/app/Support/Binder/UserGroupBill.php
+++ b/app/Support/Binder/UserGroupBill.php
@@ -41,10 +41,9 @@ class UserGroupBill implements BinderInterface
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
- $currency = Bill::where('id', (int) $value)
- ->where('user_group_id', $user->user_group_id)
- ->first()
- ;
+ $currency = Bill::where('id', (int)$value)
+ ->where('user_group_id', $user->user_group_id)
+ ->first();
if (null !== $currency) {
return $currency;
}
diff --git a/app/Support/Binder/UserGroupExchangeRate.php b/app/Support/Binder/UserGroupExchangeRate.php
index 74a65c9348..1bb8fcc374 100644
--- a/app/Support/Binder/UserGroupExchangeRate.php
+++ b/app/Support/Binder/UserGroupExchangeRate.php
@@ -38,10 +38,9 @@ class UserGroupExchangeRate implements BinderInterface
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
- $rate = CurrencyExchangeRate::where('id', (int) $value)
- ->where('user_group_id', $user->user_group_id)
- ->first()
- ;
+ $rate = CurrencyExchangeRate::where('id', (int)$value)
+ ->where('user_group_id', $user->user_group_id)
+ ->first();
if (null !== $rate) {
return $rate;
}
diff --git a/app/Support/Binder/UserGroupTransaction.php b/app/Support/Binder/UserGroupTransaction.php
index d9131400f3..61add59c73 100644
--- a/app/Support/Binder/UserGroupTransaction.php
+++ b/app/Support/Binder/UserGroupTransaction.php
@@ -38,10 +38,9 @@ class UserGroupTransaction implements BinderInterface
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();
- $group = TransactionGroup::where('id', (int) $value)
- ->where('user_group_id', $user->user_group_id)
- ->first()
- ;
+ $group = TransactionGroup::where('id', (int)$value)
+ ->where('user_group_id', $user->user_group_id)
+ ->first();
if (null !== $group) {
return $group;
}
diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php
index b81f040467..38f2863e92 100644
--- a/app/Support/CacheProperties.php
+++ b/app/Support/CacheProperties.php
@@ -27,7 +27,6 @@ use Carbon\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use JsonException;
-
use function Safe\json_encode;
/**
@@ -78,20 +77,6 @@ class CacheProperties
return Cache::has($this->hash);
}
- private function hash(): void
- {
- $content = '';
- foreach ($this->properties as $property) {
- try {
- $content = sprintf('%s%s', $content, json_encode($property, JSON_THROW_ON_ERROR));
- } catch (JsonException) {
- // @ignoreException
- $content = sprintf('%s%s', $content, hash('sha256', (string) Carbon::now()->getTimestamp()));
- }
- }
- $this->hash = substr(hash('sha256', $content), 0, 16);
- }
-
/**
* @param mixed $data
*/
@@ -99,4 +84,18 @@ class CacheProperties
{
Cache::forever($this->hash, $data);
}
+
+ private function hash(): void
+ {
+ $content = '';
+ foreach ($this->properties as $property) {
+ try {
+ $content = sprintf('%s%s', $content, json_encode($property, JSON_THROW_ON_ERROR));
+ } catch (JsonException) {
+ // @ignoreException
+ $content = sprintf('%s%s', $content, hash('sha256', (string)Carbon::now()->getTimestamp()));
+ }
+ }
+ $this->hash = substr(hash('sha256', $content), 0, 16);
+ }
}
diff --git a/app/Support/Calendar/Calculator.php b/app/Support/Calendar/Calculator.php
index 3dff9dff07..b6ee2ceebb 100644
--- a/app/Support/Calendar/Calculator.php
+++ b/app/Support/Calendar/Calculator.php
@@ -33,31 +33,10 @@ use SplObjectStorage;
*/
class Calculator
{
- public const int DEFAULT_INTERVAL = 1;
+ public const int DEFAULT_INTERVAL = 1;
private static ?SplObjectStorage $intervalMap = null; // @phpstan-ignore-line
private static array $intervals = [];
- /**
- * @throws IntervalException
- */
- public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon
- {
- if (!self::isAvailablePeriodicity($periodicity)) {
- throw IntervalException::unavailable($periodicity, self::$intervals);
- }
-
- /** @var Periodicity\Interval $periodicity */
- $periodicity = self::$intervalMap->offsetGet($periodicity);
- $interval = $this->skipInterval($skipInterval);
-
- return $periodicity->nextDate($epoch->clone(), $interval);
- }
-
- public function isAvailablePeriodicity(Periodicity $periodicity): bool
- {
- return self::containsInterval($periodicity);
- }
-
private static function containsInterval(Periodicity $periodicity): bool
{
return self::loadIntervalMap()->contains($periodicity);
@@ -78,6 +57,27 @@ class Calculator
return self::$intervalMap;
}
+ public function isAvailablePeriodicity(Periodicity $periodicity): bool
+ {
+ return self::containsInterval($periodicity);
+ }
+
+ /**
+ * @throws IntervalException
+ */
+ public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon
+ {
+ if (!self::isAvailablePeriodicity($periodicity)) {
+ throw IntervalException::unavailable($periodicity, self::$intervals);
+ }
+
+ /** @var Periodicity\Interval $periodicity */
+ $periodicity = self::$intervalMap->offsetGet($periodicity);
+ $interval = $this->skipInterval($skipInterval);
+
+ return $periodicity->nextDate($epoch->clone(), $interval);
+ }
+
private function skipInterval(int $skip): int
{
return self::DEFAULT_INTERVAL + $skip;
diff --git a/app/Support/Chart/Budget/FrontpageChartGenerator.php b/app/Support/Chart/Budget/FrontpageChartGenerator.php
index 9e351afc78..e48cc65e42 100644
--- a/app/Support/Chart/Budget/FrontpageChartGenerator.php
+++ b/app/Support/Chart/Budget/FrontpageChartGenerator.php
@@ -69,9 +69,9 @@ class FrontpageChartGenerator
Log::debug('Now in generate for budget chart.');
$budgets = $this->budgetRepository->getActiveBudgets();
$data = [
- ['label' => (string) trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
- ['label' => (string) trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'],
- ['label' => (string) trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'],
+ ['label' => (string)trans('firefly.spent_in_budget'), 'entries' => [], 'type' => 'bar'],
+ ['label' => (string)trans('firefly.left_to_spend'), 'entries' => [], 'type' => 'bar'],
+ ['label' => (string)trans('firefly.overspent'), 'entries' => [], 'type' => 'bar'],
];
// loop al budgets:
@@ -84,6 +84,64 @@ class FrontpageChartGenerator
return $data;
}
+ public function setEnd(Carbon $end): void
+ {
+ $this->end = $end;
+ }
+
+ public function setStart(Carbon $start): void
+ {
+ $this->start = $start;
+ }
+
+ /**
+ * A basic setter for the user. Also updates the repositories with the right user.
+ */
+ public function setUser(User $user): void
+ {
+ $this->budgetRepository->setUser($user);
+ $this->blRepository->setUser($user);
+ $this->opsRepository->setUser($user);
+
+ $locale = app('steam')->getLocale();
+ $this->monthAndDayFormat = (string)trans('config.month_and_day_js', [], $locale);
+ }
+
+ /**
+ * If a budget has budget limit, each limit is processed individually.
+ */
+ private function budgetLimits(array $data, Budget $budget, Collection $limits): array
+ {
+ Log::debug('Start processing budget limits.');
+
+ /** @var BudgetLimit $limit */
+ foreach ($limits as $limit) {
+ $data = $this->processLimit($data, $budget, $limit);
+ }
+ Log::debug('Done processing budget limits.');
+
+ return $data;
+ }
+
+ /**
+ * When no limits are present, the expenses of the whole period are collected and grouped.
+ * This is grouped per currency. Because there is no limit set, "left to spend" and "overspent" are empty.
+ */
+ private function noBudgetLimits(array $data, Budget $budget): array
+ {
+ $spent = $this->opsRepository->sumExpenses($this->start, $this->end, null, new Collection()->push($budget));
+
+ /** @var array $entry */
+ foreach ($spent as $entry) {
+ $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
+ $data[0]['entries'][$title] = bcmul((string)$entry['sum'], '-1'); // spent
+ $data[1]['entries'][$title] = 0; // left to spend
+ $data[2]['entries'][$title] = 0; // overspent
+ }
+
+ return $data;
+ }
+
/**
* For each budget, gets all budget limits for the current time range.
* When no limits are present, the time range is used to collect information on money spent.
@@ -108,41 +166,6 @@ class FrontpageChartGenerator
return $result;
}
- /**
- * When no limits are present, the expenses of the whole period are collected and grouped.
- * This is grouped per currency. Because there is no limit set, "left to spend" and "overspent" are empty.
- */
- private function noBudgetLimits(array $data, Budget $budget): array
- {
- $spent = $this->opsRepository->sumExpenses($this->start, $this->end, null, new Collection()->push($budget));
-
- /** @var array $entry */
- foreach ($spent as $entry) {
- $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
- $data[0]['entries'][$title] = bcmul((string) $entry['sum'], '-1'); // spent
- $data[1]['entries'][$title] = 0; // left to spend
- $data[2]['entries'][$title] = 0; // overspent
- }
-
- return $data;
- }
-
- /**
- * If a budget has budget limit, each limit is processed individually.
- */
- private function budgetLimits(array $data, Budget $budget, Collection $limits): array
- {
- Log::debug('Start processing budget limits.');
-
- /** @var BudgetLimit $limit */
- foreach ($limits as $limit) {
- $data = $this->processLimit($data, $budget, $limit);
- }
- Log::debug('Done processing budget limits.');
-
- return $data;
- }
-
/**
* For each limit, the expenses from the time range of the limit are collected. Each row from the result is
* processed individually.
@@ -158,7 +181,7 @@ class FrontpageChartGenerator
Log::debug(sprintf('Processing limit #%d with %s %s', $limit->id, $limit->transactionCurrency->code, $limit->amount));
}
- $spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
+ $spent = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
Log::debug(sprintf('Spent array has %d entries.', count($spent)));
/** @var array $entry */
@@ -185,7 +208,7 @@ class FrontpageChartGenerator
*/
private function processRow(array $data, Budget $budget, BudgetLimit $limit, array $entry): array
{
- $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
+ $title = sprintf('%s (%s)', $budget->name, $entry['currency_name']);
Log::debug(sprintf('Title is "%s"', $title));
if ($limit->start_date->startOfDay()->ne($this->start->startOfDay()) || $limit->end_date->startOfDay()->ne($this->end->startOfDay())) {
$title = sprintf(
@@ -196,22 +219,22 @@ class FrontpageChartGenerator
$limit->end_date->isoFormat($this->monthAndDayFormat)
);
}
- $usePrimary = $this->convertToPrimary && $this->default->id !== $limit->transaction_currency_id;
- $amount = $limit->amount;
+ $usePrimary = $this->convertToPrimary && $this->default->id !== $limit->transaction_currency_id;
+ $amount = $limit->amount;
Log::debug(sprintf('Amount is "%s".', $amount));
if ($usePrimary && $limit->transaction_currency_id !== $this->default->id) {
$amount = $limit->native_amount;
Log::debug(sprintf('Amount is now "%s".', $amount));
}
$amount ??= '0';
- $sumSpent = bcmul((string) $entry['sum'], '-1'); // spent
+ $sumSpent = bcmul((string)$entry['sum'], '-1'); // spent
$data[0]['entries'][$title] ??= '0';
$data[1]['entries'][$title] ??= '0';
$data[2]['entries'][$title] ??= '0';
- $data[0]['entries'][$title] = bcadd((string) $data[0]['entries'][$title], 1 === bccomp($sumSpent, $amount) ? $amount : $sumSpent); // spent
- $data[1]['entries'][$title] = bcadd((string) $data[1]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? bcadd((string) $entry['sum'], $amount) : '0'); // left to spent
- $data[2]['entries'][$title] = bcadd((string) $data[2]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? '0' : bcmul(bcadd((string) $entry['sum'], $amount), '-1')); // overspent
+ $data[0]['entries'][$title] = bcadd((string)$data[0]['entries'][$title], 1 === bccomp($sumSpent, $amount) ? $amount : $sumSpent); // spent
+ $data[1]['entries'][$title] = bcadd((string)$data[1]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? bcadd((string)$entry['sum'], $amount) : '0'); // left to spent
+ $data[2]['entries'][$title] = bcadd((string)$data[2]['entries'][$title], 1 === bccomp($amount, $sumSpent) ? '0' : bcmul(bcadd((string)$entry['sum'], $amount), '-1')); // overspent
Log::debug(sprintf('Amount [spent] is now %s.', $data[0]['entries'][$title]));
Log::debug(sprintf('Amount [left] is now %s.', $data[1]['entries'][$title]));
@@ -219,27 +242,4 @@ class FrontpageChartGenerator
return $data;
}
-
- public function setEnd(Carbon $end): void
- {
- $this->end = $end;
- }
-
- public function setStart(Carbon $start): void
- {
- $this->start = $start;
- }
-
- /**
- * A basic setter for the user. Also updates the repositories with the right user.
- */
- public function setUser(User $user): void
- {
- $this->budgetRepository->setUser($user);
- $this->blRepository->setUser($user);
- $this->opsRepository->setUser($user);
-
- $locale = app('steam')->getLocale();
- $this->monthAndDayFormat = (string) trans('config.month_and_day_js', [], $locale);
- }
}
diff --git a/app/Support/Chart/Category/FrontpageChartGenerator.php b/app/Support/Chart/Category/FrontpageChartGenerator.php
index c27f13c686..b106deafce 100644
--- a/app/Support/Chart/Category/FrontpageChartGenerator.php
+++ b/app/Support/Chart/Category/FrontpageChartGenerator.php
@@ -26,7 +26,6 @@ namespace FireflyIII\Support\Chart\Category;
use Carbon\Carbon;
use FireflyIII\Enums\AccountTypeEnum;
-use FireflyIII\Models\Category;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
@@ -66,16 +65,16 @@ class FrontpageChartGenerator
public function generate(): array
{
Log::debug(sprintf('Now in %s', __METHOD__));
- $categories = $this->repository->getCategories();
- $accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
- $collection = $this->collectExpensesAll($categories, $accounts);
+ $categories = $this->repository->getCategories();
+ $accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value]);
+ $collection = $this->collectExpensesAll($categories, $accounts);
// collect for no-category:
- $noCategory = $this->collectNoCatExpenses($accounts);
- $collection = array_merge($collection, $noCategory);
+ $noCategory = $this->collectNoCatExpenses($accounts);
+ $collection = array_merge($collection, $noCategory);
// sort temp array by amount.
- $amounts = array_column($collection, 'sum_float');
+ $amounts = array_column($collection, 'sum_float');
array_multisort($amounts, SORT_ASC, $collection);
$currencyData = $this->createCurrencyGroups($collection);
@@ -96,6 +95,30 @@ class FrontpageChartGenerator
];
}
+ private function collectExpensesAll(Collection $categories, Collection $accounts): array
+ {
+ Log::debug(sprintf('Collect expenses for %d category(ies).', count($categories)));
+ $spent = $this->opsRepos->collectExpenses($this->start, $this->end, $accounts, $categories);
+ $tempData = [];
+ foreach ($categories as $category) {
+ $sums = $this->opsRepos->sumCollectedTransactionsByCategory($spent, $category, 'negative', $this->convertToPrimary);
+ if (0 === count($sums)) {
+ continue;
+ }
+ foreach ($sums as $currency) {
+ $this->addCurrency($currency);
+ $tempData[] = [
+ 'name' => $category->name,
+ 'sum' => $currency['sum'],
+ 'sum_float' => round((float)$currency['sum'], $currency['currency_decimal_places']),
+ 'currency_id' => (int)$currency['currency_id'],
+ ];
+ }
+ }
+
+ return $tempData;
+ }
+
private function collectNoCatExpenses(Collection $accounts): array
{
$noCatExp = $this->noCatRepos->sumExpenses($this->start, $this->end, $accounts);
@@ -147,28 +170,4 @@ class FrontpageChartGenerator
return $currencyData;
}
-
- private function collectExpensesAll(Collection $categories, Collection $accounts): array
- {
- Log::debug(sprintf('Collect expenses for %d category(ies).', count($categories)));
- $spent = $this->opsRepos->collectExpenses($this->start, $this->end, $accounts, $categories);
- $tempData = [];
- foreach ($categories as $category) {
- $sums = $this->opsRepos->sumCollectedTransactionsByCategory($spent, $category, 'negative', $this->convertToPrimary);
- if (0 === count($sums)) {
- continue;
- }
- foreach ($sums as $currency) {
- $this->addCurrency($currency);
- $tempData[] = [
- 'name' => $category->name,
- 'sum' => $currency['sum'],
- 'sum_float' => round((float)$currency['sum'], $currency['currency_decimal_places']),
- 'currency_id' => (int)$currency['currency_id'],
- ];
- }
- }
-
- return $tempData;
- }
}
diff --git a/app/Support/Chart/Category/WholePeriodChartGenerator.php b/app/Support/Chart/Category/WholePeriodChartGenerator.php
index 2a28cb4d62..044b7f28a2 100644
--- a/app/Support/Chart/Category/WholePeriodChartGenerator.php
+++ b/app/Support/Chart/Category/WholePeriodChartGenerator.php
@@ -40,22 +40,22 @@ class WholePeriodChartGenerator
public function generate(Category $category, Carbon $start, Carbon $end): array
{
- $collection = new Collection()->push($category);
+ $collection = new Collection()->push($category);
/** @var OperationsRepositoryInterface $opsRepository */
- $opsRepository = app(OperationsRepositoryInterface::class);
+ $opsRepository = app(OperationsRepositoryInterface::class);
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
- $types = [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
- $accounts = $accountRepository->getAccountsByType($types);
- $step = $this->calculateStep($start, $end);
- $chartData = [];
- $spent = [];
- $earned = [];
+ $types = [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
+ $accounts = $accountRepository->getAccountsByType($types);
+ $step = $this->calculateStep($start, $end);
+ $chartData = [];
+ $spent = [];
+ $earned = [];
- $current = clone $start;
+ $current = clone $start;
while ($current <= $end) {
$key = $current->format('Y-m-d');
@@ -65,33 +65,33 @@ class WholePeriodChartGenerator
$current = app('navigation')->addPeriod($current, $step, 0);
}
- $currencies = $this->extractCurrencies($spent) + $this->extractCurrencies($earned);
+ $currencies = $this->extractCurrencies($spent) + $this->extractCurrencies($earned);
// generate chart data (for each currency)
/** @var array $currency */
foreach ($currencies as $currency) {
- $code = $currency['currency_code'];
- $name = $currency['currency_name'];
- $chartData[sprintf('spent-in-%s', $code)] = [
- 'label' => (string) trans('firefly.box_spent_in_currency', ['currency' => $name]),
+ $code = $currency['currency_code'];
+ $name = $currency['currency_name'];
+ $chartData[sprintf('spent-in-%s', $code)] = [
+ 'label' => (string)trans('firefly.box_spent_in_currency', ['currency' => $name]),
'entries' => [],
'type' => 'bar',
'backgroundColor' => 'rgba(219, 68, 55, 0.5)', // red
];
$chartData[sprintf('earned-in-%s', $code)] = [
- 'label' => (string) trans('firefly.box_earned_in_currency', ['currency' => $name]),
+ 'label' => (string)trans('firefly.box_earned_in_currency', ['currency' => $name]),
'entries' => [],
'type' => 'bar',
'backgroundColor' => 'rgba(0, 141, 76, 0.5)', // green
];
}
- $current = clone $start;
+ $current = clone $start;
while ($current <= $end) {
- $key = $current->format('Y-m-d');
- $label = app('navigation')->periodShow($current, $step);
+ $key = $current->format('Y-m-d');
+ $label = app('navigation')->periodShow($current, $step);
/** @var array $currency */
foreach ($currencies as $currency) {
diff --git a/app/Support/Chart/ChartData.php b/app/Support/Chart/ChartData.php
index ab03e3dd31..8d58c6a304 100644
--- a/app/Support/Chart/ChartData.php
+++ b/app/Support/Chart/ChartData.php
@@ -44,12 +44,12 @@ class ChartData
public function add(array $data): void
{
if (array_key_exists('currency_id', $data)) {
- $data['currency_id'] = (string) $data['currency_id'];
+ $data['currency_id'] = (string)$data['currency_id'];
}
if (array_key_exists('primary_currency_id', $data)) {
- $data['primary_currency_id'] = (string) $data['primary_currency_id'];
+ $data['primary_currency_id'] = (string)$data['primary_currency_id'];
}
- $required = ['start', 'date', 'end', 'entries'];
+ $required = ['start', 'date', 'end', 'entries'];
foreach ($required as $field) {
if (!array_key_exists($field, $data)) {
throw new FireflyException(sprintf('Data-set is missing the "%s"-variable.', $field));
diff --git a/app/Support/ChartColour.php b/app/Support/ChartColour.php
index 9e938b9946..f08de5c258 100644
--- a/app/Support/ChartColour.php
+++ b/app/Support/ChartColour.php
@@ -55,7 +55,7 @@ class ChartColour
public static function getColour(int $index): string
{
$index %= count(self::$colours);
- $row = self::$colours[$index];
+ $row = self::$colours[$index];
return sprintf('rgba(%d, %d, %d, 0.7)', $row[0], $row[1], $row[2]);
}
diff --git a/app/Support/Cronjobs/AutoBudgetCronjob.php b/app/Support/Cronjobs/AutoBudgetCronjob.php
index 9a2a5a0d66..6855884d66 100644
--- a/app/Support/Cronjobs/AutoBudgetCronjob.php
+++ b/app/Support/Cronjobs/AutoBudgetCronjob.php
@@ -39,7 +39,7 @@ class AutoBudgetCronjob extends AbstractCronjob
{
/** @var Configuration $config */
$config = FireflyConfig::get('last_ab_job', 0);
- $lastTime = (int) $config->data;
+ $lastTime = (int)$config->data;
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
if (0 === $lastTime) {
@@ -70,7 +70,7 @@ class AutoBudgetCronjob extends AbstractCronjob
Log::info(sprintf('Will now fire auto budget cron job task for date "%s".', $this->date->format('Y-m-d')));
/** @var CreateAutoBudgetLimits $job */
- $job = app(CreateAutoBudgetLimits::class, [$this->date]);
+ $job = app(CreateAutoBudgetLimits::class, [$this->date]);
$job->setDate($this->date);
$job->handle();
@@ -80,7 +80,7 @@ class AutoBudgetCronjob extends AbstractCronjob
$this->jobSucceeded = true;
$this->message = 'Auto-budget cron job fired successfully.';
- FireflyConfig::set('last_ab_job', (int) $this->date->format('U'));
+ FireflyConfig::set('last_ab_job', (int)$this->date->format('U'));
Log::info('Done with auto budget cron job task.');
}
}
diff --git a/app/Support/Cronjobs/BillWarningCronjob.php b/app/Support/Cronjobs/BillWarningCronjob.php
index 8a30bb9c0a..f192aa1224 100644
--- a/app/Support/Cronjobs/BillWarningCronjob.php
+++ b/app/Support/Cronjobs/BillWarningCronjob.php
@@ -45,7 +45,7 @@ class BillWarningCronjob extends AbstractCronjob
/** @var Configuration $config */
$config = FireflyConfig::get('last_bw_job', 0);
- $lastTime = (int) $config->data;
+ $lastTime = (int)$config->data;
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
@@ -82,7 +82,7 @@ class BillWarningCronjob extends AbstractCronjob
Log::info(sprintf('Will now fire bill notification job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
/** @var WarnAboutBills $job */
- $job = app(WarnAboutBills::class);
+ $job = app(WarnAboutBills::class);
$job->setDate($this->date);
$job->setForce($this->force);
$job->handle();
@@ -93,8 +93,8 @@ class BillWarningCronjob extends AbstractCronjob
$this->jobSucceeded = true;
$this->message = 'Bill notification cron job fired successfully.';
- FireflyConfig::set('last_bw_job', (int) $this->date->format('U'));
- Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
+ FireflyConfig::set('last_bw_job', (int)$this->date->format('U'));
+ Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int)$this->date->format('U')));
Log::info('Done with bill notification cron job task.');
}
}
diff --git a/app/Support/Cronjobs/ExchangeRatesCronjob.php b/app/Support/Cronjobs/ExchangeRatesCronjob.php
index 71c9a8e587..57cb788bc7 100644
--- a/app/Support/Cronjobs/ExchangeRatesCronjob.php
+++ b/app/Support/Cronjobs/ExchangeRatesCronjob.php
@@ -39,7 +39,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
{
/** @var Configuration $config */
$config = FireflyConfig::get('last_cer_job', 0);
- $lastTime = (int) $config->data;
+ $lastTime = (int)$config->data;
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
if (0 === $lastTime) {
@@ -71,7 +71,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
Log::info(sprintf('Will now fire exchange rates cron job task for date "%s".', $this->date->format('Y-m-d')));
/** @var DownloadExchangeRates $job */
- $job = app(DownloadExchangeRates::class);
+ $job = app(DownloadExchangeRates::class);
$job->setDate($this->date);
$job->handle();
@@ -81,7 +81,7 @@ class ExchangeRatesCronjob extends AbstractCronjob
$this->jobSucceeded = true;
$this->message = 'Exchange rates cron job fired successfully.';
- FireflyConfig::set('last_cer_job', (int) $this->date->format('U'));
+ FireflyConfig::set('last_cer_job', (int)$this->date->format('U'));
Log::info('Done with exchange rates job task.');
}
}
diff --git a/app/Support/Cronjobs/RecurringCronjob.php b/app/Support/Cronjobs/RecurringCronjob.php
index c722733253..1f8654b9a7 100644
--- a/app/Support/Cronjobs/RecurringCronjob.php
+++ b/app/Support/Cronjobs/RecurringCronjob.php
@@ -45,7 +45,7 @@ class RecurringCronjob extends AbstractCronjob
/** @var Configuration $config */
$config = FireflyConfig::get('last_rt_job', 0);
- $lastTime = (int) $config->data;
+ $lastTime = (int)$config->data;
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
@@ -80,7 +80,7 @@ class RecurringCronjob extends AbstractCronjob
{
Log::info(sprintf('Will now fire recurring cron job task for date "%s".', $this->date->format('Y-m-d H:i:s')));
- $job = new CreateRecurringTransactions($this->date);
+ $job = new CreateRecurringTransactions($this->date);
$job->setForce($this->force);
$job->handle();
@@ -90,8 +90,8 @@ class RecurringCronjob extends AbstractCronjob
$this->jobSucceeded = true;
$this->message = 'Recurring transactions cron job fired successfully.';
- FireflyConfig::set('last_rt_job', (int) $this->date->format('U'));
- Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
+ FireflyConfig::set('last_rt_job', (int)$this->date->format('U'));
+ Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int)$this->date->format('U')));
Log::info('Done with recurring cron job task.');
}
}
diff --git a/app/Support/Cronjobs/UpdateCheckCronjob.php b/app/Support/Cronjobs/UpdateCheckCronjob.php
index 6d3cea13ab..c7681037dd 100644
--- a/app/Support/Cronjobs/UpdateCheckCronjob.php
+++ b/app/Support/Cronjobs/UpdateCheckCronjob.php
@@ -41,8 +41,8 @@ class UpdateCheckCronjob extends AbstractCronjob
Log::debug('Now in checkForUpdates()');
// should not check for updates:
- $permission = FireflyConfig::get('permission_update_check', -1);
- $value = (int) $permission->data;
+ $permission = FireflyConfig::get('permission_update_check', -1);
+ $value = (int)$permission->data;
if (1 !== $value) {
Log::debug('Update check is not enabled.');
// get stuff from job:
@@ -56,9 +56,9 @@ class UpdateCheckCronjob extends AbstractCronjob
// TODO this is duplicate.
/** @var Configuration $lastCheckTime */
- $lastCheckTime = FireflyConfig::get('last_update_check', Carbon::now()->getTimestamp());
- $now = Carbon::now()->getTimestamp();
- $diff = $now - $lastCheckTime->data;
+ $lastCheckTime = FireflyConfig::get('last_update_check', Carbon::now()->getTimestamp());
+ $now = Carbon::now()->getTimestamp();
+ $diff = $now - $lastCheckTime->data;
Log::debug(sprintf('Last check time is %d, current time is %d, difference is %d', $lastCheckTime->data, $now, $diff));
if ($diff < 604800 && false === $this->force) {
// get stuff from job:
@@ -71,7 +71,7 @@ class UpdateCheckCronjob extends AbstractCronjob
}
// last check time was more than a week ago.
Log::debug('Have not checked for a new version in a week!');
- $release = $this->getLatestRelease();
+ $release = $this->getLatestRelease();
if ('error' === $release['level']) {
// get stuff from job:
$this->jobFired = true;
diff --git a/app/Support/Cronjobs/WebhookCronjob.php b/app/Support/Cronjobs/WebhookCronjob.php
index 0cd1899380..84ea6676f5 100644
--- a/app/Support/Cronjobs/WebhookCronjob.php
+++ b/app/Support/Cronjobs/WebhookCronjob.php
@@ -45,7 +45,7 @@ class WebhookCronjob extends AbstractCronjob
/** @var Configuration $config */
$config = FireflyConfig::get('last_webhook_job', 0);
- $lastTime = (int) $config->data;
+ $lastTime = (int)$config->data;
$diff = now(config('app.timezone'))->getTimestamp() - $lastTime;
$diffForHumans = now(config('app.timezone'))->diffForHumans(Carbon::createFromTimestamp($lastTime), null, true);
@@ -90,8 +90,8 @@ class WebhookCronjob extends AbstractCronjob
$this->jobSucceeded = true;
$this->message = 'Send webhook messages cron job fired successfully.';
- FireflyConfig::set('last_webhook_job', (int) $this->date->format('U'));
- Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int) $this->date->format('U')));
+ FireflyConfig::set('last_webhook_job', (int)$this->date->format('U'));
+ Log::info(sprintf('Marked the last time this job has run as "%s" (%d)', $this->date->format('Y-m-d H:i:s'), (int)$this->date->format('U')));
Log::info('Done with webhook cron job task.');
}
}
diff --git a/app/Support/Debug/Timer.php b/app/Support/Debug/Timer.php
index 94e48187b7..b23e941a0c 100644
--- a/app/Support/Debug/Timer.php
+++ b/app/Support/Debug/Timer.php
@@ -28,8 +28,8 @@ use Illuminate\Support\Facades\Log;
class Timer
{
- private array $times = [];
private static ?Timer $instance = null;
+ private array $times = [];
private function __construct()
{
diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php
index e460353ac2..1dbaeb7d8e 100644
--- a/app/Support/ExpandedForm.php
+++ b/app/Support/ExpandedForm.php
@@ -23,9 +23,9 @@ declare(strict_types=1);
namespace FireflyIII\Support;
-use Illuminate\Database\Eloquent\Model;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\Form\FormSupport;
+use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Throwable;
@@ -43,7 +43,7 @@ class ExpandedForm
*/
public function amountNoCurrency(string $name, $value = null, ?array $options = null): string
{
- $options ??= [];
+ $options ??= [];
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
@@ -74,8 +74,8 @@ class ExpandedForm
*/
public function checkbox(string $name, ?int $value = null, $checked = null, ?array $options = null): string
{
- $options ??= [];
- $value ??= 1;
+ $options ??= [];
+ $value ??= 1;
$options['checked'] = true === $checked;
if (app('session')->has('preFilled')) {
@@ -83,10 +83,10 @@ class ExpandedForm
$options['checked'] = $preFilled[$name] ?? $options['checked'];
}
- $label = $this->label($name, $options);
- $options = $this->expandOptionArray($name, $label, $options);
- $classes = $this->getHolderClasses($name);
- $value = $this->fillFieldValue($name, $value);
+ $label = $this->label($name, $options);
+ $options = $this->expandOptionArray($name, $label, $options);
+ $classes = $this->getHolderClasses($name);
+ $value = $this->fillFieldValue($name, $value);
unset($options['placeholder'], $options['autocomplete'], $options['class']);
@@ -157,10 +157,10 @@ class ExpandedForm
public function integer(string $name, $value = null, ?array $options = null): string
{
$options ??= [];
- $label = $this->label($name, $options);
- $options = $this->expandOptionArray($name, $label, $options);
- $classes = $this->getHolderClasses($name);
- $value = $this->fillFieldValue($name, $value);
+ $label = $this->label($name, $options);
+ $options = $this->expandOptionArray($name, $label, $options);
+ $classes = $this->getHolderClasses($name);
+ $value = $this->fillFieldValue($name, $value);
$options['step'] ??= '1';
try {
@@ -209,9 +209,9 @@ class ExpandedForm
/** @var Model $entry */
foreach ($set as $entry) {
// All Eloquent models have an ID
- $entryId = $entry->id;
- $current = $entry->toArray();
- $title = null;
+ $entryId = $entry->id;
+ $current = $entry->toArray();
+ $title = null;
foreach ($fields as $field) {
if (array_key_exists($field, $current) && null === $title) {
$title = $current[$field];
diff --git a/app/Support/Export/ExportDataGenerator.php b/app/Support/Export/ExportDataGenerator.php
index d4a44e4e6a..b70f2a6615 100644
--- a/app/Support/Export/ExportDataGenerator.php
+++ b/app/Support/Export/ExportDataGenerator.php
@@ -85,12 +85,12 @@ class ExportDataGenerator
private bool $exportTransactions;
private Carbon $start;
private User $user;
- private UserGroup $userGroup; // @phpstan-ignore-line
+ private UserGroup $userGroup; // @phpstan-ignore-line
public function __construct()
{
- $this->accounts = new Collection();
- $this->start = today(config('app.timezone'));
+ $this->accounts = new Collection();
+ $this->start = today(config('app.timezone'));
$this->start->subYear();
$this->end = today(config('app.timezone'));
$this->exportTransactions = false;
@@ -141,453 +141,6 @@ class ExportDataGenerator
return $return;
}
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportAccounts(): string
- {
- $header = [
- 'user_id',
- 'account_id',
- 'created_at',
- 'updated_at',
- 'type',
- 'name',
- 'virtual_balance',
- 'iban',
- 'number',
- 'active',
- 'currency_code',
- 'role',
- 'cc_type',
- 'cc_payment_date',
- 'in_net_worth',
- 'interest',
- 'interest_period',
- ];
-
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $repository->setUser($this->user);
- $allAccounts = $repository->getAccountsByType([]);
- $records = [];
-
- /** @var Account $account */
- foreach ($allAccounts as $account) {
- $currency = $repository->getAccountCurrency($account);
- $records[] = [
- $this->user->id,
- $account->id,
- $account->created_at->toAtomString(),
- $account->updated_at->toAtomString(),
- $account->accountType->type,
- $account->name,
- $account->virtual_balance,
- $account->iban,
- $account->account_number,
- $account->active,
- $currency?->code,
- $repository->getMetaValue($account, 'account_role'),
- $repository->getMetaValue($account, 'cc_type'),
- $repository->getMetaValue($account, 'cc_monthly_payment_date'),
- $repository->getMetaValue($account, 'include_net_worth'),
- $repository->getMetaValue($account, 'interest'),
- $repository->getMetaValue($account, 'interest_period'),
- ];
- }
-
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
- public function setUser(User $user): void
- {
- $this->user = $user;
- }
-
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportBills(): string
- {
- /** @var BillRepositoryInterface $repository */
- $repository = app(BillRepositoryInterface::class);
- $repository->setUser($this->user);
- $bills = $repository->getBills();
- $header = [
- 'user_id',
- 'bill_id',
- 'created_at',
- 'updated_at',
- 'currency_code',
- 'name',
- 'amount_min',
- 'amount_max',
- 'date',
- 'repeat_freq',
- 'skip',
- 'active',
- ];
- $records = [];
-
- /** @var Bill $bill */
- foreach ($bills as $bill) {
- $records[] = [
- $this->user->id,
- $bill->id,
- $bill->created_at->toAtomString(),
- $bill->updated_at->toAtomString(),
- $bill->transactionCurrency->code,
- $bill->name,
- $bill->amount_min,
- $bill->amount_max,
- $bill->date->format('Y-m-d'),
- $bill->repeat_freq,
- $bill->skip,
- $bill->active,
- ];
- }
-
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportBudgets(): string
- {
- $header = [
- 'user_id',
- 'budget_id',
- 'name',
- 'active',
- 'order',
- 'start_date',
- 'end_date',
- 'currency_code',
- 'amount',
- ];
-
- $budgetRepos = app(BudgetRepositoryInterface::class);
- $budgetRepos->setUser($this->user);
- $limitRepos = app(BudgetLimitRepositoryInterface::class);
- $budgets = $budgetRepos->getBudgets();
- $records = [];
-
- /** @var Budget $budget */
- foreach ($budgets as $budget) {
- $limits = $limitRepos->getBudgetLimits($budget);
-
- /** @var BudgetLimit $limit */
- foreach ($limits as $limit) {
- $records[] = [
- $this->user->id,
- $budget->id,
- $budget->name,
- $budget->active,
- $budget->order,
- $limit->start_date->format('Y-m-d'),
- $limit->end_date->format('Y-m-d'),
- $limit->transactionCurrency->code,
- $limit->amount,
- ];
- }
- }
-
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportCategories(): string
- {
- $header = ['user_id', 'category_id', 'created_at', 'updated_at', 'name'];
-
- /** @var CategoryRepositoryInterface $catRepos */
- $catRepos = app(CategoryRepositoryInterface::class);
- $catRepos->setUser($this->user);
-
- $records = [];
- $categories = $catRepos->getCategories();
-
- /** @var Category $category */
- foreach ($categories as $category) {
- $records[] = [
- $this->user->id,
- $category->id,
- $category->created_at->toAtomString(),
- $category->updated_at->toAtomString(),
- $category->name,
- ];
- }
-
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportPiggies(): string
- {
- /** @var PiggyBankRepositoryInterface $piggyRepos */
- $piggyRepos = app(PiggyBankRepositoryInterface::class);
- $piggyRepos->setUser($this->user);
-
- /** @var AccountRepositoryInterface $accountRepos */
- $accountRepos = app(AccountRepositoryInterface::class);
- $accountRepos->setUser($this->user);
-
- $header = [
- 'user_id',
- 'piggy_bank_id',
- 'created_at',
- 'updated_at',
- 'account_name',
- 'account_type',
- 'name',
- 'currency_code',
- 'target_amount',
- 'current_amount',
- 'start_date',
- 'target_date',
- 'order',
- 'active',
- ];
- $records = [];
- $piggies = $piggyRepos->getPiggyBanks();
-
- /** @var PiggyBank $piggy */
- foreach ($piggies as $piggy) {
- $repetition = $piggyRepos->getRepetition($piggy);
- $currency = $accountRepos->getAccountCurrency($piggy->account);
- $records[] = [
- $this->user->id,
- $piggy->id,
- $piggy->created_at->toAtomString(),
- $piggy->updated_at->toAtomString(),
- $piggy->account->name,
- $piggy->account->accountType->type,
- $piggy->name,
- $currency?->code,
- $piggy->target_amount,
- $repetition?->current_amount,
- $piggy->start_date?->format('Y-m-d'),
- $piggy->target_date?->format('Y-m-d'),
- $piggy->order,
- $piggy->active,
- ];
- }
-
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportRecurring(): string
- {
- /** @var RecurringRepositoryInterface $recurringRepos */
- $recurringRepos = app(RecurringRepositoryInterface::class);
- $recurringRepos->setUser($this->user);
- $header = [
- // recurrence:
- 'user_id', 'recurrence_id', 'row_contains', 'created_at', 'updated_at', 'type', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active',
-
- // repetition info:
- 'type', 'moment', 'skip', 'weekend',
- // transactions + meta:
- 'currency_code', 'foreign_currency_code', 'source_name', 'source_type', 'destination_name', 'destination_type', 'amount', 'foreign_amount', 'category', 'budget', 'piggy_bank', 'tags',
- ];
- $records = [];
- $recurrences = $recurringRepos->get();
-
- /** @var Recurrence $recurrence */
- foreach ($recurrences as $recurrence) {
- // add recurrence:
- $records[] = [
- $this->user->id, $recurrence->id,
- 'recurrence',
- $recurrence->created_at->toAtomString(), $recurrence->updated_at->toAtomString(), $recurrence->transactionType->type, $recurrence->title, $recurrence->description, $recurrence->first_date?->format('Y-m-d'), $recurrence->repeat_until?->format('Y-m-d'), $recurrence->latest_date?->format('Y-m-d'), $recurrence->repetitions, $recurrence->apply_rules, $recurrence->active,
- ];
-
- // add new row for each repetition
- /** @var RecurrenceRepetition $repetition */
- foreach ($recurrence->recurrenceRepetitions as $repetition) {
- $records[] = [
- // recurrence
- $this->user->id,
- $recurrence->id,
- 'repetition',
- null, null, null, null, null, null, null, null, null, null, null,
-
- // repetition:
- $repetition->repetition_type, $repetition->repetition_moment, $repetition->repetition_skip, $repetition->weekend,
- ];
- }
-
- /** @var RecurrenceTransaction $transaction */
- foreach ($recurrence->recurrenceTransactions as $transaction) {
- $categoryName = $recurringRepos->getCategoryName($transaction);
- $budgetId = $recurringRepos->getBudget($transaction);
- $piggyBankId = $recurringRepos->getPiggyBank($transaction);
- $tags = $recurringRepos->getTags($transaction);
-
- $records[] = [
- // recurrence
- $this->user->id,
- $recurrence->id,
- 'transaction',
- null, null, null, null, null, null, null, null, null, null, null,
-
- // repetition:
- null, null, null, null,
-
- // transaction:
- $transaction->transactionCurrency->code, $transaction->foreignCurrency?->code, $transaction->sourceAccount->name, $transaction->sourceAccount->accountType->type, $transaction->destinationAccount->name, $transaction->destinationAccount->accountType->type, $transaction->amount, $transaction->foreign_amount, $categoryName, $budgetId, $piggyBankId, implode(',', $tags),
- ];
- }
- }
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
@@ -596,256 +149,6 @@ class ExportDataGenerator
return null;
}
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportRules(): string
- {
- $header = [
- 'user_id', 'rule_id', 'row_contains',
- 'created_at', 'updated_at', 'group_id', 'title', 'description', 'order', 'active', 'stop_processing', 'strict',
- 'trigger_type', 'trigger_value', 'trigger_order', 'trigger_active', 'trigger_stop_processing',
- 'action_type', 'action_value', 'action_order', 'action_active', 'action_stop_processing'];
- $ruleRepos = app(RuleRepositoryInterface::class);
- $ruleRepos->setUser($this->user);
- $rules = $ruleRepos->getAll();
- $records = [];
-
- /** @var Rule $rule */
- foreach ($rules as $rule) {
- $entry = [
- $this->user->id, $rule->id,
- 'rule',
- $rule->created_at->toAtomString(), $rule->updated_at->toAtomString(), $rule->ruleGroup->id, $rule->ruleGroup->title, $rule->title, $rule->description, $rule->order, $rule->active, $rule->stop_processing, $rule->strict,
- null, null, null, null, null, null, null, null, null,
- ];
- $records[] = $entry;
-
- /** @var RuleTrigger $trigger */
- foreach ($rule->ruleTriggers as $trigger) {
- $entry = [
- $this->user->id,
- $rule->id,
- 'trigger',
- null, null, null, null, null, null, null, null, null,
- $trigger->trigger_type, $trigger->trigger_value, $trigger->order, $trigger->active, $trigger->stop_processing,
- null, null, null, null, null,
- ];
- $records[] = $entry;
- }
-
- /** @var RuleAction $action */
- foreach ($rule->ruleActions as $action) {
- $entry = [
- $this->user->id,
- $rule->id,
- 'action',
- null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- $action->action_type, $action->action_value, $action->order, $action->active, $action->stop_processing,
- ];
- $records[] = $entry;
- }
- }
-
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportTags(): string
- {
- $header = ['user_id', 'tag_id', 'created_at', 'updated_at', 'tag', 'date', 'description', 'latitude', 'longitude', 'zoom_level'];
-
- $tagRepos = app(TagRepositoryInterface::class);
- $tagRepos->setUser($this->user);
- $tags = $tagRepos->get();
- $records = [];
-
- /** @var Tag $tag */
- foreach ($tags as $tag) {
- $records[] = [
- $this->user->id,
- $tag->id,
- $tag->created_at->toAtomString(),
- $tag->updated_at->toAtomString(),
- $tag->tag,
- $tag->date?->format('Y-m-d'),
- $tag->description,
- $tag->latitude,
- $tag->longitude,
- $tag->zoomLevel,
- ];
- }
-
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
- /**
- * @throws CannotInsertRecord
- * @throws Exception
- * @throws FireflyException
- */
- private function exportTransactions(): string
- {
- Log::debug('Will now export transactions.');
- // TODO better place for keys?
- $header = ['user_id', 'group_id', 'journal_id', 'created_at', 'updated_at', 'group_title', 'type', 'currency_code', 'amount', 'foreign_currency_code', 'foreign_amount', 'primary_currency_code', 'pc_amount', 'pc_foreign_amount', 'description', 'date', 'source_name', 'source_iban', 'source_type', 'destination_name', 'destination_iban', 'destination_type', 'reconciled', 'category', 'budget', 'bill', 'tags', 'notes'];
-
- $metaFields = config('firefly.journal_meta_fields');
- $header = array_merge($header, $metaFields);
- $primary = Amount::getPrimaryCurrency();
-
- $collector = app(GroupCollectorInterface::class);
- $collector->setUser($this->user);
- $collector->setRange($this->start, $this->end)->withAccountInformation()->withCategoryInformation()->withBillInformation()->withBudgetInformation()->withTagInformation()->withNotes();
- if (0 !== $this->accounts->count()) {
- $collector->setAccounts($this->accounts);
- }
-
- $journals = $collector->getExtractedJournals();
-
- // get repository for meta data:
- $repository = app(TransactionGroupRepositoryInterface::class);
- $repository->setUser($this->user);
-
- $records = [];
-
- /** @var array $journal */
- foreach ($journals as $journal) {
- $metaData = $repository->getMetaFields($journal['transaction_journal_id'], $metaFields);
- $amount = Steam::bcround(Steam::negative($journal['amount']), $journal['currency_decimal_places']);
- $foreignAmount = null === $journal['foreign_amount'] ? null : Steam::bcround(Steam::negative($journal['foreign_amount']), $journal['foreign_currency_decimal_places']);
- $pcAmount = null === $journal['pc_amount'] ? null : Steam::bcround(Steam::negative($journal['pc_amount']), $primary->decimal_places);
- $pcForeignAmount = null === $journal['pc_foreign_amount'] ? null : Steam::bcround(Steam::negative($journal['pc_foreign_amount']), $primary->decimal_places);
-
- if (TransactionTypeEnum::WITHDRAWAL->value !== $journal['transaction_type_type']) {
- $amount = Steam::bcround(Steam::positive($journal['amount']), $journal['currency_decimal_places']);
- $foreignAmount = null === $journal['foreign_amount'] ? null : Steam::bcround(Steam::positive($journal['foreign_amount']), $journal['foreign_currency_decimal_places']);
- $pcAmount = null === $journal['pc_amount'] ? null : Steam::bcround(Steam::positive($journal['pc_amount']), $primary->decimal_places);
- $pcForeignAmount = null === $journal['pc_foreign_amount'] ? null : Steam::bcround(Steam::positive($journal['pc_foreign_amount']), $primary->decimal_places);
- }
-
- // opening balance depends on source account type.
- if (TransactionTypeEnum::OPENING_BALANCE->value === $journal['transaction_type_type'] && AccountTypeEnum::ASSET->value === $journal['source_account_type']) {
- $amount = Steam::bcround(Steam::negative($journal['amount']), $journal['currency_decimal_places']);
- $foreignAmount = null === $journal['foreign_amount'] ? null : Steam::bcround(Steam::negative($journal['foreign_amount']), $journal['foreign_currency_decimal_places']);
- $pcAmount = null === $journal['pc_amount'] ? null : Steam::bcround(Steam::negative($journal['pc_amount']), $primary->decimal_places);
- $pcForeignAmount = null === $journal['pc_foreign_amount'] ? null : Steam::bcround(Steam::negative($journal['pc_foreign_amount']), $primary->decimal_places);
- }
-
- $records[] = [
- $journal['user_id'], $journal['transaction_group_id'], $journal['transaction_journal_id'], $journal['created_at']->toAtomString(), $journal['updated_at']->toAtomString(), $journal['transaction_group_title'], $journal['transaction_type_type'],
- // amounts and currencies
- $journal['currency_code'], $amount, $journal['foreign_currency_code'], $foreignAmount, $primary->code, $pcAmount, $pcForeignAmount,
-
- // more fields
- $journal['description'], $journal['date']->toAtomString(), $journal['source_account_name'], $journal['source_account_iban'], $journal['source_account_type'], $journal['destination_account_name'], $journal['destination_account_iban'], $journal['destination_account_type'], $journal['reconciled'], $journal['category_name'], $journal['budget_name'], $journal['bill_name'],
- $this->mergeTags($journal['tags']),
- $this->clearStringKeepNewlines($journal['notes']),
-
- // sepa
- $metaData['sepa_cc'], $metaData['sepa_ct_op'], $metaData['sepa_ct_id'], $metaData['sepa_db'], $metaData['sepa_country'], $metaData['sepa_ep'], $metaData['sepa_ci'], $metaData['sepa_batch_id'], $metaData['external_url'],
-
- // dates
- $metaData['interest_date'], $metaData['book_date'], $metaData['process_date'], $metaData['due_date'], $metaData['payment_date'], $metaData['invoice_date'],
-
- // others
- $metaData['recurrence_id'], $metaData['internal_reference'], $metaData['bunq_payment_id'], $metaData['import_hash'], $metaData['import_hash_v2'], $metaData['external_id'], $metaData['original_source'],
-
- // recurring transactions
- $metaData['recurrence_total'], $metaData['recurrence_count'],
- ];
- }
-
- // load the CSV document from a string
- $csv = Writer::createFromString();
-
- // insert the header
- try {
- $csv->insertOne($header);
- } catch (CannotInsertRecord $e) {
- throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
- }
-
- // insert all the records
- $csv->insertAll($records);
-
- try {
- $string = $csv->toString();
- } catch (Exception $e) { // intentional generic exception
- app('log')->error($e->getMessage());
-
- throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
- }
-
- return $string;
- }
-
- public function setAccounts(Collection $accounts): void
- {
- $this->accounts = $accounts;
- }
-
- private function mergeTags(array $tags): string
- {
- if (0 === count($tags)) {
- return '';
- }
- $smol = [];
- foreach ($tags as $tag) {
- $smol[] = $tag['name'];
- }
-
- return implode(',', $smol);
- }
-
/**
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
@@ -854,6 +157,11 @@ class ExportDataGenerator
return null;
}
+ public function setAccounts(Collection $accounts): void
+ {
+ $this->accounts = $accounts;
+ }
+
public function setEnd(Carbon $end): void
{
$this->end = $end;
@@ -909,8 +217,700 @@ class ExportDataGenerator
$this->start = $start;
}
+ public function setUser(User $user): void
+ {
+ $this->user = $user;
+ }
+
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportAccounts(): string
+ {
+ $header = [
+ 'user_id',
+ 'account_id',
+ 'created_at',
+ 'updated_at',
+ 'type',
+ 'name',
+ 'virtual_balance',
+ 'iban',
+ 'number',
+ 'active',
+ 'currency_code',
+ 'role',
+ 'cc_type',
+ 'cc_payment_date',
+ 'in_net_worth',
+ 'interest',
+ 'interest_period',
+ ];
+
+ /** @var AccountRepositoryInterface $repository */
+ $repository = app(AccountRepositoryInterface::class);
+ $repository->setUser($this->user);
+ $allAccounts = $repository->getAccountsByType([]);
+ $records = [];
+
+ /** @var Account $account */
+ foreach ($allAccounts as $account) {
+ $currency = $repository->getAccountCurrency($account);
+ $records[] = [
+ $this->user->id,
+ $account->id,
+ $account->created_at->toAtomString(),
+ $account->updated_at->toAtomString(),
+ $account->accountType->type,
+ $account->name,
+ $account->virtual_balance,
+ $account->iban,
+ $account->account_number,
+ $account->active,
+ $currency?->code,
+ $repository->getMetaValue($account, 'account_role'),
+ $repository->getMetaValue($account, 'cc_type'),
+ $repository->getMetaValue($account, 'cc_monthly_payment_date'),
+ $repository->getMetaValue($account, 'include_net_worth'),
+ $repository->getMetaValue($account, 'interest'),
+ $repository->getMetaValue($account, 'interest_period'),
+ ];
+ }
+
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportBills(): string
+ {
+ /** @var BillRepositoryInterface $repository */
+ $repository = app(BillRepositoryInterface::class);
+ $repository->setUser($this->user);
+ $bills = $repository->getBills();
+ $header = [
+ 'user_id',
+ 'bill_id',
+ 'created_at',
+ 'updated_at',
+ 'currency_code',
+ 'name',
+ 'amount_min',
+ 'amount_max',
+ 'date',
+ 'repeat_freq',
+ 'skip',
+ 'active',
+ ];
+ $records = [];
+
+ /** @var Bill $bill */
+ foreach ($bills as $bill) {
+ $records[] = [
+ $this->user->id,
+ $bill->id,
+ $bill->created_at->toAtomString(),
+ $bill->updated_at->toAtomString(),
+ $bill->transactionCurrency->code,
+ $bill->name,
+ $bill->amount_min,
+ $bill->amount_max,
+ $bill->date->format('Y-m-d'),
+ $bill->repeat_freq,
+ $bill->skip,
+ $bill->active,
+ ];
+ }
+
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportBudgets(): string
+ {
+ $header = [
+ 'user_id',
+ 'budget_id',
+ 'name',
+ 'active',
+ 'order',
+ 'start_date',
+ 'end_date',
+ 'currency_code',
+ 'amount',
+ ];
+
+ $budgetRepos = app(BudgetRepositoryInterface::class);
+ $budgetRepos->setUser($this->user);
+ $limitRepos = app(BudgetLimitRepositoryInterface::class);
+ $budgets = $budgetRepos->getBudgets();
+ $records = [];
+
+ /** @var Budget $budget */
+ foreach ($budgets as $budget) {
+ $limits = $limitRepos->getBudgetLimits($budget);
+
+ /** @var BudgetLimit $limit */
+ foreach ($limits as $limit) {
+ $records[] = [
+ $this->user->id,
+ $budget->id,
+ $budget->name,
+ $budget->active,
+ $budget->order,
+ $limit->start_date->format('Y-m-d'),
+ $limit->end_date->format('Y-m-d'),
+ $limit->transactionCurrency->code,
+ $limit->amount,
+ ];
+ }
+ }
+
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportCategories(): string
+ {
+ $header = ['user_id', 'category_id', 'created_at', 'updated_at', 'name'];
+
+ /** @var CategoryRepositoryInterface $catRepos */
+ $catRepos = app(CategoryRepositoryInterface::class);
+ $catRepos->setUser($this->user);
+
+ $records = [];
+ $categories = $catRepos->getCategories();
+
+ /** @var Category $category */
+ foreach ($categories as $category) {
+ $records[] = [
+ $this->user->id,
+ $category->id,
+ $category->created_at->toAtomString(),
+ $category->updated_at->toAtomString(),
+ $category->name,
+ ];
+ }
+
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportPiggies(): string
+ {
+ /** @var PiggyBankRepositoryInterface $piggyRepos */
+ $piggyRepos = app(PiggyBankRepositoryInterface::class);
+ $piggyRepos->setUser($this->user);
+
+ /** @var AccountRepositoryInterface $accountRepos */
+ $accountRepos = app(AccountRepositoryInterface::class);
+ $accountRepos->setUser($this->user);
+
+ $header = [
+ 'user_id',
+ 'piggy_bank_id',
+ 'created_at',
+ 'updated_at',
+ 'account_name',
+ 'account_type',
+ 'name',
+ 'currency_code',
+ 'target_amount',
+ 'current_amount',
+ 'start_date',
+ 'target_date',
+ 'order',
+ 'active',
+ ];
+ $records = [];
+ $piggies = $piggyRepos->getPiggyBanks();
+
+ /** @var PiggyBank $piggy */
+ foreach ($piggies as $piggy) {
+ $repetition = $piggyRepos->getRepetition($piggy);
+ $currency = $accountRepos->getAccountCurrency($piggy->account);
+ $records[] = [
+ $this->user->id,
+ $piggy->id,
+ $piggy->created_at->toAtomString(),
+ $piggy->updated_at->toAtomString(),
+ $piggy->account->name,
+ $piggy->account->accountType->type,
+ $piggy->name,
+ $currency?->code,
+ $piggy->target_amount,
+ $repetition?->current_amount,
+ $piggy->start_date?->format('Y-m-d'),
+ $piggy->target_date?->format('Y-m-d'),
+ $piggy->order,
+ $piggy->active,
+ ];
+ }
+
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportRecurring(): string
+ {
+ /** @var RecurringRepositoryInterface $recurringRepos */
+ $recurringRepos = app(RecurringRepositoryInterface::class);
+ $recurringRepos->setUser($this->user);
+ $header = [
+ // recurrence:
+ 'user_id', 'recurrence_id', 'row_contains', 'created_at', 'updated_at', 'type', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active',
+
+ // repetition info:
+ 'type', 'moment', 'skip', 'weekend',
+ // transactions + meta:
+ 'currency_code', 'foreign_currency_code', 'source_name', 'source_type', 'destination_name', 'destination_type', 'amount', 'foreign_amount', 'category', 'budget', 'piggy_bank', 'tags',
+ ];
+ $records = [];
+ $recurrences = $recurringRepos->get();
+
+ /** @var Recurrence $recurrence */
+ foreach ($recurrences as $recurrence) {
+ // add recurrence:
+ $records[] = [
+ $this->user->id, $recurrence->id,
+ 'recurrence',
+ $recurrence->created_at->toAtomString(), $recurrence->updated_at->toAtomString(), $recurrence->transactionType->type, $recurrence->title, $recurrence->description, $recurrence->first_date?->format('Y-m-d'), $recurrence->repeat_until?->format('Y-m-d'), $recurrence->latest_date?->format('Y-m-d'), $recurrence->repetitions, $recurrence->apply_rules, $recurrence->active,
+ ];
+
+ // add new row for each repetition
+ /** @var RecurrenceRepetition $repetition */
+ foreach ($recurrence->recurrenceRepetitions as $repetition) {
+ $records[] = [
+ // recurrence
+ $this->user->id,
+ $recurrence->id,
+ 'repetition',
+ null, null, null, null, null, null, null, null, null, null, null,
+
+ // repetition:
+ $repetition->repetition_type, $repetition->repetition_moment, $repetition->repetition_skip, $repetition->weekend,
+ ];
+ }
+
+ /** @var RecurrenceTransaction $transaction */
+ foreach ($recurrence->recurrenceTransactions as $transaction) {
+ $categoryName = $recurringRepos->getCategoryName($transaction);
+ $budgetId = $recurringRepos->getBudget($transaction);
+ $piggyBankId = $recurringRepos->getPiggyBank($transaction);
+ $tags = $recurringRepos->getTags($transaction);
+
+ $records[] = [
+ // recurrence
+ $this->user->id,
+ $recurrence->id,
+ 'transaction',
+ null, null, null, null, null, null, null, null, null, null, null,
+
+ // repetition:
+ null, null, null, null,
+
+ // transaction:
+ $transaction->transactionCurrency->code, $transaction->foreignCurrency?->code, $transaction->sourceAccount->name, $transaction->sourceAccount->accountType->type, $transaction->destinationAccount->name, $transaction->destinationAccount->accountType->type, $transaction->amount, $transaction->foreign_amount, $categoryName, $budgetId, $piggyBankId, implode(',', $tags),
+ ];
+ }
+ }
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportRules(): string
+ {
+ $header = [
+ 'user_id', 'rule_id', 'row_contains',
+ 'created_at', 'updated_at', 'group_id', 'title', 'description', 'order', 'active', 'stop_processing', 'strict',
+ 'trigger_type', 'trigger_value', 'trigger_order', 'trigger_active', 'trigger_stop_processing',
+ 'action_type', 'action_value', 'action_order', 'action_active', 'action_stop_processing'];
+ $ruleRepos = app(RuleRepositoryInterface::class);
+ $ruleRepos->setUser($this->user);
+ $rules = $ruleRepos->getAll();
+ $records = [];
+
+ /** @var Rule $rule */
+ foreach ($rules as $rule) {
+ $entry = [
+ $this->user->id, $rule->id,
+ 'rule',
+ $rule->created_at->toAtomString(), $rule->updated_at->toAtomString(), $rule->ruleGroup->id, $rule->ruleGroup->title, $rule->title, $rule->description, $rule->order, $rule->active, $rule->stop_processing, $rule->strict,
+ null, null, null, null, null, null, null, null, null,
+ ];
+ $records[] = $entry;
+
+ /** @var RuleTrigger $trigger */
+ foreach ($rule->ruleTriggers as $trigger) {
+ $entry = [
+ $this->user->id,
+ $rule->id,
+ 'trigger',
+ null, null, null, null, null, null, null, null, null,
+ $trigger->trigger_type, $trigger->trigger_value, $trigger->order, $trigger->active, $trigger->stop_processing,
+ null, null, null, null, null,
+ ];
+ $records[] = $entry;
+ }
+
+ /** @var RuleAction $action */
+ foreach ($rule->ruleActions as $action) {
+ $entry = [
+ $this->user->id,
+ $rule->id,
+ 'action',
+ null, null, null, null, null, null, null, null, null, null, null, null, null, null,
+ $action->action_type, $action->action_value, $action->order, $action->active, $action->stop_processing,
+ ];
+ $records[] = $entry;
+ }
+ }
+
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportTags(): string
+ {
+ $header = ['user_id', 'tag_id', 'created_at', 'updated_at', 'tag', 'date', 'description', 'latitude', 'longitude', 'zoom_level'];
+
+ $tagRepos = app(TagRepositoryInterface::class);
+ $tagRepos->setUser($this->user);
+ $tags = $tagRepos->get();
+ $records = [];
+
+ /** @var Tag $tag */
+ foreach ($tags as $tag) {
+ $records[] = [
+ $this->user->id,
+ $tag->id,
+ $tag->created_at->toAtomString(),
+ $tag->updated_at->toAtomString(),
+ $tag->tag,
+ $tag->date?->format('Y-m-d'),
+ $tag->description,
+ $tag->latitude,
+ $tag->longitude,
+ $tag->zoomLevel,
+ ];
+ }
+
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ /**
+ * @throws CannotInsertRecord
+ * @throws Exception
+ * @throws FireflyException
+ */
+ private function exportTransactions(): string
+ {
+ Log::debug('Will now export transactions.');
+ // TODO better place for keys?
+ $header = ['user_id', 'group_id', 'journal_id', 'created_at', 'updated_at', 'group_title', 'type', 'currency_code', 'amount', 'foreign_currency_code', 'foreign_amount', 'primary_currency_code', 'pc_amount', 'pc_foreign_amount', 'description', 'date', 'source_name', 'source_iban', 'source_type', 'destination_name', 'destination_iban', 'destination_type', 'reconciled', 'category', 'budget', 'bill', 'tags', 'notes'];
+
+ $metaFields = config('firefly.journal_meta_fields');
+ $header = array_merge($header, $metaFields);
+ $primary = Amount::getPrimaryCurrency();
+
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setUser($this->user);
+ $collector->setRange($this->start, $this->end)->withAccountInformation()->withCategoryInformation()->withBillInformation()->withBudgetInformation()->withTagInformation()->withNotes();
+ if (0 !== $this->accounts->count()) {
+ $collector->setAccounts($this->accounts);
+ }
+
+ $journals = $collector->getExtractedJournals();
+
+ // get repository for meta data:
+ $repository = app(TransactionGroupRepositoryInterface::class);
+ $repository->setUser($this->user);
+
+ $records = [];
+
+ /** @var array $journal */
+ foreach ($journals as $journal) {
+ $metaData = $repository->getMetaFields($journal['transaction_journal_id'], $metaFields);
+ $amount = Steam::bcround(Steam::negative($journal['amount']), $journal['currency_decimal_places']);
+ $foreignAmount = null === $journal['foreign_amount'] ? null : Steam::bcround(Steam::negative($journal['foreign_amount']), $journal['foreign_currency_decimal_places']);
+ $pcAmount = null === $journal['pc_amount'] ? null : Steam::bcround(Steam::negative($journal['pc_amount']), $primary->decimal_places);
+ $pcForeignAmount = null === $journal['pc_foreign_amount'] ? null : Steam::bcround(Steam::negative($journal['pc_foreign_amount']), $primary->decimal_places);
+
+ if (TransactionTypeEnum::WITHDRAWAL->value !== $journal['transaction_type_type']) {
+ $amount = Steam::bcround(Steam::positive($journal['amount']), $journal['currency_decimal_places']);
+ $foreignAmount = null === $journal['foreign_amount'] ? null : Steam::bcround(Steam::positive($journal['foreign_amount']), $journal['foreign_currency_decimal_places']);
+ $pcAmount = null === $journal['pc_amount'] ? null : Steam::bcround(Steam::positive($journal['pc_amount']), $primary->decimal_places);
+ $pcForeignAmount = null === $journal['pc_foreign_amount'] ? null : Steam::bcround(Steam::positive($journal['pc_foreign_amount']), $primary->decimal_places);
+ }
+
+ // opening balance depends on source account type.
+ if (TransactionTypeEnum::OPENING_BALANCE->value === $journal['transaction_type_type'] && AccountTypeEnum::ASSET->value === $journal['source_account_type']) {
+ $amount = Steam::bcround(Steam::negative($journal['amount']), $journal['currency_decimal_places']);
+ $foreignAmount = null === $journal['foreign_amount'] ? null : Steam::bcround(Steam::negative($journal['foreign_amount']), $journal['foreign_currency_decimal_places']);
+ $pcAmount = null === $journal['pc_amount'] ? null : Steam::bcround(Steam::negative($journal['pc_amount']), $primary->decimal_places);
+ $pcForeignAmount = null === $journal['pc_foreign_amount'] ? null : Steam::bcround(Steam::negative($journal['pc_foreign_amount']), $primary->decimal_places);
+ }
+
+ $records[] = [
+ $journal['user_id'], $journal['transaction_group_id'], $journal['transaction_journal_id'], $journal['created_at']->toAtomString(), $journal['updated_at']->toAtomString(), $journal['transaction_group_title'], $journal['transaction_type_type'],
+ // amounts and currencies
+ $journal['currency_code'], $amount, $journal['foreign_currency_code'], $foreignAmount, $primary->code, $pcAmount, $pcForeignAmount,
+
+ // more fields
+ $journal['description'], $journal['date']->toAtomString(), $journal['source_account_name'], $journal['source_account_iban'], $journal['source_account_type'], $journal['destination_account_name'], $journal['destination_account_iban'], $journal['destination_account_type'], $journal['reconciled'], $journal['category_name'], $journal['budget_name'], $journal['bill_name'],
+ $this->mergeTags($journal['tags']),
+ $this->clearStringKeepNewlines($journal['notes']),
+
+ // sepa
+ $metaData['sepa_cc'], $metaData['sepa_ct_op'], $metaData['sepa_ct_id'], $metaData['sepa_db'], $metaData['sepa_country'], $metaData['sepa_ep'], $metaData['sepa_ci'], $metaData['sepa_batch_id'], $metaData['external_url'],
+
+ // dates
+ $metaData['interest_date'], $metaData['book_date'], $metaData['process_date'], $metaData['due_date'], $metaData['payment_date'], $metaData['invoice_date'],
+
+ // others
+ $metaData['recurrence_id'], $metaData['internal_reference'], $metaData['bunq_payment_id'], $metaData['import_hash'], $metaData['import_hash_v2'], $metaData['external_id'], $metaData['original_source'],
+
+ // recurring transactions
+ $metaData['recurrence_total'], $metaData['recurrence_count'],
+ ];
+ }
+
+ // load the CSV document from a string
+ $csv = Writer::createFromString();
+
+ // insert the header
+ try {
+ $csv->insertOne($header);
+ } catch (CannotInsertRecord $e) {
+ throw new FireflyException(sprintf(self::ADD_RECORD_ERR, $e->getMessage()), 0, $e);
+ }
+
+ // insert all the records
+ $csv->insertAll($records);
+
+ try {
+ $string = $csv->toString();
+ } catch (Exception $e) { // intentional generic exception
+ app('log')->error($e->getMessage());
+
+ throw new FireflyException(sprintf(self::EXPORT_ERR, $e->getMessage()), 0, $e);
+ }
+
+ return $string;
+ }
+
+ private function mergeTags(array $tags): string
+ {
+ if (0 === count($tags)) {
+ return '';
+ }
+ $smol = [];
+ foreach ($tags as $tag) {
+ $smol[] = $tag['name'];
+ }
+
+ return implode(',', $smol);
+ }
}
diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php
index 499b123602..b79967f8cb 100644
--- a/app/Support/FireflyConfig.php
+++ b/app/Support/FireflyConfig.php
@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Support;
+use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Configuration;
use Illuminate\Contracts\Encryption\DecryptException;
@@ -30,7 +31,6 @@ use Illuminate\Contracts\Encryption\EncryptException;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
-use Exception;
/**
* Class FireflyConfig.
@@ -39,16 +39,43 @@ class FireflyConfig
{
public function delete(string $name): void
{
- $fullName = 'ff3-config-'.$name;
+ $fullName = 'ff3-config-' . $name;
if (Cache::has($fullName)) {
Cache::forget($fullName);
}
Configuration::where('name', $name)->forceDelete();
}
- public function has(string $name): bool
+ /**
+ * @param null|bool|int|string $default
+ *
+ * @throws FireflyException
+ */
+ public function get(string $name, mixed $default = null): ?Configuration
{
- return 1 === Configuration::where('name', $name)->count();
+ $fullName = 'ff3-config-' . $name;
+ if (Cache::has($fullName)) {
+ return Cache::get($fullName);
+ }
+
+ try {
+ /** @var null|Configuration $config */
+ $config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
+ } catch (Exception | QueryException $e) {
+ throw new FireflyException(sprintf('Could not poll the database: %s', $e->getMessage()), 0, $e);
+ }
+
+ if (null !== $config) {
+ Cache::forever($fullName, $config);
+
+ return $config;
+ }
+ // no preference found and default is null:
+ if (null === $default) {
+ return null;
+ }
+
+ return $this->set($name, $default);
}
public function getEncrypted(string $name, mixed $default = null): ?Configuration
@@ -74,28 +101,10 @@ class FireflyConfig
return $result;
}
- /**
- * @param null|bool|int|string $default
- *
- * @throws FireflyException
- */
- public function get(string $name, mixed $default = null): ?Configuration
+ public function getFresh(string $name, mixed $default = null): ?Configuration
{
- $fullName = 'ff3-config-'.$name;
- if (Cache::has($fullName)) {
- return Cache::get($fullName);
- }
-
- try {
- /** @var null|Configuration $config */
- $config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
- } catch (Exception|QueryException $e) {
- throw new FireflyException(sprintf('Could not poll the database: %s', $e->getMessage()), 0, $e);
- }
-
+ $config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
if (null !== $config) {
- Cache::forever($fullName, $config);
-
return $config;
}
// no preference found and default is null:
@@ -106,6 +115,19 @@ class FireflyConfig
return $this->set($name, $default);
}
+ public function has(string $name): bool
+ {
+ return 1 === Configuration::where('name', $name)->count();
+ }
+
+ /**
+ * @param mixed $value
+ */
+ public function put(string $name, $value): Configuration
+ {
+ return $this->set($name, $value);
+ }
+
public function set(string $name, mixed $value): Configuration
{
try {
@@ -124,39 +146,17 @@ class FireflyConfig
$item->name = $name;
$item->data = $value;
$item->save();
- Cache::forget('ff3-config-'.$name);
+ Cache::forget('ff3-config-' . $name);
return $item;
}
$config->data = $value;
$config->save();
- Cache::forget('ff3-config-'.$name);
+ Cache::forget('ff3-config-' . $name);
return $config;
}
- public function getFresh(string $name, mixed $default = null): ?Configuration
- {
- $config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
- if (null !== $config) {
- return $config;
- }
- // no preference found and default is null:
- if (null === $default) {
- return null;
- }
-
- return $this->set($name, $default);
- }
-
- /**
- * @param mixed $value
- */
- public function put(string $name, $value): Configuration
- {
- return $this->set($name, $value);
- }
-
public function setEncrypted(string $name, mixed $value): Configuration
{
try {
diff --git a/app/Support/Form/AccountForm.php b/app/Support/Form/AccountForm.php
index 19a043c76d..c7a7685061 100644
--- a/app/Support/Form/AccountForm.php
+++ b/app/Support/Form/AccountForm.php
@@ -51,55 +51,24 @@ class AccountForm
$repository = $this->getAccountRepository();
$grouped = $this->getAccountsGrouped($types, $repository);
$cash = $repository->getCashAccount();
- $key = (string) trans('firefly.cash_account_type');
- $grouped[$key][$cash->id] = sprintf('(%s)', (string) trans('firefly.cash'));
+ $key = (string)trans('firefly.cash_account_type');
+ $grouped[$key][$cash->id] = sprintf('(%s)', (string)trans('firefly.cash'));
return $this->select($name, $grouped, $value, $options);
}
- private function getAccountsGrouped(array $types, ?AccountRepositoryInterface $repository = null): array
- {
- if (!$repository instanceof AccountRepositoryInterface) {
- $repository = $this->getAccountRepository();
- }
- $accountList = $repository->getActiveAccountsByType($types);
- $liabilityTypes = [AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::LOAN->value];
- $grouped = [];
-
- /** @var Account $account */
- foreach ($accountList as $account) {
- $role = (string) $repository->getMetaValue($account, 'account_role');
- if (in_array($account->accountType->type, $liabilityTypes, true)) {
- $role = sprintf('l_%s', $account->accountType->type);
- }
- if ('' === $role) {
- $role = 'no_account_type';
- if (AccountTypeEnum::EXPENSE->value === $account->accountType->type) {
- $role = 'expense_account';
- }
- if (AccountTypeEnum::REVENUE->value === $account->accountType->type) {
- $role = 'revenue_account';
- }
- }
- $key = (string) trans(sprintf('firefly.opt_group_%s', $role));
- $grouped[$key][$account->id] = $account->name;
- }
-
- return $grouped;
- }
-
/**
* Grouped dropdown list of all accounts that are valid as the destination of a withdrawal.
*/
public function activeWithdrawalDestinations(string $name, mixed $value = null, ?array $options = null): string
{
- $types = [AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::EXPENSE->value];
- $repository = $this->getAccountRepository();
- $grouped = $this->getAccountsGrouped($types, $repository);
+ $types = [AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::EXPENSE->value];
+ $repository = $this->getAccountRepository();
+ $grouped = $this->getAccountsGrouped($types, $repository);
$cash = $repository->getCashAccount();
- $key = (string) trans('firefly.cash_account_type');
- $grouped[$key][$cash->id] = sprintf('(%s)', (string) trans('firefly.cash'));
+ $key = (string)trans('firefly.cash_account_type');
+ $grouped[$key][$cash->id] = sprintf('(%s)', (string)trans('firefly.cash'));
return $this->select($name, $grouped, $value, $options);
}
@@ -111,15 +80,15 @@ class AccountForm
*/
public function assetAccountCheckList(string $name, ?array $options = null): string
{
- $options ??= [];
+ $options ??= [];
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
$selected = request()->old($name) ?? [];
// get all asset accounts:
- $types = [AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value];
- $grouped = $this->getAccountsGrouped($types);
+ $types = [AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value];
+ $grouped = $this->getAccountsGrouped($types);
unset($options['class']);
@@ -173,4 +142,35 @@ class AccountForm
return $this->select($name, $grouped, $value, $options);
}
+
+ private function getAccountsGrouped(array $types, ?AccountRepositoryInterface $repository = null): array
+ {
+ if (!$repository instanceof AccountRepositoryInterface) {
+ $repository = $this->getAccountRepository();
+ }
+ $accountList = $repository->getActiveAccountsByType($types);
+ $liabilityTypes = [AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::LOAN->value];
+ $grouped = [];
+
+ /** @var Account $account */
+ foreach ($accountList as $account) {
+ $role = (string)$repository->getMetaValue($account, 'account_role');
+ if (in_array($account->accountType->type, $liabilityTypes, true)) {
+ $role = sprintf('l_%s', $account->accountType->type);
+ }
+ if ('' === $role) {
+ $role = 'no_account_type';
+ if (AccountTypeEnum::EXPENSE->value === $account->accountType->type) {
+ $role = 'expense_account';
+ }
+ if (AccountTypeEnum::REVENUE->value === $account->accountType->type) {
+ $role = 'revenue_account';
+ }
+ }
+ $key = (string)trans(sprintf('firefly.opt_group_%s', $role));
+ $grouped[$key][$account->id] = $account->name;
+ }
+
+ return $grouped;
+ }
}
diff --git a/app/Support/Form/CurrencyForm.php b/app/Support/Form/CurrencyForm.php
index 88816667d4..6d24780f19 100644
--- a/app/Support/Form/CurrencyForm.php
+++ b/app/Support/Form/CurrencyForm.php
@@ -49,60 +49,6 @@ class CurrencyForm
return $this->currencyField($name, 'amount', $value, $options);
}
- /**
- * @phpstan-param view-string $view
- *
- * @throws FireflyException
- */
- protected function currencyField(string $name, string $view, mixed $value = null, ?array $options = null): string
- {
- $label = $this->label($name, $options);
- $options = $this->expandOptionArray($name, $label, $options);
- $classes = $this->getHolderClasses($name);
- $value = $this->fillFieldValue($name, $value);
- $options['step'] = 'any';
- $primaryCurrency = $options['currency'] ?? app('amount')->getPrimaryCurrency();
-
- /** @var Collection $currencies */
- $currencies = app('amount')->getCurrencies();
- unset($options['currency'], $options['placeholder']);
- // perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount)
- $preFilled = session('preFilled');
- if (!is_array($preFilled)) {
- $preFilled = [];
- }
- $key = 'amount_currency_id_'.$name;
- $sentCurrencyId = array_key_exists($key, $preFilled) ? (int) $preFilled[$key] : $primaryCurrency->id;
-
- app('log')->debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
-
- // find this currency in set of currencies:
- foreach ($currencies as $currency) {
- if ($currency->id === $sentCurrencyId) {
- $primaryCurrency = $currency;
- app('log')->debug(sprintf('default currency is now %s', $primaryCurrency->code));
-
- break;
- }
- }
-
- // make sure value is formatted nicely:
- if (null !== $value && '' !== $value) {
- $value = app('steam')->bcround($value, $primaryCurrency->decimal_places);
- }
-
- try {
- $html = view('form.'.$view, compact('primaryCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
- } catch (Throwable $e) {
- app('log')->debug(sprintf('Could not render currencyField(): %s', $e->getMessage()));
- $html = 'Could not render currencyField.';
-
- throw new FireflyException($html, 0, $e);
- }
-
- return $html;
- }
-
/**
* TODO describe and cleanup.
*
@@ -115,6 +61,52 @@ class CurrencyForm
return $this->allCurrencyField($name, 'balance', $value, $options);
}
+ /**
+ * TODO cleanup and describe
+ *
+ * @param mixed $value
+ */
+ public function currencyList(string $name, $value = null, ?array $options = null): string
+ {
+ /** @var CurrencyRepositoryInterface $currencyRepos */
+ $currencyRepos = app(CurrencyRepositoryInterface::class);
+
+ // get all currencies:
+ $list = $currencyRepos->get();
+ $array = [];
+
+ /** @var TransactionCurrency $currency */
+ foreach ($list as $currency) {
+ $array[$currency->id] = $currency->name . ' (' . $currency->symbol . ')';
+ }
+
+ return $this->select($name, $array, $value, $options);
+ }
+
+ /**
+ * TODO cleanup and describe
+ *
+ * @param mixed $value
+ */
+ public function currencyListEmpty(string $name, $value = null, ?array $options = null): string
+ {
+ /** @var CurrencyRepositoryInterface $currencyRepos */
+ $currencyRepos = app(CurrencyRepositoryInterface::class);
+
+ // get all currencies:
+ $list = $currencyRepos->get();
+ $array = [
+ 0 => (string)trans('firefly.no_currency'),
+ ];
+
+ /** @var TransactionCurrency $currency */
+ foreach ($list as $currency) {
+ $array[$currency->id] = $currency->name . ' (' . $currency->symbol . ')';
+ }
+
+ return $this->select($name, $array, $value, $options);
+ }
+
/**
* TODO describe and cleanup
*
@@ -132,16 +124,16 @@ class CurrencyForm
$primaryCurrency = $options['currency'] ?? app('amount')->getPrimaryCurrency();
/** @var Collection $currencies */
- $currencies = app('amount')->getAllCurrencies();
+ $currencies = app('amount')->getAllCurrencies();
unset($options['currency'], $options['placeholder']);
// perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount)
- $preFilled = session('preFilled');
+ $preFilled = session('preFilled');
if (!is_array($preFilled)) {
$preFilled = [];
}
- $key = 'amount_currency_id_'.$name;
- $sentCurrencyId = array_key_exists($key, $preFilled) ? (int) $preFilled[$key] : $primaryCurrency->id;
+ $key = 'amount_currency_id_' . $name;
+ $sentCurrencyId = array_key_exists($key, $preFilled) ? (int)$preFilled[$key] : $primaryCurrency->id;
app('log')->debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
@@ -161,7 +153,7 @@ class CurrencyForm
}
try {
- $html = view('form.'.$view, compact('primaryCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
+ $html = view('form.' . $view, compact('primaryCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
} catch (Throwable $e) {
app('log')->debug(sprintf('Could not render currencyField(): %s', $e->getMessage()));
$html = 'Could not render currencyField.';
@@ -173,48 +165,56 @@ class CurrencyForm
}
/**
- * TODO cleanup and describe
+ * @phpstan-param view-string $view
*
- * @param mixed $value
+ * @throws FireflyException
*/
- public function currencyList(string $name, $value = null, ?array $options = null): string
+ protected function currencyField(string $name, string $view, mixed $value = null, ?array $options = null): string
{
- /** @var CurrencyRepositoryInterface $currencyRepos */
- $currencyRepos = app(CurrencyRepositoryInterface::class);
+ $label = $this->label($name, $options);
+ $options = $this->expandOptionArray($name, $label, $options);
+ $classes = $this->getHolderClasses($name);
+ $value = $this->fillFieldValue($name, $value);
+ $options['step'] = 'any';
+ $primaryCurrency = $options['currency'] ?? app('amount')->getPrimaryCurrency();
- // get all currencies:
- $list = $currencyRepos->get();
- $array = [];
+ /** @var Collection $currencies */
+ $currencies = app('amount')->getCurrencies();
+ unset($options['currency'], $options['placeholder']);
+ // perhaps the currency has been sent to us in the field $amount_currency_id_$name (amount_currency_id_amount)
+ $preFilled = session('preFilled');
+ if (!is_array($preFilled)) {
+ $preFilled = [];
+ }
+ $key = 'amount_currency_id_' . $name;
+ $sentCurrencyId = array_key_exists($key, $preFilled) ? (int)$preFilled[$key] : $primaryCurrency->id;
- /** @var TransactionCurrency $currency */
- foreach ($list as $currency) {
- $array[$currency->id] = $currency->name.' ('.$currency->symbol.')';
+ app('log')->debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
+
+ // find this currency in set of currencies:
+ foreach ($currencies as $currency) {
+ if ($currency->id === $sentCurrencyId) {
+ $primaryCurrency = $currency;
+ app('log')->debug(sprintf('default currency is now %s', $primaryCurrency->code));
+
+ break;
+ }
}
- return $this->select($name, $array, $value, $options);
- }
-
- /**
- * TODO cleanup and describe
- *
- * @param mixed $value
- */
- public function currencyListEmpty(string $name, $value = null, ?array $options = null): string
- {
- /** @var CurrencyRepositoryInterface $currencyRepos */
- $currencyRepos = app(CurrencyRepositoryInterface::class);
-
- // get all currencies:
- $list = $currencyRepos->get();
- $array = [
- 0 => (string) trans('firefly.no_currency'),
- ];
-
- /** @var TransactionCurrency $currency */
- foreach ($list as $currency) {
- $array[$currency->id] = $currency->name.' ('.$currency->symbol.')';
+ // make sure value is formatted nicely:
+ if (null !== $value && '' !== $value) {
+ $value = app('steam')->bcround($value, $primaryCurrency->decimal_places);
}
- return $this->select($name, $array, $value, $options);
+ try {
+ $html = view('form.' . $view, compact('primaryCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
+ } catch (Throwable $e) {
+ app('log')->debug(sprintf('Could not render currencyField(): %s', $e->getMessage()));
+ $html = 'Could not render currencyField.';
+
+ throw new FireflyException($html, 0, $e);
+ }
+
+ return $html;
}
}
diff --git a/app/Support/Form/FormSupport.php b/app/Support/Form/FormSupport.php
index 4bcc0fcb87..e41c785f00 100644
--- a/app/Support/Form/FormSupport.php
+++ b/app/Support/Form/FormSupport.php
@@ -36,7 +36,7 @@ trait FormSupport
{
public function multiSelect(string $name, ?array $list = null, mixed $selected = null, ?array $options = null): string
{
- $list ??= [];
+ $list ??= [];
$label = $this->label($name, $options);
$options = $this->expandOptionArray($name, $label, $options);
$classes = $this->getHolderClasses($name);
@@ -54,15 +54,26 @@ trait FormSupport
return $html;
}
- protected function label(string $name, ?array $options = null): string
+ /**
+ * @param mixed $selected
+ */
+ public function select(string $name, ?array $list = null, $selected = null, ?array $options = null): string
{
- $options ??= [];
- if (array_key_exists('label', $options)) {
- return $options['label'];
- }
- $name = str_replace('[]', '', $name);
+ $list ??= [];
+ $label = $this->label($name, $options);
+ $options = $this->expandOptionArray($name, $label, $options);
+ $classes = $this->getHolderClasses($name);
+ $selected = $this->fillFieldValue($name, $selected);
+ unset($options['autocomplete'], $options['placeholder']);
- return (string)trans('form.'.$name);
+ try {
+ $html = view('form.select', compact('classes', 'name', 'label', 'selected', 'options', 'list'))->render();
+ } catch (Throwable $e) {
+ app('log')->debug(sprintf('Could not render select(): %s', $e->getMessage()));
+ $html = 'Could not render select.';
+ }
+
+ return $html;
}
/**
@@ -70,29 +81,16 @@ trait FormSupport
*/
protected function expandOptionArray(string $name, $label, ?array $options = null): array
{
- $options ??= [];
+ $options ??= [];
$name = str_replace('[]', '', $name);
$options['class'] = 'form-control';
- $options['id'] = 'ffInput_'.$name;
+ $options['id'] = 'ffInput_' . $name;
$options['autocomplete'] = 'off';
$options['placeholder'] = ucfirst((string)$label);
return $options;
}
- protected function getHolderClasses(string $name): string
- {
- // Get errors from session:
- /** @var null|MessageBag $errors */
- $errors = session('errors');
-
- if (null !== $errors && $errors->has($name)) {
- return 'form-group has-error has-feedback';
- }
-
- return 'form-group';
- }
-
/**
* @param null|mixed $value
*
@@ -116,28 +114,6 @@ trait FormSupport
return $value;
}
- /**
- * @param mixed $selected
- */
- public function select(string $name, ?array $list = null, $selected = null, ?array $options = null): string
- {
- $list ??= [];
- $label = $this->label($name, $options);
- $options = $this->expandOptionArray($name, $label, $options);
- $classes = $this->getHolderClasses($name);
- $selected = $this->fillFieldValue($name, $selected);
- unset($options['autocomplete'], $options['placeholder']);
-
- try {
- $html = view('form.select', compact('classes', 'name', 'label', 'selected', 'options', 'list'))->render();
- } catch (Throwable $e) {
- app('log')->debug(sprintf('Could not render select(): %s', $e->getMessage()));
- $html = 'Could not render select.';
- }
-
- return $html;
- }
-
protected function getAccountRepository(): AccountRepositoryInterface
{
return app(AccountRepositoryInterface::class);
@@ -147,4 +123,28 @@ trait FormSupport
{
return today(config('app.timezone'));
}
+
+ protected function getHolderClasses(string $name): string
+ {
+ // Get errors from session:
+ /** @var null|MessageBag $errors */
+ $errors = session('errors');
+
+ if (null !== $errors && $errors->has($name)) {
+ return 'form-group has-error has-feedback';
+ }
+
+ return 'form-group';
+ }
+
+ protected function label(string $name, ?array $options = null): string
+ {
+ $options ??= [];
+ if (array_key_exists('label', $options)) {
+ return $options['label'];
+ }
+ $name = str_replace('[]', '', $name);
+
+ return (string)trans('form.' . $name);
+ }
}
diff --git a/app/Support/Form/PiggyBankForm.php b/app/Support/Form/PiggyBankForm.php
index 78919b30bf..b6233fd0d6 100644
--- a/app/Support/Form/PiggyBankForm.php
+++ b/app/Support/Form/PiggyBankForm.php
@@ -47,7 +47,7 @@ class PiggyBankForm
/** @var PiggyBankRepositoryInterface $repository */
$repository = app(PiggyBankRepositoryInterface::class);
$piggyBanks = $repository->getPiggyBanksWithAmount();
- $title = (string) trans('firefly.default_group_title_name');
+ $title = (string)trans('firefly.default_group_title_name');
$array = [];
$subList = [
0 => [
@@ -55,21 +55,21 @@ class PiggyBankForm
'title' => $title,
],
'piggies' => [
- (string) trans('firefly.none_in_select_list'),
+ (string)trans('firefly.none_in_select_list'),
],
],
];
/** @var PiggyBank $piggy */
foreach ($piggyBanks as $piggy) {
- $group = $piggy->objectGroups->first();
- $groupTitle = null;
- $groupOrder = 0;
+ $group = $piggy->objectGroups->first();
+ $groupTitle = null;
+ $groupOrder = 0;
if (null !== $group) {
$groupTitle = $group->title;
$groupOrder = $group->order;
}
- $subList[$groupOrder] ??= [
+ $subList[$groupOrder] ??= [
'group' => [
'title' => $groupTitle,
],
diff --git a/app/Support/Form/RuleForm.php b/app/Support/Form/RuleForm.php
index 6baee553be..f635ed9b86 100644
--- a/app/Support/Form/RuleForm.php
+++ b/app/Support/Form/RuleForm.php
@@ -41,8 +41,8 @@ class RuleForm
$groupRepos = app(RuleGroupRepositoryInterface::class);
// get all currencies:
- $list = $groupRepos->get();
- $array = [];
+ $list = $groupRepos->get();
+ $array = [];
/** @var RuleGroup $group */
foreach ($list as $group) {
@@ -57,21 +57,21 @@ class RuleForm
*/
public function ruleGroupListWithEmpty(string $name, $value = null, ?array $options = null): string
{
- $options ??= [];
+ $options ??= [];
$options['class'] = 'form-control';
/** @var RuleGroupRepositoryInterface $groupRepos */
- $groupRepos = app(RuleGroupRepositoryInterface::class);
+ $groupRepos = app(RuleGroupRepositoryInterface::class);
// get all currencies:
- $list = $groupRepos->get();
- $array = [
- 0 => (string) trans('firefly.none_in_select_list'),
+ $list = $groupRepos->get();
+ $array = [
+ 0 => (string)trans('firefly.none_in_select_list'),
];
/** @var RuleGroup $group */
foreach ($list as $group) {
- if (array_key_exists('hidden', $options) && (int) $options['hidden'] !== $group->id) {
+ if (array_key_exists('hidden', $options) && (int)$options['hidden'] !== $group->id) {
$array[$group->id] = $group->title;
}
}
diff --git a/app/Support/Http/Api/AccountBalanceGrouped.php b/app/Support/Http/Api/AccountBalanceGrouped.php
index d1f7915d23..4c60e00870 100644
--- a/app/Support/Http/Api/AccountBalanceGrouped.php
+++ b/app/Support/Http/Api/AccountBalanceGrouped.php
@@ -146,114 +146,6 @@ class AccountBalanceGrouped
$converter->summarize();
}
- private function processJournal(array $journal): void
- {
- // format the date according to the period
- $period = $journal['date']->format($this->carbonFormat);
- $currencyId = (int)$journal['currency_id'];
- $currency = $this->findCurrency($currencyId);
-
- // set the array with monetary info, if it does not exist.
- $this->createDefaultDataEntry($journal);
- // set the array (in monetary info) with spent/earned in this $period, if it does not exist.
- $this->createDefaultPeriodEntry($journal);
-
- // is this journal's amount in- our outgoing?
- $key = $this->getDataKey($journal);
- $amount = 'spent' === $key ? Steam::negative($journal['amount']) : Steam::positive($journal['amount']);
-
- // get conversion rate
- $rate = $this->getRate($currency, $journal['date']);
- $amountConverted = bcmul($amount, $rate);
-
- // perhaps transaction already has the foreign amount in the primary currency.
- if ((int)$journal['foreign_currency_id'] === $this->primary->id) {
- $amountConverted = $journal['foreign_amount'] ?? '0';
- $amountConverted = 'earned' === $key ? Steam::positive($amountConverted) : Steam::negative($amountConverted);
- }
-
- // add normal entry
- $this->data[$currencyId][$period][$key] = bcadd((string)$this->data[$currencyId][$period][$key], $amount);
-
- // add converted entry
- $convertedKey = sprintf('pc_%s', $key);
- $this->data[$currencyId][$period][$convertedKey] = bcadd((string)$this->data[$currencyId][$period][$convertedKey], $amountConverted);
- }
-
- private function findCurrency(int $currencyId): TransactionCurrency
- {
- if (array_key_exists($currencyId, $this->currencies)) {
- return $this->currencies[$currencyId];
- }
- $this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
-
- return $this->currencies[$currencyId];
- }
-
- private function createDefaultDataEntry(array $journal): void
- {
- $currencyId = (int)$journal['currency_id'];
- $this->data[$currencyId] ??= [
- 'currency_id' => (string)$currencyId,
- 'currency_symbol' => $journal['currency_symbol'],
- 'currency_code' => $journal['currency_code'],
- 'currency_name' => $journal['currency_name'],
- 'currency_decimal_places' => $journal['currency_decimal_places'],
- // primary currency info (could be the same)
- 'primary_currency_id' => (string)$this->primary->id,
- 'primary_currency_code' => $this->primary->code,
- 'primary_currency_symbol' => $this->primary->symbol,
- 'primary_currency_decimal_places' => $this->primary->decimal_places,
- ];
- }
-
- private function createDefaultPeriodEntry(array $journal): void
- {
- $currencyId = (int)$journal['currency_id'];
- $period = $journal['date']->format($this->carbonFormat);
- $this->data[$currencyId][$period] ??= [
- 'period' => $period,
- 'spent' => '0',
- 'earned' => '0',
- 'pc_spent' => '0',
- 'pc_earned' => '0',
- ];
- }
-
- private function getDataKey(array $journal): string
- {
- // deposit = incoming
- // transfer or reconcile or opening balance, and these accounts are the destination.
- if (
- TransactionTypeEnum::DEPOSIT->value === $journal['transaction_type_type']
-
- || (
- (
- TransactionTypeEnum::TRANSFER->value === $journal['transaction_type_type']
- || TransactionTypeEnum::RECONCILIATION->value === $journal['transaction_type_type']
- || TransactionTypeEnum::OPENING_BALANCE->value === $journal['transaction_type_type']
- )
- && in_array($journal['destination_account_id'], $this->accountIds, true)
- )
- ) {
- return 'earned';
- }
-
- return 'spent';
- }
-
- private function getRate(TransactionCurrency $currency, Carbon $date): string
- {
- try {
- $rate = $this->converter->getCurrencyRate($currency, $this->primary, $date);
- } catch (FireflyException $e) {
- app('log')->error($e->getMessage());
- $rate = '1';
- }
-
- return $rate;
- }
-
public function setAccounts(Collection $accounts): void
{
$this->accountIds = $accounts->pluck('id')->toArray();
@@ -298,4 +190,112 @@ class AccountBalanceGrouped
{
$this->start = $start;
}
+
+ private function createDefaultDataEntry(array $journal): void
+ {
+ $currencyId = (int)$journal['currency_id'];
+ $this->data[$currencyId] ??= [
+ 'currency_id' => (string)$currencyId,
+ 'currency_symbol' => $journal['currency_symbol'],
+ 'currency_code' => $journal['currency_code'],
+ 'currency_name' => $journal['currency_name'],
+ 'currency_decimal_places' => $journal['currency_decimal_places'],
+ // primary currency info (could be the same)
+ 'primary_currency_id' => (string)$this->primary->id,
+ 'primary_currency_code' => $this->primary->code,
+ 'primary_currency_symbol' => $this->primary->symbol,
+ 'primary_currency_decimal_places' => $this->primary->decimal_places,
+ ];
+ }
+
+ private function createDefaultPeriodEntry(array $journal): void
+ {
+ $currencyId = (int)$journal['currency_id'];
+ $period = $journal['date']->format($this->carbonFormat);
+ $this->data[$currencyId][$period] ??= [
+ 'period' => $period,
+ 'spent' => '0',
+ 'earned' => '0',
+ 'pc_spent' => '0',
+ 'pc_earned' => '0',
+ ];
+ }
+
+ private function findCurrency(int $currencyId): TransactionCurrency
+ {
+ if (array_key_exists($currencyId, $this->currencies)) {
+ return $this->currencies[$currencyId];
+ }
+ $this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
+
+ return $this->currencies[$currencyId];
+ }
+
+ private function getDataKey(array $journal): string
+ {
+ // deposit = incoming
+ // transfer or reconcile or opening balance, and these accounts are the destination.
+ if (
+ TransactionTypeEnum::DEPOSIT->value === $journal['transaction_type_type']
+
+ || (
+ (
+ TransactionTypeEnum::TRANSFER->value === $journal['transaction_type_type']
+ || TransactionTypeEnum::RECONCILIATION->value === $journal['transaction_type_type']
+ || TransactionTypeEnum::OPENING_BALANCE->value === $journal['transaction_type_type']
+ )
+ && in_array($journal['destination_account_id'], $this->accountIds, true)
+ )
+ ) {
+ return 'earned';
+ }
+
+ return 'spent';
+ }
+
+ private function getRate(TransactionCurrency $currency, Carbon $date): string
+ {
+ try {
+ $rate = $this->converter->getCurrencyRate($currency, $this->primary, $date);
+ } catch (FireflyException $e) {
+ app('log')->error($e->getMessage());
+ $rate = '1';
+ }
+
+ return $rate;
+ }
+
+ private function processJournal(array $journal): void
+ {
+ // format the date according to the period
+ $period = $journal['date']->format($this->carbonFormat);
+ $currencyId = (int)$journal['currency_id'];
+ $currency = $this->findCurrency($currencyId);
+
+ // set the array with monetary info, if it does not exist.
+ $this->createDefaultDataEntry($journal);
+ // set the array (in monetary info) with spent/earned in this $period, if it does not exist.
+ $this->createDefaultPeriodEntry($journal);
+
+ // is this journal's amount in- our outgoing?
+ $key = $this->getDataKey($journal);
+ $amount = 'spent' === $key ? Steam::negative($journal['amount']) : Steam::positive($journal['amount']);
+
+ // get conversion rate
+ $rate = $this->getRate($currency, $journal['date']);
+ $amountConverted = bcmul($amount, $rate);
+
+ // perhaps transaction already has the foreign amount in the primary currency.
+ if ((int)$journal['foreign_currency_id'] === $this->primary->id) {
+ $amountConverted = $journal['foreign_amount'] ?? '0';
+ $amountConverted = 'earned' === $key ? Steam::positive($amountConverted) : Steam::negative($amountConverted);
+ }
+
+ // add normal entry
+ $this->data[$currencyId][$period][$key] = bcadd((string)$this->data[$currencyId][$period][$key], $amount);
+
+ // add converted entry
+ $convertedKey = sprintf('pc_%s', $key);
+ $this->data[$currencyId][$period][$convertedKey] = bcadd((string)$this->data[$currencyId][$period][$convertedKey], $amountConverted);
+ }
}
diff --git a/app/Support/Http/Api/CleansChartData.php b/app/Support/Http/Api/CleansChartData.php
index a3d54974cf..aef2ec443f 100644
--- a/app/Support/Http/Api/CleansChartData.php
+++ b/app/Support/Http/Api/CleansChartData.php
@@ -43,7 +43,7 @@ trait CleansChartData
$return = [];
/**
- * @var int $index
+ * @var int $index
* @var array $array
*/
foreach ($data as $index => $array) {
diff --git a/app/Support/Http/Api/ExchangeRateConverter.php b/app/Support/Http/Api/ExchangeRateConverter.php
index c78b7afce8..84d57a4649 100644
--- a/app/Support/Http/Api/ExchangeRateConverter.php
+++ b/app/Support/Http/Api/ExchangeRateConverter.php
@@ -94,57 +94,22 @@ class ExchangeRateConverter
return '0' === $rate ? '1' : $rate;
}
- /**
- * @throws FireflyException
- */
- private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
+ public function setIgnoreSettings(bool $ignoreSettings): void
{
- $key = $this->getCacheKey($from, $to, $date);
- $res = Cache::get($key, null);
+ $this->ignoreSettings = $ignoreSettings;
+ }
- // find in cache
- if (null !== $res) {
- Log::debug(sprintf('ExchangeRateConverter: Return cached rate (%s) from %s to %s on %s.', $res, $from->code, $to->code, $date->format('Y-m-d')));
+ public function setUserGroup(UserGroup $userGroup): void
+ {
+ $this->userGroup = $userGroup;
+ }
- return $res;
+ public function summarize(): void
+ {
+ if (false === $this->enabled()) {
+ return;
}
-
- // find in database
- $rate = $this->getFromDB($from->id, $to->id, $date->format('Y-m-d'));
- if (null !== $rate) {
- Cache::forever($key, $rate);
- Log::debug(sprintf('ExchangeRateConverter: Return DB rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d')));
-
- return $rate;
- }
-
- // find reverse in database
- $rate = $this->getFromDB($to->id, $from->id, $date->format('Y-m-d'));
- if (null !== $rate) {
- $rate = bcdiv('1', $rate);
- Cache::forever($key, $rate);
- Log::debug(sprintf('ExchangeRateConverter: Return inverse DB rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d')));
-
- return $rate;
- }
-
- // fallback scenario.
- $first = $this->getEuroRate($from, $date);
- $second = $this->getEuroRate($to, $date);
-
- // combined (if present), they can be used to calculate the necessary conversion rate.
- if (0 === bccomp('0', $first) || 0 === bccomp('0', $second)) {
- Log::warning(sprintf('There is not enough information to convert %s to %s on date %s', $from->code, $to->code, $date->format('Y-m-d')));
-
- return '1';
- }
-
- $second = bcdiv('1', $second);
- $rate = bcmul($first, $second);
- Log::debug(sprintf('ExchangeRateConverter: Return DB rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d')));
- Cache::forever($key, $rate);
-
- return $rate;
+ Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount));
}
private function getCacheKey(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
@@ -152,6 +117,57 @@ class ExchangeRateConverter
return sprintf('cer-%d-%d-%s', $from->id, $to->id, $date->format('Y-m-d'));
}
+ /**
+ * @throws FireflyException
+ */
+ private function getEuroId(): int
+ {
+ Log::debug('getEuroId()');
+ $cache = new CacheProperties();
+ $cache->addProperty('cer-euro-id');
+ if ($cache->has()) {
+ return (int)$cache->get();
+ }
+ $euro = Amount::getTransactionCurrencyByCode('EUR');
+ ++$this->queryCount;
+ $cache->store($euro->id);
+
+ return $euro->id;
+ }
+
+ /**
+ * @throws FireflyException
+ */
+ private function getEuroRate(TransactionCurrency $currency, Carbon $date): string
+ {
+ $euroId = $this->getEuroId();
+ if ($euroId === $currency->id) {
+ return '1';
+ }
+ $rate = $this->getFromDB($currency->id, $euroId, $date->format('Y-m-d'));
+
+ if (null !== $rate) {
+ // app('log')->debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate));
+ return $rate;
+ }
+ $rate = $this->getFromDB($euroId, $currency->id, $date->format('Y-m-d'));
+ if (null !== $rate) {
+ return bcdiv('1', $rate);
+ // app('log')->debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate));
+ // return $rate;
+ }
+ // grab backup values from config file:
+ $backup = config(sprintf('cer.rates.%s', $currency->code));
+ if (null !== $backup) {
+ return bcdiv('1', (string)$backup);
+ // app('log')->debug(sprintf('Backup rate for %s to EUR is %s.', $currency->code, $backup));
+ // return $backup;
+ }
+
+ // app('log')->debug(sprintf('No rate for %s to EUR.', $currency->code));
+ return '0';
+ }
+
private function getFromDB(int $from, int $to, string $date): ?string
{
if ($from === $to) {
@@ -223,69 +239,53 @@ class ExchangeRateConverter
/**
* @throws FireflyException
*/
- private function getEuroRate(TransactionCurrency $currency, Carbon $date): string
+ private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{
- $euroId = $this->getEuroId();
- if ($euroId === $currency->id) {
- return '1';
- }
- $rate = $this->getFromDB($currency->id, $euroId, $date->format('Y-m-d'));
+ $key = $this->getCacheKey($from, $to, $date);
+ $res = Cache::get($key, null);
+ // find in cache
+ if (null !== $res) {
+ Log::debug(sprintf('ExchangeRateConverter: Return cached rate (%s) from %s to %s on %s.', $res, $from->code, $to->code, $date->format('Y-m-d')));
+
+ return $res;
+ }
+
+ // find in database
+ $rate = $this->getFromDB($from->id, $to->id, $date->format('Y-m-d'));
if (null !== $rate) {
- // app('log')->debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate));
+ Cache::forever($key, $rate);
+ Log::debug(sprintf('ExchangeRateConverter: Return DB rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d')));
+
return $rate;
}
- $rate = $this->getFromDB($euroId, $currency->id, $date->format('Y-m-d'));
+
+ // find reverse in database
+ $rate = $this->getFromDB($to->id, $from->id, $date->format('Y-m-d'));
if (null !== $rate) {
- return bcdiv('1', $rate);
- // app('log')->debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate));
- // return $rate;
- }
- // grab backup values from config file:
- $backup = config(sprintf('cer.rates.%s', $currency->code));
- if (null !== $backup) {
- return bcdiv('1', (string)$backup);
- // app('log')->debug(sprintf('Backup rate for %s to EUR is %s.', $currency->code, $backup));
- // return $backup;
+ $rate = bcdiv('1', $rate);
+ Cache::forever($key, $rate);
+ Log::debug(sprintf('ExchangeRateConverter: Return inverse DB rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d')));
+
+ return $rate;
}
- // app('log')->debug(sprintf('No rate for %s to EUR.', $currency->code));
- return '0';
- }
+ // fallback scenario.
+ $first = $this->getEuroRate($from, $date);
+ $second = $this->getEuroRate($to, $date);
- /**
- * @throws FireflyException
- */
- private function getEuroId(): int
- {
- Log::debug('getEuroId()');
- $cache = new CacheProperties();
- $cache->addProperty('cer-euro-id');
- if ($cache->has()) {
- return (int)$cache->get();
+ // combined (if present), they can be used to calculate the necessary conversion rate.
+ if (0 === bccomp('0', $first) || 0 === bccomp('0', $second)) {
+ Log::warning(sprintf('There is not enough information to convert %s to %s on date %s', $from->code, $to->code, $date->format('Y-m-d')));
+
+ return '1';
}
- $euro = Amount::getTransactionCurrencyByCode('EUR');
- ++$this->queryCount;
- $cache->store($euro->id);
- return $euro->id;
- }
+ $second = bcdiv('1', $second);
+ $rate = bcmul($first, $second);
+ Log::debug(sprintf('ExchangeRateConverter: Return DB rate from %s to %s on %s.', $from->code, $to->code, $date->format('Y-m-d')));
+ Cache::forever($key, $rate);
- public function setIgnoreSettings(bool $ignoreSettings): void
- {
- $this->ignoreSettings = $ignoreSettings;
- }
-
- public function setUserGroup(UserGroup $userGroup): void
- {
- $this->userGroup = $userGroup;
- }
-
- public function summarize(): void
- {
- if (false === $this->enabled()) {
- return;
- }
- Log::debug(sprintf('ExchangeRateConverter ran %d queries.', $this->queryCount));
+ return $rate;
}
}
diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php
index 7371b997bd..1dda34bf8d 100644
--- a/app/Support/Http/Controllers/PeriodOverview.php
+++ b/app/Support/Http/Controllers/PeriodOverview.php
@@ -38,6 +38,7 @@ use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Navigation;
+use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
@@ -72,6 +73,7 @@ trait PeriodOverview
protected AccountRepositoryInterface $accountRepository;
protected JournalRepositoryInterface $journalRepos;
protected PeriodStatisticRepositoryInterface $periodStatisticRepo;
+ private Collection $statistics;
/**
* This method returns "period entries", so nov-2015, dec-2015, etc. (this depends on the users session range)
@@ -82,30 +84,231 @@ trait PeriodOverview
*/
protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array
{
- Log::debug(sprintf('Now in getAccountPeriodOverview(#%d, %s %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
+ Log::debug(sprintf('Now in getAccountPeriodOverview(#%d, %s %s)', $account->id, $start->format('Y-m-d H:i:s.u'), $end->format('Y-m-d H:i:s.u')));
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->periodStatisticRepo = app(PeriodStatisticRepositoryInterface::class);
$range = Navigation::getViewRange(true);
[$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
+ $this->statistics = $this->periodStatisticRepo->allInRangeForModel($account, $start, $end);
+
+ // TODO needs to be re-arranged:
+ // get all period stats for entire range.
+ // loop blocks, an loop the types, and select the missing ones.
+ // create new ones, or use collected.
+
/** @var array $dates */
$dates = Navigation::blockPeriods($start, $end, $range);
$entries = [];
+ $types = ['spent', 'earned', 'transferred_in', 'transferred_away'];
Log::debug(sprintf('Count of loops: %d', count($dates)));
foreach ($dates as $currentDate) {
- $entries[] = $this->getSingleAccountPeriod($account, $currentDate['start'], $currentDate['end']);
+ $entries[] = $this->getSingleAccountPeriod($account, $currentDate['period'], $currentDate['start'], $currentDate['end']);
}
Log::debug('End of getAccountPeriodOverview()');
return $entries;
}
- protected function getSingleAccountPeriod(Account $account, Carbon $start, Carbon $end): array
+ /**
+ * Overview for single category. Has been refactored recently.
+ *
+ * @throws FireflyException
+ */
+ protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array
+ {
+ $range = Navigation::getViewRange(true);
+ [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
+
+ // properties for entries with their amounts.
+ $cache = new CacheProperties();
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty($range);
+ $cache->addProperty('category-show-period-entries');
+ $cache->addProperty($category->id);
+
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ /** @var array $dates */
+ $dates = Navigation::blockPeriods($start, $end, $range);
+ $entries = [];
+
+ // collect all expenses in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setCategory($category);
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
+ $earnedSet = $collector->getExtractedJournals();
+
+ // collect all income in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setCategory($category);
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
+ $spentSet = $collector->getExtractedJournals();
+
+ // collect all transfers in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setCategory($category);
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
+ $transferSet = $collector->getExtractedJournals();
+ foreach ($dates as $currentDate) {
+ $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
+ $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
+ $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
+ $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
+ $entries[]
+ = [
+ 'transactions' => 0,
+ 'title' => $title,
+ 'route' => route(
+ 'categories.show',
+ [$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
+ ),
+ 'total_transactions' => count($spent) + count($earned) + count($transferred),
+ 'spent' => $this->groupByCurrency($spent),
+ 'earned' => $this->groupByCurrency($earned),
+ 'transferred' => $this->groupByCurrency($transferred),
+ ];
+ }
+ $cache->store($entries);
+
+ return $entries;
+ }
+
+ /**
+ * Same as above, but for lists that involve transactions without a budget.
+ *
+ * This method has been refactored recently.
+ *
+ * @throws FireflyException
+ */
+ protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array
+ {
+ $range = Navigation::getViewRange(true);
+
+ [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
+
+ $cache = new CacheProperties();
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty($this->convertToPrimary);
+ $cache->addProperty('no-budget-period-entries');
+
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ /** @var array $dates */
+ $dates = Navigation::blockPeriods($start, $end, $range);
+ $entries = [];
+
+ // get all expenses without a budget.
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
+ $journals = $collector->getExtractedJournals();
+
+ foreach ($dates as $currentDate) {
+ $set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']);
+ $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
+ $entries[]
+ = [
+ 'title' => $title,
+ 'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
+ 'total_transactions' => count($set),
+ 'spent' => $this->groupByCurrency($set),
+ 'earned' => [],
+ 'transferred_away' => [],
+ 'transferred_in' => [],
+ ];
+ }
+ $cache->store($entries);
+
+ return $entries;
+ }
+
+ /**
+ * TODO fix the date.
+ *
+ * Show period overview for no category view.
+ *
+ * @throws FireflyException
+ */
+ protected function getNoCategoryPeriodOverview(Carbon $theDate): array
+ {
+ Log::debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
+ $range = Navigation::getViewRange(true);
+ $first = $this->journalRepos->firstNull();
+ $start = null === $first ? new Carbon() : $first->date;
+ $end = clone $theDate;
+ $end = Navigation::endOfPeriod($end, $range);
+
+ Log::debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
+ Log::debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
+
+ // properties for cache
+ $dates = Navigation::blockPeriods($start, $end, $range);
+ $entries = [];
+
+ // collect all expenses in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->withoutCategory();
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
+ $earnedSet = $collector->getExtractedJournals();
+
+ // collect all income in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->withoutCategory();
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
+ $spentSet = $collector->getExtractedJournals();
+
+ // collect all transfers in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->withoutCategory();
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
+ $transferSet = $collector->getExtractedJournals();
+
+ /** @var array $currentDate */
+ foreach ($dates as $currentDate) {
+ $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
+ $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
+ $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
+ $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
+ $entries[]
+ = [
+ 'title' => $title,
+ 'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
+ 'total_transactions' => count($spent) + count($earned) + count($transferred),
+ 'spent' => $this->groupByCurrency($spent),
+ 'earned' => $this->groupByCurrency($earned),
+ 'transferred' => $this->groupByCurrency($transferred),
+ ];
+ }
+ Log::debug('End of loops');
+
+ return $entries;
+ }
+
+ protected function getSingleAccountPeriod(Account $account, string $period, Carbon $start, Carbon $end): array
{
Log::debug(sprintf('Now in getSingleAccountPeriod(#%d, %s %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d')));
$types = ['spent', 'earned', 'transferred_in', 'transferred_away'];
$return = [
- 'title' => Navigation::periodShow($start, $end),
+ 'title' => Navigation::periodShow($start, $period),
'route' => route('accounts.show', [$account->id, $start->format('Y-m-d'), $start->format('Y-m-d')]),
'total_transactions' => 0,
];
@@ -119,13 +322,34 @@ trait PeriodOverview
return $return;
}
+ protected function filterStatistics(Carbon $start, Carbon $end, string $type): Collection
+ {
+ return $this->statistics->filter(
+ function (PeriodStatistic $statistic) use ($start, $end, $type) {
+ if(
+ !$statistic->end->equalTo($end)
+ && $statistic->end->format('Y-m-d H:i:s') === $end->format('Y-m-d H:i:s')
+ ) {
+ echo sprintf('End: "%s" vs "%s": %s', $statistic->end->toW3cString(), $end->toW3cString(), var_export($statistic->end->eq($end), true));
+ var_dump($statistic->end);
+ var_dump($end);
+ exit;
+ }
+
+
+ return $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type;
+ }
+ );
+ }
+
protected function getSingleAccountPeriodByType(Account $account, Carbon $start, Carbon $end, string $type): array
{
Log::debug(sprintf('Now in getSingleAccountPeriodByType(#%d, %s %s, %s)', $account->id, $start->format('Y-m-d'), $end->format('Y-m-d'), $type));
- $statistics = $this->periodStatisticRepo->findPeriodStatistic($account, $start, $end, $type);
+ $statistics = $this->filterStatistics($start, $end, $type);
// nothing found, regenerate them.
if (0 === $statistics->count()) {
+ Log::debug(sprintf('Found nothing in this period for type "%s"', $type));
$transactions = $this->accountRepository->periodCollection($account, $start, $end);
switch ($type) {
@@ -183,12 +407,195 @@ trait PeriodOverview
return $grouped;
}
+ /**
+ * This shows a period overview for a tag. It goes back in time and lists all relevant transactions and sums.
+ *
+ * @throws FireflyException
+ */
+ protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags.
+ {
+ $range = Navigation::getViewRange(true);
+ [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
+
+ // properties for cache
+ $cache = new CacheProperties();
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('tag-period-entries');
+ $cache->addProperty($tag->id);
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ /** @var array $dates */
+ $dates = Navigation::blockPeriods($start, $end, $range);
+ $entries = [];
+
+ // collect all expenses in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setTag($tag);
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
+ $earnedSet = $collector->getExtractedJournals();
+
+ // collect all income in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setTag($tag);
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
+ $spentSet = $collector->getExtractedJournals();
+
+ // collect all transfers in this period:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setTag($tag);
+ $collector->setRange($start, $end);
+ $collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
+ $transferSet = $collector->getExtractedJournals();
+
+ // filer all of them:
+ $earnedSet = $this->filterJournalsByTag($earnedSet, $tag);
+ $spentSet = $this->filterJournalsByTag($spentSet, $tag);
+ $transferSet = $this->filterJournalsByTag($transferSet, $tag);
+
+ foreach ($dates as $currentDate) {
+ $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
+ $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
+ $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
+ $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
+ $entries[]
+ = [
+ 'transactions' => 0,
+ 'title' => $title,
+ 'route' => route(
+ 'tags.show',
+ [$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
+ ),
+ 'total_transactions' => count($spent) + count($earned) + count($transferred),
+ 'spent' => $this->groupByCurrency($spent),
+ 'earned' => $this->groupByCurrency($earned),
+ 'transferred' => $this->groupByCurrency($transferred),
+ ];
+ }
+
+ return $entries;
+ }
+
+ /**
+ * @throws FireflyException
+ */
+ protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array
+ {
+ $range = Navigation::getViewRange(true);
+ $types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType));
+ [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
+
+ // properties for cache
+ $cache = new CacheProperties();
+ $cache->addProperty($start);
+ $cache->addProperty($end);
+ $cache->addProperty('transactions-period-entries');
+ $cache->addProperty($transactionType);
+ if ($cache->has()) {
+ return $cache->get();
+ }
+
+ /** @var array $dates */
+ $dates = Navigation::blockPeriods($start, $end, $range);
+ $entries = [];
+ $spent = [];
+ $earned = [];
+ $transferred = [];
+ // collect all journals in this period (regardless of type)
+ $collector = app(GroupCollectorInterface::class);
+ $collector->setTypes($types)->setRange($start, $end);
+ $genericSet = $collector->getExtractedJournals();
+ $loops = 0;
+
+ foreach ($dates as $currentDate) {
+ $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
+
+ if ($loops < 10) {
+ // set to correct array
+ if ('expenses' === $transactionType || 'withdrawal' === $transactionType) {
+ $spent = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
+ }
+ if ('revenue' === $transactionType || 'deposit' === $transactionType) {
+ $earned = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
+ }
+ if ('transfer' === $transactionType || 'transfers' === $transactionType) {
+ $transferred = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
+ }
+ }
+ $entries[]
+ = [
+ 'title' => $title,
+ 'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
+ 'total_transactions' => count($spent) + count($earned) + count($transferred),
+ 'spent' => $this->groupByCurrency($spent),
+ 'earned' => $this->groupByCurrency($earned),
+ 'transferred' => $this->groupByCurrency($transferred),
+ ];
+ ++$loops;
+ }
+
+ return $entries;
+ }
+
+ protected function saveGroupedAsStatistics(Account $account, Carbon $start, Carbon $end, string $type, array $array): void
+ {
+ unset($array['count']);
+ foreach ($array as $entry) {
+ $this->periodStatisticRepo->saveStatistic($account, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']);
+ }
+ }
+
+ /**
+ * Filter a list of journals by a set of dates, and then group them by currency.
+ */
+ private function filterJournalsByDate(array $array, Carbon $start, Carbon $end): array
+ {
+ $result = [];
+
+ /** @var array $journal */
+ foreach ($array as $journal) {
+ if ($journal['date'] <= $end && $journal['date'] >= $start) {
+ $result[] = $journal;
+ }
+ }
+
+ return $result;
+ }
+
+ private function filterJournalsByTag(array $set, Tag $tag): array
+ {
+ $return = [];
+ foreach ($set as $entry) {
+ $found = false;
+
+ /** @var array $localTag */
+ foreach ($entry['tags'] as $localTag) {
+ if ($localTag['id'] === $tag->id) {
+ $found = true;
+ }
+ }
+ if (false === $found) {
+ continue;
+ }
+ $return[] = $entry;
+ }
+
+ return $return;
+ }
+
private function filterTransactionsByType(TransactionTypeEnum $type, array $transactions, Carbon $start, Carbon $end): array
{
$result = [];
/**
- * @var int $index
+ * @var int $index
* @var array $item
*/
foreach ($transactions as $index => $item) {
@@ -203,12 +610,46 @@ trait PeriodOverview
return $result;
}
+ /**
+ * Return only transactions where $account is the source.
+ */
+ private function filterTransferredAway(Account $account, array $journals): array
+ {
+ $return = [];
+
+ /** @var array $journal */
+ foreach ($journals as $journal) {
+ if ($account->id === (int)$journal['source_account_id']) {
+ $return[] = $journal;
+ }
+ }
+
+ return $return;
+ }
+
+ /**
+ * Return only transactions where $account is the source.
+ */
+ private function filterTransferredIn(Account $account, array $journals): array
+ {
+ $return = [];
+
+ /** @var array $journal */
+ foreach ($journals as $journal) {
+ if ($account->id === (int)$journal['destination_account_id']) {
+ $return[] = $journal;
+ }
+ }
+
+ return $return;
+ }
+
private function filterTransfers(string $direction, array $transactions, Carbon $start, Carbon $end): array
{
$result = [];
/**
- * @var int $index
+ * @var int $index
* @var array $item
*/
foreach ($transactions as $index => $item) {
@@ -289,414 +730,4 @@ trait PeriodOverview
return $return;
}
-
- protected function saveGroupedAsStatistics(Account $account, Carbon $start, Carbon $end, string $type, array $array): void
- {
- unset($array['count']);
- foreach ($array as $entry) {
- $this->periodStatisticRepo->saveStatistic($account, $entry['currency_id'], $start, $end, $type, $entry['count'], $entry['amount']);
- }
- }
-
- /**
- * Overview for single category. Has been refactored recently.
- *
- * @throws FireflyException
- */
- protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array
- {
- $range = Navigation::getViewRange(true);
- [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
-
- // properties for entries with their amounts.
- $cache = new CacheProperties();
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty($range);
- $cache->addProperty('category-show-period-entries');
- $cache->addProperty($category->id);
-
- if ($cache->has()) {
- return $cache->get();
- }
-
- /** @var array $dates */
- $dates = Navigation::blockPeriods($start, $end, $range);
- $entries = [];
-
- // collect all expenses in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->setCategory($category);
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
- $earnedSet = $collector->getExtractedJournals();
-
- // collect all income in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->setCategory($category);
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
- $spentSet = $collector->getExtractedJournals();
-
- // collect all transfers in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->setCategory($category);
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
- $transferSet = $collector->getExtractedJournals();
- foreach ($dates as $currentDate) {
- $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
- $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
- $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
- $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
- $entries[]
- = [
- 'transactions' => 0,
- 'title' => $title,
- 'route' => route(
- 'categories.show',
- [$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
- ),
- 'total_transactions' => count($spent) + count($earned) + count($transferred),
- 'spent' => $this->groupByCurrency($spent),
- 'earned' => $this->groupByCurrency($earned),
- 'transferred' => $this->groupByCurrency($transferred),
- ];
- }
- $cache->store($entries);
-
- return $entries;
- }
-
- /**
- * Filter a list of journals by a set of dates, and then group them by currency.
- */
- private function filterJournalsByDate(array $array, Carbon $start, Carbon $end): array
- {
- $result = [];
-
- /** @var array $journal */
- foreach ($array as $journal) {
- if ($journal['date'] <= $end && $journal['date'] >= $start) {
- $result[] = $journal;
- }
- }
-
- return $result;
- }
-
- /**
- * Same as above, but for lists that involve transactions without a budget.
- *
- * This method has been refactored recently.
- *
- * @throws FireflyException
- */
- protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array
- {
- $range = Navigation::getViewRange(true);
-
- [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
-
- $cache = new CacheProperties();
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty($this->convertToPrimary);
- $cache->addProperty('no-budget-period-entries');
-
- if ($cache->has()) {
- return $cache->get();
- }
-
- /** @var array $dates */
- $dates = Navigation::blockPeriods($start, $end, $range);
- $entries = [];
-
- // get all expenses without a budget.
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
- $journals = $collector->getExtractedJournals();
-
- foreach ($dates as $currentDate) {
- $set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']);
- $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
- $entries[]
- = [
- 'title' => $title,
- 'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
- 'total_transactions' => count($set),
- 'spent' => $this->groupByCurrency($set),
- 'earned' => [],
- 'transferred_away' => [],
- 'transferred_in' => [],
- ];
- }
- $cache->store($entries);
-
- return $entries;
- }
-
- /**
- * TODO fix the date.
- *
- * Show period overview for no category view.
- *
- * @throws FireflyException
- */
- protected function getNoCategoryPeriodOverview(Carbon $theDate): array
- {
- app('log')->debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d')));
- $range = Navigation::getViewRange(true);
- $first = $this->journalRepos->firstNull();
- $start = null === $first ? new Carbon() : $first->date;
- $end = clone $theDate;
- $end = Navigation::endOfPeriod($end, $range);
-
- app('log')->debug(sprintf('Start for getNoCategoryPeriodOverview() is %s', $start->format('Y-m-d')));
- app('log')->debug(sprintf('End for getNoCategoryPeriodOverview() is %s', $end->format('Y-m-d')));
-
- // properties for cache
- $dates = Navigation::blockPeriods($start, $end, $range);
- $entries = [];
-
- // collect all expenses in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->withoutCategory();
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
- $earnedSet = $collector->getExtractedJournals();
-
- // collect all income in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->withoutCategory();
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
- $spentSet = $collector->getExtractedJournals();
-
- // collect all transfers in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->withoutCategory();
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
- $transferSet = $collector->getExtractedJournals();
-
- /** @var array $currentDate */
- foreach ($dates as $currentDate) {
- $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
- $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
- $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
- $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
- $entries[]
- = [
- 'title' => $title,
- 'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
- 'total_transactions' => count($spent) + count($earned) + count($transferred),
- 'spent' => $this->groupByCurrency($spent),
- 'earned' => $this->groupByCurrency($earned),
- 'transferred' => $this->groupByCurrency($transferred),
- ];
- }
- app('log')->debug('End of loops');
-
- return $entries;
- }
-
- /**
- * This shows a period overview for a tag. It goes back in time and lists all relevant transactions and sums.
- *
- * @throws FireflyException
- */
- protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags.
- {
- $range = Navigation::getViewRange(true);
- [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
-
- // properties for cache
- $cache = new CacheProperties();
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty('tag-period-entries');
- $cache->addProperty($tag->id);
- if ($cache->has()) {
- return $cache->get();
- }
-
- /** @var array $dates */
- $dates = Navigation::blockPeriods($start, $end, $range);
- $entries = [];
-
- // collect all expenses in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->setTag($tag);
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::DEPOSIT->value]);
- $earnedSet = $collector->getExtractedJournals();
-
- // collect all income in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->setTag($tag);
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::WITHDRAWAL->value]);
- $spentSet = $collector->getExtractedJournals();
-
- // collect all transfers in this period:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector->setTag($tag);
- $collector->setRange($start, $end);
- $collector->setTypes([TransactionTypeEnum::TRANSFER->value]);
- $transferSet = $collector->getExtractedJournals();
-
- // filer all of them:
- $earnedSet = $this->filterJournalsByTag($earnedSet, $tag);
- $spentSet = $this->filterJournalsByTag($spentSet, $tag);
- $transferSet = $this->filterJournalsByTag($transferSet, $tag);
-
- foreach ($dates as $currentDate) {
- $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']);
- $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']);
- $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']);
- $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
- $entries[]
- = [
- 'transactions' => 0,
- 'title' => $title,
- 'route' => route(
- 'tags.show',
- [$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]
- ),
- 'total_transactions' => count($spent) + count($earned) + count($transferred),
- 'spent' => $this->groupByCurrency($spent),
- 'earned' => $this->groupByCurrency($earned),
- 'transferred' => $this->groupByCurrency($transferred),
- ];
- }
-
- return $entries;
- }
-
- private function filterJournalsByTag(array $set, Tag $tag): array
- {
- $return = [];
- foreach ($set as $entry) {
- $found = false;
-
- /** @var array $localTag */
- foreach ($entry['tags'] as $localTag) {
- if ($localTag['id'] === $tag->id) {
- $found = true;
- }
- }
- if (false === $found) {
- continue;
- }
- $return[] = $entry;
- }
-
- return $return;
- }
-
- /**
- * @throws FireflyException
- */
- protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array
- {
- $range = Navigation::getViewRange(true);
- $types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType));
- [$start, $end] = $end < $start ? [$end, $start] : [$start, $end];
-
- // properties for cache
- $cache = new CacheProperties();
- $cache->addProperty($start);
- $cache->addProperty($end);
- $cache->addProperty('transactions-period-entries');
- $cache->addProperty($transactionType);
- if ($cache->has()) {
- return $cache->get();
- }
-
- /** @var array $dates */
- $dates = Navigation::blockPeriods($start, $end, $range);
- $entries = [];
- $spent = [];
- $earned = [];
- $transferred = [];
- // collect all journals in this period (regardless of type)
- $collector = app(GroupCollectorInterface::class);
- $collector->setTypes($types)->setRange($start, $end);
- $genericSet = $collector->getExtractedJournals();
- $loops = 0;
-
- foreach ($dates as $currentDate) {
- $title = Navigation::periodShow($currentDate['end'], $currentDate['period']);
-
- if ($loops < 10) {
- // set to correct array
- if ('expenses' === $transactionType || 'withdrawal' === $transactionType) {
- $spent = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
- }
- if ('revenue' === $transactionType || 'deposit' === $transactionType) {
- $earned = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
- }
- if ('transfer' === $transactionType || 'transfers' === $transactionType) {
- $transferred = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']);
- }
- }
- $entries[]
- = [
- 'title' => $title,
- 'route' => route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]),
- 'total_transactions' => count($spent) + count($earned) + count($transferred),
- 'spent' => $this->groupByCurrency($spent),
- 'earned' => $this->groupByCurrency($earned),
- 'transferred' => $this->groupByCurrency($transferred),
- ];
- ++$loops;
- }
-
- return $entries;
- }
-
- /**
- * Return only transactions where $account is the source.
- */
- private function filterTransferredAway(Account $account, array $journals): array
- {
- $return = [];
-
- /** @var array $journal */
- foreach ($journals as $journal) {
- if ($account->id === (int)$journal['source_account_id']) {
- $return[] = $journal;
- }
- }
-
- return $return;
- }
-
- /**
- * Return only transactions where $account is the source.
- */
- private function filterTransferredIn(Account $account, array $journals): array
- {
- $return = [];
-
- /** @var array $journal */
- foreach ($journals as $journal) {
- if ($account->id === (int)$journal['destination_account_id']) {
- $return[] = $journal;
- }
- }
-
- return $return;
- }
}
diff --git a/app/Support/Http/Controllers/RequestInformation.php b/app/Support/Http/Controllers/RequestInformation.php
index 73a39655e6..b7600f778a 100644
--- a/app/Support/Http/Controllers/RequestInformation.php
+++ b/app/Support/Http/Controllers/RequestInformation.php
@@ -53,6 +53,22 @@ trait RequestInformation
return $parts['host'] ?? '';
}
+ final protected function getPageName(): string // get request info
+ {
+ return str_replace('.', '_', RouteFacade::currentRouteName());
+ }
+
+ /**
+ * Get the specific name of a page for intro.
+ */
+ final protected function getSpecificPageName(): string // get request info
+ {
+ /** @var null|string $param */
+ $param = RouteFacade::current()->parameter('objectType');
+
+ return null === $param ? '' : sprintf('_%s', $param);
+ }
+
/**
* Get a list of triggers.
*/
@@ -102,22 +118,6 @@ trait RequestInformation
return $shownDemo;
}
- final protected function getPageName(): string // get request info
- {
- return str_replace('.', '_', RouteFacade::currentRouteName());
- }
-
- /**
- * Get the specific name of a page for intro.
- */
- final protected function getSpecificPageName(): string // get request info
- {
- /** @var null|string $param */
- $param = RouteFacade::current()->parameter('objectType');
-
- return null === $param ? '' : sprintf('_%s', $param);
- }
-
/**
* Check if date is outside session range.
*/
diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php
index 62dee47082..7dc3cb444c 100644
--- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php
@@ -53,29 +53,29 @@ use Override;
*/
class AccountEnrichment implements EnrichmentInterface
{
- private array $ids = [];
- private array $accountTypeIds = [];
- private array $accountTypes = [];
- private Collection $collection;
- private array $currencies = [];
- private array $locations = [];
- private array $meta = [];
- private readonly TransactionCurrency $primaryCurrency;
- private array $notes = [];
- private array $openingBalances = [];
- private User $user;
- private UserGroup $userGroup;
- private array $lastActivities = [];
- private ?Carbon $date = null;
- private ?Carbon $start = null;
- private ?Carbon $end = null;
+ private array $accountTypeIds = [];
+ private array $accountTypes = [];
+ private array $balances = [];
+ private Collection $collection;
private readonly bool $convertToPrimary;
- private array $balances = [];
- private array $startBalances = [];
- private array $endBalances = [];
- private array $objectGroups = [];
- private array $mappedObjects = [];
- private array $sort = [];
+ private array $currencies = [];
+ private ?Carbon $date = null;
+ private ?Carbon $end = null;
+ private array $endBalances = [];
+ private array $ids = [];
+ private array $lastActivities = [];
+ private array $locations = [];
+ private array $mappedObjects = [];
+ private array $meta = [];
+ private array $notes = [];
+ private array $objectGroups = [];
+ private array $openingBalances = [];
+ private readonly TransactionCurrency $primaryCurrency;
+ private array $sort = [];
+ private ?Carbon $start = null;
+ private array $startBalances = [];
+ private User $user;
+ private UserGroup $userGroup;
/**
* TODO The account enricher must do conversion from and to the primary currency.
@@ -86,16 +86,6 @@ class AccountEnrichment implements EnrichmentInterface
$this->convertToPrimary = Amount::convertToPrimary();
}
- #[Override]
- public function enrichSingle(array|Model $model): Account|array
- {
- Log::debug(__METHOD__);
- $collection = new Collection()->push($model);
- $collection = $this->enrich($collection);
-
- return $collection->first();
- }
-
#[Override]
/**
* Do the actual enrichment.
@@ -121,114 +111,47 @@ class AccountEnrichment implements EnrichmentInterface
return $this->collection;
}
- private function collectIds(): void
+ #[Override]
+ public function enrichSingle(array | Model $model): Account | array
{
- /** @var Account $account */
- foreach ($this->collection as $account) {
- $this->ids[] = (int)$account->id;
- $this->accountTypeIds[] = (int)$account->account_type_id;
- }
- $this->ids = array_unique($this->ids);
- $this->accountTypeIds = array_unique($this->accountTypeIds);
+ Log::debug(__METHOD__);
+ $collection = new Collection()->push($model);
+ $collection = $this->enrich($collection);
+
+ return $collection->first();
}
- private function getAccountTypes(): void
+ public function getDate(): Carbon
{
- $types = AccountType::whereIn('id', $this->accountTypeIds)->get();
-
- /** @var AccountType $type */
- foreach ($types as $type) {
- $this->accountTypes[(int)$type->id] = $type->type;
+ if (!$this->date instanceof Carbon) {
+ return now();
}
+
+ return $this->date;
}
- private function collectMetaData(): void
+ public function setDate(?Carbon $date): void
{
- $set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt'])
- ->whereIn('account_id', $this->ids)
- ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray()
- ;
-
- /** @var array $entry */
- foreach ($set as $entry) {
- $this->meta[(int)$entry['account_id']][$entry['name']] = (string)$entry['data'];
- if ('currency_id' === $entry['name']) {
- $this->currencies[(int)$entry['data']] = true;
- }
- }
- if (count($this->currencies) > 0) {
- $currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
- foreach ($currencies as $currency) {
- $this->currencies[(int)$currency->id] = $currency;
- }
- }
- $this->currencies[0] = $this->primaryCurrency;
- foreach ($this->currencies as $id => $currency) {
- if (true === $currency) {
- throw new FireflyException(sprintf('Currency #%d not found.', $id));
- }
+ if ($date instanceof Carbon) {
+ $date->endOfDay();
+ Log::debug(sprintf('Date is now %s', $date->toW3cString()));
}
+ $this->date = $date;
}
- private function collectNotes(): void
+ public function setEnd(?Carbon $end): void
{
- $notes = Note::query()->whereIn('noteable_id', $this->ids)
- ->whereNotNull('notes.text')
- ->where('notes.text', '!=', '')
- ->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
- ;
- foreach ($notes as $note) {
- $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
- }
- Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
+ $this->end = $end;
}
- private function collectLocations(): void
+ public function setSort(array $sort): void
{
- $locations = Location::query()->whereIn('locatable_id', $this->ids)
- ->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray()
- ;
- foreach ($locations as $location) {
- $this->locations[(int)$location['locatable_id']]
- = [
- 'latitude' => (float)$location['latitude'],
- 'longitude' => (float)$location['longitude'],
- 'zoom_level' => (int)$location['zoom_level'],
- ];
- }
- Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
+ $this->sort = $sort;
}
- private function collectOpeningBalances(): void
+ public function setStart(?Carbon $start): void
{
- // use new group collector:
- /** @var GroupCollectorInterface $collector */
- $collector = app(GroupCollectorInterface::class);
- $collector
- ->setUser($this->user)
- ->setUserGroup($this->userGroup)
- ->setAccounts($this->collection)
- ->withAccountInformation()
- ->setTypes([TransactionTypeEnum::OPENING_BALANCE->value])
- ;
- $journals = $collector->getExtractedJournals();
- foreach ($journals as $journal) {
- $this->openingBalances[(int)$journal['source_account_id']]
- = [
- 'amount' => Steam::negative($journal['amount']),
- 'date' => $journal['date'],
- ];
- $this->openingBalances[(int)$journal['destination_account_id']]
- = [
- 'amount' => Steam::positive($journal['amount']),
- 'date' => $journal['date'],
- ];
- }
- }
-
- public function setUserGroup(UserGroup $userGroup): void
- {
- $this->userGroup = $userGroup;
+ $this->start = $start;
}
public function setUser(User $user): void
@@ -237,12 +160,17 @@ class AccountEnrichment implements EnrichmentInterface
$this->userGroup = $user->userGroup;
}
+ public function setUserGroup(UserGroup $userGroup): void
+ {
+ $this->userGroup = $userGroup;
+ }
+
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (Account $item) {
- $id = (int)$item->id;
- $item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null;
- $meta = [
+ $id = (int)$item->id;
+ $item->full_account_type = $this->accountTypes[(int)$item->account_type_id] ?? null;
+ $meta = [
'currency' => null,
'location' => [
'latitude' => null,
@@ -289,30 +217,30 @@ class AccountEnrichment implements EnrichmentInterface
// add balances
// get currencies:
- $currency = $this->primaryCurrency; // assume primary currency
+ $currency = $this->primaryCurrency; // assume primary currency
if (null !== $meta['currency']) {
$currency = $meta['currency'];
}
// get the current balance:
- $date = $this->getDate();
+ $date = $this->getDate();
// $finalBalance = Steam::finalAccountBalance($item, $date, $this->primaryCurrency, $this->convertToPrimary);
- $finalBalance = $this->balances[$id];
- $balanceDifference = $this->getBalanceDifference($id, $currency);
+ $finalBalance = $this->balances[$id];
+ $balanceDifference = $this->getBalanceDifference($id, $currency);
Log::debug(sprintf('Call finalAccountBalance(%s) with date/time "%s"', var_export($this->convertToPrimary, true), $date->toIso8601String()), $finalBalance);
// collect current balances:
- $currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
- $openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places);
- $virtualBalance = Steam::bcround($item->virtual_balance ?? '0', $currency->decimal_places);
- $debtAmount = $meta['current_debt'] ?? null;
+ $currentBalance = Steam::bcround($finalBalance[$currency->code] ?? '0', $currency->decimal_places);
+ $openingBalance = Steam::bcround($meta['opening_balance_amount'] ?? '0', $currency->decimal_places);
+ $virtualBalance = Steam::bcround($item->virtual_balance ?? '0', $currency->decimal_places);
+ $debtAmount = $meta['current_debt'] ?? null;
// set some pc_ default values to NULL:
- $pcCurrentBalance = null;
- $pcOpeningBalance = null;
- $pcVirtualBalance = null;
- $pcDebtAmount = null;
- $pcBalanceDifference = null;
+ $pcCurrentBalance = null;
+ $pcOpeningBalance = null;
+ $pcVirtualBalance = null;
+ $pcDebtAmount = null;
+ $pcBalanceDifference = null;
// convert to primary currency if needed:
if ($this->convertToPrimary && $currency->id !== $this->primaryCurrency->id) {
@@ -351,17 +279,12 @@ class AccountEnrichment implements EnrichmentInterface
'pc_balance_difference' => $pcBalanceDifference,
];
// end add balances
- $item->meta = $meta;
+ $item->meta = $meta;
return $item;
});
}
- private function collectLastActivities(): void
- {
- $this->lastActivities = Steam::getLastActivities($this->ids);
- }
-
private function collectBalances(): void
{
$this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary);
@@ -371,15 +294,84 @@ class AccountEnrichment implements EnrichmentInterface
}
}
+ private function collectIds(): void
+ {
+ /** @var Account $account */
+ foreach ($this->collection as $account) {
+ $this->ids[] = (int)$account->id;
+ $this->accountTypeIds[] = (int)$account->account_type_id;
+ }
+ $this->ids = array_unique($this->ids);
+ $this->accountTypeIds = array_unique($this->accountTypeIds);
+ }
+
+ private function collectLastActivities(): void
+ {
+ $this->lastActivities = Steam::getLastActivities($this->ids);
+ }
+
+ private function collectLocations(): void
+ {
+ $locations = Location::query()->whereIn('locatable_id', $this->ids)
+ ->where('locatable_type', Account::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray();
+ foreach ($locations as $location) {
+ $this->locations[(int)$location['locatable_id']]
+ = [
+ 'latitude' => (float)$location['latitude'],
+ 'longitude' => (float)$location['longitude'],
+ 'zoom_level' => (int)$location['zoom_level'],
+ ];
+ }
+ Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
+ }
+
+ private function collectMetaData(): void
+ {
+ $set = AccountMeta::whereIn('name', ['is_multi_currency', 'include_net_worth', 'currency_id', 'account_role', 'account_number', 'BIC', 'liability_direction', 'interest', 'interest_period', 'current_debt'])
+ ->whereIn('account_id', $this->ids)
+ ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data'])->toArray();
+
+ /** @var array $entry */
+ foreach ($set as $entry) {
+ $this->meta[(int)$entry['account_id']][$entry['name']] = (string)$entry['data'];
+ if ('currency_id' === $entry['name']) {
+ $this->currencies[(int)$entry['data']] = true;
+ }
+ }
+ if (count($this->currencies) > 0) {
+ $currencies = TransactionCurrency::whereIn('id', array_keys($this->currencies))->get();
+ foreach ($currencies as $currency) {
+ $this->currencies[(int)$currency->id] = $currency;
+ }
+ }
+ $this->currencies[0] = $this->primaryCurrency;
+ foreach ($this->currencies as $id => $currency) {
+ if (true === $currency) {
+ throw new FireflyException(sprintf('Currency #%d not found.', $id));
+ }
+ }
+ }
+
+ private function collectNotes(): void
+ {
+ $notes = Note::query()->whereIn('noteable_id', $this->ids)
+ ->whereNotNull('notes.text')
+ ->where('notes.text', '!=', '')
+ ->where('noteable_type', Account::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
+ foreach ($notes as $note) {
+ $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
+ }
+ Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
+ }
+
private function collectObjectGroups(): void
{
- $set = DB::table('object_groupables')
- ->whereIn('object_groupable_id', $this->ids)
- ->where('object_groupable_type', Account::class)
- ->get(['object_groupable_id', 'object_group_id'])
- ;
+ $set = DB::table('object_groupables')
+ ->whereIn('object_groupable_id', $this->ids)
+ ->where('object_groupable_type', Account::class)
+ ->get(['object_groupable_id', 'object_group_id']);
- $ids = array_unique($set->pluck('object_group_id')->toArray());
+ $ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
@@ -393,32 +385,40 @@ class AccountEnrichment implements EnrichmentInterface
}
}
- public function setDate(?Carbon $date): void
+ private function collectOpeningBalances(): void
{
- if ($date instanceof Carbon) {
- $date->endOfDay();
- Log::debug(sprintf('Date is now %s', $date->toW3cString()));
+ // use new group collector:
+ /** @var GroupCollectorInterface $collector */
+ $collector = app(GroupCollectorInterface::class);
+ $collector
+ ->setUser($this->user)
+ ->setUserGroup($this->userGroup)
+ ->setAccounts($this->collection)
+ ->withAccountInformation()
+ ->setTypes([TransactionTypeEnum::OPENING_BALANCE->value]);
+ $journals = $collector->getExtractedJournals();
+ foreach ($journals as $journal) {
+ $this->openingBalances[(int)$journal['source_account_id']]
+ = [
+ 'amount' => Steam::negative($journal['amount']),
+ 'date' => $journal['date'],
+ ];
+ $this->openingBalances[(int)$journal['destination_account_id']]
+ = [
+ 'amount' => Steam::positive($journal['amount']),
+ 'date' => $journal['date'],
+ ];
}
- $this->date = $date;
}
- public function getDate(): Carbon
+ private function getAccountTypes(): void
{
- if (!$this->date instanceof Carbon) {
- return now();
+ $types = AccountType::whereIn('id', $this->accountTypeIds)->get();
+
+ /** @var AccountType $type */
+ foreach ($types as $type) {
+ $this->accountTypes[(int)$type->id] = $type->type;
}
-
- return $this->date;
- }
-
- public function setStart(?Carbon $start): void
- {
- $this->start = $start;
- }
-
- public function setEnd(?Carbon $end): void
- {
- $this->end = $end;
}
private function getBalanceDifference(int $id, TransactionCurrency $currency): ?string
@@ -431,17 +431,12 @@ class AccountEnrichment implements EnrichmentInterface
if (0 === count($startBalance) || 0 === count($endBalance)) {
return null;
}
- $start = $startBalance[$currency->code] ?? '0';
- $end = $endBalance[$currency->code] ?? '0';
+ $start = $startBalance[$currency->code] ?? '0';
+ $end = $endBalance[$currency->code] ?? '0';
return bcsub($end, $start);
}
- public function setSort(array $sort): void
- {
- $this->sort = $sort;
- }
-
private function sortData(): void
{
$dbParams = config('firefly.allowed_db_sort_parameters.Account', []);
@@ -458,7 +453,7 @@ class AccountEnrichment implements EnrichmentInterface
case 'current_balance':
case 'pc_current_balance':
- $this->collection = $this->collection->sortBy(static fn (Account $account) => $account->meta['balances'][$parameter[0]] ?? '0', SORT_NUMERIC, 'desc' === $parameter[1]);
+ $this->collection = $this->collection->sortBy(static fn(Account $account) => $account->meta['balances'][$parameter[0]] ?? '0', SORT_NUMERIC, 'desc' === $parameter[1]);
break;
}
diff --git a/app/Support/JsonApi/Enrichments/AvailableBudgetEnrichment.php b/app/Support/JsonApi/Enrichments/AvailableBudgetEnrichment.php
index 6154c941aa..85711c7efb 100644
--- a/app/Support/JsonApi/Enrichments/AvailableBudgetEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/AvailableBudgetEnrichment.php
@@ -40,20 +40,20 @@ use Override;
class AvailableBudgetEnrichment implements EnrichmentInterface
{
- private User $user; // @phpstan-ignore-line
- private UserGroup $userGroup; // @phpstan-ignore-line
- private readonly bool $convertToPrimary;
- private array $ids = [];
- private array $currencyIds = [];
+ private Collection $collection; // @phpstan-ignore-line
+ private readonly bool $convertToPrimary; // @phpstan-ignore-line
private array $currencies = [];
- private Collection $collection;
- private array $spentInBudgets = [];
- private array $spentOutsideBudgets = [];
- private array $pcSpentInBudgets = [];
- private array $pcSpentOutsideBudgets = [];
+ private array $currencyIds = [];
+ private array $ids = [];
private readonly NoBudgetRepositoryInterface $noBudgetRepository;
private readonly OperationsRepositoryInterface $opsRepository;
+ private array $pcSpentInBudgets = [];
+ private array $pcSpentOutsideBudgets = [];
private readonly BudgetRepositoryInterface $repository;
+ private array $spentInBudgets = [];
+ private array $spentOutsideBudgets = [];
+ private User $user;
+ private UserGroup $userGroup;
public function __construct()
{
@@ -79,7 +79,7 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
}
#[Override]
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -104,6 +104,34 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
$this->repository->setUserGroup($userGroup);
}
+ private function appendCollectedData(): void
+ {
+ $this->collection = $this->collection->map(function (AvailableBudget $item) {
+ $id = (int)$item->id;
+ $currencyId = $this->currencyIds[$id];
+ $currency = $this->currencies[$currencyId];
+ $meta = [
+ 'currency' => $currency,
+ 'spent_in_budgets' => $this->spentInBudgets[$id] ?? [],
+ 'pc_spent_in_budgets' => $this->pcSpentInBudgets[$id] ?? [],
+ 'spent_outside_budgets' => $this->spentOutsideBudgets[$id] ?? [],
+ 'pc_spent_outside_budgets' => $this->pcSpentOutsideBudgets[$id] ?? [],
+ ];
+ $item->meta = $meta;
+
+ return $item;
+ });
+ }
+
+ private function collectCurrencies(): void
+ {
+ $ids = array_unique(array_values($this->currencyIds));
+ $set = TransactionCurrency::whereIn('id', $ids)->get();
+ foreach ($set as $currency) {
+ $this->currencies[(int)$currency->id] = $currency;
+ }
+ }
+
private function collectIds(): void
{
/** @var AvailableBudget $availableBudget */
@@ -138,32 +166,4 @@ class AvailableBudgetEnrichment implements EnrichmentInterface
}
}
}
-
- private function appendCollectedData(): void
- {
- $this->collection = $this->collection->map(function (AvailableBudget $item) {
- $id = (int)$item->id;
- $currencyId = $this->currencyIds[$id];
- $currency = $this->currencies[$currencyId];
- $meta = [
- 'currency' => $currency,
- 'spent_in_budgets' => $this->spentInBudgets[$id] ?? [],
- 'pc_spent_in_budgets' => $this->pcSpentInBudgets[$id] ?? [],
- 'spent_outside_budgets' => $this->spentOutsideBudgets[$id] ?? [],
- 'pc_spent_outside_budgets' => $this->pcSpentOutsideBudgets[$id] ?? [],
- ];
- $item->meta = $meta;
-
- return $item;
- });
- }
-
- private function collectCurrencies(): void
- {
- $ids = array_unique(array_values($this->currencyIds));
- $set = TransactionCurrency::whereIn('id', $ids)->get();
- foreach ($set as $currency) {
- $this->currencies[(int)$currency->id] = $currency;
- }
- }
}
diff --git a/app/Support/JsonApi/Enrichments/BudgetEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetEnrichment.php
index 2aa21bc9cf..71e4ff160b 100644
--- a/app/Support/JsonApi/Enrichments/BudgetEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/BudgetEnrichment.php
@@ -40,19 +40,19 @@ use Illuminate\Support\Facades\Log;
class BudgetEnrichment implements EnrichmentInterface
{
- private Collection $collection;
- private User $user;
- private UserGroup $userGroup;
- private array $ids = [];
- private array $notes = [];
- private array $autoBudgets = [];
- private array $currencies = [];
- private ?Carbon $start = null;
- private ?Carbon $end = null;
- private array $spent = [];
- private array $pcSpent = [];
- private array $objectGroups = [];
- private array $mappedObjects = [];
+ private array $autoBudgets = [];
+ private Collection $collection;
+ private array $currencies = [];
+ private ?Carbon $end = null;
+ private array $ids = [];
+ private array $mappedObjects = [];
+ private array $notes = [];
+ private array $objectGroups = [];
+ private array $pcSpent = [];
+ private array $spent = [];
+ private ?Carbon $start = null;
+ private User $user;
+ private UserGroup $userGroup;
public function __construct() {}
@@ -70,7 +70,7 @@ class BudgetEnrichment implements EnrichmentInterface
return $this->collection;
}
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -79,6 +79,16 @@ class BudgetEnrichment implements EnrichmentInterface
return $collection->first();
}
+ public function setEnd(?Carbon $end): void
+ {
+ $this->end = $end;
+ }
+
+ public function setStart(?Carbon $start): void
+ {
+ $this->start = $start;
+ }
+
public function setUser(User $user): void
{
$this->user = $user;
@@ -90,33 +100,11 @@ class BudgetEnrichment implements EnrichmentInterface
$this->userGroup = $userGroup;
}
- private function collectIds(): void
- {
- /** @var Budget $budget */
- foreach ($this->collection as $budget) {
- $this->ids[] = (int)$budget->id;
- }
- $this->ids = array_unique($this->ids);
- }
-
- private function collectNotes(): void
- {
- $notes = Note::query()->whereIn('noteable_id', $this->ids)
- ->whereNotNull('notes.text')
- ->where('notes.text', '!=', '')
- ->where('noteable_type', Budget::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
- ;
- foreach ($notes as $note) {
- $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
- }
- Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
- }
-
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (Budget $item) {
- $id = (int)$item->id;
- $meta = [
+ $id = (int)$item->id;
+ $meta = [
'object_group_id' => null,
'object_group_order' => null,
'object_group_title' => null,
@@ -130,7 +118,7 @@ class BudgetEnrichment implements EnrichmentInterface
// add object group if available
if (array_key_exists($id, $this->mappedObjects)) {
$key = $this->mappedObjects[$id];
- $meta['object_group_id'] = (string) $this->objectGroups[$key]['id'];
+ $meta['object_group_id'] = (string)$this->objectGroups[$key]['id'];
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
}
@@ -168,7 +156,7 @@ class BudgetEnrichment implements EnrichmentInterface
$opsRepository->setUserGroup($this->userGroup);
// $spent = $this->beautify();
// $set = $this->opsRepository->sumExpenses($start, $end, null, new Collection()->push($budget))
- $expenses = $opsRepository->collectExpenses($this->start, $this->end, null, $this->collection, null);
+ $expenses = $opsRepository->collectExpenses($this->start, $this->end, null, $this->collection, null);
foreach ($this->collection as $item) {
$id = (int)$item->id;
$this->spent[$id] = array_values($opsRepository->sumCollectedExpensesByBudget($expenses, $item, false));
@@ -177,25 +165,35 @@ class BudgetEnrichment implements EnrichmentInterface
}
}
- public function setEnd(?Carbon $end): void
+ private function collectIds(): void
{
- $this->end = $end;
+ /** @var Budget $budget */
+ foreach ($this->collection as $budget) {
+ $this->ids[] = (int)$budget->id;
+ }
+ $this->ids = array_unique($this->ids);
}
- public function setStart(?Carbon $start): void
+ private function collectNotes(): void
{
- $this->start = $start;
+ $notes = Note::query()->whereIn('noteable_id', $this->ids)
+ ->whereNotNull('notes.text')
+ ->where('notes.text', '!=', '')
+ ->where('noteable_type', Budget::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
+ foreach ($notes as $note) {
+ $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
+ }
+ Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void
{
- $set = DB::table('object_groupables')
- ->whereIn('object_groupable_id', $this->ids)
- ->where('object_groupable_type', Budget::class)
- ->get(['object_groupable_id', 'object_group_id'])
- ;
+ $set = DB::table('object_groupables')
+ ->whereIn('object_groupable_id', $this->ids)
+ ->where('object_groupable_type', Budget::class)
+ ->get(['object_groupable_id', 'object_group_id']);
- $ids = array_unique($set->pluck('object_group_id')->toArray());
+ $ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
diff --git a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php
index 0ba9e1f1a3..f0a7fd3479 100644
--- a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php
@@ -40,19 +40,19 @@ use Illuminate\Support\Facades\Log;
class BudgetLimitEnrichment implements EnrichmentInterface
{
- private User $user;
- private UserGroup $userGroup; // @phpstan-ignore-line
private Collection $collection;
- private array $ids = [];
- private array $notes = [];
- private Carbon $start;
+ private bool $convertToPrimary = true; // @phpstan-ignore-line
+ private array $currencies = [];
+ private array $currencyIds = [];
private Carbon $end;
private array $expenses = [];
+ private array $ids = [];
+ private array $notes = [];
private array $pcExpenses = [];
- private array $currencyIds = [];
- private array $currencies = [];
- private bool $convertToPrimary = true;
private readonly TransactionCurrency $primaryCurrency;
+ private Carbon $start;
+ private User $user;
+ private UserGroup $userGroup;
public function __construct()
{
@@ -73,7 +73,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface
return $this->collection;
}
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -93,36 +93,6 @@ class BudgetLimitEnrichment implements EnrichmentInterface
$this->userGroup = $userGroup;
}
- private function collectIds(): void
- {
- $this->start = $this->collection->min('start_date') ?? Carbon::now()->startOfMonth();
- $this->end = $this->collection->max('end_date') ?? Carbon::now()->endOfMonth();
-
- /** @var BudgetLimit $limit */
- foreach ($this->collection as $limit) {
- $id = (int)$limit->id;
- $this->ids[] = $id;
- if (0 !== (int)$limit->transaction_currency_id) {
- $this->currencyIds[$id] = (int)$limit->transaction_currency_id;
- }
- }
- $this->ids = array_unique($this->ids);
- $this->currencyIds = array_unique($this->currencyIds);
- }
-
- private function collectNotes(): void
- {
- $notes = Note::query()->whereIn('noteable_id', $this->ids)
- ->whereNotNull('notes.text')
- ->where('notes.text', '!=', '')
- ->where('noteable_type', BudgetLimit::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
- ;
- foreach ($notes as $note) {
- $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
- }
- Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
- }
-
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (BudgetLimit $item) {
@@ -145,12 +115,12 @@ class BudgetLimitEnrichment implements EnrichmentInterface
private function collectBudgets(): void
{
- $budgetIds = $this->collection->pluck('budget_id')->unique()->toArray();
- $budgets = Budget::whereIn('id', $budgetIds)->get();
+ $budgetIds = $this->collection->pluck('budget_id')->unique()->toArray();
+ $budgets = Budget::whereIn('id', $budgetIds)->get();
$repository = app(OperationsRepository::class);
$repository->setUser($this->user);
- $expenses = $repository->collectExpenses($this->start, $this->end, null, $budgets, null);
+ $expenses = $repository->collectExpenses($this->start, $this->end, null, $budgets, null);
/** @var BudgetLimit $budgetLimit */
foreach ($this->collection as $budgetLimit) {
@@ -179,26 +149,55 @@ class BudgetLimitEnrichment implements EnrichmentInterface
}
}
- private function stringifyIds(): void
+ private function collectIds(): void
{
- $this->expenses = array_map(fn ($first) => array_map(function ($second) {
- $second['currency_id'] = (string)($second['currency_id'] ?? 0);
+ $this->start = $this->collection->min('start_date') ?? Carbon::now()->startOfMonth();
+ $this->end = $this->collection->max('end_date') ?? Carbon::now()->endOfMonth();
- return $second;
- }, $first), $this->expenses);
+ /** @var BudgetLimit $limit */
+ foreach ($this->collection as $limit) {
+ $id = (int)$limit->id;
+ $this->ids[] = $id;
+ if (0 !== (int)$limit->transaction_currency_id) {
+ $this->currencyIds[$id] = (int)$limit->transaction_currency_id;
+ }
+ }
+ $this->ids = array_unique($this->ids);
+ $this->currencyIds = array_unique($this->currencyIds);
+ }
- $this->pcExpenses = array_map(fn ($first) => array_map(function ($second) {
- $second['currency_id'] = (string)($second['currency_id'] ?? 0);
-
- return $second;
- }, $first), $this->expenses);
+ private function collectNotes(): void
+ {
+ $notes = Note::query()->whereIn('noteable_id', $this->ids)
+ ->whereNotNull('notes.text')
+ ->where('notes.text', '!=', '')
+ ->where('noteable_type', BudgetLimit::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
+ foreach ($notes as $note) {
+ $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
+ }
+ Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function filterToBudget(array $expenses, int $budget): array
{
- $result = array_filter($expenses, fn (array $item) => (int)$item['budget_id'] === $budget);
+ $result = array_filter($expenses, fn(array $item) => (int)$item['budget_id'] === $budget);
Log::debug(sprintf('filterToBudget for budget #%d, from %d to %d items', $budget, count($expenses), count($result)));
return $result;
}
+
+ private function stringifyIds(): void
+ {
+ $this->expenses = array_map(fn($first) => array_map(function ($second) {
+ $second['currency_id'] = (string)($second['currency_id'] ?? 0);
+
+ return $second;
+ }, $first), $this->expenses);
+
+ $this->pcExpenses = array_map(fn($first) => array_map(function ($second) {
+ $second['currency_id'] = (string)($second['currency_id'] ?? 0);
+
+ return $second;
+ }, $first), $this->expenses);
+ }
}
diff --git a/app/Support/JsonApi/Enrichments/CategoryEnrichment.php b/app/Support/JsonApi/Enrichments/CategoryEnrichment.php
index bbe24f94c2..074747011b 100644
--- a/app/Support/JsonApi/Enrichments/CategoryEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/CategoryEnrichment.php
@@ -38,18 +38,18 @@ use Illuminate\Support\Facades\Log;
class CategoryEnrichment implements EnrichmentInterface
{
private Collection $collection;
- private User $user;
- private UserGroup $userGroup;
+ private array $earned = [];
+ private ?Carbon $end = null;
private array $ids = [];
private array $notes = [];
- private ?Carbon $start = null;
- private ?Carbon $end = null;
- private array $spent = [];
- private array $pcSpent = [];
- private array $earned = [];
private array $pcEarned = [];
- private array $transfers = [];
+ private array $pcSpent = [];
private array $pcTransfers = [];
+ private array $spent = [];
+ private ?Carbon $start = null;
+ private array $transfers = [];
+ private User $user;
+ private UserGroup $userGroup;
public function enrich(Collection $collection): Collection
{
@@ -62,7 +62,7 @@ class CategoryEnrichment implements EnrichmentInterface
return $collection;
}
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -71,6 +71,16 @@ class CategoryEnrichment implements EnrichmentInterface
return $collection->first();
}
+ public function setEnd(?Carbon $end): void
+ {
+ $this->end = $end;
+ }
+
+ public function setStart(?Carbon $start): void
+ {
+ $this->start = $start;
+ }
+
public function setUser(User $user): void
{
$this->user = $user;
@@ -82,15 +92,6 @@ class CategoryEnrichment implements EnrichmentInterface
$this->userGroup = $userGroup;
}
- private function collectIds(): void
- {
- /** @var Category $category */
- foreach ($this->collection as $category) {
- $this->ids[] = (int)$category->id;
- }
- $this->ids = array_unique($this->ids);
- }
-
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (Category $item) {
@@ -110,23 +111,21 @@ class CategoryEnrichment implements EnrichmentInterface
});
}
- public function setEnd(?Carbon $end): void
+ private function collectIds(): void
{
- $this->end = $end;
- }
-
- public function setStart(?Carbon $start): void
- {
- $this->start = $start;
+ /** @var Category $category */
+ foreach ($this->collection as $category) {
+ $this->ids[] = (int)$category->id;
+ }
+ $this->ids = array_unique($this->ids);
}
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->ids)
- ->whereNotNull('notes.text')
- ->where('notes.text', '!=', '')
- ->where('noteable_type', Category::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
- ;
+ ->whereNotNull('notes.text')
+ ->where('notes.text', '!=', '')
+ ->where('noteable_type', Category::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
}
@@ -140,9 +139,9 @@ class CategoryEnrichment implements EnrichmentInterface
$opsRepository = app(OperationsRepositoryInterface::class);
$opsRepository->setUser($this->user);
$opsRepository->setUserGroup($this->userGroup);
- $expenses = $opsRepository->collectExpenses($this->start, $this->end, null, $this->collection);
- $income = $opsRepository->collectIncome($this->start, $this->end, null, $this->collection);
- $transfers = $opsRepository->collectTransfers($this->start, $this->end, null, $this->collection);
+ $expenses = $opsRepository->collectExpenses($this->start, $this->end, null, $this->collection);
+ $income = $opsRepository->collectIncome($this->start, $this->end, null, $this->collection);
+ $transfers = $opsRepository->collectTransfers($this->start, $this->end, null, $this->collection);
foreach ($this->collection as $item) {
$id = (int)$item->id;
$this->spent[$id] = array_values($opsRepository->sumCollectedTransactionsByCategory($expenses, $item, 'negative', false));
diff --git a/app/Support/JsonApi/Enrichments/EnrichmentInterface.php b/app/Support/JsonApi/Enrichments/EnrichmentInterface.php
index 0ccfb7c060..e93ddcf283 100644
--- a/app/Support/JsonApi/Enrichments/EnrichmentInterface.php
+++ b/app/Support/JsonApi/Enrichments/EnrichmentInterface.php
@@ -33,7 +33,7 @@ interface EnrichmentInterface
{
public function enrich(Collection $collection): Collection;
- public function enrichSingle(array|Model $model): array|Model;
+ public function enrichSingle(array | Model $model): array | Model;
public function setUser(User $user): void;
diff --git a/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php b/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php
index bacf8d12c3..aebdde8f67 100644
--- a/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/PiggyBankEnrichment.php
@@ -43,20 +43,20 @@ use Illuminate\Support\Facades\Log;
class PiggyBankEnrichment implements EnrichmentInterface
{
- private User $user; // @phpstan-ignore-line
- private UserGroup $userGroup; // @phpstan-ignore-line
- private Collection $collection;
- private array $ids = [];
- private array $currencyIds = [];
- private array $currencies = [];
- private array $accountIds = [];
+ private array $accountIds = []; // @phpstan-ignore-line
+ private array $accounts = []; // @phpstan-ignore-line
+ private array $amounts = [];
+ private Collection $collection;
+ private array $currencies = [];
+ private array $currencyIds = [];
+ private array $ids = [];
// private array $accountCurrencies = [];
- private array $notes = [];
- private array $mappedObjects = [];
+ private array $mappedObjects = [];
+ private array $notes = [];
+ private array $objectGroups = [];
private readonly TransactionCurrency $primaryCurrency;
- private array $amounts = [];
- private array $accounts = [];
- private array $objectGroups = [];
+ private User $user;
+ private UserGroup $userGroup;
public function __construct()
{
@@ -77,7 +77,7 @@ class PiggyBankEnrichment implements EnrichmentInterface
return $this->collection;
}
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -97,80 +97,17 @@ class PiggyBankEnrichment implements EnrichmentInterface
$this->userGroup = $userGroup;
}
- private function collectIds(): void
- {
- /** @var PiggyBank $piggy */
- foreach ($this->collection as $piggy) {
- $id = (int)$piggy->id;
- $this->ids[] = $id;
- $this->currencyIds[$id] = (int)$piggy->transaction_currency_id;
- }
- $this->ids = array_unique($this->ids);
-
- // collect currencies.
- $currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->get();
- foreach ($currencies as $currency) {
- $this->currencies[(int)$currency->id] = $currency;
- }
-
- // collect accounts
- $set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->ids)->get(['piggy_bank_id', 'account_id', 'current_amount', 'native_current_amount']);
- foreach ($set as $item) {
- $id = (int)$item->piggy_bank_id;
- $accountId = (int)$item->account_id;
- $this->amounts[$id] ??= [];
- if (!array_key_exists($id, $this->accountIds)) {
- $this->accountIds[$id] = (int)$item->account_id;
- }
- if (!array_key_exists($accountId, $this->amounts[$id])) {
- $this->amounts[$id][$accountId] = [
- 'current_amount' => '0',
- 'pc_current_amount' => '0',
- ];
- }
- $this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], (string) $item->current_amount);
- if (null !== $this->amounts[$id][$accountId]['pc_current_amount'] && null !== $item->native_current_amount) {
- $this->amounts[$id][$accountId]['pc_current_amount'] = bcadd($this->amounts[$id][$accountId]['pc_current_amount'], (string) $item->native_current_amount);
- }
- }
-
- // get account currency preference for ALL.
- $set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
-
- /** @var AccountMeta $item */
- foreach ($set as $item) {
- $accountId = (int)$item->account_id;
- $currencyId = (int)$item->data;
- if (!array_key_exists($currencyId, $this->currencies)) {
- $this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
- }
- // $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
- }
-
- // get account info.
- $set = Account::whereIn('id', array_values($this->accountIds))->get();
-
- /** @var Account $item */
- foreach ($set as $item) {
- $id = (int)$item->id;
- $this->accounts[$id] = [
- 'id' => $id,
- 'name' => $item->name,
- ];
- }
- }
-
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (PiggyBank $item) {
- $id = (int)$item->id;
- $currencyId = (int)$item->transaction_currency_id;
- $currency = $this->currencies[$currencyId] ?? $this->primaryCurrency;
- $targetAmount = null;
+ $id = (int)$item->id;
+ $currencyId = (int)$item->transaction_currency_id;
+ $currency = $this->currencies[$currencyId] ?? $this->primaryCurrency;
+ $targetAmount = null;
if (0 !== bccomp($item->target_amount, '0')) {
$targetAmount = $item->target_amount;
}
- $meta = [
+ $meta = [
'notes' => $this->notes[$id] ?? null,
'currency' => $this->currencies[$currencyId] ?? null,
// 'auto_budget' => $this->autoBudgets[$id] ?? null,
@@ -193,23 +130,23 @@ class PiggyBankEnrichment implements EnrichmentInterface
// add object group if available
if (array_key_exists($id, $this->mappedObjects)) {
$key = $this->mappedObjects[$id];
- $meta['object_group_id'] = (string) $this->objectGroups[$key]['id'];
+ $meta['object_group_id'] = (string)$this->objectGroups[$key]['id'];
$meta['object_group_title'] = $this->objectGroups[$key]['title'];
$meta['object_group_order'] = $this->objectGroups[$key]['order'];
}
// add current amount(s).
foreach ($this->amounts[$id] as $accountId => $row) {
- $meta['accounts'][] = [
+ $meta['accounts'][] = [
'account_id' => (string)$accountId,
'name' => $this->accounts[$accountId]['name'] ?? '',
'current_amount' => Steam::bcround($row['current_amount'], $currency->decimal_places),
'pc_current_amount' => Steam::bcround($row['pc_current_amount'], $this->primaryCurrency->decimal_places),
];
- $meta['current_amount'] = bcadd($meta['current_amount'], $row['current_amount']);
+ $meta['current_amount'] = bcadd($meta['current_amount'], $row['current_amount']);
// only add pc_current_amount when the pc_current_amount is set
$meta['pc_current_amount'] = null === $row['pc_current_amount'] ? null : bcadd($meta['pc_current_amount'], $row['pc_current_amount']);
}
- $meta['current_amount'] = Steam::bcround($meta['current_amount'], $currency->decimal_places);
+ $meta['current_amount'] = Steam::bcround($meta['current_amount'], $currency->decimal_places);
// only round this number when pc_current_amount is set.
$meta['pc_current_amount'] = null === $meta['pc_current_amount'] ? null : Steam::bcround($meta['pc_current_amount'], $this->primaryCurrency->decimal_places);
@@ -223,19 +160,83 @@ class PiggyBankEnrichment implements EnrichmentInterface
$meta['save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['target_amount'], $meta['current_amount']), $currency->decimal_places);
$meta['pc_save_per_month'] = Steam::bcround($this->getSuggestedMonthlyAmount($item->start_date, $item->target_date, $meta['pc_target_amount'], $meta['pc_current_amount']), $currency->decimal_places);
- $item->meta = $meta;
+ $item->meta = $meta;
return $item;
});
}
+ private function collectCurrentAmounts(): void {}
+
+ private function collectIds(): void
+ {
+ /** @var PiggyBank $piggy */
+ foreach ($this->collection as $piggy) {
+ $id = (int)$piggy->id;
+ $this->ids[] = $id;
+ $this->currencyIds[$id] = (int)$piggy->transaction_currency_id;
+ }
+ $this->ids = array_unique($this->ids);
+
+ // collect currencies.
+ $currencies = TransactionCurrency::whereIn('id', $this->currencyIds)->get();
+ foreach ($currencies as $currency) {
+ $this->currencies[(int)$currency->id] = $currency;
+ }
+
+ // collect accounts
+ $set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->ids)->get(['piggy_bank_id', 'account_id', 'current_amount', 'native_current_amount']);
+ foreach ($set as $item) {
+ $id = (int)$item->piggy_bank_id;
+ $accountId = (int)$item->account_id;
+ $this->amounts[$id] ??= [];
+ if (!array_key_exists($id, $this->accountIds)) {
+ $this->accountIds[$id] = (int)$item->account_id;
+ }
+ if (!array_key_exists($accountId, $this->amounts[$id])) {
+ $this->amounts[$id][$accountId] = [
+ 'current_amount' => '0',
+ 'pc_current_amount' => '0',
+ ];
+ }
+ $this->amounts[$id][$accountId]['current_amount'] = bcadd($this->amounts[$id][$accountId]['current_amount'], (string)$item->current_amount);
+ if (null !== $this->amounts[$id][$accountId]['pc_current_amount'] && null !== $item->native_current_amount) {
+ $this->amounts[$id][$accountId]['pc_current_amount'] = bcadd($this->amounts[$id][$accountId]['pc_current_amount'], (string)$item->native_current_amount);
+ }
+ }
+
+ // get account currency preference for ALL.
+ $set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
+
+ /** @var AccountMeta $item */
+ foreach ($set as $item) {
+ $accountId = (int)$item->account_id;
+ $currencyId = (int)$item->data;
+ if (!array_key_exists($currencyId, $this->currencies)) {
+ $this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
+ }
+ // $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
+ }
+
+ // get account info.
+ $set = Account::whereIn('id', array_values($this->accountIds))->get();
+
+ /** @var Account $item */
+ foreach ($set as $item) {
+ $id = (int)$item->id;
+ $this->accounts[$id] = [
+ 'id' => $id,
+ 'name' => $item->name,
+ ];
+ }
+ }
+
private function collectNotes(): void
{
$notes = Note::query()->whereIn('noteable_id', $this->ids)
- ->whereNotNull('notes.text')
- ->where('notes.text', '!=', '')
- ->where('noteable_type', PiggyBank::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
- ;
+ ->whereNotNull('notes.text')
+ ->where('notes.text', '!=', '')
+ ->where('noteable_type', PiggyBank::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
foreach ($notes as $note) {
$this->notes[(int)$note['noteable_id']] = (string)$note['text'];
}
@@ -244,13 +245,12 @@ class PiggyBankEnrichment implements EnrichmentInterface
private function collectObjectGroups(): void
{
- $set = DB::table('object_groupables')
- ->whereIn('object_groupable_id', $this->ids)
- ->where('object_groupable_type', PiggyBank::class)
- ->get(['object_groupable_id', 'object_group_id'])
- ;
+ $set = DB::table('object_groupables')
+ ->whereIn('object_groupable_id', $this->ids)
+ ->where('object_groupable_type', PiggyBank::class)
+ ->get(['object_groupable_id', 'object_group_id']);
- $ids = array_unique($set->pluck('object_group_id')->toArray());
+ $ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
@@ -264,8 +264,6 @@ class PiggyBankEnrichment implements EnrichmentInterface
}
}
- private function collectCurrentAmounts(): void {}
-
/**
* Returns the suggested amount the user should save per month, or "".
*/
diff --git a/app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php b/app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php
index fad6293f90..8758dfe94d 100644
--- a/app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/PiggyBankEventEnrichment.php
@@ -38,16 +38,16 @@ use Illuminate\Support\Facades\Log;
class PiggyBankEventEnrichment implements EnrichmentInterface
{
- private User $user; // @phpstan-ignore-line
- private UserGroup $userGroup; // @phpstan-ignore-line
+ private array $accountCurrencies = []; // @phpstan-ignore-line
+ private array $accountIds = []; // @phpstan-ignore-line
private Collection $collection;
+ private array $currencies = [];
+ private array $groupIds = [];
private array $ids = [];
private array $journalIds = [];
- private array $groupIds = [];
- private array $accountIds = [];
private array $piggyBankIds = [];
- private array $accountCurrencies = [];
- private array $currencies = [];
+ private User $user;
+ private UserGroup $userGroup;
// private bool $convertToPrimary = false;
// private TransactionCurrency $primaryCurrency;
@@ -66,7 +66,7 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
return $this->collection;
}
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -86,53 +86,13 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
$this->userGroup = $userGroup;
}
- private function collectIds(): void
- {
- /** @var PiggyBankEvent $event */
- foreach ($this->collection as $event) {
- $this->ids[] = (int)$event->id;
- $this->journalIds[(int)$event->id] = (int)$event->transaction_journal_id;
- $this->piggyBankIds[(int)$event->id] = (int)$event->piggy_bank_id;
- }
- $this->ids = array_unique($this->ids);
- // collect groups with journal info.
- $set = TransactionJournal::whereIn('id', $this->journalIds)->get(['id', 'transaction_group_id']);
-
- /** @var TransactionJournal $item */
- foreach ($set as $item) {
- $this->groupIds[(int)$item->id] = (int)$item->transaction_group_id;
- }
-
- // collect account info.
- $set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->piggyBankIds)->get(['piggy_bank_id', 'account_id']);
- foreach ($set as $item) {
- $id = (int)$item->piggy_bank_id;
- if (!array_key_exists($id, $this->accountIds)) {
- $this->accountIds[$id] = (int)$item->account_id;
- }
- }
-
- // get account currency preference for ALL.
- $set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
-
- /** @var AccountMeta $item */
- foreach ($set as $item) {
- $accountId = (int)$item->account_id;
- $currencyId = (int)$item->data;
- if (!array_key_exists($currencyId, $this->currencies)) {
- $this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
- }
- $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
- }
- }
-
private function appendCollectedData(): void
{
$this->collection = $this->collection->map(function (PiggyBankEvent $item) {
- $id = (int)$item->id;
- $piggyId = (int)$item->piggy_bank_id;
- $journalId = (int)$item->transaction_journal_id;
- $currency = null;
+ $id = (int)$item->id;
+ $piggyId = (int)$item->piggy_bank_id;
+ $journalId = (int)$item->transaction_journal_id;
+ $currency = null;
if (array_key_exists($piggyId, $this->accountIds)) {
$accountId = $this->accountIds[$piggyId];
if (array_key_exists($accountId, $this->accountCurrencies)) {
@@ -149,4 +109,44 @@ class PiggyBankEventEnrichment implements EnrichmentInterface
});
}
+
+ private function collectIds(): void
+ {
+ /** @var PiggyBankEvent $event */
+ foreach ($this->collection as $event) {
+ $this->ids[] = (int)$event->id;
+ $this->journalIds[(int)$event->id] = (int)$event->transaction_journal_id;
+ $this->piggyBankIds[(int)$event->id] = (int)$event->piggy_bank_id;
+ }
+ $this->ids = array_unique($this->ids);
+ // collect groups with journal info.
+ $set = TransactionJournal::whereIn('id', $this->journalIds)->get(['id', 'transaction_group_id']);
+
+ /** @var TransactionJournal $item */
+ foreach ($set as $item) {
+ $this->groupIds[(int)$item->id] = (int)$item->transaction_group_id;
+ }
+
+ // collect account info.
+ $set = DB::table('account_piggy_bank')->whereIn('piggy_bank_id', $this->piggyBankIds)->get(['piggy_bank_id', 'account_id']);
+ foreach ($set as $item) {
+ $id = (int)$item->piggy_bank_id;
+ if (!array_key_exists($id, $this->accountIds)) {
+ $this->accountIds[$id] = (int)$item->account_id;
+ }
+ }
+
+ // get account currency preference for ALL.
+ $set = AccountMeta::whereIn('account_id', array_values($this->accountIds))->where('name', 'currency_id')->get();
+
+ /** @var AccountMeta $item */
+ foreach ($set as $item) {
+ $accountId = (int)$item->account_id;
+ $currencyId = (int)$item->data;
+ if (!array_key_exists($currencyId, $this->currencies)) {
+ $this->currencies[$currencyId] = Amount::getTransactionCurrencyById($currencyId);
+ }
+ $this->accountCurrencies[$accountId] = $this->currencies[$currencyId];
+ }
+ }
}
diff --git a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php
index c8a3d8707a..30484f5388 100644
--- a/app/Support/JsonApi/Enrichments/RecurringEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/RecurringEnrichment.php
@@ -51,30 +51,29 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
-
use function Safe\json_decode;
class RecurringEnrichment implements EnrichmentInterface
{
- private Collection $collection;
- private array $ids = [];
+ private array $accounts = [];
+ private Collection $collection;
// private array $transactionTypeIds = [];
// private array $transactionTypes = [];
- private array $notes = [];
- private array $repetitions = [];
- private array $transactions = [];
- private User $user;
- private UserGroup $userGroup;
- private string $language = 'en_US';
- private array $currencyIds = [];
- private array $foreignCurrencyIds = [];
- private array $sourceAccountIds = [];
- private array $destinationAccountIds = [];
- private array $accounts = [];
- private array $currencies = [];
- private array $recurrenceIds = [];
+ private bool $convertToPrimary = false;
+ private array $currencies = [];
+ private array $currencyIds = [];
+ private array $destinationAccountIds = [];
+ private array $foreignCurrencyIds = [];
+ private array $ids = [];
+ private string $language = 'en_US';
+ private array $notes = [];
private readonly TransactionCurrency $primaryCurrency;
- private bool $convertToPrimary = false;
+ private array $recurrenceIds = [];
+ private array $repetitions = [];
+ private array $sourceAccountIds = [];
+ private array $transactions = [];
+ private User $user;
+ private UserGroup $userGroup;
public function __construct()
{
@@ -98,7 +97,7 @@ class RecurringEnrichment implements EnrichmentInterface
return $this->collection;
}
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -107,139 +106,6 @@ class RecurringEnrichment implements EnrichmentInterface
return $collection->first();
}
- public function setUser(User $user): void
- {
- $this->user = $user;
- $this->setUserGroup($user->userGroup);
- $this->getLanguage();
- }
-
- public function setUserGroup(UserGroup $userGroup): void
- {
- $this->userGroup = $userGroup;
- }
-
- private function collectIds(): void
- {
- /** @var Recurrence $recurrence */
- foreach ($this->collection as $recurrence) {
- $id = (int)$recurrence->id;
- // $typeId = (int)$recurrence->transaction_type_id;
- $this->ids[] = $id;
- // $this->transactionTypeIds[$id] = $typeId;
- }
- $this->ids = array_unique($this->ids);
-
- // collect transaction types.
- // $transactionTypes = TransactionType::whereIn('id', array_unique($this->transactionTypeIds))->get();
- // foreach ($transactionTypes as $transactionType) {
- // $id = (int)$transactionType->id;
- // $this->transactionTypes[$id] = TransactionTypeEnum::from($transactionType->type);
- // }
- }
-
- private function collectRepetitions(): void
- {
- Log::debug('Start of enrichment: collectRepetitions()');
- $repository = app(RecurringRepositoryInterface::class);
- $repository->setUserGroup($this->userGroup);
- $set = RecurrenceRepetition::whereIn('recurrence_id', $this->ids)->get();
-
- /** @var RecurrenceRepetition $repetition */
- foreach ($set as $repetition) {
- $recurrence = $this->collection->filter(fn (Recurrence $item) => (int)$item->id === (int)$repetition->recurrence_id)->first();
- $fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date);
- $id = (int)$repetition->recurrence_id;
- $repId = (int)$repetition->id;
- $this->repetitions[$id] ??= [];
-
- // get the (future) occurrences for this specific type of repetition:
- $amount = 'daily' === $repetition->repetition_type ? 9 : 5;
- $set = $repository->getXOccurrencesSince($repetition, $fromDate, now(config('app.timezone')), $amount);
- $occurrences = [];
-
- /** @var Carbon $carbon */
- foreach ($set as $carbon) {
- $occurrences[] = $carbon->toAtomString();
- }
- $this->repetitions[$id][$repId] = [
- 'id' => (string)$repId,
- 'created_at' => $repetition->created_at->toAtomString(),
- 'updated_at' => $repetition->updated_at->toAtomString(),
- 'type' => $repetition->repetition_type,
- 'moment' => (string)$repetition->repetition_moment,
- 'skip' => (int)$repetition->repetition_skip,
- 'weekend' => RecurrenceRepetitionWeekend::from((int)$repetition->weekend)->value,
- 'description' => $this->getRepetitionDescription($repetition),
- 'occurrences' => $occurrences,
- ];
- }
- Log::debug('End of enrichment: collectRepetitions()');
- }
-
- private function collectTransactions(): void
- {
- $set = RecurrenceTransaction::whereIn('recurrence_id', $this->ids)->get();
-
- /** @var RecurrenceTransaction $transaction */
- foreach ($set as $transaction) {
- $id = (int)$transaction->recurrence_id;
- $transactionId = (int)$transaction->id;
- $this->recurrenceIds[$transactionId] = $id;
- $this->transactions[$id] ??= [];
- $amount = $transaction->amount;
- $foreignAmount = $transaction->foreign_amount;
-
- $this->transactions[$id][$transactionId] = [
- 'id' => (string)$transactionId,
- // 'recurrence_id' => $id,
- 'transaction_currency_id' => (int)$transaction->transaction_currency_id,
- 'foreign_currency_id' => null === $transaction->foreign_currency_id ? null : (int)$transaction->foreign_currency_id,
- 'source_id' => (int)$transaction->source_id,
- 'object_has_currency_setting' => true,
- 'destination_id' => (int)$transaction->destination_id,
- 'amount' => $amount,
- 'foreign_amount' => $foreignAmount,
- 'pc_amount' => null,
- 'pc_foreign_amount' => null,
- 'description' => $transaction->description,
- 'tags' => [],
- 'category_id' => null,
- 'category_name' => null,
- 'budget_id' => null,
- 'budget_name' => null,
- 'piggy_bank_id' => null,
- 'piggy_bank_name' => null,
- 'subscription_id' => null,
- 'subscription_name' => null,
-
- ];
- // collect all kinds of meta data to be collected later.
- $this->currencyIds[$transactionId] = (int)$transaction->transaction_currency_id;
- $this->sourceAccountIds[$transactionId] = (int)$transaction->source_id;
- $this->destinationAccountIds[$transactionId] = (int)$transaction->destination_id;
- if (null !== $transaction->foreign_currency_id) {
- $this->foreignCurrencyIds[$transactionId] = (int)$transaction->foreign_currency_id;
- }
- }
- }
-
- private function appendCollectedData(): void
- {
- $this->collection = $this->collection->map(function (Recurrence $item) {
- $id = (int)$item->id;
- $meta = [
- 'notes' => $this->notes[$id] ?? null,
- 'repetitions' => array_values($this->repetitions[$id] ?? []),
- 'transactions' => $this->processTransactions(array_values($this->transactions[$id] ?? [])),
- ];
-
- $item->meta = $meta;
-
- return $item;
- });
- }
-
/**
* Parse the repetition in a string that is user readable.
* TODO duplicate with repository.
@@ -265,7 +131,7 @@ class RecurringEnrichment implements EnrichmentInterface
return (string)trans('firefly.recurring_monthly', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip - 1], $this->language);
}
if ('ndom' === $repetition->repetition_type) {
- $parts = explode(',', $repetition->repetition_moment);
+ $parts = explode(',', $repetition->repetition_moment);
// first part is number of week, second is weekday.
$dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $this->language);
if ($repetition->repetition_skip > 0) {
@@ -282,7 +148,7 @@ class RecurringEnrichment implements EnrichmentInterface
}
// $diffInYears = (int)$today->diffInYears($repDate, true);
// $repDate->addYears($diffInYears); // technically not necessary.
- $string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
+ $string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js'));
return (string)trans('firefly.recurring_yearly', ['date' => $string], $this->language);
}
@@ -290,96 +156,32 @@ class RecurringEnrichment implements EnrichmentInterface
return '';
}
- private function getLanguage(): void
+ public function setUser(User $user): void
{
- /** @var Preference $preference */
- $preference = Preferences::getForUser($this->user, 'language', config('firefly.default_language', 'en_US'));
- $language = $preference->data;
- if (is_array($language)) {
- $language = 'en_US';
- }
- $language = (string)$language;
- $this->language = $language;
+ $this->user = $user;
+ $this->setUserGroup($user->userGroup);
+ $this->getLanguage();
}
- private function collectCurrencies(): void
+ public function setUserGroup(UserGroup $userGroup): void
{
- $all = array_merge(array_unique($this->currencyIds), array_unique($this->foreignCurrencyIds));
- $currencies = TransactionCurrency::whereIn('id', array_unique($all))->get();
- foreach ($currencies as $currency) {
- $id = (int)$currency->id;
- $this->currencies[$id] = $currency;
- }
+ $this->userGroup = $userGroup;
}
- private function processTransactions(array $transactions): array
+ private function appendCollectedData(): void
{
- $return = [];
- $converter = new ExchangeRateConverter();
- foreach ($transactions as $transaction) {
- $currencyId = $transaction['transaction_currency_id'];
- $pcAmount = null;
- $pcForeignAmount = null;
- // set the same amount in the primary currency, if both are the same anyway.
- if (true === $this->convertToPrimary && $currencyId === (int)$this->primaryCurrency->id) {
- $pcAmount = $transaction['amount'];
- }
- // convert the amount to the primary currency, if it is not the same.
- if (true === $this->convertToPrimary && $currencyId !== (int)$this->primaryCurrency->id) {
- $pcAmount = $converter->convert($this->currencies[$currencyId], $this->primaryCurrency, today(), $transaction['amount']);
- }
- if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
- $foreignCurrencyId = $transaction['foreign_currency_id'];
- if ($foreignCurrencyId !== $this->primaryCurrency->id) {
- $pcForeignAmount = $converter->convert($this->currencies[$foreignCurrencyId], $this->primaryCurrency, today(), $transaction['foreign_amount']);
- }
- }
+ $this->collection = $this->collection->map(function (Recurrence $item) {
+ $id = (int)$item->id;
+ $meta = [
+ 'notes' => $this->notes[$id] ?? null,
+ 'repetitions' => array_values($this->repetitions[$id] ?? []),
+ 'transactions' => $this->processTransactions(array_values($this->transactions[$id] ?? [])),
+ ];
- $transaction['pc_amount'] = $pcAmount;
- $transaction['pc_foreign_amount'] = $pcForeignAmount;
+ $item->meta = $meta;
- $sourceId = $transaction['source_id'];
- $transaction['source_name'] = $this->accounts[$sourceId]->name;
- $transaction['source_iban'] = $this->accounts[$sourceId]->iban;
- $transaction['source_type'] = $this->accounts[$sourceId]->accountType->type;
- $transaction['source_id'] = (string)$transaction['source_id'];
-
- $destId = $transaction['destination_id'];
- $transaction['destination_name'] = $this->accounts[$destId]->name;
- $transaction['destination_iban'] = $this->accounts[$destId]->iban;
- $transaction['destination_type'] = $this->accounts[$destId]->accountType->type;
- $transaction['destination_id'] = (string)$transaction['destination_id'];
-
- $transaction['currency_id'] = (string)$currencyId;
- $transaction['currency_name'] = $this->currencies[$currencyId]->name;
- $transaction['currency_code'] = $this->currencies[$currencyId]->code;
- $transaction['currency_symbol'] = $this->currencies[$currencyId]->symbol;
- $transaction['currency_decimal_places'] = $this->currencies[$currencyId]->decimal_places;
-
- $transaction['primary_currency_id'] = (string)$this->primaryCurrency->id;
- $transaction['primary_currency_name'] = $this->primaryCurrency->name;
- $transaction['primary_currency_code'] = $this->primaryCurrency->code;
- $transaction['primary_currency_symbol'] = $this->primaryCurrency->symbol;
- $transaction['primary_currency_decimal_places'] = $this->primaryCurrency->decimal_places;
-
- // $transaction['foreign_currency_id'] = null;
- $transaction['foreign_currency_name'] = null;
- $transaction['foreign_currency_code'] = null;
- $transaction['foreign_currency_symbol'] = null;
- $transaction['foreign_currency_decimal_places'] = null;
- if (null !== $transaction['foreign_currency_id']) {
- $currencyId = $transaction['foreign_currency_id'];
- $transaction['foreign_currency_id'] = (string)$currencyId;
- $transaction['foreign_currency_name'] = $this->currencies[$currencyId]->name;
- $transaction['foreign_currency_code'] = $this->currencies[$currencyId]->code;
- $transaction['foreign_currency_symbol'] = $this->currencies[$currencyId]->symbol;
- $transaction['foreign_currency_decimal_places'] = $this->currencies[$currencyId]->decimal_places;
- }
- unset($transaction['transaction_currency_id']);
- $return[] = $transaction;
- }
-
- return $return;
+ return $item;
+ });
}
private function collectAccounts(): void
@@ -394,10 +196,183 @@ class RecurringEnrichment implements EnrichmentInterface
}
}
+ private function collectBillInfo(array $billIds): void
+ {
+ if (0 === count($billIds)) {
+ return;
+ }
+ $ids = Arr::pluck($billIds, 'bill_id');
+ $bills = Bill::whereIn('id', $ids)->get();
+ $mapped = [];
+ foreach ($bills as $bill) {
+ $mapped[(int)$bill->id] = $bill;
+ }
+ foreach ($billIds as $info) {
+ $recurrenceId = $info['recurrence_id'];
+ $transactionId = $info['transaction_id'];
+ $this->transactions[$recurrenceId][$transactionId]['subscription_name'] = $mapped[$info['bill_id']]->name ?? '';
+ }
+ }
+
+ private function collectBudgetInfo(array $budgetIds): void
+ {
+ if (0 === count($budgetIds)) {
+ return;
+ }
+ $ids = Arr::pluck($budgetIds, 'budget_id');
+ $categories = Budget::whereIn('id', $ids)->get();
+ $mapped = [];
+ foreach ($categories as $category) {
+ $mapped[(int)$category->id] = $category;
+ }
+ foreach ($budgetIds as $info) {
+ $recurrenceId = $info['recurrence_id'];
+ $transactionId = $info['transaction_id'];
+ $this->transactions[$recurrenceId][$transactionId]['budget_name'] = $mapped[$info['budget_id']]->name ?? '';
+ }
+ }
+
+ private function collectCategoryIdInfo(array $categoryIds): void
+ {
+ if (0 === count($categoryIds)) {
+ return;
+ }
+ $ids = Arr::pluck($categoryIds, 'category_id');
+ $categories = Category::whereIn('id', $ids)->get();
+ $mapped = [];
+ foreach ($categories as $category) {
+ $mapped[(int)$category->id] = $category;
+ }
+ foreach ($categoryIds as $info) {
+ $recurrenceId = $info['recurrence_id'];
+ $transactionId = $info['transaction_id'];
+ $this->transactions[$recurrenceId][$transactionId]['category_name'] = $mapped[$info['category_id']]->name ?? '';
+ }
+ }
+
+ /**
+ * TODO This method does look-up in a loop.
+ */
+ private function collectCategoryNameInfo(array $categoryNames): void
+ {
+ if (0 === count($categoryNames)) {
+ return;
+ }
+ $factory = app(CategoryFactory::class);
+ $factory->setUser($this->user);
+ foreach ($categoryNames as $info) {
+ $recurrenceId = $info['recurrence_id'];
+ $transactionId = $info['transaction_id'];
+ $category = $factory->findOrCreate(null, $info['category_name']);
+ if (null !== $category) {
+ $this->transactions[$recurrenceId][$transactionId]['category_id'] = (string)$category->id;
+ $this->transactions[$recurrenceId][$transactionId]['category_name'] = $category->name;
+ }
+ }
+ }
+
+ private function collectCurrencies(): void
+ {
+ $all = array_merge(array_unique($this->currencyIds), array_unique($this->foreignCurrencyIds));
+ $currencies = TransactionCurrency::whereIn('id', array_unique($all))->get();
+ foreach ($currencies as $currency) {
+ $id = (int)$currency->id;
+ $this->currencies[$id] = $currency;
+ }
+ }
+
+ private function collectIds(): void
+ {
+ /** @var Recurrence $recurrence */
+ foreach ($this->collection as $recurrence) {
+ $id = (int)$recurrence->id;
+ // $typeId = (int)$recurrence->transaction_type_id;
+ $this->ids[] = $id;
+ // $this->transactionTypeIds[$id] = $typeId;
+ }
+ $this->ids = array_unique($this->ids);
+
+ // collect transaction types.
+ // $transactionTypes = TransactionType::whereIn('id', array_unique($this->transactionTypeIds))->get();
+ // foreach ($transactionTypes as $transactionType) {
+ // $id = (int)$transactionType->id;
+ // $this->transactionTypes[$id] = TransactionTypeEnum::from($transactionType->type);
+ // }
+ }
+
+ private function collectNotes(): void
+ {
+ $notes = Note::query()->whereIn('noteable_id', $this->ids)
+ ->whereNotNull('notes.text')
+ ->where('notes.text', '!=', '')
+ ->where('noteable_type', Recurrence::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
+ foreach ($notes as $note) {
+ $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
+ }
+ Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
+ }
+
+ private function collectPiggyBankInfo(array $piggyBankIds): void
+ {
+ if (0 === count($piggyBankIds)) {
+ return;
+ }
+ $ids = Arr::pluck($piggyBankIds, 'piggy_bank_id');
+ $piggyBanks = PiggyBank::whereIn('id', $ids)->get();
+ $mapped = [];
+ foreach ($piggyBanks as $piggyBank) {
+ $mapped[(int)$piggyBank->id] = $piggyBank;
+ }
+ foreach ($piggyBankIds as $info) {
+ $recurrenceId = $info['recurrence_id'];
+ $transactionId = $info['transaction_id'];
+ $this->transactions[$recurrenceId][$transactionId]['piggy_bank_name'] = $mapped[$info['piggy_bank_id']]->name ?? '';
+ }
+ }
+
+ private function collectRepetitions(): void
+ {
+ Log::debug('Start of enrichment: collectRepetitions()');
+ $repository = app(RecurringRepositoryInterface::class);
+ $repository->setUserGroup($this->userGroup);
+ $set = RecurrenceRepetition::whereIn('recurrence_id', $this->ids)->get();
+
+ /** @var RecurrenceRepetition $repetition */
+ foreach ($set as $repetition) {
+ $recurrence = $this->collection->filter(fn(Recurrence $item) => (int)$item->id === (int)$repetition->recurrence_id)->first();
+ $fromDate = clone($recurrence->latest_date ?? $recurrence->first_date);
+ $id = (int)$repetition->recurrence_id;
+ $repId = (int)$repetition->id;
+ $this->repetitions[$id] ??= [];
+
+ // get the (future) occurrences for this specific type of repetition:
+ $amount = 'daily' === $repetition->repetition_type ? 9 : 5;
+ $set = $repository->getXOccurrencesSince($repetition, $fromDate, now(config('app.timezone')), $amount);
+ $occurrences = [];
+
+ /** @var Carbon $carbon */
+ foreach ($set as $carbon) {
+ $occurrences[] = $carbon->toAtomString();
+ }
+ $this->repetitions[$id][$repId] = [
+ 'id' => (string)$repId,
+ 'created_at' => $repetition->created_at->toAtomString(),
+ 'updated_at' => $repetition->updated_at->toAtomString(),
+ 'type' => $repetition->repetition_type,
+ 'moment' => (string)$repetition->repetition_moment,
+ 'skip' => (int)$repetition->repetition_skip,
+ 'weekend' => RecurrenceRepetitionWeekend::from((int)$repetition->weekend)->value,
+ 'description' => $this->getRepetitionDescription($repetition),
+ 'occurrences' => $occurrences,
+ ];
+ }
+ Log::debug('End of enrichment: collectRepetitions()');
+ }
+
private function collectTransactionMetaData(): void
{
- $ids = array_keys($this->transactions);
- $meta = RecurrenceTransactionMeta::whereNull('deleted_at')->whereIn('rt_id', $ids)->get();
+ $ids = array_keys($this->transactions);
+ $meta = RecurrenceTransactionMeta::whereNull('deleted_at')->whereIn('rt_id', $ids)->get();
// other meta-data to be collected:
$billIds = [];
$piggyBankIds = [];
@@ -409,8 +384,8 @@ class RecurringEnrichment implements EnrichmentInterface
$transactionId = (int)$entry->rt_id;
// this should refer to another array, were rtIds can be used to find the recurrence.
- $recurrenceId = $this->recurrenceIds[$transactionId] ?? 0;
- $name = (string)($entry->name ?? '');
+ $recurrenceId = $this->recurrenceIds[$transactionId] ?? 0;
+ $name = (string)($entry->name ?? '');
if (0 === $recurrenceId) {
Log::error(sprintf('Could not find recurrence ID for recurrence transaction ID %d', $transactionId));
@@ -504,109 +479,132 @@ class RecurringEnrichment implements EnrichmentInterface
$this->collectBudgetInfo($budgetIds);
}
- private function collectBillInfo(array $billIds): void
+ private function collectTransactions(): void
{
- if (0 === count($billIds)) {
- return;
- }
- $ids = Arr::pluck($billIds, 'bill_id');
- $bills = Bill::whereIn('id', $ids)->get();
- $mapped = [];
- foreach ($bills as $bill) {
- $mapped[(int)$bill->id] = $bill;
- }
- foreach ($billIds as $info) {
- $recurrenceId = $info['recurrence_id'];
- $transactionId = $info['transaction_id'];
- $this->transactions[$recurrenceId][$transactionId]['subscription_name'] = $mapped[$info['bill_id']]->name ?? '';
- }
- }
+ $set = RecurrenceTransaction::whereIn('recurrence_id', $this->ids)->get();
- private function collectPiggyBankInfo(array $piggyBankIds): void
- {
- if (0 === count($piggyBankIds)) {
- return;
- }
- $ids = Arr::pluck($piggyBankIds, 'piggy_bank_id');
- $piggyBanks = PiggyBank::whereIn('id', $ids)->get();
- $mapped = [];
- foreach ($piggyBanks as $piggyBank) {
- $mapped[(int)$piggyBank->id] = $piggyBank;
- }
- foreach ($piggyBankIds as $info) {
- $recurrenceId = $info['recurrence_id'];
- $transactionId = $info['transaction_id'];
- $this->transactions[$recurrenceId][$transactionId]['piggy_bank_name'] = $mapped[$info['piggy_bank_id']]->name ?? '';
- }
- }
+ /** @var RecurrenceTransaction $transaction */
+ foreach ($set as $transaction) {
+ $id = (int)$transaction->recurrence_id;
+ $transactionId = (int)$transaction->id;
+ $this->recurrenceIds[$transactionId] = $id;
+ $this->transactions[$id] ??= [];
+ $amount = $transaction->amount;
+ $foreignAmount = $transaction->foreign_amount;
- private function collectCategoryIdInfo(array $categoryIds): void
- {
- if (0 === count($categoryIds)) {
- return;
- }
- $ids = Arr::pluck($categoryIds, 'category_id');
- $categories = Category::whereIn('id', $ids)->get();
- $mapped = [];
- foreach ($categories as $category) {
- $mapped[(int)$category->id] = $category;
- }
- foreach ($categoryIds as $info) {
- $recurrenceId = $info['recurrence_id'];
- $transactionId = $info['transaction_id'];
- $this->transactions[$recurrenceId][$transactionId]['category_name'] = $mapped[$info['category_id']]->name ?? '';
- }
- }
+ $this->transactions[$id][$transactionId] = [
+ 'id' => (string)$transactionId,
+ // 'recurrence_id' => $id,
+ 'transaction_currency_id' => (int)$transaction->transaction_currency_id,
+ 'foreign_currency_id' => null === $transaction->foreign_currency_id ? null : (int)$transaction->foreign_currency_id,
+ 'source_id' => (int)$transaction->source_id,
+ 'object_has_currency_setting' => true,
+ 'destination_id' => (int)$transaction->destination_id,
+ 'amount' => $amount,
+ 'foreign_amount' => $foreignAmount,
+ 'pc_amount' => null,
+ 'pc_foreign_amount' => null,
+ 'description' => $transaction->description,
+ 'tags' => [],
+ 'category_id' => null,
+ 'category_name' => null,
+ 'budget_id' => null,
+ 'budget_name' => null,
+ 'piggy_bank_id' => null,
+ 'piggy_bank_name' => null,
+ 'subscription_id' => null,
+ 'subscription_name' => null,
- /**
- * TODO This method does look-up in a loop.
- */
- private function collectCategoryNameInfo(array $categoryNames): void
- {
- if (0 === count($categoryNames)) {
- return;
- }
- $factory = app(CategoryFactory::class);
- $factory->setUser($this->user);
- foreach ($categoryNames as $info) {
- $recurrenceId = $info['recurrence_id'];
- $transactionId = $info['transaction_id'];
- $category = $factory->findOrCreate(null, $info['category_name']);
- if (null !== $category) {
- $this->transactions[$recurrenceId][$transactionId]['category_id'] = (string)$category->id;
- $this->transactions[$recurrenceId][$transactionId]['category_name'] = $category->name;
+ ];
+ // collect all kinds of meta data to be collected later.
+ $this->currencyIds[$transactionId] = (int)$transaction->transaction_currency_id;
+ $this->sourceAccountIds[$transactionId] = (int)$transaction->source_id;
+ $this->destinationAccountIds[$transactionId] = (int)$transaction->destination_id;
+ if (null !== $transaction->foreign_currency_id) {
+ $this->foreignCurrencyIds[$transactionId] = (int)$transaction->foreign_currency_id;
}
}
}
- private function collectBudgetInfo(array $budgetIds): void
+ private function getLanguage(): void
{
- if (0 === count($budgetIds)) {
- return;
- }
- $ids = Arr::pluck($budgetIds, 'budget_id');
- $categories = Budget::whereIn('id', $ids)->get();
- $mapped = [];
- foreach ($categories as $category) {
- $mapped[(int)$category->id] = $category;
- }
- foreach ($budgetIds as $info) {
- $recurrenceId = $info['recurrence_id'];
- $transactionId = $info['transaction_id'];
- $this->transactions[$recurrenceId][$transactionId]['budget_name'] = $mapped[$info['budget_id']]->name ?? '';
+ /** @var Preference $preference */
+ $preference = Preferences::getForUser($this->user, 'language', config('firefly.default_language', 'en_US'));
+ $language = $preference->data;
+ if (is_array($language)) {
+ $language = 'en_US';
}
+ $language = (string)$language;
+ $this->language = $language;
}
- private function collectNotes(): void
+ private function processTransactions(array $transactions): array
{
- $notes = Note::query()->whereIn('noteable_id', $this->ids)
- ->whereNotNull('notes.text')
- ->where('notes.text', '!=', '')
- ->where('noteable_type', Recurrence::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
- ;
- foreach ($notes as $note) {
- $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
+ $return = [];
+ $converter = new ExchangeRateConverter();
+ foreach ($transactions as $transaction) {
+ $currencyId = $transaction['transaction_currency_id'];
+ $pcAmount = null;
+ $pcForeignAmount = null;
+ // set the same amount in the primary currency, if both are the same anyway.
+ if (true === $this->convertToPrimary && $currencyId === (int)$this->primaryCurrency->id) {
+ $pcAmount = $transaction['amount'];
+ }
+ // convert the amount to the primary currency, if it is not the same.
+ if (true === $this->convertToPrimary && $currencyId !== (int)$this->primaryCurrency->id) {
+ $pcAmount = $converter->convert($this->currencies[$currencyId], $this->primaryCurrency, today(), $transaction['amount']);
+ }
+ if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
+ $foreignCurrencyId = $transaction['foreign_currency_id'];
+ if ($foreignCurrencyId !== $this->primaryCurrency->id) {
+ $pcForeignAmount = $converter->convert($this->currencies[$foreignCurrencyId], $this->primaryCurrency, today(), $transaction['foreign_amount']);
+ }
+ }
+
+ $transaction['pc_amount'] = $pcAmount;
+ $transaction['pc_foreign_amount'] = $pcForeignAmount;
+
+ $sourceId = $transaction['source_id'];
+ $transaction['source_name'] = $this->accounts[$sourceId]->name;
+ $transaction['source_iban'] = $this->accounts[$sourceId]->iban;
+ $transaction['source_type'] = $this->accounts[$sourceId]->accountType->type;
+ $transaction['source_id'] = (string)$transaction['source_id'];
+
+ $destId = $transaction['destination_id'];
+ $transaction['destination_name'] = $this->accounts[$destId]->name;
+ $transaction['destination_iban'] = $this->accounts[$destId]->iban;
+ $transaction['destination_type'] = $this->accounts[$destId]->accountType->type;
+ $transaction['destination_id'] = (string)$transaction['destination_id'];
+
+ $transaction['currency_id'] = (string)$currencyId;
+ $transaction['currency_name'] = $this->currencies[$currencyId]->name;
+ $transaction['currency_code'] = $this->currencies[$currencyId]->code;
+ $transaction['currency_symbol'] = $this->currencies[$currencyId]->symbol;
+ $transaction['currency_decimal_places'] = $this->currencies[$currencyId]->decimal_places;
+
+ $transaction['primary_currency_id'] = (string)$this->primaryCurrency->id;
+ $transaction['primary_currency_name'] = $this->primaryCurrency->name;
+ $transaction['primary_currency_code'] = $this->primaryCurrency->code;
+ $transaction['primary_currency_symbol'] = $this->primaryCurrency->symbol;
+ $transaction['primary_currency_decimal_places'] = $this->primaryCurrency->decimal_places;
+
+ // $transaction['foreign_currency_id'] = null;
+ $transaction['foreign_currency_name'] = null;
+ $transaction['foreign_currency_code'] = null;
+ $transaction['foreign_currency_symbol'] = null;
+ $transaction['foreign_currency_decimal_places'] = null;
+ if (null !== $transaction['foreign_currency_id']) {
+ $currencyId = $transaction['foreign_currency_id'];
+ $transaction['foreign_currency_id'] = (string)$currencyId;
+ $transaction['foreign_currency_name'] = $this->currencies[$currencyId]->name;
+ $transaction['foreign_currency_code'] = $this->currencies[$currencyId]->code;
+ $transaction['foreign_currency_symbol'] = $this->currencies[$currencyId]->symbol;
+ $transaction['foreign_currency_decimal_places'] = $this->currencies[$currencyId]->decimal_places;
+ }
+ unset($transaction['transaction_currency_id']);
+ $return[] = $transaction;
}
- Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
+
+ return $return;
}
}
diff --git a/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php b/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
index 06388a80bc..285e0ad37e 100644
--- a/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/SubscriptionEnrichment.php
@@ -46,20 +46,20 @@ use Illuminate\Support\Facades\Log;
class SubscriptionEnrichment implements EnrichmentInterface
{
- private User $user;
- private UserGroup $userGroup; // @phpstan-ignore-line
- private Collection $collection;
+ private BillDateCalculator $calculator;
+ private Collection $collection; // @phpstan-ignore-line
private readonly bool $convertToPrimary;
- private ?Carbon $start = null;
- private ?Carbon $end = null;
- private array $subscriptionIds = [];
- private array $objectGroups = [];
- private array $mappedObjects = [];
- private array $paidDates = [];
- private array $notes = [];
- private array $payDates = [];
+ private ?Carbon $end = null;
+ private array $mappedObjects = [];
+ private array $notes = [];
+ private array $objectGroups = [];
+ private array $paidDates = [];
+ private array $payDates = [];
private readonly TransactionCurrency $primaryCurrency;
- private BillDateCalculator $calculator;
+ private ?Carbon $start = null;
+ private array $subscriptionIds = [];
+ private User $user;
+ private UserGroup $userGroup;
public function __construct()
{
@@ -86,11 +86,11 @@ class SubscriptionEnrichment implements EnrichmentInterface
$paidDates = $this->paidDates;
$payDates = $this->payDates;
$this->collection = $this->collection->map(function (Bill $item) use ($notes, $objectGroups, $paidDates, $payDates) {
- $id = (int)$item->id;
- $currency = $item->transactionCurrency;
- $nem = $this->getNextExpectedMatch($payDates[$id] ?? []);
+ $id = (int)$item->id;
+ $currency = $item->transactionCurrency;
+ $nem = $this->getNextExpectedMatch($payDates[$id] ?? []);
- $meta = [
+ $meta = [
'notes' => null,
'object_group_id' => null,
'object_group_title' => null,
@@ -101,7 +101,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
'nem' => $nem,
'nem_diff' => $this->getNextExpectedMatchDiff($nem, $payDates[$id] ?? []),
];
- $amounts = [
+ $amounts = [
'amount_min' => Steam::bcround($item->amount_min, $currency->decimal_places),
'amount_max' => Steam::bcround($item->amount_max, $currency->decimal_places),
'average' => Steam::bcround(bcdiv(bcadd($item->amount_min, $item->amount_max), '2'), $currency->decimal_places),
@@ -142,7 +142,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
return $collection;
}
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -151,17 +151,14 @@ class SubscriptionEnrichment implements EnrichmentInterface
return $collection->first();
}
- private function collectNotes(): void
+ public function setEnd(?Carbon $end): void
{
- $notes = Note::query()->whereIn('noteable_id', $this->subscriptionIds)
- ->whereNotNull('notes.text')
- ->where('notes.text', '!=', '')
- ->where('noteable_type', Bill::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
- ;
- foreach ($notes as $note) {
- $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
- }
- Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
+ $this->end = $end;
+ }
+
+ public function setStart(?Carbon $start): void
+ {
+ $this->start = $start;
}
public function setUser(User $user): void
@@ -175,24 +172,49 @@ class SubscriptionEnrichment implements EnrichmentInterface
$this->userGroup = $userGroup;
}
- private function collectSubscriptionIds(): void
+ /**
+ * Returns the latest date in the set, or start when set is empty.
+ */
+ protected function lastPaidDate(Bill $subscription, Collection $dates, Carbon $default): Carbon
{
- /** @var Bill $bill */
- foreach ($this->collection as $bill) {
- $this->subscriptionIds[] = (int)$bill->id;
+ $filtered = $dates->filter(fn(TransactionJournal $journal) => (int)$journal->bill_id === (int)$subscription->id);
+ Log::debug(sprintf('Filtered down from %d to %d entries for bill #%d.', $dates->count(), $filtered->count(), $subscription->id));
+ if (0 === $filtered->count()) {
+ return $default;
}
- $this->subscriptionIds = array_unique($this->subscriptionIds);
+
+ $latest = $filtered->first()->date;
+
+ /** @var TransactionJournal $journal */
+ foreach ($filtered as $journal) {
+ if ($journal->date->gte($latest)) {
+ $latest = $journal->date;
+ }
+ }
+
+ return $latest;
+ }
+
+ private function collectNotes(): void
+ {
+ $notes = Note::query()->whereIn('noteable_id', $this->subscriptionIds)
+ ->whereNotNull('notes.text')
+ ->where('notes.text', '!=', '')
+ ->where('noteable_type', Bill::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
+ foreach ($notes as $note) {
+ $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
+ }
+ Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
}
private function collectObjectGroups(): void
{
- $set = DB::table('object_groupables')
- ->whereIn('object_groupable_id', $this->subscriptionIds)
- ->where('object_groupable_type', Bill::class)
- ->get(['object_groupable_id', 'object_group_id'])
- ;
+ $set = DB::table('object_groupables')
+ ->whereIn('object_groupable_id', $this->subscriptionIds)
+ ->where('object_groupable_type', Bill::class)
+ ->get(['object_groupable_id', 'object_group_id']);
- $ids = array_unique($set->pluck('object_group_id')->toArray());
+ $ids = array_unique($set->pluck('object_group_id')->toArray());
foreach ($set as $entry) {
$this->mappedObjects[(int)$entry->object_groupable_id] = (int)$entry->object_group_id;
@@ -220,13 +242,13 @@ class SubscriptionEnrichment implements EnrichmentInterface
// 2023-07-18 this particular date is used to search for the last paid date.
// 2023-07-18 the cloned $searchDate is used to grab the correct transactions.
/** @var Carbon $start */
- $start = clone $this->start;
- $searchStart = clone $start;
+ $start = clone $this->start;
+ $searchStart = clone $start;
$start->subDay();
/** @var Carbon $end */
- $end = clone $this->end;
- $searchEnd = clone $end;
+ $end = clone $this->end;
+ $searchEnd = clone $end;
// move the search dates to the start of the day.
$searchStart->startOfDay();
@@ -235,13 +257,13 @@ class SubscriptionEnrichment implements EnrichmentInterface
Log::debug(sprintf('Search parameters are: start: %s, end: %s', $searchStart->format('Y-m-d H:i:s'), $searchEnd->format('Y-m-d H:i:s')));
// Get from database when bills were paid.
- $set = $this->user->transactionJournals()
- ->whereIn('bill_id', $this->subscriptionIds)
- ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
- ->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
- ->where('transactions.amount', '>', 0)
- ->before($searchEnd)->after($searchStart)->get(
+ $set = $this->user->transactionJournals()
+ ->whereIn('bill_id', $this->subscriptionIds)
+ ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->leftJoin('transaction_currencies AS currency', 'currency.id', '=', 'transactions.transaction_currency_id')
+ ->leftJoin('transaction_currencies AS foreign_currency', 'foreign_currency.id', '=', 'transactions.foreign_currency_id')
+ ->where('transactions.amount', '>', 0)
+ ->before($searchEnd)->after($searchStart)->get(
[
'transaction_journals.id',
'transaction_journals.date',
@@ -258,25 +280,24 @@ class SubscriptionEnrichment implements EnrichmentInterface
'transactions.amount',
'transactions.foreign_amount',
]
- )
- ;
+ );
Log::debug(sprintf('Count %d entries in set', $set->count()));
// for each bill, do a loop.
- $converter = new ExchangeRateConverter();
+ $converter = new ExchangeRateConverter();
/** @var Bill $subscription */
foreach ($this->collection as $subscription) {
// Grab from array the most recent payment. If none exist, fall back to the start date and pretend *that* was the last paid date.
Log::debug(sprintf('Grab last paid date from function, return %s if it comes up with nothing.', $start->format('Y-m-d')));
- $lastPaidDate = $this->lastPaidDate($subscription, $set, $start);
+ $lastPaidDate = $this->lastPaidDate($subscription, $set, $start);
Log::debug(sprintf('Result of lastPaidDate is %s', $lastPaidDate->format('Y-m-d')));
// At this point the "next match" is exactly after the last time the bill was paid.
- $result = [];
- $filtered = $set->filter(fn (TransactionJournal $journal) => (int)$journal->bill_id === (int)$subscription->id);
+ $result = [];
+ $filtered = $set->filter(fn(TransactionJournal $journal) => (int)$journal->bill_id === (int)$subscription->id);
foreach ($filtered as $entry) {
- $array = [
+ $array = [
'transaction_group_id' => (string)$entry->transaction_group_id,
'transaction_journal_id' => (string)$entry->id,
'date' => $entry->date->toAtomString(),
@@ -329,37 +350,47 @@ class SubscriptionEnrichment implements EnrichmentInterface
}
- public function setStart(?Carbon $start): void
+ private function collectPayDates(): void
{
- $this->start = $start;
- }
+ if (!$this->start instanceof Carbon || !$this->end instanceof Carbon) {
+ Log::debug('Parameters are NULL, set empty array');
- public function setEnd(?Carbon $end): void
- {
- $this->end = $end;
- }
-
- /**
- * Returns the latest date in the set, or start when set is empty.
- */
- protected function lastPaidDate(Bill $subscription, Collection $dates, Carbon $default): Carbon
- {
- $filtered = $dates->filter(fn (TransactionJournal $journal) => (int)$journal->bill_id === (int)$subscription->id);
- Log::debug(sprintf('Filtered down from %d to %d entries for bill #%d.', $dates->count(), $filtered->count(), $subscription->id));
- if (0 === $filtered->count()) {
- return $default;
+ return;
}
- $latest = $filtered->first()->date;
-
- /** @var TransactionJournal $journal */
- foreach ($filtered as $journal) {
- if ($journal->date->gte($latest)) {
- $latest = $journal->date;
+ /** @var Bill $subscription */
+ foreach ($this->collection as $subscription) {
+ $id = (int)$subscription->id;
+ $lastPaidDate = $this->getLastPaidDate($this->paidDates[$id] ?? []);
+ $payDates = $this->calculator->getPayDates($this->start, $this->end, $subscription->date, $subscription->repeat_freq, $subscription->skip, $lastPaidDate);
+ $payDatesFormatted = [];
+ foreach ($payDates as $string) {
+ $date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
+ if (!$date instanceof Carbon) {
+ $date = today(config('app.timezone'));
+ }
+ $payDatesFormatted[] = $date->toAtomString();
}
+ $this->payDates[$id] = $payDatesFormatted;
}
+ }
- return $latest;
+ private function collectSubscriptionIds(): void
+ {
+ /** @var Bill $bill */
+ foreach ($this->collection as $bill) {
+ $this->subscriptionIds[] = (int)$bill->id;
+ }
+ $this->subscriptionIds = array_unique($this->subscriptionIds);
+ }
+
+ private function filterPaidDates(array $entries): array
+ {
+ return array_map(function (array $entry) {
+ unset($entry['date_object']);
+
+ return $entry;
+ }, $entries);
}
private function getLastPaidDate(array $paidData): ?Carbon
@@ -386,40 +417,6 @@ class SubscriptionEnrichment implements EnrichmentInterface
return $return;
}
- private function collectPayDates(): void
- {
- if (!$this->start instanceof Carbon || !$this->end instanceof Carbon) {
- Log::debug('Parameters are NULL, set empty array');
-
- return;
- }
-
- /** @var Bill $subscription */
- foreach ($this->collection as $subscription) {
- $id = (int)$subscription->id;
- $lastPaidDate = $this->getLastPaidDate($this->paidDates[$id] ?? []);
- $payDates = $this->calculator->getPayDates($this->start, $this->end, $subscription->date, $subscription->repeat_freq, $subscription->skip, $lastPaidDate);
- $payDatesFormatted = [];
- foreach ($payDates as $string) {
- $date = Carbon::createFromFormat('!Y-m-d', $string, config('app.timezone'));
- if (!$date instanceof Carbon) {
- $date = today(config('app.timezone'));
- }
- $payDatesFormatted[] = $date->toAtomString();
- }
- $this->payDates[$id] = $payDatesFormatted;
- }
- }
-
- private function filterPaidDates(array $entries): array
- {
- return array_map(function (array $entry) {
- unset($entry['date_object']);
-
- return $entry;
- }, $entries);
- }
-
private function getNextExpectedMatch(array $payDates): ?Carbon
{
// next expected match
diff --git a/app/Support/JsonApi/Enrichments/TransactionGroupEnrichment.php b/app/Support/JsonApi/Enrichments/TransactionGroupEnrichment.php
index d322331135..014aff8e68 100644
--- a/app/Support/JsonApi/Enrichments/TransactionGroupEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/TransactionGroupEnrichment.php
@@ -45,17 +45,17 @@ use Override;
class TransactionGroupEnrichment implements EnrichmentInterface
{
- private array $attachmentCount = [];
- private Collection $collection;
- private readonly array $dateFields;
- private array $journalIds = [];
- private array $locations = [];
- private array $metaData = [];
- private array $notes = [];
- private array $tags = [];
- private User $user; // @phpstan-ignore-line
+ private array $attachmentCount = [];
+ private Collection $collection;
+ private readonly array $dateFields;
+ private array $journalIds = [];
+ private array $locations = [];
+ private array $metaData = [];
+ private array $notes = [];
private readonly TransactionCurrency $primaryCurrency;
- private UserGroup $userGroup; // @phpstan-ignore-line
+ private array $tags = []; // @phpstan-ignore-line
+ private User $user;
+ private UserGroup $userGroup; // @phpstan-ignore-line
public function __construct()
{
@@ -63,20 +63,6 @@ class TransactionGroupEnrichment implements EnrichmentInterface
$this->primaryCurrency = Amount::getPrimaryCurrency();
}
- #[Override]
- public function enrichSingle(array|Model $model): array|TransactionGroup
- {
- Log::debug(__METHOD__);
- if (is_array($model)) {
- $collection = new Collection()->push($model);
- $collection = $this->enrich($collection);
-
- return $collection->first();
- }
-
- throw new FireflyException('Cannot enrich single model.');
- }
-
#[Override]
public function enrich(Collection $collection): Collection
{
@@ -96,119 +82,55 @@ class TransactionGroupEnrichment implements EnrichmentInterface
return $this->collection;
}
- private function collectJournalIds(): void
+ #[Override]
+ public function enrichSingle(array | Model $model): array | TransactionGroup
{
- /** @var array $group */
- foreach ($this->collection as $group) {
- foreach ($group['transactions'] as $journal) {
- $this->journalIds[] = $journal['transaction_journal_id'];
- }
+ Log::debug(__METHOD__);
+ if (is_array($model)) {
+ $collection = new Collection()->push($model);
+ $collection = $this->enrich($collection);
+
+ return $collection->first();
}
- $this->journalIds = array_unique($this->journalIds);
+
+ throw new FireflyException('Cannot enrich single model.');
}
- private function collectNotes(): void
+ public function setUser(User $user): void
{
- $notes = Note::query()->whereIn('noteable_id', $this->journalIds)
- ->whereNotNull('notes.text')
- ->where('notes.text', '!=', '')
- ->where('noteable_type', TransactionJournal::class)->get(['notes.noteable_id', 'notes.text'])->toArray()
- ;
- foreach ($notes as $note) {
- $this->notes[(int) $note['noteable_id']] = (string) $note['text'];
- }
- Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
+ $this->user = $user;
+ $this->userGroup = $user->userGroup;
}
- private function collectTags(): void
+ public function setUserGroup(UserGroup $userGroup): void
{
- $set = Tag::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id')
- ->whereIn('tag_transaction_journal.transaction_journal_id', $this->journalIds)
- ->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag'])->toArray()
- ;
- foreach ($set as $item) {
- $journalId = $item['transaction_journal_id'];
- $this->tags[$journalId] ??= [];
- $this->tags[$journalId][] = $item['tag'];
- }
- }
-
- private function collectMetaData(): void
- {
- $set = TransactionJournalMeta::whereIn('transaction_journal_id', $this->journalIds)->get(['transaction_journal_id', 'name', 'data'])->toArray();
- foreach ($set as $entry) {
- $name = $entry['name'];
- $data = (string) $entry['data'];
- if ('' === $data) {
- continue;
- }
- if (in_array($name, $this->dateFields, true)) {
- // Log::debug(sprintf('Meta data for "%s" is a date : "%s"', $name, $data));
- $this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data, config('app.timezone'));
- // Log::debug(sprintf('Meta data for "%s" converts to: "%s"', $name, $this->metaData[$entry['transaction_journal_id']][$name]->toW3CString()));
-
- continue;
- }
- $this->metaData[(int) $entry['transaction_journal_id']][$name] = $data;
- }
- }
-
- private function collectLocations(): void
- {
- $locations = Location::query()->whereIn('locatable_id', $this->journalIds)
- ->where('locatable_type', TransactionJournal::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray()
- ;
- foreach ($locations as $location) {
- $this->locations[(int) $location['locatable_id']]
- = [
- 'latitude' => (float) $location['latitude'],
- 'longitude' => (float) $location['longitude'],
- 'zoom_level' => (int) $location['zoom_level'],
- ];
- }
- Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
- }
-
- private function collectAttachmentCount(): void
- {
- // select count(id) as nr_of_attachments, attachable_id from attachments
- // group by attachable_id
- $attachments = Attachment::query()
- ->whereIn('attachable_id', $this->journalIds)
- ->where('attachable_type', TransactionJournal::class)
- ->groupBy('attachable_id')
- ->get(['attachable_id', DB::raw('COUNT(id) as nr_of_attachments')])
- ->toArray()
- ;
- foreach ($attachments as $row) {
- $this->attachmentCount[(int) $row['attachable_id']] = (int) $row['nr_of_attachments'];
- }
+ $this->userGroup = $userGroup;
}
private function appendCollectedData(): void
{
- $notes = $this->notes;
- $tags = $this->tags;
- $metaData = $this->metaData;
- $locations = $this->locations;
- $attachmentCount = $this->attachmentCount;
- $primaryCurrency = $this->primaryCurrency;
+ $notes = $this->notes;
+ $tags = $this->tags;
+ $metaData = $this->metaData;
+ $locations = $this->locations;
+ $attachmentCount = $this->attachmentCount;
+ $primaryCurrency = $this->primaryCurrency;
$this->collection = $this->collection->map(function (array $item) use ($primaryCurrency, $notes, $tags, $metaData, $locations, $attachmentCount) {
foreach ($item['transactions'] as $index => $transaction) {
- $journalId = (int) $transaction['transaction_journal_id'];
+ $journalId = (int)$transaction['transaction_journal_id'];
// attach notes if they exist:
- $item['transactions'][$index]['notes'] = array_key_exists($journalId, $notes) ? $notes[$journalId] : null;
+ $item['transactions'][$index]['notes'] = array_key_exists($journalId, $notes) ? $notes[$journalId] : null;
// attach tags if they exist:
- $item['transactions'][$index]['tags'] = array_key_exists($journalId, $tags) ? $tags[$journalId] : [];
+ $item['transactions'][$index]['tags'] = array_key_exists($journalId, $tags) ? $tags[$journalId] : [];
// attachment count
$item['transactions'][$index]['attachment_count'] = array_key_exists($journalId, $attachmentCount) ? $attachmentCount[$journalId] : 0;
// default location data
- $item['transactions'][$index]['location'] = [
+ $item['transactions'][$index]['location'] = [
'latitude' => null,
'longitude' => null,
'zoom_level' => null,
@@ -216,16 +138,16 @@ class TransactionGroupEnrichment implements EnrichmentInterface
// primary currency
$item['transactions'][$index]['primary_currency'] = [
- 'id' => (string) $primaryCurrency->id,
- 'code' => $primaryCurrency->code,
- 'name' => $primaryCurrency->name,
- 'symbol' => $primaryCurrency->symbol,
- 'decimal_places' => $primaryCurrency->decimal_places,
+ 'id' => (string)$primaryCurrency->id,
+ 'code' => $primaryCurrency->code,
+ 'name' => $primaryCurrency->name,
+ 'symbol' => $primaryCurrency->symbol,
+ 'decimal_places' => $primaryCurrency->decimal_places,
];
// append meta data
- $item['transactions'][$index]['meta'] = [];
- $item['transactions'][$index]['meta_date'] = [];
+ $item['transactions'][$index]['meta'] = [];
+ $item['transactions'][$index]['meta_date'] = [];
if (array_key_exists($journalId, $metaData)) {
// loop al meta data:
foreach ($metaData[$journalId] as $name => $value) {
@@ -248,14 +170,88 @@ class TransactionGroupEnrichment implements EnrichmentInterface
});
}
- public function setUser(User $user): void
+ private function collectAttachmentCount(): void
{
- $this->user = $user;
- $this->userGroup = $user->userGroup;
+ // select count(id) as nr_of_attachments, attachable_id from attachments
+ // group by attachable_id
+ $attachments = Attachment::query()
+ ->whereIn('attachable_id', $this->journalIds)
+ ->where('attachable_type', TransactionJournal::class)
+ ->groupBy('attachable_id')
+ ->get(['attachable_id', DB::raw('COUNT(id) as nr_of_attachments')])
+ ->toArray();
+ foreach ($attachments as $row) {
+ $this->attachmentCount[(int)$row['attachable_id']] = (int)$row['nr_of_attachments'];
+ }
}
- public function setUserGroup(UserGroup $userGroup): void
+ private function collectJournalIds(): void
{
- $this->userGroup = $userGroup;
+ /** @var array $group */
+ foreach ($this->collection as $group) {
+ foreach ($group['transactions'] as $journal) {
+ $this->journalIds[] = $journal['transaction_journal_id'];
+ }
+ }
+ $this->journalIds = array_unique($this->journalIds);
+ }
+
+ private function collectLocations(): void
+ {
+ $locations = Location::query()->whereIn('locatable_id', $this->journalIds)
+ ->where('locatable_type', TransactionJournal::class)->get(['locations.locatable_id', 'locations.latitude', 'locations.longitude', 'locations.zoom_level'])->toArray();
+ foreach ($locations as $location) {
+ $this->locations[(int)$location['locatable_id']]
+ = [
+ 'latitude' => (float)$location['latitude'],
+ 'longitude' => (float)$location['longitude'],
+ 'zoom_level' => (int)$location['zoom_level'],
+ ];
+ }
+ Log::debug(sprintf('Enrich with %d locations(s)', count($this->locations)));
+ }
+
+ private function collectMetaData(): void
+ {
+ $set = TransactionJournalMeta::whereIn('transaction_journal_id', $this->journalIds)->get(['transaction_journal_id', 'name', 'data'])->toArray();
+ foreach ($set as $entry) {
+ $name = $entry['name'];
+ $data = (string)$entry['data'];
+ if ('' === $data) {
+ continue;
+ }
+ if (in_array($name, $this->dateFields, true)) {
+ // Log::debug(sprintf('Meta data for "%s" is a date : "%s"', $name, $data));
+ $this->metaData[$entry['transaction_journal_id']][$name] = Carbon::parse($data, config('app.timezone'));
+ // Log::debug(sprintf('Meta data for "%s" converts to: "%s"', $name, $this->metaData[$entry['transaction_journal_id']][$name]->toW3CString()));
+
+ continue;
+ }
+ $this->metaData[(int)$entry['transaction_journal_id']][$name] = $data;
+ }
+ }
+
+ private function collectNotes(): void
+ {
+ $notes = Note::query()->whereIn('noteable_id', $this->journalIds)
+ ->whereNotNull('notes.text')
+ ->where('notes.text', '!=', '')
+ ->where('noteable_type', TransactionJournal::class)->get(['notes.noteable_id', 'notes.text'])->toArray();
+ foreach ($notes as $note) {
+ $this->notes[(int)$note['noteable_id']] = (string)$note['text'];
+ }
+ Log::debug(sprintf('Enrich with %d note(s)', count($this->notes)));
+ }
+
+ private function collectTags(): void
+ {
+ $set = Tag::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id')
+ ->whereIn('tag_transaction_journal.transaction_journal_id', $this->journalIds)
+ ->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag'])->toArray();
+ foreach ($set as $item) {
+ $journalId = $item['transaction_journal_id'];
+ $this->tags[$journalId] ??= [];
+ $this->tags[$journalId][] = $item['tag'];
+ }
}
}
diff --git a/app/Support/JsonApi/Enrichments/WebhookEnrichment.php b/app/Support/JsonApi/Enrichments/WebhookEnrichment.php
index 516705892a..0004c291c8 100644
--- a/app/Support/JsonApi/Enrichments/WebhookEnrichment.php
+++ b/app/Support/JsonApi/Enrichments/WebhookEnrichment.php
@@ -43,16 +43,15 @@ use stdClass;
class WebhookEnrichment implements EnrichmentInterface
{
private Collection $collection;
- private User $user; // @phpstan-ignore-line
- private UserGroup $userGroup; // @phpstan-ignore-line
- private array $ids = [];
- private array $deliveries = [];
- private array $responses = [];
- private array $triggers = [];
-
- private array $webhookDeliveries = [];
- private array $webhookResponses = [];
- private array $webhookTriggers = [];
+ private array $deliveries = []; // @phpstan-ignore-line
+ private array $ids = []; // @phpstan-ignore-line
+ private array $responses = [];
+ private array $triggers = [];
+ private User $user;
+ private UserGroup $userGroup;
+ private array $webhookDeliveries = [];
+ private array $webhookResponses = [];
+ private array $webhookTriggers = [];
public function enrich(Collection $collection): Collection
{
@@ -67,7 +66,7 @@ class WebhookEnrichment implements EnrichmentInterface
return $this->collection;
}
- public function enrichSingle(array|Model $model): array|Model
+ public function enrichSingle(array | Model $model): array | Model
{
Log::debug(__METHOD__);
$collection = new Collection()->push($model);
@@ -86,6 +85,20 @@ class WebhookEnrichment implements EnrichmentInterface
$this->userGroup = $userGroup;
}
+ private function appendCollectedInfo(): void
+ {
+ $this->collection = $this->collection->map(function (Webhook $item) {
+ $meta = [
+ 'deliveries' => $this->webhookDeliveries[$item->id] ?? [],
+ 'responses' => $this->webhookResponses[$item->id] ?? [],
+ 'triggers' => $this->webhookTriggers[$item->id] ?? [],
+ ];
+ $item->meta = $meta;
+
+ return $item;
+ });
+ }
+
private function collectIds(): void
{
/** @var Webhook $webhook */
@@ -147,18 +160,4 @@ class WebhookEnrichment implements EnrichmentInterface
$this->webhookTriggers[$id][] = WebhookTriggerEnum::from($this->triggers[$triggerId])->name;
}
}
-
- private function appendCollectedInfo(): void
- {
- $this->collection = $this->collection->map(function (Webhook $item) {
- $meta = [
- 'deliveries' => $this->webhookDeliveries[$item->id] ?? [],
- 'responses' => $this->webhookResponses[$item->id] ?? [],
- 'triggers' => $this->webhookTriggers[$item->id] ?? [],
- ];
- $item->meta = $meta;
-
- return $item;
- });
- }
}
diff --git a/app/Support/Models/AccountBalanceCalculator.php b/app/Support/Models/AccountBalanceCalculator.php
index d2a3572b7d..d9e616f9b9 100644
--- a/app/Support/Models/AccountBalanceCalculator.php
+++ b/app/Support/Models/AccountBalanceCalculator.php
@@ -62,6 +62,46 @@ class AccountBalanceCalculator
$object->optimizedCalculation(new Collection());
}
+ public static function recalculateForJournal(TransactionJournal $transactionJournal): void
+ {
+ Log::debug(__METHOD__);
+ $object = new self();
+
+ $set = [];
+ foreach ($transactionJournal->transactions as $transaction) {
+ $set[$transaction->account_id] = $transaction->account;
+ }
+ $accounts = new Collection()->push(...$set);
+ $object->optimizedCalculation($accounts, $transactionJournal->date);
+ }
+
+ private function getLatestBalance(int $accountId, int $currencyId, ?Carbon $notBefore): string
+ {
+ if (!$notBefore instanceof Carbon) {
+ return '0';
+ }
+ Log::debug(sprintf('getLatestBalance: notBefore date is "%s", calculating', $notBefore->format('Y-m-d')));
+ $query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->whereNull('transactions.deleted_at')
+ ->where('transaction_journals.transaction_currency_id', $currencyId)
+ ->whereNull('transaction_journals.deleted_at')
+ // this order is the same as GroupCollector
+ ->orderBy('transaction_journals.date', 'DESC')
+ ->orderBy('transaction_journals.order', 'ASC')
+ ->orderBy('transaction_journals.id', 'DESC')
+ ->orderBy('transaction_journals.description', 'DESC')
+ ->orderBy('transactions.amount', 'DESC')
+ ->where('transactions.account_id', $accountId);
+ $notBefore->startOfDay();
+ $query->where('transaction_journals.date', '<', $notBefore);
+
+ $first = $query->first(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount', 'transactions.balance_after']);
+ $balance = (string)($first->balance_after ?? '0');
+ Log::debug(sprintf('getLatestBalance: found balance: %s in transaction #%d', $balance, $first->id ?? 0));
+
+ return $balance;
+ }
+
private function optimizedCalculation(Collection $accounts, ?Carbon $notBefore = null): void
{
Log::debug('start of optimizedCalculation');
@@ -72,15 +112,14 @@ class AccountBalanceCalculator
$balances = [];
$count = 0;
$query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->whereNull('transactions.deleted_at')
- ->whereNull('transaction_journals.deleted_at')
+ ->whereNull('transactions.deleted_at')
+ ->whereNull('transaction_journals.deleted_at')
// this order is the same as GroupCollector, but in the exact reverse.
- ->orderBy('transaction_journals.date', 'asc')
- ->orderBy('transaction_journals.order', 'desc')
- ->orderBy('transaction_journals.id', 'asc')
- ->orderBy('transaction_journals.description', 'asc')
- ->orderBy('transactions.amount', 'asc')
- ;
+ ->orderBy('transaction_journals.date', 'asc')
+ ->orderBy('transaction_journals.order', 'desc')
+ ->orderBy('transaction_journals.id', 'asc')
+ ->orderBy('transaction_journals.description', 'asc')
+ ->orderBy('transactions.amount', 'asc');
if ($accounts->count() > 0) {
$query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray());
}
@@ -89,7 +128,7 @@ class AccountBalanceCalculator
$query->where('transaction_journals.date', '>=', $notBefore);
}
- $set = $query->get(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount']);
+ $set = $query->get(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount']);
Log::debug(sprintf('Counted %d transaction(s)', $set->count()));
// the balance value is an array.
@@ -102,8 +141,8 @@ class AccountBalanceCalculator
$balances[$entry->account_id][$entry->transaction_currency_id] ??= [$this->getLatestBalance($entry->account_id, $entry->transaction_currency_id, $notBefore), null];
// before and after are easy:
- $before = $balances[$entry->account_id][$entry->transaction_currency_id][0];
- $after = bcadd($before, (string)$entry->amount);
+ $before = $balances[$entry->account_id][$entry->transaction_currency_id][0];
+ $after = bcadd($before, (string)$entry->amount);
if (true === $entry->balance_dirty || $accounts->count() > 0) {
// update the transaction:
$entry->balance_before = $before;
@@ -123,34 +162,6 @@ class AccountBalanceCalculator
$this->storeAccountBalances($balances);
}
- private function getLatestBalance(int $accountId, int $currencyId, ?Carbon $notBefore): string
- {
- if (!$notBefore instanceof Carbon) {
- return '0';
- }
- Log::debug(sprintf('getLatestBalance: notBefore date is "%s", calculating', $notBefore->format('Y-m-d')));
- $query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->whereNull('transactions.deleted_at')
- ->where('transaction_journals.transaction_currency_id', $currencyId)
- ->whereNull('transaction_journals.deleted_at')
- // this order is the same as GroupCollector
- ->orderBy('transaction_journals.date', 'DESC')
- ->orderBy('transaction_journals.order', 'ASC')
- ->orderBy('transaction_journals.id', 'DESC')
- ->orderBy('transaction_journals.description', 'DESC')
- ->orderBy('transactions.amount', 'DESC')
- ->where('transactions.account_id', $accountId)
- ;
- $notBefore->startOfDay();
- $query->where('transaction_journals.date', '<', $notBefore);
-
- $first = $query->first(['transactions.id', 'transactions.balance_dirty', 'transactions.transaction_currency_id', 'transaction_journals.date', 'transactions.account_id', 'transactions.amount', 'transactions.balance_after']);
- $balance = (string)($first->balance_after ?? '0');
- Log::debug(sprintf('getLatestBalance: found balance: %s in transaction #%d', $balance, $first->id ?? 0));
-
- return $balance;
- }
-
private function storeAccountBalances(array $balances): void
{
/**
@@ -196,17 +207,4 @@ class AccountBalanceCalculator
}
}
}
-
- public static function recalculateForJournal(TransactionJournal $transactionJournal): void
- {
- Log::debug(__METHOD__);
- $object = new self();
-
- $set = [];
- foreach ($transactionJournal->transactions as $transaction) {
- $set[$transaction->account_id] = $transaction->account;
- }
- $accounts = new Collection()->push(...$set);
- $object->optimizedCalculation($accounts, $transactionJournal->date);
- }
}
diff --git a/app/Support/Models/BillDateCalculator.php b/app/Support/Models/BillDateCalculator.php
index 3d49c867a3..9f40348777 100644
--- a/app/Support/Models/BillDateCalculator.php
+++ b/app/Support/Models/BillDateCalculator.php
@@ -49,15 +49,15 @@ class BillDateCalculator
Log::debug(sprintf('Dates must be between %s and %s.', $earliest->format('Y-m-d'), $latest->format('Y-m-d')));
Log::debug(sprintf('Bill started on %s, period is "%s", skip is %d, last paid = "%s".', $billStart->format('Y-m-d'), $period, $skip, $lastPaid?->format('Y-m-d')));
- $daysUntilEOM = app('navigation')->daysUntilEndOfMonth($billStart);
+ $daysUntilEOM = app('navigation')->daysUntilEndOfMonth($billStart);
Log::debug(sprintf('For bill start, days until end of month is %d', $daysUntilEOM));
- $set = new Collection();
- $currentStart = clone $earliest;
+ $set = new Collection();
+ $currentStart = clone $earliest;
// 2023-06-23 subDay to fix 7655
$currentStart->subDay();
- $loop = 0;
+ $loop = 0;
Log::debug('Start of loop');
while ($currentStart <= $latest) {
@@ -107,7 +107,7 @@ class BillDateCalculator
// for the next loop, go to end of period, THEN add day.
Log::debug('Add one day to nextExpectedMatch/currentStart.');
$nextExpectedMatch->addDay();
- $currentStart = clone $nextExpectedMatch;
+ $currentStart = clone $nextExpectedMatch;
++$loop;
if ($loop > 31) {
@@ -117,8 +117,8 @@ class BillDateCalculator
}
}
Log::debug('end of loop');
- $simple = $set->map( // @phpstan-ignore-line
- static fn (Carbon $date) => $date->format('Y-m-d')
+ $simple = $set->map( // @phpstan-ignore-line
+ static fn(Carbon $date) => $date->format('Y-m-d')
);
Log::debug(sprintf('Found %d pay dates', $set->count()), $simple->toArray());
@@ -140,7 +140,7 @@ class BillDateCalculator
return $billStartDate;
}
- $steps = app('navigation')->diffInPeriods($period, $skip, $earliest, $billStartDate);
+ $steps = app('navigation')->diffInPeriods($period, $skip, $earliest, $billStartDate);
if ($steps === $this->diffInMonths) {
Log::debug(sprintf('Steps is %d, which is the same as diffInMonths (%d), so we add another 1.', $steps, $this->diffInMonths));
++$steps;
diff --git a/app/Support/Models/ReturnsIntegerIdTrait.php b/app/Support/Models/ReturnsIntegerIdTrait.php
index 804b9be384..d8178e07a5 100644
--- a/app/Support/Models/ReturnsIntegerIdTrait.php
+++ b/app/Support/Models/ReturnsIntegerIdTrait.php
@@ -39,7 +39,7 @@ trait ReturnsIntegerIdTrait
protected function id(): Attribute
{
return Attribute::make(
- get: static fn ($value) => (int) $value,
+ get: static fn($value) => (int)$value,
);
}
}
diff --git a/app/Support/Models/ReturnsIntegerUserIdTrait.php b/app/Support/Models/ReturnsIntegerUserIdTrait.php
index a0d2bc79e9..8eca6e943c 100644
--- a/app/Support/Models/ReturnsIntegerUserIdTrait.php
+++ b/app/Support/Models/ReturnsIntegerUserIdTrait.php
@@ -37,14 +37,14 @@ trait ReturnsIntegerUserIdTrait
protected function userGroupId(): Attribute
{
return Attribute::make(
- get: static fn ($value) => (int) $value,
+ get: static fn($value) => (int)$value,
);
}
protected function userId(): Attribute
{
return Attribute::make(
- get: static fn ($value) => (int) $value,
+ get: static fn($value) => (int)$value,
);
}
}
diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php
index d88b01ba38..a1e2c256c7 100644
--- a/app/Support/Navigation.php
+++ b/app/Support/Navigation.php
@@ -77,10 +77,10 @@ class Navigation
if (!array_key_exists($repeatFreq, $functionMap)) {
Log::error(sprintf(
- 'The periodicity %s is unknown. Choose one of available periodicity: %s',
- $repeatFreq,
- implode(', ', array_keys($functionMap))
- ));
+ 'The periodicity %s is unknown. Choose one of available periodicity: %s',
+ $repeatFreq,
+ implode(', ', array_keys($functionMap))
+ ));
return $theDate;
}
@@ -88,30 +88,12 @@ class Navigation
return $this->nextDateByInterval($date, $functionMap[$repeatFreq], $skip);
}
- public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon
- {
- try {
- return $this->calculator->nextDateByInterval($epoch, $periodicity, $skipInterval);
- } catch (IntervalException $exception) {
- Log::warning($exception->getMessage(), ['exception' => $exception]);
- } catch (Throwable $exception) {
- Log::error($exception->getMessage(), ['exception' => $exception]);
- }
-
- Log::debug(
- 'Any error occurred to calculate the next date.',
- ['date' => $epoch, 'periodicity' => $periodicity->name, 'skipInterval' => $skipInterval]
- );
-
- return $epoch;
- }
-
public function blockPeriods(Carbon $start, Carbon $end, string $range): array
{
if ($end < $start) {
[$start, $end] = [$end, $start];
}
- $periods = [];
+ $periods = [];
// first, 13 periods of [range]
$loopCount = 0;
$loopDate = clone $end;
@@ -159,86 +141,71 @@ class Navigation
return $periods;
}
- public function startOfPeriod(Carbon $theDate, string $repeatFreq): Carbon
+ public function daysUntilEndOfMonth(Carbon $date): int
{
- $date = clone $theDate;
- // Log::debug(sprintf('Now in startOfPeriod("%s", "%s")', $date->toIso8601String(), $repeatFreq));
- $functionMap = [
- '1D' => 'startOfDay',
- 'daily' => 'startOfDay',
- '1W' => 'startOfWeek',
- 'week' => 'startOfWeek',
- 'weekly' => 'startOfWeek',
- 'month' => 'startOfMonth',
- '1M' => 'startOfMonth',
- 'monthly' => 'startOfMonth',
- '3M' => 'firstOfQuarter',
- 'quarter' => 'firstOfQuarter',
- 'quarterly' => 'firstOfQuarter',
- 'year' => 'startOfYear',
- 'yearly' => 'startOfYear',
- '1Y' => 'startOfYear',
- 'MTD' => 'startOfMonth',
+ $endOfMonth = $date->copy()->endOfMonth();
+
+ return (int)$date->diffInDays($endOfMonth, true);
+ }
+
+ public function diffInPeriods(string $period, int $skip, Carbon $beginning, Carbon $end): int
+ {
+ Log::debug(sprintf(
+ 'diffInPeriods: %s (skip: %d), between %s and %s.',
+ $period,
+ $skip,
+ $beginning->format('Y-m-d'),
+ $end->format('Y-m-d')
+ ));
+ $map = [
+ 'daily' => 'diffInDays',
+ 'weekly' => 'diffInWeeks',
+ 'monthly' => 'diffInMonths',
+ 'quarterly' => 'diffInMonths',
+ 'half-year' => 'diffInMonths',
+ 'yearly' => 'diffInYears',
];
+ if (!array_key_exists($period, $map)) {
+ Log::warning(sprintf('No diffInPeriods for period "%s"', $period));
- $parameterMap = [
- 'startOfWeek' => [Carbon::MONDAY],
- ];
-
- if (array_key_exists($repeatFreq, $functionMap)) {
- $function = $functionMap[$repeatFreq];
- // Log::debug(sprintf('Function is ->%s()', $function));
- if (array_key_exists($function, $parameterMap)) {
- // Log::debug(sprintf('Parameter map, function becomes ->%s(%s)', $function, implode(', ', $parameterMap[$function])));
- $date->{$function}($parameterMap[$function][0]); // @phpstan-ignore-line
- // Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
-
- return $date;
- }
-
- $date->{$function}(); // @phpstan-ignore-line
- // Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
-
- return $date;
+ return 1;
}
- if ('half-year' === $repeatFreq || '6M' === $repeatFreq) {
- $skipTo = $date->month > 7 ? 6 : 0;
- $date->startOfYear()->addMonths($skipTo);
- // Log::debug(sprintf('Custom call for "%s": addMonths(%d)', $repeatFreq, $skipTo));
- // Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
+ $func = $map[$period];
+ // first do the diff
+ $floatDiff = $beginning->{$func}($end, true); // @phpstan-ignore-line
- return $date;
+ // then correct for quarterly or half-year
+ if ('quarterly' === $period) {
+ Log::debug(sprintf('Q: Corrected %f to %f', $floatDiff, $floatDiff / 3));
+ $floatDiff /= 3;
+ }
+ if ('half-year' === $period) {
+ Log::debug(sprintf('H: Corrected %f to %f', $floatDiff, $floatDiff / 6));
+ $floatDiff /= 6;
}
- $result = match ($repeatFreq) {
- 'last7' => $date->subDays(7)->startOfDay(),
- 'last30' => $date->subDays(30)->startOfDay(),
- 'last90' => $date->subDays(90)->startOfDay(),
- 'last365' => $date->subDays(365)->startOfDay(),
- 'MTD' => $date->startOfMonth()->startOfDay(),
- 'QTD' => $date->firstOfQuarter()->startOfDay(),
- 'YTD' => $date->startOfYear()->startOfDay(),
- default => null,
- };
- if (null !== $result) {
- // Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
+ // then do ceil()
+ $diff = ceil($floatDiff);
- return $result;
+ Log::debug(sprintf('Diff is %f periods (%d rounded up)', $floatDiff, $diff));
+
+ if ($skip > 0) {
+ $parameter = $skip + 1;
+ $diff = ceil($diff / $parameter) * $parameter;
+ Log::debug(sprintf(
+ 'diffInPeriods: skip is %d, so param is %d, and diff becomes %d',
+ $skip,
+ $parameter,
+ $diff
+ ));
}
- if ('custom' === $repeatFreq) {
- // Log::debug(sprintf('Custom, result is "%s"', $date->toIso8601String()));
-
- return $date; // the date is already at the start.
- }
- Log::error(sprintf('Cannot do startOfPeriod for $repeat_freq "%s"', $repeatFreq));
-
- return $theDate;
+ return (int)$diff;
}
public function endOfPeriod(Carbon $end, string $repeatFreq): Carbon
{
- $currentEnd = clone $end;
+ $currentEnd = clone $end;
// Log::debug(sprintf('Now in endOfPeriod("%s", "%s").', $currentEnd->toIso8601String(), $repeatFreq));
$functionMap = [
@@ -272,11 +239,11 @@ class Navigation
Log::debug('Session data available.');
/** @var Carbon $tStart */
- $tStart = session('start', today(config('app.timezone'))->startOfMonth());
+ $tStart = session('start', today(config('app.timezone'))->startOfMonth());
/** @var Carbon $tEnd */
$tEnd = session('end', today(config('app.timezone'))->endOfMonth());
- $diffInDays = (int) $tStart->diffInDays($tEnd, true);
+ $diffInDays = (int)$tStart->diffInDays($tEnd, true);
}
Log::debug(sprintf('Diff in days is %d', $diffInDays));
$currentEnd->addDays($diffInDays);
@@ -286,13 +253,13 @@ class Navigation
if ('MTD' === $repeatFreq) {
$today = today();
if ($today->isSameMonth($end)) {
- return $today->endOfDay();
+ return $today->endOfDay()->milli(0);
}
return $end->endOfMonth();
}
- $result = match ($repeatFreq) {
+ $result = match ($repeatFreq) {
'last7' => $currentEnd->addDays(7)->startOfDay(),
'last30' => $currentEnd->addDays(30)->startOfDay(),
'last90' => $currentEnd->addDays(90)->startOfDay(),
@@ -312,19 +279,19 @@ class Navigation
return $end;
}
- $function = $functionMap[$repeatFreq];
+ $function = $functionMap[$repeatFreq];
if (array_key_exists($repeatFreq, $modifierMap)) {
- $currentEnd->{$function}($modifierMap[$repeatFreq]); // @phpstan-ignore-line
+ $currentEnd->{$function}($modifierMap[$repeatFreq])->milli(0); // @phpstan-ignore-line
if (in_array($repeatFreq, $subDay, true)) {
$currentEnd->subDay();
}
- $currentEnd->endOfDay();
+ $currentEnd->endOfDay()->milli(0);
return $currentEnd;
}
$currentEnd->{$function}(); // @phpstan-ignore-line
- $currentEnd->endOfDay();
+ $currentEnd->endOfDay()->milli(0);
if (in_array($repeatFreq, $subDay, true)) {
$currentEnd->subDay();
}
@@ -333,68 +300,6 @@ class Navigation
return $currentEnd;
}
- public function daysUntilEndOfMonth(Carbon $date): int
- {
- $endOfMonth = $date->copy()->endOfMonth();
-
- return (int) $date->diffInDays($endOfMonth, true);
- }
-
- public function diffInPeriods(string $period, int $skip, Carbon $beginning, Carbon $end): int
- {
- Log::debug(sprintf(
- 'diffInPeriods: %s (skip: %d), between %s and %s.',
- $period,
- $skip,
- $beginning->format('Y-m-d'),
- $end->format('Y-m-d')
- ));
- $map = [
- 'daily' => 'diffInDays',
- 'weekly' => 'diffInWeeks',
- 'monthly' => 'diffInMonths',
- 'quarterly' => 'diffInMonths',
- 'half-year' => 'diffInMonths',
- 'yearly' => 'diffInYears',
- ];
- if (!array_key_exists($period, $map)) {
- Log::warning(sprintf('No diffInPeriods for period "%s"', $period));
-
- return 1;
- }
- $func = $map[$period];
- // first do the diff
- $floatDiff = $beginning->{$func}($end, true); // @phpstan-ignore-line
-
- // then correct for quarterly or half-year
- if ('quarterly' === $period) {
- Log::debug(sprintf('Q: Corrected %f to %f', $floatDiff, $floatDiff / 3));
- $floatDiff /= 3;
- }
- if ('half-year' === $period) {
- Log::debug(sprintf('H: Corrected %f to %f', $floatDiff, $floatDiff / 6));
- $floatDiff /= 6;
- }
-
- // then do ceil()
- $diff = ceil($floatDiff);
-
- Log::debug(sprintf('Diff is %f periods (%d rounded up)', $floatDiff, $diff));
-
- if ($skip > 0) {
- $parameter = $skip + 1;
- $diff = ceil($diff / $parameter) * $parameter;
- Log::debug(sprintf(
- 'diffInPeriods: skip is %d, so param is %d, and diff becomes %d',
- $skip,
- $parameter,
- $diff
- ));
- }
-
- return (int) $diff;
- }
-
public function endOfX(Carbon $theCurrentEnd, string $repeatFreq, ?Carbon $maxDate): Carbon
{
$functionMap = [
@@ -414,7 +319,7 @@ class Navigation
'yearly' => 'endOfYear',
];
- $currentEnd = clone $theCurrentEnd;
+ $currentEnd = clone $theCurrentEnd;
if (array_key_exists($repeatFreq, $functionMap)) {
$function = $functionMap[$repeatFreq];
@@ -438,7 +343,7 @@ class Navigation
if (is_array($range)) {
$range = '1M';
}
- $range = (string) $range;
+ $range = (string)$range;
if (!$correct) {
return $range;
}
@@ -457,25 +362,25 @@ class Navigation
*/
public function listOfPeriods(Carbon $start, Carbon $end): array
{
- $locale = app('steam')->getLocale();
+ $locale = app('steam')->getLocale();
// define period to increment
$increment = 'addDay';
$format = $this->preferredCarbonFormat($start, $end);
- $displayFormat = (string) trans('config.month_and_day_js', [], $locale);
+ $displayFormat = (string)trans('config.month_and_day_js', [], $locale);
$diff = $start->diffInMonths($end, true);
// increment by month (for year)
if ($diff >= 1.0001 && $diff < 12.001) {
$increment = 'addMonth';
- $displayFormat = (string) trans('config.month_js');
+ $displayFormat = (string)trans('config.month_js');
}
// increment by year (for multi-year)
if ($diff >= 12.0001) {
$increment = 'addYear';
- $displayFormat = (string) trans('config.year_js');
+ $displayFormat = (string)trans('config.year_js');
}
- $begin = clone $start;
- $entries = [];
+ $begin = clone $start;
+ $entries = [];
while ($begin < $end) {
$formatted = $begin->format($format);
$displayed = $begin->isoFormat($displayFormat);
@@ -486,6 +391,59 @@ class Navigation
return $entries;
}
+ public function nextDateByInterval(Carbon $epoch, Periodicity $periodicity, int $skipInterval = 0): Carbon
+ {
+ try {
+ return $this->calculator->nextDateByInterval($epoch, $periodicity, $skipInterval);
+ } catch (IntervalException $exception) {
+ Log::warning($exception->getMessage(), ['exception' => $exception]);
+ } catch (Throwable $exception) {
+ Log::error($exception->getMessage(), ['exception' => $exception]);
+ }
+
+ Log::debug(
+ 'Any error occurred to calculate the next date.',
+ ['date' => $epoch, 'periodicity' => $periodicity->name, 'skipInterval' => $skipInterval]
+ );
+
+ return $epoch;
+ }
+
+ public function periodShow(Carbon $theDate, string $repeatFrequency): string
+ {
+ $date = clone $theDate;
+ $formatMap = [
+ '1D' => (string)trans('config.specific_day_js'),
+ 'daily' => (string)trans('config.specific_day_js'),
+ 'custom' => (string)trans('config.specific_day_js'),
+ '1W' => (string)trans('config.week_in_year_js'),
+ 'week' => (string)trans('config.week_in_year_js'),
+ 'weekly' => (string)trans('config.week_in_year_js'),
+ '1M' => (string)trans('config.month_js'),
+ 'month' => (string)trans('config.month_js'),
+ 'monthly' => (string)trans('config.month_js'),
+ '1Y' => (string)trans('config.year_js'),
+ 'year' => (string)trans('config.year_js'),
+ 'yearly' => (string)trans('config.year_js'),
+ '6M' => (string)trans('config.half_year_js'),
+ ];
+
+ if (array_key_exists($repeatFrequency, $formatMap)) {
+ return $date->isoFormat($formatMap[$repeatFrequency]);
+ }
+ if ('3M' === $repeatFrequency || 'quarter' === $repeatFrequency) {
+ $quarter = ceil($theDate->month / 3);
+
+ return sprintf('Q%d %d', $quarter, $theDate->year);
+ }
+
+ // special formatter for quarter of year
+ Log::error(sprintf('No date formats for frequency "%s"!', $repeatFrequency));
+ throw new FireflyException(sprintf('No date formats for frequency "%s"!', $repeatFrequency));
+
+ return $date->format('Y-m-d');
+ }
+
/**
* If the date difference between start and end is less than a month, method returns "Y-m-d". If the difference is
* less than a year, method returns "Y-m". If the date difference is larger, method returns "Y".
@@ -508,40 +466,6 @@ class Navigation
return $format;
}
- public function periodShow(Carbon $theDate, string $repeatFrequency): string
- {
- $date = clone $theDate;
- $formatMap = [
- '1D' => (string) trans('config.specific_day_js'),
- 'daily' => (string) trans('config.specific_day_js'),
- 'custom' => (string) trans('config.specific_day_js'),
- '1W' => (string) trans('config.week_in_year_js'),
- 'week' => (string) trans('config.week_in_year_js'),
- 'weekly' => (string) trans('config.week_in_year_js'),
- '1M' => (string) trans('config.month_js'),
- 'month' => (string) trans('config.month_js'),
- 'monthly' => (string) trans('config.month_js'),
- '1Y' => (string) trans('config.year_js'),
- 'year' => (string) trans('config.year_js'),
- 'yearly' => (string) trans('config.year_js'),
- '6M' => (string) trans('config.half_year_js'),
- ];
-
- if (array_key_exists($repeatFrequency, $formatMap)) {
- return $date->isoFormat($formatMap[$repeatFrequency]);
- }
- if ('3M' === $repeatFrequency || 'quarter' === $repeatFrequency) {
- $quarter = ceil($theDate->month / 3);
-
- return sprintf('Q%d %d', $quarter, $theDate->year);
- }
-
- // special formatter for quarter of year
- Log::error(sprintf('No date formats for frequency "%s"!', $repeatFrequency));
-
- return $date->format('Y-m-d');
- }
-
/**
* Same as preferredCarbonFormat but by string
*/
@@ -567,14 +491,14 @@ class Navigation
$locale = app('steam')->getLocale();
$diff = $start->diffInMonths($end, true);
if ($diff >= 1.001 && $diff < 12.001) {
- return (string) trans('config.month_js', [], $locale);
+ return (string)trans('config.month_js', [], $locale);
}
if ($diff >= 12.001) {
- return (string) trans('config.year_js', [], $locale);
+ return (string)trans('config.year_js', [], $locale);
}
- return (string) trans('config.month_and_day_js', [], $locale);
+ return (string)trans('config.month_and_day_js', [], $locale);
}
/**
@@ -631,13 +555,90 @@ class Navigation
return '%Y-%m-%d';
}
+ public function startOfPeriod(Carbon $theDate, string $repeatFreq): Carbon
+ {
+ $date = clone $theDate;
+ // Log::debug(sprintf('Now in startOfPeriod("%s", "%s")', $date->toIso8601String(), $repeatFreq));
+ $functionMap = [
+ '1D' => 'startOfDay',
+ 'daily' => 'startOfDay',
+ '1W' => 'startOfWeek',
+ 'week' => 'startOfWeek',
+ 'weekly' => 'startOfWeek',
+ 'month' => 'startOfMonth',
+ '1M' => 'startOfMonth',
+ 'monthly' => 'startOfMonth',
+ '3M' => 'firstOfQuarter',
+ 'quarter' => 'firstOfQuarter',
+ 'quarterly' => 'firstOfQuarter',
+ 'year' => 'startOfYear',
+ 'yearly' => 'startOfYear',
+ '1Y' => 'startOfYear',
+ 'MTD' => 'startOfMonth',
+ ];
+
+ $parameterMap = [
+ 'startOfWeek' => [Carbon::MONDAY],
+ ];
+
+ if (array_key_exists($repeatFreq, $functionMap)) {
+ $function = $functionMap[$repeatFreq];
+ // Log::debug(sprintf('Function is ->%s()', $function));
+ if (array_key_exists($function, $parameterMap)) {
+ // Log::debug(sprintf('Parameter map, function becomes ->%s(%s)', $function, implode(', ', $parameterMap[$function])));
+ $date->{$function}($parameterMap[$function][0]); // @phpstan-ignore-line
+ // Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
+
+ return $date;
+ }
+
+ $date->{$function}(); // @phpstan-ignore-line
+ // Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
+
+ return $date;
+ }
+ if ('half-year' === $repeatFreq || '6M' === $repeatFreq) {
+ $skipTo = $date->month > 7 ? 6 : 0;
+ $date->startOfYear()->addMonths($skipTo);
+ // Log::debug(sprintf('Custom call for "%s": addMonths(%d)', $repeatFreq, $skipTo));
+ // Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
+
+ return $date;
+ }
+
+ $result = match ($repeatFreq) {
+ 'last7' => $date->subDays(7)->startOfDay(),
+ 'last30' => $date->subDays(30)->startOfDay(),
+ 'last90' => $date->subDays(90)->startOfDay(),
+ 'last365' => $date->subDays(365)->startOfDay(),
+ 'MTD' => $date->startOfMonth()->startOfDay(),
+ 'QTD' => $date->firstOfQuarter()->startOfDay(),
+ 'YTD' => $date->startOfYear()->startOfDay(),
+ default => null,
+ };
+ if (null !== $result) {
+ // Log::debug(sprintf('Result is "%s"', $date->toIso8601String()));
+
+ return $result;
+ }
+
+ if ('custom' === $repeatFreq) {
+ // Log::debug(sprintf('Custom, result is "%s"', $date->toIso8601String()));
+
+ return $date; // the date is already at the start.
+ }
+ Log::error(sprintf('Cannot do startOfPeriod for $repeat_freq "%s"', $repeatFreq));
+
+ return $theDate;
+ }
+
/**
* @throws FireflyException
*/
public function subtractPeriod(Carbon $theDate, string $repeatFreq, ?int $subtract = null): Carbon
{
$subtract ??= 1;
- $date = clone $theDate;
+ $date = clone $theDate;
// 1D 1W 1M 3M 6M 1Y
$functionMap = [
'1D' => 'subDays',
@@ -676,11 +677,11 @@ class Navigation
// this is then subtracted from $theDate (* $subtract).
if ('custom' === $repeatFreq) {
/** @var Carbon $tStart */
- $tStart = session('start', today(config('app.timezone'))->startOfMonth());
+ $tStart = session('start', today(config('app.timezone'))->startOfMonth());
/** @var Carbon $tEnd */
$tEnd = session('end', today(config('app.timezone'))->endOfMonth());
- $diffInDays = (int) $tStart->diffInDays($tEnd, true);
+ $diffInDays = (int)$tStart->diffInDays($tEnd, true);
$date->subDays($diffInDays * $subtract);
return $date;
@@ -770,7 +771,7 @@ class Navigation
return $fiscalHelper->endOfFiscalYear($end);
}
- $list = [
+ $list = [
'last7',
'last30',
'last90',
diff --git a/app/Support/Observers/RecalculatesAvailableBudgetsTrait.php b/app/Support/Observers/RecalculatesAvailableBudgetsTrait.php
index 3f50036ab1..d71872971c 100644
--- a/app/Support/Observers/RecalculatesAvailableBudgetsTrait.php
+++ b/app/Support/Observers/RecalculatesAvailableBudgetsTrait.php
@@ -39,12 +39,98 @@ use Spatie\Period\Precision;
trait RecalculatesAvailableBudgetsTrait
{
+ private function calculateAmount(AvailableBudget $availableBudget): void
+ {
+ $repository = app(BudgetLimitRepositoryInterface::class);
+ $repository->setUser($availableBudget->user);
+ $newAmount = '0';
+ $abPeriod = Period::make($availableBudget->start_date, $availableBudget->end_date, Precision::DAY());
+ Log::debug(
+ sprintf(
+ 'Now at AB #%d, ("%s" to "%s")',
+ $availableBudget->id,
+ $availableBudget->start_date->format('Y-m-d'),
+ $availableBudget->end_date->format('Y-m-d')
+ )
+ );
+ // have to recalculate everything just in case.
+ $set = $repository->getAllBudgetLimitsByCurrency($availableBudget->transactionCurrency, $availableBudget->start_date, $availableBudget->end_date);
+ Log::debug(sprintf('Found %d interesting budget limit(s).', $set->count()));
+
+ /** @var BudgetLimit $budgetLimit */
+ foreach ($set as $budgetLimit) {
+ Log::debug(
+ sprintf(
+ 'Found interesting budget limit #%d ("%s" to "%s")',
+ $budgetLimit->id,
+ $budgetLimit->start_date->format('Y-m-d'),
+ $budgetLimit->end_date->format('Y-m-d')
+ )
+ );
+ // overlap in days:
+ $limitPeriod = Period::make(
+ $budgetLimit->start_date,
+ $budgetLimit->end_date,
+ precision : Precision::DAY(),
+ boundaries: Boundaries::EXCLUDE_NONE()
+ );
+ // if both equal each other, amount from this BL must be added to the AB
+ if ($limitPeriod->equals($abPeriod)) {
+ Log::debug('This budget limit is equal to the available budget period.');
+ $newAmount = bcadd($newAmount, (string)$budgetLimit->amount);
+ }
+ // if budget limit period is inside AB period, it can be added in full.
+ if (!$limitPeriod->equals($abPeriod) && $abPeriod->contains($limitPeriod)) {
+ Log::debug('This budget limit is smaller than the available budget period.');
+ $newAmount = bcadd($newAmount, (string)$budgetLimit->amount);
+ }
+ if (!$limitPeriod->equals($abPeriod) && !$abPeriod->contains($limitPeriod) && $abPeriod->overlapsWith($limitPeriod)) {
+ Log::debug('This budget limit is something else entirely!');
+ $overlap = $abPeriod->overlap($limitPeriod);
+ if ($overlap instanceof Period) {
+ $length = $overlap->length();
+ $daily = bcmul($this->getDailyAmount($budgetLimit), (string)$length);
+ $newAmount = bcadd($newAmount, $daily);
+ }
+ }
+ }
+ if (0 === bccomp('0', $newAmount)) {
+ Log::debug('New amount is zero, deleting AB.');
+ $availableBudget->delete();
+
+ return;
+ }
+ Log::debug(sprintf('Concluded new amount for this AB must be %s', $newAmount));
+ $availableBudget->amount = app('steam')->bcround($newAmount, $availableBudget->transactionCurrency->decimal_places);
+ $availableBudget->save();
+ }
+
+ private function getDailyAmount(BudgetLimit $budgetLimit): string
+ {
+ if (0 === $budgetLimit->id) {
+ return '0';
+ }
+ $limitPeriod = Period::make(
+ $budgetLimit->start_date,
+ $budgetLimit->end_date,
+ precision : Precision::DAY(),
+ boundaries: Boundaries::EXCLUDE_NONE()
+ );
+ $days = $limitPeriod->length();
+ $amount = bcdiv($budgetLimit->amount, (string)$days, 12);
+ Log::debug(
+ sprintf('Total amount for budget limit #%d is %s. Nr. of days is %d. Amount per day is %s', $budgetLimit->id, $budgetLimit->amount, $days, $amount)
+ );
+
+ return $amount;
+ }
+
private function updateAvailableBudget(BudgetLimit $budgetLimit): void
{
Log::debug(sprintf('Now in updateAvailableBudget(limit #%d)', $budgetLimit->id));
/** @var null|Budget $budget */
- $budget = Budget::find($budgetLimit->budget_id);
+ $budget = Budget::find($budgetLimit->budget_id);
if (null === $budget) {
Log::warning('Budget is null, probably deleted, find deleted version.');
@@ -59,7 +145,7 @@ trait RecalculatesAvailableBudgetsTrait
}
/** @var null|User $user */
- $user = $budget->user;
+ $user = $budget->user;
// sanity check. It happens when the budget has been deleted so the original user is unknown.
if (null === $user) {
@@ -75,7 +161,7 @@ trait RecalculatesAvailableBudgetsTrait
// all have to be created or updated.
try {
$viewRange = app('preferences')->getForUser($user, 'viewRange', '1M')->data;
- } catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
+ } catch (ContainerExceptionInterface | NotFoundExceptionInterface $e) {
Log::error($e->getMessage());
$viewRange = '1M';
}
@@ -83,20 +169,20 @@ trait RecalculatesAvailableBudgetsTrait
if (null === $viewRange || is_array($viewRange)) {
$viewRange = '1M';
}
- $viewRange = (string) $viewRange;
+ $viewRange = (string)$viewRange;
- $start = app('navigation')->startOfPeriod($budgetLimit->start_date, $viewRange);
- $end = app('navigation')->startOfPeriod($budgetLimit->end_date, $viewRange);
- $end = app('navigation')->endOfPeriod($end, $viewRange);
+ $start = app('navigation')->startOfPeriod($budgetLimit->start_date, $viewRange);
+ $end = app('navigation')->startOfPeriod($budgetLimit->end_date, $viewRange);
+ $end = app('navigation')->endOfPeriod($end, $viewRange);
// limit period in total is:
$limitPeriod = Period::make($start, $end, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE());
Log::debug(sprintf('Limit period is from %s to %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
// from the start until the end of the budget limit, need to loop!
- $current = clone $start;
+ $current = clone $start;
while ($current <= $end) {
- $currentEnd = app('navigation')->endOfPeriod($current, $viewRange);
+ $currentEnd = app('navigation')->endOfPeriod($current, $viewRange);
// create or find AB for this particular period, and set the amount accordingly.
/** @var null|AvailableBudget $availableBudget */
@@ -111,7 +197,7 @@ trait RecalculatesAvailableBudgetsTrait
// if not exists:
$currentPeriod = Period::make($current, $currentEnd, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE());
$daily = $this->getDailyAmount($budgetLimit);
- $amount = bcmul($daily, (string) $currentPeriod->length(), 12);
+ $amount = bcmul($daily, (string)$currentPeriod->length(), 12);
// no need to calculate if period is equal.
if ($currentPeriod->equals($limitPeriod)) {
@@ -141,93 +227,7 @@ trait RecalculatesAvailableBudgetsTrait
}
// prep for next loop
- $current = app('navigation')->addPeriod($current, $viewRange, 0);
+ $current = app('navigation')->addPeriod($current, $viewRange, 0);
}
}
-
- private function calculateAmount(AvailableBudget $availableBudget): void
- {
- $repository = app(BudgetLimitRepositoryInterface::class);
- $repository->setUser($availableBudget->user);
- $newAmount = '0';
- $abPeriod = Period::make($availableBudget->start_date, $availableBudget->end_date, Precision::DAY());
- Log::debug(
- sprintf(
- 'Now at AB #%d, ("%s" to "%s")',
- $availableBudget->id,
- $availableBudget->start_date->format('Y-m-d'),
- $availableBudget->end_date->format('Y-m-d')
- )
- );
- // have to recalculate everything just in case.
- $set = $repository->getAllBudgetLimitsByCurrency($availableBudget->transactionCurrency, $availableBudget->start_date, $availableBudget->end_date);
- Log::debug(sprintf('Found %d interesting budget limit(s).', $set->count()));
-
- /** @var BudgetLimit $budgetLimit */
- foreach ($set as $budgetLimit) {
- Log::debug(
- sprintf(
- 'Found interesting budget limit #%d ("%s" to "%s")',
- $budgetLimit->id,
- $budgetLimit->start_date->format('Y-m-d'),
- $budgetLimit->end_date->format('Y-m-d')
- )
- );
- // overlap in days:
- $limitPeriod = Period::make(
- $budgetLimit->start_date,
- $budgetLimit->end_date,
- precision : Precision::DAY(),
- boundaries: Boundaries::EXCLUDE_NONE()
- );
- // if both equal each other, amount from this BL must be added to the AB
- if ($limitPeriod->equals($abPeriod)) {
- Log::debug('This budget limit is equal to the available budget period.');
- $newAmount = bcadd($newAmount, (string) $budgetLimit->amount);
- }
- // if budget limit period is inside AB period, it can be added in full.
- if (!$limitPeriod->equals($abPeriod) && $abPeriod->contains($limitPeriod)) {
- Log::debug('This budget limit is smaller than the available budget period.');
- $newAmount = bcadd($newAmount, (string) $budgetLimit->amount);
- }
- if (!$limitPeriod->equals($abPeriod) && !$abPeriod->contains($limitPeriod) && $abPeriod->overlapsWith($limitPeriod)) {
- Log::debug('This budget limit is something else entirely!');
- $overlap = $abPeriod->overlap($limitPeriod);
- if ($overlap instanceof Period) {
- $length = $overlap->length();
- $daily = bcmul($this->getDailyAmount($budgetLimit), (string) $length);
- $newAmount = bcadd($newAmount, $daily);
- }
- }
- }
- if (0 === bccomp('0', $newAmount)) {
- Log::debug('New amount is zero, deleting AB.');
- $availableBudget->delete();
-
- return;
- }
- Log::debug(sprintf('Concluded new amount for this AB must be %s', $newAmount));
- $availableBudget->amount = app('steam')->bcround($newAmount, $availableBudget->transactionCurrency->decimal_places);
- $availableBudget->save();
- }
-
- private function getDailyAmount(BudgetLimit $budgetLimit): string
- {
- if (0 === $budgetLimit->id) {
- return '0';
- }
- $limitPeriod = Period::make(
- $budgetLimit->start_date,
- $budgetLimit->end_date,
- precision : Precision::DAY(),
- boundaries: Boundaries::EXCLUDE_NONE()
- );
- $days = $limitPeriod->length();
- $amount = bcdiv($budgetLimit->amount, (string) $days, 12);
- Log::debug(
- sprintf('Total amount for budget limit #%d is %s. Nr. of days is %d. Amount per day is %s', $budgetLimit->id, $budgetLimit->amount, $days, $amount)
- );
-
- return $amount;
- }
}
diff --git a/app/Support/ParseDateString.php b/app/Support/ParseDateString.php
index f2ab22a672..bd91585f8b 100644
--- a/app/Support/ParseDateString.php
+++ b/app/Support/ParseDateString.php
@@ -29,7 +29,6 @@ use Carbon\CarbonInterface;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Exceptions\FireflyException;
use Illuminate\Support\Facades\Log;
-
use function Safe\preg_match;
/**
@@ -79,15 +78,15 @@ class ParseDateString
public function parseDate(string $date): Carbon
{
Log::debug(sprintf('parseDate("%s")', $date));
- $date = strtolower($date);
+ $date = strtolower($date);
// parse keywords:
if (in_array($date, $this->keywords, true)) {
return $this->parseKeyword($date);
}
// if regex for YYYY-MM-DD:
- $pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/';
- $result = preg_match($pattern, $date);
+ $pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/';
+ $result = preg_match($pattern, $date);
if (0 !== $result) {
return $this->parseDefaultDate($date);
}
@@ -114,99 +113,13 @@ class ParseDateString
return new Carbon('1984-09-17');
}
// maybe a year, nothing else?
- if (4 === strlen($date) && is_numeric($date) && (int) $date > 1000 && (int) $date <= 3000) {
+ if (4 === strlen($date) && is_numeric($date) && (int)$date > 1000 && (int)$date <= 3000) {
return new Carbon(sprintf('%d-01-01', $date));
}
throw new FireflyException(sprintf('[d] Not a recognised date format: "%s"', $date));
}
- protected function parseKeyword(string $keyword): Carbon
- {
- $today = today(config('app.timezone'))->startOfDay();
-
- return match ($keyword) {
- default => $today,
- 'yesterday' => $today->subDay(),
- 'tomorrow' => $today->addDay(),
- 'start of this week' => $today->startOfWeek(CarbonInterface::MONDAY),
- 'end of this week' => $today->endOfWeek(CarbonInterface::SUNDAY),
- 'start of this month' => $today->startOfMonth(),
- 'end of this month' => $today->endOfMonth(),
- 'start of this quarter' => $today->startOfQuarter(),
- 'end of this quarter' => $today->endOfQuarter(),
- 'start of this year' => $today->startOfYear(),
- 'end of this year' => $today->endOfYear(),
- };
- }
-
- protected function parseDefaultDate(string $date): Carbon
- {
- $result = false;
-
- try {
- $result = Carbon::createFromFormat('Y-m-d', $date);
- } catch (InvalidFormatException $e) {
- Log::error(sprintf('parseDefaultDate("%s") ran into an error, but dont mind: %s', $date, $e->getMessage()));
- }
- if (false === $result) {
- return today(config('app.timezone'))->startOfDay();
- }
-
- return $result;
- }
-
- protected function parseRelativeDate(string $date): Carbon
- {
- Log::debug(sprintf('Now in parseRelativeDate("%s")', $date));
- $parts = explode(' ', $date);
- $today = today(config('app.timezone'))->startOfDay();
- $functions = [
- [
- 'd' => 'subDays',
- 'w' => 'subWeeks',
- 'm' => 'subMonths',
- 'q' => 'subQuarters',
- 'y' => 'subYears',
- ],
- [
- 'd' => 'addDays',
- 'w' => 'addWeeks',
- 'm' => 'addMonths',
- 'q' => 'addQuarters',
- 'y' => 'addYears',
- ],
- ];
-
- foreach ($parts as $part) {
- Log::debug(sprintf('Now parsing part "%s"', $part));
- $part = trim($part);
-
- // verify if correct
- $pattern = '/[+-]\d+[wqmdy]/';
- $result = preg_match($pattern, $part);
- if (0 === $result) {
- Log::error(sprintf('Part "%s" does not match regular expression. Will be skipped.', $part));
-
- continue;
- }
- $direction = str_starts_with($part, '+') ? 1 : 0;
- $period = $part[strlen($part) - 1];
- $number = (int) substr($part, 1, -1);
- if (!array_key_exists($period, $functions[$direction])) {
- Log::error(sprintf('No method for direction %d and period "%s".', $direction, $period));
-
- continue;
- }
- $func = $functions[$direction][$period];
- Log::debug(sprintf('Will now do %s(%d) on %s', $func, $number, $today->format('Y-m-d')));
- $today->{$func}($number); // @phpstan-ignore-line
- Log::debug(sprintf('Resulting date is %s', $today->format('Y-m-d')));
- }
-
- return $today;
- }
-
public function parseRange(string $date): array
{
// several types of range can be submitted
@@ -269,16 +182,34 @@ class ParseDateString
return false;
}
- /**
- * format of string is xxxx-xx-DD
- */
- protected function parseDayRange(string $date): array
+ protected function isDayYearRange(string $date): bool
{
- $parts = explode('-', $date);
+ // if regex for YYYY-xx-DD:
+ $pattern = '/^(19|20)\d\d-xx-(0[1-9]|[12]\d|3[01])$/';
+ $result = preg_match($pattern, $date);
+ if (0 !== $result) {
+ Log::debug(sprintf('"%s" is a day/year range.', $date));
- return [
- 'day' => $parts[2],
- ];
+ return true;
+ }
+ Log::debug(sprintf('"%s" is not a day/year range.', $date));
+
+ return false;
+ }
+
+ protected function isMonthDayRange(string $date): bool
+ {
+ // if regex for xxxx-MM-DD:
+ $pattern = '/^xxxx-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/';
+ $result = preg_match($pattern, $date);
+ if (0 !== $result) {
+ Log::debug(sprintf('"%s" is a month/day range.', $date));
+
+ return true;
+ }
+ Log::debug(sprintf('"%s" is not a month/day range.', $date));
+
+ return false;
}
protected function isMonthRange(string $date): bool
@@ -296,17 +227,19 @@ class ParseDateString
return false;
}
- /**
- * format of string is xxxx-MM-xx
- */
- protected function parseMonthRange(string $date): array
+ protected function isMonthYearRange(string $date): bool
{
- Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date));
- $parts = explode('-', $date);
+ // if regex for YYYY-MM-xx:
+ $pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/';
+ $result = preg_match($pattern, $date);
+ if (0 !== $result) {
+ Log::debug(sprintf('"%s" is a month/year range.', $date));
- return [
- 'month' => $parts[1],
- ];
+ return true;
+ }
+ Log::debug(sprintf('"%s" is not a month/year range.', $date));
+
+ return false;
}
protected function isYearRange(string $date): bool
@@ -324,6 +257,131 @@ class ParseDateString
return false;
}
+ /**
+ * format of string is xxxx-xx-DD
+ */
+ protected function parseDayRange(string $date): array
+ {
+ $parts = explode('-', $date);
+
+ return [
+ 'day' => $parts[2],
+ ];
+ }
+
+ protected function parseDefaultDate(string $date): Carbon
+ {
+ $result = false;
+
+ try {
+ $result = Carbon::createFromFormat('Y-m-d', $date);
+ } catch (InvalidFormatException $e) {
+ Log::error(sprintf('parseDefaultDate("%s") ran into an error, but dont mind: %s', $date, $e->getMessage()));
+ }
+ if (false === $result) {
+ return today(config('app.timezone'))->startOfDay();
+ }
+
+ return $result;
+ }
+
+ protected function parseKeyword(string $keyword): Carbon
+ {
+ $today = today(config('app.timezone'))->startOfDay();
+
+ return match ($keyword) {
+ default => $today,
+ 'yesterday' => $today->subDay(),
+ 'tomorrow' => $today->addDay(),
+ 'start of this week' => $today->startOfWeek(CarbonInterface::MONDAY),
+ 'end of this week' => $today->endOfWeek(CarbonInterface::SUNDAY),
+ 'start of this month' => $today->startOfMonth(),
+ 'end of this month' => $today->endOfMonth(),
+ 'start of this quarter' => $today->startOfQuarter(),
+ 'end of this quarter' => $today->endOfQuarter(),
+ 'start of this year' => $today->startOfYear(),
+ 'end of this year' => $today->endOfYear(),
+ };
+ }
+
+ /**
+ * format of string is xxxx-MM-xx
+ */
+ protected function parseMonthRange(string $date): array
+ {
+ Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date));
+ $parts = explode('-', $date);
+
+ return [
+ 'month' => $parts[1],
+ ];
+ }
+
+ /**
+ * format of string is YYYY-MM-xx
+ */
+ protected function parseMonthYearRange(string $date): array
+ {
+ Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date));
+ $parts = explode('-', $date);
+
+ return [
+ 'year' => $parts[0],
+ 'month' => $parts[1],
+ ];
+ }
+
+ protected function parseRelativeDate(string $date): Carbon
+ {
+ Log::debug(sprintf('Now in parseRelativeDate("%s")', $date));
+ $parts = explode(' ', $date);
+ $today = today(config('app.timezone'))->startOfDay();
+ $functions = [
+ [
+ 'd' => 'subDays',
+ 'w' => 'subWeeks',
+ 'm' => 'subMonths',
+ 'q' => 'subQuarters',
+ 'y' => 'subYears',
+ ],
+ [
+ 'd' => 'addDays',
+ 'w' => 'addWeeks',
+ 'm' => 'addMonths',
+ 'q' => 'addQuarters',
+ 'y' => 'addYears',
+ ],
+ ];
+
+ foreach ($parts as $part) {
+ Log::debug(sprintf('Now parsing part "%s"', $part));
+ $part = trim($part);
+
+ // verify if correct
+ $pattern = '/[+-]\d+[wqmdy]/';
+ $result = preg_match($pattern, $part);
+ if (0 === $result) {
+ Log::error(sprintf('Part "%s" does not match regular expression. Will be skipped.', $part));
+
+ continue;
+ }
+ $direction = str_starts_with($part, '+') ? 1 : 0;
+ $period = $part[strlen($part) - 1];
+ $number = (int)substr($part, 1, -1);
+ if (!array_key_exists($period, $functions[$direction])) {
+ Log::error(sprintf('No method for direction %d and period "%s".', $direction, $period));
+
+ continue;
+ }
+ $func = $functions[$direction][$period];
+ Log::debug(sprintf('Will now do %s(%d) on %s', $func, $number, $today->format('Y-m-d')));
+ $today->{$func}($number); // @phpstan-ignore-line
+ Log::debug(sprintf('Resulting date is %s', $today->format('Y-m-d')));
+ }
+
+ return $today;
+ }
+
/**
* format of string is YYYY-xx-xx
*/
@@ -337,50 +395,6 @@ class ParseDateString
];
}
- protected function isMonthDayRange(string $date): bool
- {
- // if regex for xxxx-MM-DD:
- $pattern = '/^xxxx-(0[1-9]|1[012])-(0[1-9]|[12]\d|3[01])$/';
- $result = preg_match($pattern, $date);
- if (0 !== $result) {
- Log::debug(sprintf('"%s" is a month/day range.', $date));
-
- return true;
- }
- Log::debug(sprintf('"%s" is not a month/day range.', $date));
-
- return false;
- }
-
- /**
- * format of string is xxxx-MM-DD
- */
- private function parseMonthDayRange(string $date): array
- {
- Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date));
- $parts = explode('-', $date);
-
- return [
- 'month' => $parts[1],
- 'day' => $parts[2],
- ];
- }
-
- protected function isDayYearRange(string $date): bool
- {
- // if regex for YYYY-xx-DD:
- $pattern = '/^(19|20)\d\d-xx-(0[1-9]|[12]\d|3[01])$/';
- $result = preg_match($pattern, $date);
- if (0 !== $result) {
- Log::debug(sprintf('"%s" is a day/year range.', $date));
-
- return true;
- }
- Log::debug(sprintf('"%s" is not a day/year range.', $date));
-
- return false;
- }
-
/**
* format of string is YYYY-xx-DD
*/
@@ -395,32 +409,17 @@ class ParseDateString
];
}
- protected function isMonthYearRange(string $date): bool
- {
- // if regex for YYYY-MM-xx:
- $pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/';
- $result = preg_match($pattern, $date);
- if (0 !== $result) {
- Log::debug(sprintf('"%s" is a month/year range.', $date));
-
- return true;
- }
- Log::debug(sprintf('"%s" is not a month/year range.', $date));
-
- return false;
- }
-
/**
- * format of string is YYYY-MM-xx
+ * format of string is xxxx-MM-DD
*/
- protected function parseMonthYearRange(string $date): array
+ private function parseMonthDayRange(string $date): array
{
- Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date));
+ Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
- 'year' => $parts[0],
'month' => $parts[1],
+ 'day' => $parts[2],
];
}
}
diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php
index 4a068cae06..f8fc423cc7 100644
--- a/app/Support/Preferences.php
+++ b/app/Support/Preferences.php
@@ -48,73 +48,19 @@ class Preferences
}
return Preference::where('user_id', $user->id)
- ->where('name', '!=', 'currencyPreference')
- ->where(function (Builder $q) use ($user): void {
- $q->whereNull('user_group_id');
- $q->orWhere('user_group_id', $user->user_group_id);
- })
- ->get()
- ;
+ ->where('name', '!=', 'currencyPreference')
+ ->where(function (Builder $q) use ($user): void {
+ $q->whereNull('user_group_id');
+ $q->orWhere('user_group_id', $user->user_group_id);
+ })
+ ->get();
}
- public function get(string $name, array|bool|int|string|null $default = null): ?Preference
+ public function beginsWith(User $user, string $search): Collection
{
- /** @var null|User $user */
- $user = auth()->user();
- if (null === $user) {
- $preference = new Preference();
- $preference->data = $default;
+ $value = sprintf('%s%%', $search);
- return $preference;
- }
-
- return $this->getForUser($user, $name, $default);
- }
-
- public function getForUser(User $user, string $name, array|bool|int|string|null $default = null): ?Preference
- {
- // Log::debug(sprintf('getForUser(#%d, "%s")', $user->id, $name));
- // don't care about user group ID, except for some specific preferences.
- $userGroupId = $this->getUserGroupId($user, $name);
- $query = Preference::where('user_id', $user->id)->where('name', $name);
- if (null !== $userGroupId) {
- Log::debug('Include user group ID in query');
- $query->where('user_group_id', $userGroupId);
- }
-
- $preference = $query->first(['id', 'user_id', 'user_group_id', 'name', 'data', 'updated_at', 'created_at']);
-
- if (null !== $preference && null === $preference->data) {
- $preference->delete();
- $preference = null;
- Log::debug('Removed empty preference.');
- }
-
- if (null !== $preference) {
- // Log::debug(sprintf('Found preference #%d for user #%d: %s', $preference->id, $user->id, $name));
-
- return $preference;
- }
- // no preference found and default is null:
- if (null === $default) {
- Log::debug('Return NULL, create no preference.');
-
- // return NULL
- return null;
- }
-
- return $this->setForUser($user, $name, $default);
- }
-
- private function getUserGroupId(User $user, string $preferenceName): ?int
- {
- $groupId = null;
- $items = config('firefly.admin_specific_prefs') ?? [];
- if (in_array($preferenceName, $items, true)) {
- return (int) $user->user_group_id;
- }
-
- return $groupId;
+ return Preference::where('user_id', $user->id)->whereLike('name', $value)->get();
}
public function delete(string $name): bool
@@ -128,58 +74,6 @@ class Preferences
return true;
}
- public function forget(User $user, string $name): void
- {
- $key = sprintf('preference%s%s', $user->id, $name);
- Cache::forget($key);
- Cache::put($key, '', 5);
- }
-
- public function setForUser(User $user, string $name, array|bool|int|string|null $value): Preference
- {
- $fullName = sprintf('preference%s%s', $user->id, $name);
- $userGroupId = $this->getUserGroupId($user, $name);
- $userGroupId = 0 === (int) $userGroupId ? null : (int) $userGroupId;
-
- Cache::forget($fullName);
-
- $query = Preference::where('user_id', $user->id)->where('name', $name);
- if (null !== $userGroupId) {
- Log::debug('Include user group ID in query');
- $query->where('user_group_id', $userGroupId);
- }
-
- $preference = $query->first(['id', 'user_id', 'user_group_id', 'name', 'data', 'updated_at', 'created_at']);
-
- if (null !== $preference && null === $value) {
- $preference->delete();
-
- return new Preference();
- }
- if (null === $value) {
- return new Preference();
- }
- if (null === $preference) {
- $preference = new Preference();
- $preference->user_id = (int) $user->id;
- $preference->user_group_id = $userGroupId;
- $preference->name = $name;
-
- }
- $preference->data = $value;
- $preference->save();
- Cache::forever($fullName, $preference);
-
- return $preference;
- }
-
- public function beginsWith(User $user, string $search): Collection
- {
- $value = sprintf('%s%%', $search);
-
- return Preference::where('user_id', $user->id)->whereLike('name', $value)->get();
- }
-
/**
* Find by name, has no user ID in it, because the method is called from an unauthenticated route any way.
*/
@@ -188,17 +82,37 @@ class Preferences
return Preference::where('name', $name)->get();
}
+ public function forget(User $user, string $name): void
+ {
+ $key = sprintf('preference%s%s', $user->id, $name);
+ Cache::forget($key);
+ Cache::put($key, '', 5);
+ }
+
+ public function get(string $name, array | bool | int | string | null $default = null): ?Preference
+ {
+ /** @var null|User $user */
+ $user = auth()->user();
+ if (null === $user) {
+ $preference = new Preference();
+ $preference->data = $default;
+
+ return $preference;
+ }
+
+ return $this->getForUser($user, $name, $default);
+ }
+
public function getArrayForUser(User $user, array $list): array
{
$result = [];
$preferences = Preference::where('user_id', $user->id)
- ->where(function (Builder $q) use ($user): void {
- $q->whereNull('user_group_id');
- $q->orWhere('user_group_id', $user->user_group_id);
- })
- ->whereIn('name', $list)
- ->get(['id', 'name', 'data'])
- ;
+ ->where(function (Builder $q) use ($user): void {
+ $q->whereNull('user_group_id');
+ $q->orWhere('user_group_id', $user->user_group_id);
+ })
+ ->whereIn('name', $list)
+ ->get(['id', 'name', 'data']);
/** @var Preference $preference */
foreach ($preferences as $preference) {
@@ -240,7 +154,7 @@ class Preferences
return $result;
}
- public function getEncryptedForUser(User $user, string $name, array|bool|int|string|null $default = null): ?Preference
+ public function getEncryptedForUser(User $user, string $name, array | bool | int | string | null $default = null): ?Preference
{
$result = $this->getForUser($user, $name, $default);
if ('' === $result->data) {
@@ -265,7 +179,42 @@ class Preferences
return $result;
}
- public function getFresh(string $name, array|bool|int|string|null $default = null): ?Preference
+ public function getForUser(User $user, string $name, array | bool | int | string | null $default = null): ?Preference
+ {
+ // Log::debug(sprintf('getForUser(#%d, "%s")', $user->id, $name));
+ // don't care about user group ID, except for some specific preferences.
+ $userGroupId = $this->getUserGroupId($user, $name);
+ $query = Preference::where('user_id', $user->id)->where('name', $name);
+ if (null !== $userGroupId) {
+ Log::debug('Include user group ID in query');
+ $query->where('user_group_id', $userGroupId);
+ }
+
+ $preference = $query->first(['id', 'user_id', 'user_group_id', 'name', 'data', 'updated_at', 'created_at']);
+
+ if (null !== $preference && null === $preference->data) {
+ $preference->delete();
+ $preference = null;
+ Log::debug('Removed empty preference.');
+ }
+
+ if (null !== $preference) {
+ // Log::debug(sprintf('Found preference #%d for user #%d: %s', $preference->id, $user->id, $name));
+
+ return $preference;
+ }
+ // no preference found and default is null:
+ if (null === $default) {
+ Log::debug('Return NULL, create no preference.');
+
+ // return NULL
+ return null;
+ }
+
+ return $this->setForUser($user, $name, $default);
+ }
+
+ public function getFresh(string $name, array | bool | int | string | null $default = null): ?Preference
{
/** @var null|User $user */
$user = auth()->user();
@@ -284,8 +233,8 @@ class Preferences
*/
public function lastActivity(): string
{
- $instance = PreferencesSingleton::getInstance();
- $pref = $instance->getPreference('last_activity');
+ $instance = PreferencesSingleton::getInstance();
+ $pref = $instance->getPreference('last_activity');
if (null !== $pref) {
// Log::debug(sprintf('Found last activity in singleton: %s', $pref));
return $pref;
@@ -299,7 +248,7 @@ class Preferences
if (is_array($lastActivity)) {
$lastActivity = implode(',', $lastActivity);
}
- $setting = hash('sha256', (string) $lastActivity);
+ $setting = hash('sha256', (string)$lastActivity);
$instance->setPreference('last_activity', $setting);
return $setting;
@@ -313,7 +262,7 @@ class Preferences
Session::forget('first');
}
- public function set(string $name, array|bool|int|string|null $value): Preference
+ public function set(string $name, array | bool | int | string | null $value): Preference
{
/** @var null|User $user */
$user = auth()->user();
@@ -341,4 +290,53 @@ class Preferences
return $this->set($name, $encrypted);
}
+
+ public function setForUser(User $user, string $name, array | bool | int | string | null $value): Preference
+ {
+ $fullName = sprintf('preference%s%s', $user->id, $name);
+ $userGroupId = $this->getUserGroupId($user, $name);
+ $userGroupId = 0 === (int)$userGroupId ? null : (int)$userGroupId;
+
+ Cache::forget($fullName);
+
+ $query = Preference::where('user_id', $user->id)->where('name', $name);
+ if (null !== $userGroupId) {
+ Log::debug('Include user group ID in query');
+ $query->where('user_group_id', $userGroupId);
+ }
+
+ $preference = $query->first(['id', 'user_id', 'user_group_id', 'name', 'data', 'updated_at', 'created_at']);
+
+ if (null !== $preference && null === $value) {
+ $preference->delete();
+
+ return new Preference();
+ }
+ if (null === $value) {
+ return new Preference();
+ }
+ if (null === $preference) {
+ $preference = new Preference();
+ $preference->user_id = (int)$user->id;
+ $preference->user_group_id = $userGroupId;
+ $preference->name = $name;
+
+ }
+ $preference->data = $value;
+ $preference->save();
+ Cache::forever($fullName, $preference);
+
+ return $preference;
+ }
+
+ private function getUserGroupId(User $user, string $preferenceName): ?int
+ {
+ $groupId = null;
+ $items = config('firefly.admin_specific_prefs') ?? [];
+ if (in_array($preferenceName, $items, true)) {
+ return (int)$user->user_group_id;
+ }
+
+ return $groupId;
+ }
}
diff --git a/app/Support/Report/Budget/BudgetReportGenerator.php b/app/Support/Report/Budget/BudgetReportGenerator.php
index c478e3cce4..b859847748 100644
--- a/app/Support/Report/Budget/BudgetReportGenerator.php
+++ b/app/Support/Report/Budget/BudgetReportGenerator.php
@@ -76,7 +76,7 @@ class BudgetReportGenerator
/** @var Account $account */
foreach ($this->accounts as $account) {
- $accountId = $account->id;
+ $accountId = $account->id;
$this->report[$accountId] ??= [
'name' => $account->name,
'id' => $account->id,
@@ -91,43 +91,6 @@ class BudgetReportGenerator
}
}
- /**
- * Process each row of expenses collected for the "Account per budget" partial
- */
- private function processExpenses(array $expenses): void
- {
- foreach ($expenses['budgets'] as $budget) {
- $this->processBudgetExpenses($expenses, $budget);
- }
- }
-
- /**
- * Process each set of transactions for each row of expenses.
- */
- private function processBudgetExpenses(array $expenses, array $budget): void
- {
- $budgetId = (int) $budget['id'];
- $currencyId = (int) $expenses['currency_id'];
- foreach ($budget['transaction_journals'] as $journal) {
- $sourceAccountId = $journal['source_account_id'];
-
- $this->report[$sourceAccountId]['currencies'][$currencyId]
- ??= [
- 'currency_id' => $expenses['currency_id'],
- 'currency_symbol' => $expenses['currency_symbol'],
- 'currency_name' => $expenses['currency_name'],
- 'currency_decimal_places' => $expenses['currency_decimal_places'],
- 'budgets' => [],
- ];
-
- $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId]
- ??= '0';
-
- $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId]
- = bcadd($this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId], (string) $journal['amount']);
- }
- }
-
/**
* Generates the data necessary to create the card that displays
* the budget overview in the general report.
@@ -144,175 +107,6 @@ class BudgetReportGenerator
$this->percentageReport();
}
- /**
- * Start the budgets block on the default report by processing every budget.
- */
- private function generalBudgetReport(): void
- {
- $budgetList = $this->repository->getBudgets();
-
- /** @var Budget $budget */
- foreach ($budgetList as $budget) {
- $this->processBudget($budget);
- }
- }
-
- /**
- * Process expenses etc. for a single budget for the budgets block on the default report.
- */
- private function processBudget(Budget $budget): void
- {
- $budgetId = $budget->id;
- $this->report['budgets'][$budgetId] ??= [
- 'budget_id' => $budgetId,
- 'budget_name' => $budget->name,
- 'no_budget' => false,
- 'budget_limits' => [],
- ];
-
- // get all budget limits for budget in period:
- $limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end);
-
- /** @var BudgetLimit $limit */
- foreach ($limits as $limit) {
- $this->processLimit($budget, $limit);
- }
- }
-
- /**
- * Process a single budget limit for the budgets block on the default report.
- */
- private function processLimit(Budget $budget, BudgetLimit $limit): void
- {
- $budgetId = $budget->id;
- $limitId = $limit->id;
- $limitCurrency = $limit->transactionCurrency ?? $this->currency;
- $currencyId = $limitCurrency->id;
- $expenses = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, $this->accounts, new Collection()->push($budget));
- $spent = $expenses[$currencyId]['sum'] ?? '0';
- $left = -1 === bccomp(bcadd($limit->amount, $spent), '0') ? '0' : bcadd($limit->amount, $spent);
- $overspent = 1 === bccomp(bcmul($spent, '-1'), $limit->amount) ? bcadd($spent, $limit->amount) : '0';
-
- $this->report['budgets'][$budgetId]['budget_limits'][$limitId] ??= [
- 'budget_limit_id' => $limitId,
- 'start_date' => $limit->start_date,
- 'end_date' => $limit->end_date,
- 'budgeted' => $limit->amount,
- 'budgeted_pct' => '0',
- 'spent' => $spent,
- 'spent_pct' => '0',
- 'left' => $left,
- 'overspent' => $overspent,
- 'currency_id' => $currencyId,
- 'currency_code' => $limitCurrency->code,
- 'currency_name' => $limitCurrency->name,
- 'currency_symbol' => $limitCurrency->symbol,
- 'currency_decimal_places' => $limitCurrency->decimal_places,
- ];
-
- // make sum information:
- $this->report['sums'][$currencyId]
- ??= [
- 'budgeted' => '0',
- 'spent' => '0',
- 'left' => '0',
- 'overspent' => '0',
- 'currency_id' => $currencyId,
- 'currency_code' => $limitCurrency->code,
- 'currency_name' => $limitCurrency->name,
- 'currency_symbol' => $limitCurrency->symbol,
- 'currency_decimal_places' => $limitCurrency->decimal_places,
- ];
- $this->report['sums'][$currencyId]['budgeted'] = bcadd((string) $this->report['sums'][$currencyId]['budgeted'], $limit->amount);
- $this->report['sums'][$currencyId]['spent'] = bcadd((string) $this->report['sums'][$currencyId]['spent'], $spent);
- $this->report['sums'][$currencyId]['left'] = bcadd((string) $this->report['sums'][$currencyId]['left'], bcadd($limit->amount, $spent));
- $this->report['sums'][$currencyId]['overspent'] = bcadd((string) $this->report['sums'][$currencyId]['overspent'], $overspent);
- }
-
- /**
- * Calculate the expenses for transactions without a budget. Part of the "budgets" block of the default report.
- */
- private function noBudgetReport(): void
- {
- // add no budget info.
- $this->report['budgets'][0] = [
- 'budget_id' => null,
- 'budget_name' => null,
- 'no_budget' => true,
- 'budget_limits' => [],
- ];
-
- $noBudget = $this->nbRepository->sumExpenses($this->start, $this->end, $this->accounts);
- foreach ($noBudget as $noBudgetEntry) {
- // currency information:
- $nbCurrencyId = (int) ($noBudgetEntry['currency_id'] ?? $this->currency->id);
- $nbCurrencyCode = $noBudgetEntry['currency_code'] ?? $this->currency->code;
- $nbCurrencyName = $noBudgetEntry['currency_name'] ?? $this->currency->name;
- $nbCurrencySymbol = $noBudgetEntry['currency_symbol'] ?? $this->currency->symbol;
- $nbCurrencyDp = $noBudgetEntry['currency_decimal_places'] ?? $this->currency->decimal_places;
-
- $this->report['budgets'][0]['budget_limits'][] = [
- 'budget_limit_id' => null,
- 'start_date' => $this->start,
- 'end_date' => $this->end,
- 'budgeted' => '0',
- 'budgeted_pct' => '0',
- 'spent' => $noBudgetEntry['sum'],
- 'spent_pct' => '0',
- 'left' => '0',
- 'overspent' => '0',
- 'currency_id' => $nbCurrencyId,
- 'currency_code' => $nbCurrencyCode,
- 'currency_name' => $nbCurrencyName,
- 'currency_symbol' => $nbCurrencySymbol,
- 'currency_decimal_places' => $nbCurrencyDp,
- ];
- $this->report['sums'][$nbCurrencyId]['spent'] = bcadd($this->report['sums'][$nbCurrencyId]['spent'] ?? '0', (string) $noBudgetEntry['sum']);
- // append currency info because it may be missing:
- $this->report['sums'][$nbCurrencyId]['currency_id'] = $nbCurrencyId;
- $this->report['sums'][$nbCurrencyId]['currency_code'] = $nbCurrencyCode;
- $this->report['sums'][$nbCurrencyId]['currency_name'] = $nbCurrencyName;
- $this->report['sums'][$nbCurrencyId]['currency_symbol'] = $nbCurrencySymbol;
- $this->report['sums'][$nbCurrencyId]['currency_decimal_places'] = $nbCurrencyDp;
-
- // append other sums because they might be missing:
- $this->report['sums'][$nbCurrencyId]['overspent'] ??= '0';
- $this->report['sums'][$nbCurrencyId]['left'] ??= '0';
- $this->report['sums'][$nbCurrencyId]['budgeted'] ??= '0';
- }
- }
-
- /**
- * Calculate the percentages for each budget. Part of the "budgets" block on the default report.
- */
- private function percentageReport(): void
- {
- // make percentages based on total amount.
- foreach ($this->report['budgets'] as $budgetId => $data) {
- foreach ($data['budget_limits'] as $limitId => $entry) {
- $budgetId = (int) $budgetId;
- $limitId = (int) $limitId;
- $currencyId = (int) $entry['currency_id'];
- $spent = $entry['spent'];
- $totalSpent = $this->report['sums'][$currencyId]['spent'] ?? '0';
- $spentPct = '0';
- $budgeted = $entry['budgeted'];
- $totalBudgeted = $this->report['sums'][$currencyId]['budgeted'] ?? '0';
- $budgetedPct = '0';
-
- if (0 !== bccomp((string) $spent, '0') && 0 !== bccomp($totalSpent, '0')) {
- $spentPct = round((float) bcmul(bcdiv((string) $spent, $totalSpent), '100'));
- }
- if (0 !== bccomp((string) $budgeted, '0') && 0 !== bccomp($totalBudgeted, '0')) {
- $budgetedPct = round((float) bcmul(bcdiv((string) $budgeted, $totalBudgeted), '100'));
- }
- $this->report['sums'][$currencyId]['budgeted'] ??= '0';
- $this->report['budgets'][$budgetId]['budget_limits'][$limitId]['spent_pct'] = $spentPct;
- $this->report['budgets'][$budgetId]['budget_limits'][$limitId]['budgeted_pct'] = $budgetedPct;
- }
- }
- }
-
public function getReport(): array
{
return $this->report;
@@ -349,4 +143,210 @@ class BudgetReportGenerator
$this->nbRepository->setUser($user);
$this->currency = app('amount')->getPrimaryCurrencyByUserGroup($user->userGroup);
}
+
+ /**
+ * Start the budgets block on the default report by processing every budget.
+ */
+ private function generalBudgetReport(): void
+ {
+ $budgetList = $this->repository->getBudgets();
+
+ /** @var Budget $budget */
+ foreach ($budgetList as $budget) {
+ $this->processBudget($budget);
+ }
+ }
+
+ /**
+ * Calculate the expenses for transactions without a budget. Part of the "budgets" block of the default report.
+ */
+ private function noBudgetReport(): void
+ {
+ // add no budget info.
+ $this->report['budgets'][0] = [
+ 'budget_id' => null,
+ 'budget_name' => null,
+ 'no_budget' => true,
+ 'budget_limits' => [],
+ ];
+
+ $noBudget = $this->nbRepository->sumExpenses($this->start, $this->end, $this->accounts);
+ foreach ($noBudget as $noBudgetEntry) {
+ // currency information:
+ $nbCurrencyId = (int)($noBudgetEntry['currency_id'] ?? $this->currency->id);
+ $nbCurrencyCode = $noBudgetEntry['currency_code'] ?? $this->currency->code;
+ $nbCurrencyName = $noBudgetEntry['currency_name'] ?? $this->currency->name;
+ $nbCurrencySymbol = $noBudgetEntry['currency_symbol'] ?? $this->currency->symbol;
+ $nbCurrencyDp = $noBudgetEntry['currency_decimal_places'] ?? $this->currency->decimal_places;
+
+ $this->report['budgets'][0]['budget_limits'][] = [
+ 'budget_limit_id' => null,
+ 'start_date' => $this->start,
+ 'end_date' => $this->end,
+ 'budgeted' => '0',
+ 'budgeted_pct' => '0',
+ 'spent' => $noBudgetEntry['sum'],
+ 'spent_pct' => '0',
+ 'left' => '0',
+ 'overspent' => '0',
+ 'currency_id' => $nbCurrencyId,
+ 'currency_code' => $nbCurrencyCode,
+ 'currency_name' => $nbCurrencyName,
+ 'currency_symbol' => $nbCurrencySymbol,
+ 'currency_decimal_places' => $nbCurrencyDp,
+ ];
+ $this->report['sums'][$nbCurrencyId]['spent'] = bcadd($this->report['sums'][$nbCurrencyId]['spent'] ?? '0', (string)$noBudgetEntry['sum']);
+ // append currency info because it may be missing:
+ $this->report['sums'][$nbCurrencyId]['currency_id'] = $nbCurrencyId;
+ $this->report['sums'][$nbCurrencyId]['currency_code'] = $nbCurrencyCode;
+ $this->report['sums'][$nbCurrencyId]['currency_name'] = $nbCurrencyName;
+ $this->report['sums'][$nbCurrencyId]['currency_symbol'] = $nbCurrencySymbol;
+ $this->report['sums'][$nbCurrencyId]['currency_decimal_places'] = $nbCurrencyDp;
+
+ // append other sums because they might be missing:
+ $this->report['sums'][$nbCurrencyId]['overspent'] ??= '0';
+ $this->report['sums'][$nbCurrencyId]['left'] ??= '0';
+ $this->report['sums'][$nbCurrencyId]['budgeted'] ??= '0';
+ }
+ }
+
+ /**
+ * Calculate the percentages for each budget. Part of the "budgets" block on the default report.
+ */
+ private function percentageReport(): void
+ {
+ // make percentages based on total amount.
+ foreach ($this->report['budgets'] as $budgetId => $data) {
+ foreach ($data['budget_limits'] as $limitId => $entry) {
+ $budgetId = (int)$budgetId;
+ $limitId = (int)$limitId;
+ $currencyId = (int)$entry['currency_id'];
+ $spent = $entry['spent'];
+ $totalSpent = $this->report['sums'][$currencyId]['spent'] ?? '0';
+ $spentPct = '0';
+ $budgeted = $entry['budgeted'];
+ $totalBudgeted = $this->report['sums'][$currencyId]['budgeted'] ?? '0';
+ $budgetedPct = '0';
+
+ if (0 !== bccomp((string)$spent, '0') && 0 !== bccomp($totalSpent, '0')) {
+ $spentPct = round((float)bcmul(bcdiv((string)$spent, $totalSpent), '100'));
+ }
+ if (0 !== bccomp((string)$budgeted, '0') && 0 !== bccomp($totalBudgeted, '0')) {
+ $budgetedPct = round((float)bcmul(bcdiv((string)$budgeted, $totalBudgeted), '100'));
+ }
+ $this->report['sums'][$currencyId]['budgeted'] ??= '0';
+ $this->report['budgets'][$budgetId]['budget_limits'][$limitId]['spent_pct'] = $spentPct;
+ $this->report['budgets'][$budgetId]['budget_limits'][$limitId]['budgeted_pct'] = $budgetedPct;
+ }
+ }
+ }
+
+ /**
+ * Process expenses etc. for a single budget for the budgets block on the default report.
+ */
+ private function processBudget(Budget $budget): void
+ {
+ $budgetId = $budget->id;
+ $this->report['budgets'][$budgetId] ??= [
+ 'budget_id' => $budgetId,
+ 'budget_name' => $budget->name,
+ 'no_budget' => false,
+ 'budget_limits' => [],
+ ];
+
+ // get all budget limits for budget in period:
+ $limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end);
+
+ /** @var BudgetLimit $limit */
+ foreach ($limits as $limit) {
+ $this->processLimit($budget, $limit);
+ }
+ }
+
+ /**
+ * Process each set of transactions for each row of expenses.
+ */
+ private function processBudgetExpenses(array $expenses, array $budget): void
+ {
+ $budgetId = (int)$budget['id'];
+ $currencyId = (int)$expenses['currency_id'];
+ foreach ($budget['transaction_journals'] as $journal) {
+ $sourceAccountId = $journal['source_account_id'];
+
+ $this->report[$sourceAccountId]['currencies'][$currencyId]
+ ??= [
+ 'currency_id' => $expenses['currency_id'],
+ 'currency_symbol' => $expenses['currency_symbol'],
+ 'currency_name' => $expenses['currency_name'],
+ 'currency_decimal_places' => $expenses['currency_decimal_places'],
+ 'budgets' => [],
+ ];
+
+ $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId]
+ ??= '0';
+
+ $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId]
+ = bcadd($this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId], (string)$journal['amount']);
+ }
+ }
+
+ /**
+ * Process each row of expenses collected for the "Account per budget" partial
+ */
+ private function processExpenses(array $expenses): void
+ {
+ foreach ($expenses['budgets'] as $budget) {
+ $this->processBudgetExpenses($expenses, $budget);
+ }
+ }
+
+ /**
+ * Process a single budget limit for the budgets block on the default report.
+ */
+ private function processLimit(Budget $budget, BudgetLimit $limit): void
+ {
+ $budgetId = $budget->id;
+ $limitId = $limit->id;
+ $limitCurrency = $limit->transactionCurrency ?? $this->currency;
+ $currencyId = $limitCurrency->id;
+ $expenses = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, $this->accounts, new Collection()->push($budget));
+ $spent = $expenses[$currencyId]['sum'] ?? '0';
+ $left = -1 === bccomp(bcadd($limit->amount, $spent), '0') ? '0' : bcadd($limit->amount, $spent);
+ $overspent = 1 === bccomp(bcmul($spent, '-1'), $limit->amount) ? bcadd($spent, $limit->amount) : '0';
+
+ $this->report['budgets'][$budgetId]['budget_limits'][$limitId] ??= [
+ 'budget_limit_id' => $limitId,
+ 'start_date' => $limit->start_date,
+ 'end_date' => $limit->end_date,
+ 'budgeted' => $limit->amount,
+ 'budgeted_pct' => '0',
+ 'spent' => $spent,
+ 'spent_pct' => '0',
+ 'left' => $left,
+ 'overspent' => $overspent,
+ 'currency_id' => $currencyId,
+ 'currency_code' => $limitCurrency->code,
+ 'currency_name' => $limitCurrency->name,
+ 'currency_symbol' => $limitCurrency->symbol,
+ 'currency_decimal_places' => $limitCurrency->decimal_places,
+ ];
+
+ // make sum information:
+ $this->report['sums'][$currencyId]
+ ??= [
+ 'budgeted' => '0',
+ 'spent' => '0',
+ 'left' => '0',
+ 'overspent' => '0',
+ 'currency_id' => $currencyId,
+ 'currency_code' => $limitCurrency->code,
+ 'currency_name' => $limitCurrency->name,
+ 'currency_symbol' => $limitCurrency->symbol,
+ 'currency_decimal_places' => $limitCurrency->decimal_places,
+ ];
+ $this->report['sums'][$currencyId]['budgeted'] = bcadd((string)$this->report['sums'][$currencyId]['budgeted'], $limit->amount);
+ $this->report['sums'][$currencyId]['spent'] = bcadd((string)$this->report['sums'][$currencyId]['spent'], $spent);
+ $this->report['sums'][$currencyId]['left'] = bcadd((string)$this->report['sums'][$currencyId]['left'], bcadd($limit->amount, $spent));
+ $this->report['sums'][$currencyId]['overspent'] = bcadd((string)$this->report['sums'][$currencyId]['overspent'], $overspent);
+ }
}
diff --git a/app/Support/Report/Category/CategoryReportGenerator.php b/app/Support/Report/Category/CategoryReportGenerator.php
index 91f1470bb8..8f800d411d 100644
--- a/app/Support/Report/Category/CategoryReportGenerator.php
+++ b/app/Support/Report/Category/CategoryReportGenerator.php
@@ -62,17 +62,17 @@ class CategoryReportGenerator
*/
public function operations(): void
{
- $earnedWith = $this->opsRepository->listIncome($this->start, $this->end, $this->accounts);
- $spentWith = $this->opsRepository->listExpenses($this->start, $this->end, $this->accounts);
+ $earnedWith = $this->opsRepository->listIncome($this->start, $this->end, $this->accounts);
+ $spentWith = $this->opsRepository->listExpenses($this->start, $this->end, $this->accounts);
// also transferred out and transferred into these accounts in this category:
$transferredIn = $this->opsRepository->listTransferredIn($this->start, $this->end, $this->accounts);
$transferredOut = $this->opsRepository->listTransferredOut($this->start, $this->end, $this->accounts);
- $earnedWithout = $this->noCatRepository->listIncome($this->start, $this->end, $this->accounts);
- $spentWithout = $this->noCatRepository->listExpenses($this->start, $this->end, $this->accounts);
+ $earnedWithout = $this->noCatRepository->listIncome($this->start, $this->end, $this->accounts);
+ $spentWithout = $this->noCatRepository->listExpenses($this->start, $this->end, $this->accounts);
- $this->report = [
+ $this->report = [
'categories' => [],
'sums' => [],
];
@@ -83,17 +83,69 @@ class CategoryReportGenerator
}
}
- /**
- * Process one of the spent arrays from the operations method.
- */
- private function processOpsArray(array $data): void
+ public function setAccounts(Collection $accounts): void
{
- /**
- * @var int $currencyId
- * @var array $currencyRow
- */
- foreach ($data as $currencyId => $currencyRow) {
- $this->processCurrencyArray($currencyId, $currencyRow);
+ $this->accounts = $accounts;
+ }
+
+ public function setEnd(Carbon $end): void
+ {
+ $this->end = $end;
+ }
+
+ public function setStart(Carbon $start): void
+ {
+ $this->start = $start;
+ }
+
+ public function setUser(User $user): void
+ {
+ $this->noCatRepository->setUser($user);
+ $this->opsRepository->setUser($user);
+ }
+
+ private function processCategoryRow(int $currencyId, array $currencyRow, int $categoryId, array $categoryRow): void
+ {
+ $key = sprintf('%s-%s', $currencyId, $categoryId);
+ $this->report['categories'][$key] ??= [
+ 'id' => $categoryId,
+ 'title' => $categoryRow['name'],
+ 'currency_id' => $currencyRow['currency_id'],
+ 'currency_symbol' => $currencyRow['currency_symbol'],
+ 'currency_name' => $currencyRow['currency_name'],
+ 'currency_code' => $currencyRow['currency_code'],
+ 'currency_decimal_places' => $currencyRow['currency_decimal_places'],
+ 'spent' => '0',
+ 'earned' => '0',
+ 'sum' => '0',
+ ];
+ // loop journals:
+ foreach ($categoryRow['transaction_journals'] as $journal) {
+ // sum of sums
+ $this->report['sums'][$currencyId]['sum'] = bcadd((string)$this->report['sums'][$currencyId]['sum'], (string)$journal['amount']);
+ // sum of spent:
+ $this->report['sums'][$currencyId]['spent'] = -1 === bccomp((string)$journal['amount'], '0') ? bcadd(
+ (string)$this->report['sums'][$currencyId]['spent'],
+ (string)$journal['amount']
+ ) : $this->report['sums'][$currencyId]['spent'];
+ // sum of earned
+ $this->report['sums'][$currencyId]['earned'] = 1 === bccomp((string)$journal['amount'], '0') ? bcadd(
+ (string)$this->report['sums'][$currencyId]['earned'],
+ (string)$journal['amount']
+ ) : $this->report['sums'][$currencyId]['earned'];
+
+ // sum of category
+ $this->report['categories'][$key]['sum'] = bcadd((string)$this->report['categories'][$key]['sum'], (string)$journal['amount']);
+ // total spent in category
+ $this->report['categories'][$key]['spent'] = -1 === bccomp((string)$journal['amount'], '0') ? bcadd(
+ (string)$this->report['categories'][$key]['spent'],
+ (string)$journal['amount']
+ ) : $this->report['categories'][$key]['spent'];
+ // total earned in category
+ $this->report['categories'][$key]['earned'] = 1 === bccomp((string)$journal['amount'], '0') ? bcadd(
+ (string)$this->report['categories'][$key]['earned'],
+ (string)$journal['amount']
+ ) : $this->report['categories'][$key]['earned'];
}
}
@@ -119,69 +171,17 @@ class CategoryReportGenerator
}
}
- private function processCategoryRow(int $currencyId, array $currencyRow, int $categoryId, array $categoryRow): void
+ /**
+ * Process one of the spent arrays from the operations method.
+ */
+ private function processOpsArray(array $data): void
{
- $key = sprintf('%s-%s', $currencyId, $categoryId);
- $this->report['categories'][$key] ??= [
- 'id' => $categoryId,
- 'title' => $categoryRow['name'],
- 'currency_id' => $currencyRow['currency_id'],
- 'currency_symbol' => $currencyRow['currency_symbol'],
- 'currency_name' => $currencyRow['currency_name'],
- 'currency_code' => $currencyRow['currency_code'],
- 'currency_decimal_places' => $currencyRow['currency_decimal_places'],
- 'spent' => '0',
- 'earned' => '0',
- 'sum' => '0',
- ];
- // loop journals:
- foreach ($categoryRow['transaction_journals'] as $journal) {
- // sum of sums
- $this->report['sums'][$currencyId]['sum'] = bcadd((string) $this->report['sums'][$currencyId]['sum'], (string) $journal['amount']);
- // sum of spent:
- $this->report['sums'][$currencyId]['spent'] = -1 === bccomp((string) $journal['amount'], '0') ? bcadd(
- (string) $this->report['sums'][$currencyId]['spent'],
- (string) $journal['amount']
- ) : $this->report['sums'][$currencyId]['spent'];
- // sum of earned
- $this->report['sums'][$currencyId]['earned'] = 1 === bccomp((string) $journal['amount'], '0') ? bcadd(
- (string) $this->report['sums'][$currencyId]['earned'],
- (string) $journal['amount']
- ) : $this->report['sums'][$currencyId]['earned'];
-
- // sum of category
- $this->report['categories'][$key]['sum'] = bcadd((string) $this->report['categories'][$key]['sum'], (string) $journal['amount']);
- // total spent in category
- $this->report['categories'][$key]['spent'] = -1 === bccomp((string) $journal['amount'], '0') ? bcadd(
- (string) $this->report['categories'][$key]['spent'],
- (string) $journal['amount']
- ) : $this->report['categories'][$key]['spent'];
- // total earned in category
- $this->report['categories'][$key]['earned'] = 1 === bccomp((string) $journal['amount'], '0') ? bcadd(
- (string) $this->report['categories'][$key]['earned'],
- (string) $journal['amount']
- ) : $this->report['categories'][$key]['earned'];
+ /**
+ * @var int $currencyId
+ * @var array $currencyRow
+ */
+ foreach ($data as $currencyId => $currencyRow) {
+ $this->processCurrencyArray($currencyId, $currencyRow);
}
}
-
- public function setAccounts(Collection $accounts): void
- {
- $this->accounts = $accounts;
- }
-
- public function setEnd(Carbon $end): void
- {
- $this->end = $end;
- }
-
- public function setStart(Carbon $start): void
- {
- $this->start = $start;
- }
-
- public function setUser(User $user): void
- {
- $this->noCatRepository->setUser($user);
- $this->opsRepository->setUser($user);
- }
}
diff --git a/app/Support/Report/Summarizer/TransactionSummarizer.php b/app/Support/Report/Summarizer/TransactionSummarizer.php
index aea25a5663..84e0ec3231 100644
--- a/app/Support/Report/Summarizer/TransactionSummarizer.php
+++ b/app/Support/Report/Summarizer/TransactionSummarizer.php
@@ -43,26 +43,19 @@ class TransactionSummarizer
}
}
- public function setUser(User $user): void
- {
- $this->user = $user;
- $this->default = Amount::getPrimaryCurrencyByUserGroup($user->userGroup);
- $this->convertToPrimary = Amount::convertToPrimary($user);
- }
-
public function groupByCurrencyId(array $journals, string $method = 'negative', bool $includeForeign = true): array
{
Log::debug(sprintf('Now in groupByCurrencyId([%d journals], "%s", %s)', count($journals), $method, var_export($includeForeign, true)));
$array = [];
foreach ($journals as $journal) {
- $field = 'amount';
+ $field = 'amount';
// grab default currency information.
- $currencyId = (int) $journal['currency_id'];
- $currencyName = $journal['currency_name'];
- $currencySymbol = $journal['currency_symbol'];
- $currencyCode = $journal['currency_code'];
- $currencyDecimalPlaces = $journal['currency_decimal_places'];
+ $currencyId = (int)$journal['currency_id'];
+ $currencyName = $journal['currency_name'];
+ $currencySymbol = $journal['currency_symbol'];
+ $currencyCode = $journal['currency_code'];
+ $currencyDecimalPlaces = $journal['currency_decimal_places'];
// prepare foreign currency info:
$foreignCurrencyId = 0;
@@ -74,8 +67,8 @@ class TransactionSummarizer
if ($this->convertToPrimary) {
// Log::debug('convertToPrimary is true.');
// if convert to primary currency, use the primary currency amount yes or no?
- $usePrimary = $this->default->id !== (int) $journal['currency_id'];
- $useForeign = $this->default->id === (int) $journal['foreign_currency_id'];
+ $usePrimary = $this->default->id !== (int)$journal['currency_id'];
+ $useForeign = $this->default->id === (int)$journal['foreign_currency_id'];
if ($usePrimary) {
// Log::debug(sprintf('Journal #%d switches to primary currency amount (original is %s)', $journal['transaction_journal_id'], $journal['currency_code']));
$field = 'pc_amount';
@@ -88,7 +81,7 @@ class TransactionSummarizer
if ($useForeign) {
// Log::debug(sprintf('Journal #%d switches to foreign amount (foreign is %s)', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
$field = 'foreign_amount';
- $currencyId = (int) $journal['foreign_currency_id'];
+ $currencyId = (int)$journal['foreign_currency_id'];
$currencyName = $journal['foreign_currency_name'];
$currencySymbol = $journal['foreign_currency_symbol'];
$currencyCode = $journal['foreign_currency_code'];
@@ -98,7 +91,7 @@ class TransactionSummarizer
if (!$this->convertToPrimary) {
// Log::debug('convertToPrimary is false.');
// use foreign amount?
- $foreignCurrencyId = (int) $journal['foreign_currency_id'];
+ $foreignCurrencyId = (int)$journal['foreign_currency_id'];
if (0 !== $foreignCurrencyId) {
Log::debug(sprintf('Journal #%d also includes foreign amount (foreign is "%s")', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
$foreignCurrencyName = $journal['foreign_currency_name'];
@@ -109,7 +102,7 @@ class TransactionSummarizer
}
// first process normal amount
- $amount = (string) ($journal[$field] ?? '0');
+ $amount = (string)($journal[$field] ?? '0');
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => $currencyId,
@@ -128,7 +121,7 @@ class TransactionSummarizer
// then process foreign amount, if it exists.
if (0 !== $foreignCurrencyId && true === $includeForeign) {
- $amount = (string) ($journal['foreign_amount'] ?? '0');
+ $amount = (string)($journal['foreign_amount'] ?? '0');
$array[$foreignCurrencyId] ??= [
'sum' => '0',
'currency_id' => $foreignCurrencyId,
@@ -186,7 +179,7 @@ class TransactionSummarizer
if ($convertToPrimary && $journal['currency_id'] !== $primary->id && $primary->id === $journal['foreign_currency_id']) {
$field = 'foreign_amount';
}
- $key = sprintf('%s-%s', $journal[$idKey], $currencyId);
+ $key = sprintf('%s-%s', $journal[$idKey], $currencyId);
// sum it all up or create a new array.
$array[$key] ??= [
'id' => $journal[$idKey],
@@ -200,15 +193,15 @@ class TransactionSummarizer
];
// add the data from the $field to the array.
- $array[$key]['sum'] = bcadd($array[$key]['sum'], Steam::{$method}((string) ($journal[$field] ?? '0'))); // @phpstan-ignore-line
+ $array[$key]['sum'] = bcadd($array[$key]['sum'], Steam::{$method}((string)($journal[$field] ?? '0'))); // @phpstan-ignore-line
Log::debug(sprintf('Field for transaction #%d is "%s" (%s). Sum: %s', $journal['transaction_group_id'], $currencyCode, $field, $array[$key]['sum']));
// also do foreign amount, but only when convertToPrimary is false (otherwise we have it already)
// or when convertToPrimary is true and the foreign currency is ALSO not the default currency.
- if ((!$convertToPrimary || $journal['foreign_currency_id'] !== $primary->id) && 0 !== (int) $journal['foreign_currency_id']) {
+ if ((!$convertToPrimary || $journal['foreign_currency_id'] !== $primary->id) && 0 !== (int)$journal['foreign_currency_id']) {
Log::debug(sprintf('Use foreign amount from transaction #%d: %s %s. Sum: %s', $journal['transaction_group_id'], $currencyCode, $journal['foreign_amount'], $array[$key]['sum']));
$key = sprintf('%s-%s', $journal[$idKey], $journal['foreign_currency_id']);
- $array[$key] ??= [
+ $array[$key] ??= [
'id' => $journal[$idKey],
'name' => $journal[$nameKey],
'sum' => '0',
@@ -218,7 +211,7 @@ class TransactionSummarizer
'currency_code' => $journal['foreign_currency_code'],
'currency_decimal_places' => $journal['foreign_currency_decimal_places'],
];
- $array[$key]['sum'] = bcadd($array[$key]['sum'], Steam::{$method}((string) $journal['foreign_amount'])); // @phpstan-ignore-line
+ $array[$key]['sum'] = bcadd($array[$key]['sum'], Steam::{$method}((string)$journal['foreign_amount'])); // @phpstan-ignore-line
}
}
@@ -230,4 +223,11 @@ class TransactionSummarizer
Log::debug(sprintf('Overrule convertToPrimary to become %s', var_export($convertToPrimary, true)));
$this->convertToPrimary = $convertToPrimary;
}
+
+ public function setUser(User $user): void
+ {
+ $this->user = $user;
+ $this->default = Amount::getPrimaryCurrencyByUserGroup($user->userGroup);
+ $this->convertToPrimary = Amount::convertToPrimary($user);
+ }
}
diff --git a/app/Support/Repositories/Recurring/CalculateRangeOccurrences.php b/app/Support/Repositories/Recurring/CalculateRangeOccurrences.php
index 439fd1f50a..4ca2c8c29e 100644
--- a/app/Support/Repositories/Recurring/CalculateRangeOccurrences.php
+++ b/app/Support/Repositories/Recurring/CalculateRangeOccurrences.php
@@ -58,7 +58,7 @@ trait CalculateRangeOccurrences
{
$return = [];
$attempts = 0;
- $dayOfMonth = (int) $moment;
+ $dayOfMonth = (int)$moment;
if ($start->day > $dayOfMonth) {
// day has passed already, add a month.
$start->addMonth();
@@ -82,8 +82,8 @@ trait CalculateRangeOccurrences
*/
protected function getNdomInRange(Carbon $start, Carbon $end, int $skipMod, string $moment): array
{
- $return = [];
- $attempts = 0;
+ $return = [];
+ $attempts = 0;
$start->startOfMonth();
// this feels a bit like a cop out but why reinvent the wheel?
$counters = [1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth'];
@@ -108,12 +108,12 @@ trait CalculateRangeOccurrences
*/
protected function getWeeklyInRange(Carbon $start, Carbon $end, int $skipMod, string $moment): array
{
- $return = [];
- $attempts = 0;
+ $return = [];
+ $attempts = 0;
app('log')->debug('Rep is weekly.');
// monday = 1
// sunday = 7
- $dayOfWeek = (int) $moment;
+ $dayOfWeek = (int)$moment;
app('log')->debug(sprintf('DoW in repetition is %d, in mutator is %d', $dayOfWeek, $start->dayOfWeekIso));
if ($start->dayOfWeekIso > $dayOfWeek) {
// day has already passed this week, add one week:
@@ -154,8 +154,8 @@ trait CalculateRangeOccurrences
}
// is $date between $start and $end?
- $obj = clone $date;
- $count = 0;
+ $obj = clone $date;
+ $count = 0;
while ($obj <= $end && $obj >= $start && $count < 10) {
if (0 === $attempts % $skipMod) {
$return[] = clone $obj;
diff --git a/app/Support/Repositories/Recurring/CalculateXOccurrences.php b/app/Support/Repositories/Recurring/CalculateXOccurrences.php
index 04f07046d4..602cb03d02 100644
--- a/app/Support/Repositories/Recurring/CalculateXOccurrences.php
+++ b/app/Support/Repositories/Recurring/CalculateXOccurrences.php
@@ -63,7 +63,7 @@ trait CalculateXOccurrences
$mutator = clone $date;
$total = 0;
$attempts = 0;
- $dayOfMonth = (int) $moment;
+ $dayOfMonth = (int)$moment;
if ($mutator->day > $dayOfMonth) {
// day has passed already, add a month.
$mutator->addMonth();
@@ -89,10 +89,10 @@ trait CalculateXOccurrences
*/
protected function getXNDomOccurrences(Carbon $date, int $count, int $skipMod, string $moment): array
{
- $return = [];
- $total = 0;
- $attempts = 0;
- $mutator = clone $date;
+ $return = [];
+ $total = 0;
+ $attempts = 0;
+ $mutator = clone $date;
$mutator->addDay(); // always assume today has passed.
$mutator->startOfMonth();
// this feels a bit like a cop out but why reinvent the wheel?
@@ -120,14 +120,14 @@ trait CalculateXOccurrences
*/
protected function getXWeeklyOccurrences(Carbon $date, int $count, int $skipMod, string $moment): array
{
- $return = [];
- $total = 0;
- $attempts = 0;
- $mutator = clone $date;
+ $return = [];
+ $total = 0;
+ $attempts = 0;
+ $mutator = clone $date;
// monday = 1
// sunday = 7
$mutator->addDay(); // always assume today has passed.
- $dayOfWeek = (int) $moment;
+ $dayOfWeek = (int)$moment;
if ($mutator->dayOfWeekIso > $dayOfWeek) {
// day has already passed this week, add one week:
$mutator->addWeek();
@@ -164,7 +164,7 @@ trait CalculateXOccurrences
if ($mutator > $date) {
$date->addYear();
}
- $obj = clone $date;
+ $obj = clone $date;
while ($total < $count) {
if (0 === $attempts % $skipMod) {
$return[] = clone $obj;
diff --git a/app/Support/Repositories/Recurring/CalculateXOccurrencesSince.php b/app/Support/Repositories/Recurring/CalculateXOccurrencesSince.php
index 215c11bf0a..bd4b44fd7d 100644
--- a/app/Support/Repositories/Recurring/CalculateXOccurrencesSince.php
+++ b/app/Support/Repositories/Recurring/CalculateXOccurrencesSince.php
@@ -68,7 +68,7 @@ trait CalculateXOccurrencesSince
$mutator = clone $date;
$total = 0;
$attempts = 0;
- $dayOfMonth = (int) $moment;
+ $dayOfMonth = (int)$moment;
$dayOfMonth = 0 === $dayOfMonth ? 1 : $dayOfMonth;
if ($mutator->day > $dayOfMonth) {
Log::debug(sprintf('%d is after %d, add a month. Mutator is now...', $mutator->day, $dayOfMonth));
@@ -87,7 +87,7 @@ trait CalculateXOccurrencesSince
++$total;
}
++$attempts;
- $mutator = $mutator->endOfMonth()->addDay();
+ $mutator = $mutator->endOfMonth()->addDay();
}
Log::debug('Collected enough occurrences.');
@@ -103,10 +103,10 @@ trait CalculateXOccurrencesSince
protected function getXNDomOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
{
Log::debug(sprintf('Now in %s', __METHOD__));
- $return = [];
- $total = 0;
- $attempts = 0;
- $mutator = clone $date;
+ $return = [];
+ $total = 0;
+ $attempts = 0;
+ $mutator = clone $date;
$mutator->addDay(); // always assume today has passed.
$mutator->startOfMonth();
// this feels a bit like a cop out but why reinvent the wheel?
@@ -137,15 +137,15 @@ trait CalculateXOccurrencesSince
protected function getXWeeklyOccurrencesSince(Carbon $date, Carbon $afterDate, int $count, int $skipMod, string $moment): array
{
Log::debug(sprintf('Now in %s', __METHOD__));
- $return = [];
- $total = 0;
- $attempts = 0;
- $mutator = clone $date;
+ $return = [];
+ $total = 0;
+ $attempts = 0;
+ $mutator = clone $date;
// monday = 1
// sunday = 7
// Removed assumption today has passed, see issue https://github.com/firefly-iii/firefly-iii/issues/4798
// $mutator->addDay(); // always assume today has passed.
- $dayOfWeek = (int) $moment;
+ $dayOfWeek = (int)$moment;
if ($mutator->dayOfWeekIso > $dayOfWeek) {
// day has already passed this week, add one week:
$mutator->addWeek();
@@ -189,7 +189,7 @@ trait CalculateXOccurrencesSince
$date->addYear();
Log::debug(sprintf('Date is now %s', $date->format('Y-m-d')));
}
- $obj = clone $date;
+ $obj = clone $date;
while ($total < $count) {
Log::debug(sprintf('total (%d) < count (%d) so go.', $total, $count));
Log::debug(sprintf('attempts (%d) %% skipmod (%d) === %d', $attempts, $skipMod, $attempts % $skipMod));
diff --git a/app/Support/Repositories/Recurring/FiltersWeekends.php b/app/Support/Repositories/Recurring/FiltersWeekends.php
index 508e13f638..886679ced1 100644
--- a/app/Support/Repositories/Recurring/FiltersWeekends.php
+++ b/app/Support/Repositories/Recurring/FiltersWeekends.php
@@ -46,7 +46,7 @@ trait FiltersWeekends
return $dates;
}
- $return = [];
+ $return = [];
/** @var Carbon $date */
foreach ($dates as $date) {
@@ -60,7 +60,7 @@ trait FiltersWeekends
// is weekend and must set back to Friday?
if (RecurrenceRepetitionWeekend::WEEKEND_TO_FRIDAY->value === $repetition->weekend) {
- $clone = clone $date;
+ $clone = clone $date;
$clone->addDays(5 - $date->dayOfWeekIso);
Log::debug(
sprintf('Date is %s, and this is in the weekend, so corrected to %s (Friday).', $date->format('D d M Y'), $clone->format('D d M Y'))
@@ -72,7 +72,7 @@ trait FiltersWeekends
// postpone to Monday?
if (RecurrenceRepetitionWeekend::WEEKEND_TO_MONDAY->value === $repetition->weekend) {
- $clone = clone $date;
+ $clone = clone $date;
$clone->addDays(8 - $date->dayOfWeekIso);
Log::debug(
sprintf('Date is %s, and this is in the weekend, so corrected to %s (Monday).', $date->format('D d M Y'), $clone->format('D d M Y'))
diff --git a/app/Support/Repositories/UserGroup/UserGroupInterface.php b/app/Support/Repositories/UserGroup/UserGroupInterface.php
index d7a737b919..67e7fe3ea3 100644
--- a/app/Support/Repositories/UserGroup/UserGroupInterface.php
+++ b/app/Support/Repositories/UserGroup/UserGroupInterface.php
@@ -37,7 +37,7 @@ interface UserGroupInterface
public function getUserGroup(): ?UserGroup;
- public function setUser(Authenticatable|User|null $user): void;
+ public function setUser(Authenticatable | User | null $user): void;
public function setUserGroup(UserGroup $userGroup): void;
diff --git a/app/Support/Repositories/UserGroup/UserGroupTrait.php b/app/Support/Repositories/UserGroup/UserGroupTrait.php
index 98781e5596..b6a1c94f5a 100644
--- a/app/Support/Repositories/UserGroup/UserGroupTrait.php
+++ b/app/Support/Repositories/UserGroup/UserGroupTrait.php
@@ -61,10 +61,10 @@ trait UserGroupTrait
/**
* @throws FireflyException
*/
- public function setUser(Authenticatable|User|null $user): void
+ public function setUser(Authenticatable | User | null $user): void
{
if ($user instanceof User) {
- $this->user = $user;
+ $this->user = $user;
if (null === $user->userGroup) {
throw new FireflyException(sprintf('User #%d ("%s") has no user group.', $user->id, $user->email));
}
@@ -99,15 +99,14 @@ trait UserGroupTrait
public function setUserGroupById(int $userGroupId): void
{
$memberships = GroupMembership::where('user_id', $this->user->id)
- ->where('user_group_id', $userGroupId)
- ->count()
- ;
+ ->where('user_group_id', $userGroupId)
+ ->count();
if (0 === $memberships) {
throw new FireflyException(sprintf('User #%d has no access to administration #%d', $this->user->id, $userGroupId));
}
/** @var null|UserGroup $userGroup */
- $userGroup = UserGroup::find($userGroupId);
+ $userGroup = UserGroup::find($userGroupId);
if (null === $userGroup) {
throw new FireflyException(sprintf('Cannot find administration for user #%d', $this->user->id));
}
diff --git a/app/Support/Request/AppendsLocationData.php b/app/Support/Request/AppendsLocationData.php
index 149898f986..01a6de40d3 100644
--- a/app/Support/Request/AppendsLocationData.php
+++ b/app/Support/Request/AppendsLocationData.php
@@ -46,19 +46,17 @@ trait AppendsLocationData
return $return;
}
- private function validLongitude(string $longitude): bool
- {
- $number = (float) $longitude;
-
- return $number >= -180 && $number <= 180;
- }
-
- private function validLatitude(string $latitude): bool
- {
- $number = (float) $latitude;
-
- return $number >= -90 && $number <= 90;
- }
+ /**
+ * Abstract method stolen from "InteractsWithInput".
+ *
+ * @param null $key
+ * @param bool $default
+ *
+ * @return mixed
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ abstract public function boolean($key = null, $default = false);
/**
* Abstract method.
@@ -69,6 +67,22 @@ trait AppendsLocationData
*/
abstract public function has($key);
+ /**
+ * Abstract method.
+ *
+ * @return string
+ */
+ abstract public function method();
+
+ /**
+ * Abstract method.
+ *
+ * @param mixed ...$patterns
+ *
+ * @return mixed
+ */
+ abstract public function routeIs(...$patterns);
+
/**
* Read the submitted Request data and add new or updated Location data to the array.
*/
@@ -82,12 +96,12 @@ trait AppendsLocationData
$data['latitude'] = null;
$data['zoom_level'] = null;
- $longitudeKey = $this->getLocationKey($prefix, 'longitude');
- $latitudeKey = $this->getLocationKey($prefix, 'latitude');
- $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
- $isValidPOST = $this->isValidPost($prefix);
- $isValidPUT = $this->isValidPUT($prefix);
- $isValidEmptyPUT = $this->isValidEmptyPUT($prefix);
+ $longitudeKey = $this->getLocationKey($prefix, 'longitude');
+ $latitudeKey = $this->getLocationKey($prefix, 'latitude');
+ $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
+ $isValidPOST = $this->isValidPost($prefix);
+ $isValidPUT = $this->isValidPUT($prefix);
+ $isValidEmptyPUT = $this->isValidEmptyPUT($prefix);
// for a POST (store), all fields must be present and not NULL.
if ($isValidPOST) {
@@ -132,72 +146,22 @@ trait AppendsLocationData
return sprintf('%s_%s', $prefix, $key);
}
- private function isValidPost(?string $prefix): bool
+ private function isValidEmptyPUT(?string $prefix): bool
{
- app('log')->debug('Now in isValidPost()');
- $longitudeKey = $this->getLocationKey($prefix, 'longitude');
- $latitudeKey = $this->getLocationKey($prefix, 'latitude');
- $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
- $hasLocationKey = $this->getLocationKey($prefix, 'has_location');
- // fields must not be null:
- if (null !== $this->get($longitudeKey) && null !== $this->get($latitudeKey) && null !== $this->get($zoomLevelKey)) {
- app('log')->debug('All fields present');
- // if is POST and route contains API, this is enough:
- if ('POST' === $this->method() && $this->routeIs('api.v1.*')) {
- app('log')->debug('Is API location');
+ $longitudeKey = $this->getLocationKey($prefix, 'longitude');
+ $latitudeKey = $this->getLocationKey($prefix, 'latitude');
+ $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
- return true;
- }
- // if is POST and route does not contain API, must also have "has_location" = true
- if ('POST' === $this->method() && $this->routeIs('*.store') && !$this->routeIs('api.v1.*') && '' !== $hasLocationKey) {
- app('log')->debug('Is POST + store route.');
- $hasLocation = $this->boolean($hasLocationKey);
- if (true === $hasLocation) {
- app('log')->debug('Has form form location');
-
- return true;
- }
- app('log')->debug('Does not have form location');
-
- return false;
- }
- app('log')->debug('Is not POST API or POST form');
-
- return false;
- }
- app('log')->debug('Fields not present');
-
- return false;
+ return (
+ null === $this->get($longitudeKey)
+ && null === $this->get($latitudeKey)
+ && null === $this->get($zoomLevelKey))
+ && (
+ 'PUT' === $this->method()
+ || ('POST' === $this->method() && $this->routeIs('*.update'))
+ );
}
- /**
- * Abstract method.
- *
- * @return string
- */
- abstract public function method();
-
- /**
- * Abstract method.
- *
- * @param mixed ...$patterns
- *
- * @return mixed
- */
- abstract public function routeIs(...$patterns);
-
- /**
- * Abstract method stolen from "InteractsWithInput".
- *
- * @param null $key
- * @param bool $default
- *
- * @return mixed
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- abstract public function boolean($key = null, $default = false);
-
private function isValidPUT(?string $prefix): bool
{
$longitudeKey = $this->getLocationKey($prefix, 'longitude');
@@ -238,19 +202,55 @@ trait AppendsLocationData
return false;
}
- private function isValidEmptyPUT(?string $prefix): bool
+ private function isValidPost(?string $prefix): bool
{
- $longitudeKey = $this->getLocationKey($prefix, 'longitude');
- $latitudeKey = $this->getLocationKey($prefix, 'latitude');
- $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
+ app('log')->debug('Now in isValidPost()');
+ $longitudeKey = $this->getLocationKey($prefix, 'longitude');
+ $latitudeKey = $this->getLocationKey($prefix, 'latitude');
+ $zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
+ $hasLocationKey = $this->getLocationKey($prefix, 'has_location');
+ // fields must not be null:
+ if (null !== $this->get($longitudeKey) && null !== $this->get($latitudeKey) && null !== $this->get($zoomLevelKey)) {
+ app('log')->debug('All fields present');
+ // if is POST and route contains API, this is enough:
+ if ('POST' === $this->method() && $this->routeIs('api.v1.*')) {
+ app('log')->debug('Is API location');
- return (
- null === $this->get($longitudeKey)
- && null === $this->get($latitudeKey)
- && null === $this->get($zoomLevelKey))
- && (
- 'PUT' === $this->method()
- || ('POST' === $this->method() && $this->routeIs('*.update'))
- );
+ return true;
+ }
+ // if is POST and route does not contain API, must also have "has_location" = true
+ if ('POST' === $this->method() && $this->routeIs('*.store') && !$this->routeIs('api.v1.*') && '' !== $hasLocationKey) {
+ app('log')->debug('Is POST + store route.');
+ $hasLocation = $this->boolean($hasLocationKey);
+ if (true === $hasLocation) {
+ app('log')->debug('Has form form location');
+
+ return true;
+ }
+ app('log')->debug('Does not have form location');
+
+ return false;
+ }
+ app('log')->debug('Is not POST API or POST form');
+
+ return false;
+ }
+ app('log')->debug('Fields not present');
+
+ return false;
+ }
+
+ private function validLatitude(string $latitude): bool
+ {
+ $number = (float)$latitude;
+
+ return $number >= -90 && $number <= 90;
+ }
+
+ private function validLongitude(string $longitude): bool
+ {
+ $number = (float)$longitude;
+
+ return $number >= -180 && $number <= 180;
}
}
diff --git a/app/Support/Request/ChecksLogin.php b/app/Support/Request/ChecksLogin.php
index 9fd2e11883..8576b17473 100644
--- a/app/Support/Request/ChecksLogin.php
+++ b/app/Support/Request/ChecksLogin.php
@@ -40,7 +40,7 @@ trait ChecksLogin
{
app('log')->debug(sprintf('Now in %s', __METHOD__));
// Only allow logged-in users
- $check = auth()->check();
+ $check = auth()->check();
if (!$check) {
return false;
}
@@ -79,19 +79,19 @@ trait ChecksLogin
public function getUserGroup(): ?UserGroup
{
/** @var User $user */
- $user = auth()->user();
+ $user = auth()->user();
app('log')->debug('Now in getUserGroup()');
/** @var null|UserGroup $userGroup */
$userGroup = $this->route()?->parameter('userGroup');
if (null === $userGroup) {
app('log')->debug('Request class has no userGroup parameter, but perhaps there is a parameter.');
- $userGroupId = (int) $this->get('user_group_id');
+ $userGroupId = (int)$this->get('user_group_id');
if (0 === $userGroupId) {
app('log')->debug(sprintf('Request class has no user_group_id parameter, grab default from user (group #%d).', $user->user_group_id));
- $userGroupId = (int) $user->user_group_id;
+ $userGroupId = (int)$user->user_group_id;
}
- $userGroup = UserGroup::find($userGroupId);
+ $userGroup = UserGroup::find($userGroupId);
if (null === $userGroup) {
app('log')->error(sprintf('Request class has user_group_id (#%d), but group does not exist.', $userGroupId));
diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php
index bc8da96efb..aa3896eb72 100644
--- a/app/Support/Request/ConvertsDataTypes.php
+++ b/app/Support/Request/ConvertsDataTypes.php
@@ -31,7 +31,6 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Steam;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
-
use function Safe\preg_replace;
/**
@@ -99,28 +98,6 @@ trait ConvertsDataTypes
return Steam::filterSpaces($string);
}
- public function convertSortParameters(string $field, string $class): array
- {
- // assume this all works, because the validator would have caught any errors.
- $parameter = (string)request()->query->get($field);
- if ('' === $parameter) {
- return [];
- }
- $parts = explode(',', $parameter);
- $sortParameters = [];
- foreach ($parts as $part) {
- $part = trim($part);
- $direction = 'asc';
- if ('-' === $part[0]) {
- $part = substr($part, 1);
- $direction = 'desc';
- }
- $sortParameters[] = [$part, $direction];
- }
-
- return $sortParameters;
- }
-
public function clearString(?string $string): ?string
{
$string = $this->clearStringKeepNewlines($string);
@@ -159,6 +136,36 @@ trait ConvertsDataTypes
return Steam::filterSpaces($this->convertString($field));
}
+ /**
+ * Return integer value.
+ */
+ public function convertInteger(string $field): int
+ {
+ return (int)$this->get($field);
+ }
+
+ public function convertSortParameters(string $field, string $class): array
+ {
+ // assume this all works, because the validator would have caught any errors.
+ $parameter = (string)request()->query->get($field);
+ if ('' === $parameter) {
+ return [];
+ }
+ $parts = explode(',', $parameter);
+ $sortParameters = [];
+ foreach ($parts as $part) {
+ $part = trim($part);
+ $direction = 'asc';
+ if ('-' === $part[0]) {
+ $part = substr($part, 1);
+ $direction = 'desc';
+ }
+ $sortParameters[] = [$part, $direction];
+ }
+
+ return $sortParameters;
+ }
+
/**
* Return string value.
*/
@@ -178,14 +185,6 @@ trait ConvertsDataTypes
*/
abstract public function get(string $key, mixed $default = null): mixed;
- /**
- * Return integer value.
- */
- public function convertInteger(string $field): int
- {
- return (int)$this->get($field);
- }
-
/**
* TODO duplicate, see SelectTransactionsRequest
*
@@ -218,6 +217,16 @@ trait ConvertsDataTypes
return $collection;
}
+ /**
+ * Abstract method that always exists in the Request classes that use this
+ * trait, OR a stub needs to be added by any other class that uses this train.
+ *
+ * @param mixed $key
+ *
+ * @return mixed
+ */
+ abstract public function has($key);
+
/**
* Return string value with newlines.
*/
@@ -386,16 +395,6 @@ trait ConvertsDataTypes
return $return;
}
- /**
- * Abstract method that always exists in the Request classes that use this
- * trait, OR a stub needs to be added by any other class that uses this train.
- *
- * @param mixed $key
- *
- * @return mixed
- */
- abstract public function has($key);
-
/**
* Return date or NULL.
*/
@@ -418,6 +417,21 @@ trait ConvertsDataTypes
return $result;
}
+ /**
+ * Parse to integer
+ */
+ protected function integerFromValue(?string $string): ?int
+ {
+ if (null === $string) {
+ return null;
+ }
+ if ('' === $string) {
+ return null;
+ }
+
+ return (int)$string;
+ }
+
/**
* Return integer value, or NULL when it's not set.
*/
@@ -445,7 +459,7 @@ trait ConvertsDataTypes
if (!is_array($entry)) {
continue;
}
- $amount = null;
+ $amount = null;
if (array_key_exists('current_amount', $entry)) {
$amount = $this->clearString((string)($entry['current_amount'] ?? '0'));
if (null === $entry['current_amount']) {
@@ -463,19 +477,4 @@ trait ConvertsDataTypes
return $return;
}
-
- /**
- * Parse to integer
- */
- protected function integerFromValue(?string $string): ?int
- {
- if (null === $string) {
- return null;
- }
- if ('' === $string) {
- return null;
- }
-
- return (int)$string;
- }
}
diff --git a/app/Support/Request/GetRecurrenceData.php b/app/Support/Request/GetRecurrenceData.php
index 50dc0cd8f2..40f23738dd 100644
--- a/app/Support/Request/GetRecurrenceData.php
+++ b/app/Support/Request/GetRecurrenceData.php
@@ -38,12 +38,12 @@ trait GetRecurrenceData
foreach ($stringKeys as $key) {
if (array_key_exists($key, $transaction)) {
- $return[$key] = (string) $transaction[$key];
+ $return[$key] = (string)$transaction[$key];
}
}
foreach ($intKeys as $key) {
if (array_key_exists($key, $transaction)) {
- $return[$key] = (int) $transaction[$key];
+ $return[$key] = (int)$transaction[$key];
}
}
foreach ($keys as $key) {
diff --git a/app/Support/Request/ValidatesWebhooks.php b/app/Support/Request/ValidatesWebhooks.php
index 5647184ef4..dff1541fde 100644
--- a/app/Support/Request/ValidatesWebhooks.php
+++ b/app/Support/Request/ValidatesWebhooks.php
@@ -25,10 +25,10 @@ declare(strict_types=1);
namespace FireflyIII\Support\Request;
-use Illuminate\Validation\Validator;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Models\Webhook;
use Illuminate\Support\Facades\Log;
+use Illuminate\Validation\Validator;
trait ValidatesWebhooks
{
@@ -40,9 +40,9 @@ trait ValidatesWebhooks
if (count($validator->failed()) > 0) {
return;
}
- $data = $validator->getData();
- $triggers = $data['triggers'] ?? [];
- $responses = $data['responses'] ?? [];
+ $data = $validator->getData();
+ $triggers = $data['triggers'] ?? [];
+ $responses = $data['responses'] ?? [];
if (0 === count($triggers) || 0 === count($responses)) {
Log::debug('No trigger or response, return.');
diff --git a/app/Support/Search/AccountSearch.php b/app/Support/Search/AccountSearch.php
index 99b89f9c99..fe6e817c72 100644
--- a/app/Support/Search/AccountSearch.php
+++ b/app/Support/Search/AccountSearch.php
@@ -28,7 +28,6 @@ use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
-
use function Safe\json_encode;
/**
@@ -37,16 +36,16 @@ use function Safe\json_encode;
class AccountSearch implements GenericSearchInterface
{
/** @var string */
- public const string SEARCH_ALL = 'all';
+ public const string SEARCH_ALL = 'all';
/** @var string */
- public const string SEARCH_IBAN = 'iban';
+ public const string SEARCH_IBAN = 'iban';
/** @var string */
- public const string SEARCH_ID = 'id';
+ public const string SEARCH_ID = 'id';
/** @var string */
- public const string SEARCH_NAME = 'name';
+ public const string SEARCH_NAME = 'name';
/** @var string */
public const string SEARCH_NUMBER = 'number';
@@ -63,10 +62,9 @@ class AccountSearch implements GenericSearchInterface
public function search(): Collection
{
$searchQuery = $this->user->accounts()
- ->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id')
- ->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
- ->whereIn('account_types.type', $this->types)
- ;
+ ->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id')
+ ->leftJoin('account_meta', 'accounts.id', '=', 'account_meta.account_id')
+ ->whereIn('account_types.type', $this->types);
$like = sprintf('%%%s%%', $this->query);
$originalQuery = $this->query;
@@ -92,7 +90,7 @@ class AccountSearch implements GenericSearchInterface
break;
case self::SEARCH_ID:
- $searchQuery->where('accounts.id', '=', (int) $originalQuery);
+ $searchQuery->where('accounts.id', '=', (int)$originalQuery);
break;
@@ -137,7 +135,7 @@ class AccountSearch implements GenericSearchInterface
$this->types = $types;
}
- public function setUser(Authenticatable|User|null $user): void
+ public function setUser(Authenticatable | User | null $user): void
{
if ($user instanceof User) {
$this->user = $user;
diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php
index dc975609f0..2e813bd125 100644
--- a/app/Support/Search/OperatorQuerySearch.php
+++ b/app/Support/Search/OperatorQuerySearch.php
@@ -105,6 +105,41 @@ class OperatorQuerySearch implements SearchInterface
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
}
+ /**
+ * @throws FireflyException
+ */
+ public static function getRootOperator(string $operator): string
+ {
+ $original = $operator;
+ // if the string starts with "-" (not), we can remove it and recycle
+ // the configuration from the original operator.
+ if (str_starts_with($operator, '-')) {
+ $operator = substr($operator, 1);
+ }
+
+ $config = config(sprintf('search.operators.%s', $operator));
+ if (null === $config) {
+ throw new FireflyException(sprintf('No configuration for search operator "%s"', $operator));
+ }
+ if (true === $config['alias']) {
+ $return = $config['alias_for'];
+ if (str_starts_with($original, '-')) {
+ $return = sprintf('-%s', $config['alias_for']);
+ }
+ Log::debug(sprintf('"%s" is an alias for "%s", so return that instead.', $original, $return));
+
+ return $return;
+ }
+ Log::debug(sprintf('"%s" is not an alias.', $operator));
+
+ return $original;
+ }
+
+ public function getExcludedWords(): array
+ {
+ return $this->prohibitedWords;
+ }
+
public function getInvalidOperators(): array
{
return $this->invalidOperators;
@@ -120,6 +155,11 @@ class OperatorQuerySearch implements SearchInterface
return $this->operators;
}
+ public function getWords(): array
+ {
+ return $this->words;
+ }
+
public function getWordsAsString(): string
{
return implode(' ', $this->words);
@@ -146,7 +186,7 @@ class OperatorQuerySearch implements SearchInterface
try {
$parsedQuery = $parser->parse($query);
- } catch (LogicException|TypeError $e) {
+ } catch (LogicException | TypeError $e) {
Log::error($e->getMessage());
Log::error(sprintf('Could not parse search: "%s".', $query));
@@ -163,6 +203,124 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->excludeSearchWords($this->prohibitedWords);
}
+ public function searchTime(): float
+ {
+ return microtime(true) - $this->startTime;
+ }
+
+ public function searchTransactions(): LengthAwarePaginator
+ {
+ $this->parseTagInstructions();
+ if (0 === count($this->getWords()) && 0 === count($this->getExcludedWords()) && 0 === count($this->getOperators())) {
+ return new LengthAwarePaginator([], 0, 5, 1);
+ }
+
+ return $this->collector->getPaginatedGroups();
+ }
+
+ public function setDate(Carbon $date): void
+ {
+ $this->date = $date;
+ }
+
+ public function setLimit(int $limit): void
+ {
+ $this->limit = $limit;
+ $this->collector->setLimit($this->limit);
+ }
+
+ public function setPage(int $page): void
+ {
+ $this->page = $page;
+ $this->collector->setPage($this->page);
+ }
+
+ public function setUser(User $user): void
+ {
+ $this->accountRepository->setUser($user);
+ $this->billRepository->setUser($user);
+ $this->categoryRepository->setUser($user);
+ $this->budgetRepository->setUser($user);
+ $this->tagRepository->setUser($user);
+ $this->collector = app(GroupCollectorInterface::class);
+ $this->collector->setUser($user);
+ $this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation();
+
+ $this->setLimit((int)app('preferences')->getForUser($user, 'listPageSize', 50)->data);
+ }
+
+ private function findCurrency(string $value): ?TransactionCurrency
+ {
+ if (str_contains($value, '(') && str_contains($value, ')')) {
+ // bad method to split and get the currency code:
+ $parts = explode(' ', $value);
+ $value = trim($parts[count($parts) - 1], "() \t\n\r\0\x0B");
+ }
+ $result = $this->currencyRepository->findByCode($value);
+ if (null === $result) {
+ return $this->currencyRepository->findByName($value);
+ }
+
+ return $result;
+ }
+
+ private function getCashAccount(): Account
+ {
+ return $this->accountRepository->getCashAccount();
+ }
+
+ /**
+ * @throws FireflyException
+ */
+ private function handleFieldNode(FieldNode $node, bool $flipProhibitedFlag): void
+ {
+ $operator = strtolower($node->getOperator());
+ $value = $node->getValue();
+ $prohibited = $node->isProhibited($flipProhibitedFlag);
+
+ $context = config(sprintf('search.operators.%s.needs_context', $operator));
+
+ // is an operator that needs no context, and value is false, then prohibited = true.
+ if ('false' === $value && in_array($operator, $this->validOperators, true) && false === $context && !$prohibited) {
+ $prohibited = true;
+ $value = 'true';
+ }
+ // if the operator is prohibited, but the value is false, do an uno reverse
+ if ('false' === $value && $prohibited && in_array($operator, $this->validOperators, true) && false === $context) {
+ $prohibited = false;
+ $value = 'true';
+ }
+
+ // must be valid operator:
+ $inArray = in_array($operator, $this->validOperators, true);
+ if ($inArray) {
+ if ($this->updateCollector($operator, $value, $prohibited)) {
+ $this->operators->push([
+ 'type' => self::getRootOperator($operator),
+ 'value' => $value,
+ 'prohibited' => $prohibited,
+ ]);
+ Log::debug(sprintf('Added operator type "%s"', $operator));
+ }
+ }
+ if (!$inArray) {
+ Log::debug(sprintf('Added INVALID operator type "%s"', $operator));
+ $this->invalidOperators[] = [
+ 'type' => $operator,
+ 'value' => $value,
+ ];
+ }
+ }
+
+ private function handleNodeGroup(NodeGroup $node, bool $flipProhibitedFlag): void
+ {
+ $prohibited = $node->isProhibited($flipProhibitedFlag);
+
+ foreach ($node->getNodes() as $subNode) {
+ $this->handleSearchNode($subNode, $prohibited);
+ }
+ }
+
/**
* @throws FireflyException
*
@@ -197,7 +355,7 @@ class OperatorQuerySearch implements SearchInterface
private function handleStringNode(StringNode $node, bool $flipProhibitedFlag): void
{
- $string = $node->getValue();
+ $string = $node->getValue();
$prohibited = $node->isProhibited($flipProhibitedFlag);
@@ -214,43 +372,857 @@ class OperatorQuerySearch implements SearchInterface
/**
* @throws FireflyException
*/
- private function handleFieldNode(FieldNode $node, bool $flipProhibitedFlag): void
+ private function parseDateRange(string $type, string $value): array
{
- $operator = strtolower($node->getOperator());
- $value = $node->getValue();
- $prohibited = $node->isProhibited($flipProhibitedFlag);
-
- $context = config(sprintf('search.operators.%s.needs_context', $operator));
-
- // is an operator that needs no context, and value is false, then prohibited = true.
- if ('false' === $value && in_array($operator, $this->validOperators, true) && false === $context && !$prohibited) {
- $prohibited = true;
- $value = 'true';
- }
- // if the operator is prohibited, but the value is false, do an uno reverse
- if ('false' === $value && $prohibited && in_array($operator, $this->validOperators, true) && false === $context) {
- $prohibited = false;
- $value = 'true';
+ $parser = new ParseDateString();
+ if ($parser->isDateRange($value)) {
+ return $parser->parseRange($value);
}
- // must be valid operator:
- $inArray = in_array($operator, $this->validOperators, true);
- if ($inArray) {
- if ($this->updateCollector($operator, $value, $prohibited)) {
- $this->operators->push([
- 'type' => self::getRootOperator($operator),
- 'value' => $value,
- 'prohibited' => $prohibited,
- ]);
- Log::debug(sprintf('Added operator type "%s"', $operator));
- }
- }
- if (!$inArray) {
- Log::debug(sprintf('Added INVALID operator type "%s"', $operator));
+ try {
+ $parsedDate = $parser->parseDate($value);
+ } catch (FireflyException) {
+ Log::debug(sprintf('Could not parse date "%s", will return empty array.', $value));
$this->invalidOperators[] = [
- 'type' => $operator,
+ 'type' => $type,
'value' => $value,
];
+
+ return [];
+ }
+
+ return [
+ 'exact' => $parsedDate,
+ ];
+ }
+
+ private function parseTagInstructions(): void
+ {
+ Log::debug('Now in parseTagInstructions()');
+ // if exclude tags, remove excluded tags.
+ if (count($this->excludeTags) > 0) {
+ Log::debug(sprintf('%d exclude tag(s)', count($this->excludeTags)));
+ $collection = new Collection();
+ foreach ($this->excludeTags as $tagId) {
+ $tag = $this->tagRepository->find($tagId);
+ if (null !== $tag) {
+ Log::debug(sprintf('Exclude tag "%s"', $tag->tag));
+ $collection->push($tag);
+ }
+ }
+ Log::debug(sprintf('Selecting all tags except %d excluded tag(s).', $collection->count()));
+ $this->collector->setWithoutSpecificTags($collection);
+ }
+ // if include tags, include them:
+ if (count($this->includeTags) > 0) {
+ Log::debug(sprintf('%d include tag(s)', count($this->includeTags)));
+ $collection = new Collection();
+ foreach ($this->includeTags as $tagId) {
+ $tag = $this->tagRepository->find($tagId);
+ if (null !== $tag) {
+ Log::debug(sprintf('Include tag "%s"', $tag->tag));
+ $collection->push($tag);
+ }
+ }
+ $this->collector->setAllTags($collection);
+ }
+ // if include ANY tags, include them: (see #8632)
+ if (count($this->includeAnyTags) > 0) {
+ Log::debug(sprintf('%d include ANY tag(s)', count($this->includeAnyTags)));
+ $collection = new Collection();
+ foreach ($this->includeAnyTags as $tagId) {
+ $tag = $this->tagRepository->find($tagId);
+ if (null !== $tag) {
+ Log::debug(sprintf('Include ANY tag "%s"', $tag->tag));
+ $collection->push($tag);
+ }
+ }
+ $this->collector->setTags($collection);
+ }
+ }
+
+ /**
+ * searchDirection: 1 = source (default), 2 = destination, 3 = both
+ * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ * @SuppressWarnings("PHPMD.NPathComplexity")
+ */
+ private function searchAccount(string $value, SearchDirection $searchDirection, StringPosition $stringPosition, bool $prohibited = false): void
+ {
+ Log::debug(sprintf('searchAccount("%s", %s, %s)', $value, $stringPosition->name, $searchDirection->name));
+
+ // search direction (default): for source accounts
+ $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::REVENUE->value];
+ $collectorMethod = 'setSourceAccounts';
+ if ($prohibited) {
+ $collectorMethod = 'excludeSourceAccounts';
+ }
+
+ // search direction: for destination accounts
+ if (SearchDirection::DESTINATION === $searchDirection) { // destination
+ // destination can be
+ $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::EXPENSE->value];
+ $collectorMethod = 'setDestinationAccounts';
+ if ($prohibited) {
+ $collectorMethod = 'excludeDestinationAccounts';
+ }
+ }
+ // either account could be:
+ if (SearchDirection::BOTH === $searchDirection) {
+ $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::REVENUE->value];
+ $collectorMethod = 'setAccounts';
+ if ($prohibited) {
+ $collectorMethod = 'excludeAccounts';
+ }
+ }
+ // string position (default): starts with:
+ $stringMethod = 'str_starts_with';
+
+ // string position: ends with:
+ if (StringPosition::ENDS === $stringPosition) {
+ $stringMethod = 'str_ends_with';
+ }
+ if (StringPosition::CONTAINS === $stringPosition) {
+ $stringMethod = 'str_contains';
+ }
+ if (StringPosition::IS === $stringPosition) {
+ $stringMethod = 'stringIsEqual';
+ }
+
+ // get accounts:
+ $accounts = $this->accountRepository->searchAccount($value, $searchTypes, 1337);
+ if (0 === $accounts->count() && false === $prohibited) {
+ Log::warning('Found zero accounts, search for non existing account, NO results will be returned.');
+ $this->collector->findNothing();
+
+ return;
+ }
+ if (0 === $accounts->count() && true === $prohibited) {
+ Log::debug('Found zero accounts, but the search is negated, so effectively we ignore the search parameter.');
+
+ return;
+ }
+ Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count()));
+ $filtered = $accounts->filter(
+ static fn(Account $account) => $stringMethod(strtolower($account->name), strtolower($value))
+ );
+
+ if (0 === $filtered->count()) {
+ Log::warning('Left with zero accounts, so cannot find anything, NO results will be returned.');
+ $this->collector->findNothing();
+
+ return;
+ }
+ Log::debug(sprintf('Left with %d, set as %s().', $filtered->count(), $collectorMethod));
+ $this->collector->{$collectorMethod}($filtered); // @phpstan-ignore-line
+ }
+
+ /**
+ * TODO make enums
+ * searchDirection: 1 = source (default), 2 = destination, 3 = both
+ * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ * @SuppressWarnings("PHPMD.NPathComplexity")
+ */
+ private function searchAccountNr(string $value, SearchDirection $searchDirection, StringPosition $stringPosition, bool $prohibited = false): void
+ {
+ Log::debug(sprintf('searchAccountNr(%s, %d, %d)', $value, $searchDirection->name, $stringPosition->name));
+
+ // search direction (default): for source accounts
+ $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::REVENUE->value];
+ $collectorMethod = 'setSourceAccounts';
+ if (true === $prohibited) {
+ $collectorMethod = 'excludeSourceAccounts';
+ }
+
+ // search direction: for destination accounts
+ if (SearchDirection::DESTINATION === $searchDirection) {
+ // destination can be
+ $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::EXPENSE->value];
+ $collectorMethod = 'setDestinationAccounts';
+ if (true === $prohibited) {
+ $collectorMethod = 'excludeDestinationAccounts';
+ }
+ }
+
+ // either account could be:
+ if (SearchDirection::BOTH === $searchDirection) {
+ $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::REVENUE->value];
+ $collectorMethod = 'setAccounts';
+ if (true === $prohibited) {
+ $collectorMethod = 'excludeAccounts';
+ }
+ }
+
+ // string position (default): starts with:
+ $stringMethod = 'str_starts_with';
+
+ // string position: ends with:
+ if (StringPosition::ENDS === $stringPosition) {
+ $stringMethod = 'str_ends_with';
+ }
+ if (StringPosition::CONTAINS === $stringPosition) {
+ $stringMethod = 'str_contains';
+ }
+ if (StringPosition::IS === $stringPosition) {
+ $stringMethod = 'stringIsEqual';
+ }
+
+ // search for accounts:
+ $accounts = $this->accountRepository->searchAccountNr($value, $searchTypes, 1337);
+ if (0 === $accounts->count()) {
+ Log::debug('Found zero accounts, search for invalid account.');
+ Log::warning('Call to findNothing() from searchAccountNr().');
+ $this->collector->findNothing();
+
+ return;
+ }
+
+ // if found, do filter
+ Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count()));
+ $filtered = $accounts->filter(
+ static function (Account $account) use ($value, $stringMethod) {
+ // either IBAN or account number
+ $ibanMatch = $stringMethod(strtolower((string)$account->iban), strtolower($value));
+ $accountNrMatch = false;
+
+ /** @var AccountMeta $meta */
+ foreach ($account->accountMeta as $meta) {
+ if ('account_number' === $meta->name && $stringMethod(strtolower((string)$meta->data), strtolower($value))) {
+ $accountNrMatch = true;
+ }
+ }
+
+ return $ibanMatch || $accountNrMatch;
+ }
+ );
+
+ if (0 === $filtered->count()) {
+ Log::debug('Left with zero, search for invalid account');
+ Log::warning('Call to findNothing() from searchAccountNr().');
+ $this->collector->findNothing();
+
+ return;
+ }
+ Log::debug(sprintf('Left with %d, set as %s().', $filtered->count(), $collectorMethod));
+ $this->collector->{$collectorMethod}($filtered); // @phpstan-ignore-line
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setDateAfterParams(array $range, bool $prohibited = false): void
+ {
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setDateAfterParams()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ $this->collector->setAfter($value);
+ $this->operators->push(['type' => 'date_after', 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value));
+ $this->collector->yearAfter($value);
+ $this->operators->push(['type' => 'date_after_year', 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value));
+ $this->collector->monthAfter($value);
+ $this->operators->push(['type' => 'date_after_month', 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_after DAY value "%s"', $value));
+ $this->collector->dayAfter($value);
+ $this->operators->push(['type' => 'date_after_day', 'value' => $value]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setDateBeforeParams(array $range, bool $prohibited = false): void
+ {
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setDateBeforeParams()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ $this->collector->setBefore($value);
+ $this->operators->push(['type' => 'date_before', 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value));
+ $this->collector->yearBefore($value);
+ $this->operators->push(['type' => 'date_before_year', 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value));
+ $this->collector->monthBefore($value);
+ $this->operators->push(['type' => 'date_before_month', 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_before DAY value "%s"', $value));
+ $this->collector->dayBefore($value);
+ $this->operators->push(['type' => 'date_before_day', 'value' => $value]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setExactDateParams(array $range, bool $prohibited = false): void
+ {
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setExactParameters()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ Log::debug(sprintf('Set date_is_exact value "%s"', $value->format('Y-m-d')));
+ $this->collector->setRange($value, $value);
+ $this->operators->push(['type' => 'date_on', 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'exact_not':
+ if ($value instanceof Carbon) {
+ $this->collector->excludeRange($value, $value);
+ $this->operators->push(['type' => 'not_date_on', 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_exact YEAR value "%s"', $value));
+ $this->collector->yearIs($value);
+ $this->operators->push(['type' => 'date_on_year', 'value' => $value]);
+ }
+
+ break;
+
+ case 'year_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_exact_not YEAR value "%s"', $value));
+ $this->collector->yearIsNot($value);
+ $this->operators->push(['type' => 'not_date_on_year', 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_exact MONTH value "%s"', $value));
+ $this->collector->monthIs($value);
+ $this->operators->push(['type' => 'date_on_month', 'value' => $value]);
+ }
+
+ break;
+
+ case 'month_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_exact not MONTH value "%s"', $value));
+ $this->collector->monthIsNot($value);
+ $this->operators->push(['type' => 'not_date_on_month', 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_exact DAY value "%s"', $value));
+ $this->collector->dayIs($value);
+ $this->operators->push(['type' => 'date_on_day', 'value' => $value]);
+ }
+
+ break;
+
+ case 'day_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set not date_is_exact DAY value "%s"', $value));
+ $this->collector->dayIsNot($value);
+ $this->operators->push(['type' => 'not_date_on_day', 'value' => $value]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setExactMetaDateParams(string $field, array $range, bool $prohibited = false): void
+ {
+ Log::debug('Now in setExactMetaDateParams()');
+
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setExactMetaDateParams()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ Log::debug(sprintf('Set %s_is_exact value "%s"', $field, $value->format('Y-m-d')));
+ $this->collector->setMetaDateRange($value, $value, $field);
+ $this->operators->push(['type' => sprintf('%s_on', $field), 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'exact_not':
+ if ($value instanceof Carbon) {
+ Log::debug(sprintf('Set NOT %s_is_exact value "%s"', $field, $value->format('Y-m-d')));
+ $this->collector->excludeMetaDateRange($value, $value, $field);
+ $this->operators->push(['type' => sprintf('not_%s_on', $field), 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_exact YEAR value "%s"', $field, $value));
+ $this->collector->metaYearIs($value, $field);
+ $this->operators->push(['type' => sprintf('%s_on_year', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'year_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set NOT %s_is_exact YEAR value "%s"', $field, $value));
+ $this->collector->metaYearIsNot($value, $field);
+ $this->operators->push(['type' => sprintf('not_%s_on_year', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_exact MONTH value "%s"', $field, $value));
+ $this->collector->metaMonthIs($value, $field);
+ $this->operators->push(['type' => sprintf('%s_on_month', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'month_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set NOT %s_is_exact MONTH value "%s"', $field, $value));
+ $this->collector->metaMonthIsNot($value, $field);
+ $this->operators->push(['type' => sprintf('not_%s_on_month', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_exact DAY value "%s"', $field, $value));
+ $this->collector->metaDayIs($value, $field);
+ $this->operators->push(['type' => sprintf('%s_on_day', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'day_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set NOT %s_is_exact DAY value "%s"', $field, $value));
+ $this->collector->metaDayIsNot($value, $field);
+ $this->operators->push(['type' => sprintf('not_%s_on_day', $field), 'value' => $value]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setExactObjectDateParams(string $field, array $range, bool $prohibited = false): void
+ {
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setExactObjectDateParams()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ Log::debug(sprintf('Set %s_is_exact value "%s"', $field, $value->format('Y-m-d')));
+ $this->collector->setObjectRange($value, clone $value, $field);
+ $this->operators->push(['type' => sprintf('%s_on', $field), 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'exact_not':
+ if ($value instanceof Carbon) {
+ Log::debug(sprintf('Set NOT %s_is_exact value "%s"', $field, $value->format('Y-m-d')));
+ $this->collector->excludeObjectRange($value, clone $value, $field);
+ $this->operators->push(['type' => sprintf('not_%s_on', $field), 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_exact YEAR value "%s"', $field, $value));
+ $this->collector->objectYearIs($value, $field);
+ $this->operators->push(['type' => sprintf('%s_on_year', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'year_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set NOT %s_is_exact YEAR value "%s"', $field, $value));
+ $this->collector->objectYearIsNot($value, $field);
+ $this->operators->push(['type' => sprintf('not_%s_on_year', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_exact MONTH value "%s"', $field, $value));
+ $this->collector->objectMonthIs($value, $field);
+ $this->operators->push(['type' => sprintf('%s_on_month', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'month_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set NOT %s_is_exact MONTH value "%s"', $field, $value));
+ $this->collector->objectMonthIsNot($value, $field);
+ $this->operators->push(['type' => sprintf('not_%s_on_month', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_exact DAY value "%s"', $field, $value));
+ $this->collector->objectDayIs($value, $field);
+ $this->operators->push(['type' => sprintf('%s_on_day', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'day_not':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set NOT %s_is_exact DAY value "%s"', $field, $value));
+ $this->collector->objectDayIsNot($value, $field);
+ $this->operators->push(['type' => sprintf('not_%s_on_day', $field), 'value' => $value]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setMetaDateAfterParams(string $field, array $range, bool $prohibited = false): void
+ {
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setMetaDateAfterParams()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ $this->collector->setMetaAfter($value, $field);
+ $this->operators->push(['type' => sprintf('%s_after', $field), 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_after YEAR value "%s"', $field, $value));
+ $this->collector->metaYearAfter($value, $field);
+ $this->operators->push(['type' => sprintf('%s_after_year', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_after MONTH value "%s"', $field, $value));
+ $this->collector->metaMonthAfter($value, $field);
+ $this->operators->push(['type' => sprintf('%s_after_month', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_after DAY value "%s"', $field, $value));
+ $this->collector->metaDayAfter($value, $field);
+ $this->operators->push(['type' => sprintf('%s_after_day', $field), 'value' => $value]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setMetaDateBeforeParams(string $field, array $range, bool $prohibited = false): void
+ {
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setMetaDateBeforeParams()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ $this->collector->setMetaBefore($value, $field);
+ $this->operators->push(['type' => sprintf('%s_before', $field), 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_before YEAR value "%s"', $field, $value));
+ $this->collector->metaYearBefore($value, $field);
+ $this->operators->push(['type' => sprintf('%s_before_year', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_before MONTH value "%s"', $field, $value));
+ $this->collector->metaMonthBefore($value, $field);
+ $this->operators->push(['type' => sprintf('%s_before_month', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set %s_is_before DAY value "%s"', $field, $value));
+ $this->collector->metaDayBefore($value, $field);
+ $this->operators->push(['type' => sprintf('%s_before_day', $field), 'value' => $value]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setObjectDateAfterParams(string $field, array $range, bool $prohibited = false): void
+ {
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setObjectDateAfterParams()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ $this->collector->setObjectAfter($value, $field);
+ $this->operators->push(['type' => sprintf('%s_after', $field), 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value));
+ $this->collector->objectYearAfter($value, $field);
+ $this->operators->push(['type' => sprintf('%s_after_year', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value));
+ $this->collector->objectMonthAfter($value, $field);
+ $this->operators->push(['type' => sprintf('%s_after_month', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_after DAY value "%s"', $value));
+ $this->collector->objectDayAfter($value, $field);
+ $this->operators->push(['type' => sprintf('%s_after_day', $field), 'value' => $value]);
+ }
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * @throws FireflyException
+ *
+ * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
+ */
+ private function setObjectDateBeforeParams(string $field, array $range, bool $prohibited = false): void
+ {
+ /**
+ * @var string $key
+ * @var Carbon|string $value
+ */
+ foreach ($range as $key => $value) {
+ $key = $prohibited ? sprintf('%s_not', $key) : $key;
+
+ switch ($key) {
+ default:
+ throw new FireflyException(sprintf('Cannot handle key "%s" in setObjectDateBeforeParams()', $key));
+
+ case 'exact':
+ if ($value instanceof Carbon) {
+ $this->collector->setObjectBefore($value, $field);
+ $this->operators->push(['type' => sprintf('%s_before', $field), 'value' => $value->format('Y-m-d')]);
+ }
+
+ break;
+
+ case 'year':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value));
+ $this->collector->objectYearBefore($value, $field);
+ $this->operators->push(['type' => sprintf('%s_before_year', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'month':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value));
+ $this->collector->objectMonthBefore($value, $field);
+ $this->operators->push(['type' => sprintf('%s_before_month', $field), 'value' => $value]);
+ }
+
+ break;
+
+ case 'day':
+ if (is_string($value)) {
+ Log::debug(sprintf('Set date_is_before DAY value "%s"', $value));
+ $this->collector->objectDayBefore($value, $field);
+ $this->operators->push(['type' => sprintf('%s_before_day', $field), 'value' => $value]);
+ }
+
+ break;
+ }
}
}
@@ -278,15 +1250,15 @@ class OperatorQuerySearch implements SearchInterface
throw new FireflyException(sprintf('Unsupported search operator: "%s"', $operator));
- // some search operators are ignored, basically:
+ // some search operators are ignored, basically:
case 'user_action':
Log::info(sprintf('Ignore search operator "%s"', $operator));
return false;
- //
- // all account related searches:
- //
+ //
+ // all account related searches:
+ //
case 'account_is':
$this->searchAccount($value, SearchDirection::BOTH, StringPosition::IS);
@@ -448,7 +1420,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'source_account_id':
- $account = $this->accountRepository->find((int) $value);
+ $account = $this->accountRepository->find((int)$value);
if (null !== $account) {
$this->collector->setSourceAccounts(new Collection()->push($account));
}
@@ -461,7 +1433,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-source_account_id':
- $account = $this->accountRepository->find((int) $value);
+ $account = $this->accountRepository->find((int)$value);
if (null !== $account) {
$this->collector->excludeSourceAccounts(new Collection()->push($account));
}
@@ -474,25 +1446,25 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'journal_id':
- $parts = explode(',', $value);
+ $parts = explode(',', $value);
$this->collector->setJournalIds($parts);
break;
case '-journal_id':
- $parts = explode(',', $value);
+ $parts = explode(',', $value);
$this->collector->excludeJournalIds($parts);
break;
case 'id':
- $parts = explode(',', $value);
+ $parts = explode(',', $value);
$this->collector->setIds($parts);
break;
case '-id':
- $parts = explode(',', $value);
+ $parts = explode(',', $value);
$this->collector->excludeIds($parts);
break;
@@ -578,7 +1550,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'destination_account_id':
- $account = $this->accountRepository->find((int) $value);
+ $account = $this->accountRepository->find((int)$value);
if (null !== $account) {
$this->collector->setDestinationAccounts(new Collection()->push($account));
}
@@ -590,7 +1562,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-destination_account_id':
- $account = $this->accountRepository->find((int) $value);
+ $account = $this->accountRepository->find((int)$value);
if (null !== $account) {
$this->collector->excludeDestinationAccounts(new Collection()->push($account));
}
@@ -603,12 +1575,12 @@ class OperatorQuerySearch implements SearchInterface
case 'account_id':
Log::debug(sprintf('Now in "account_id" with value "%s"', $value));
- $parts = explode(',', $value);
- $collection = new Collection();
+ $parts = explode(',', $value);
+ $collection = new Collection();
foreach ($parts as $accountId) {
- $accountId = (int) $accountId;
+ $accountId = (int)$accountId;
Log::debug(sprintf('Searching for account with ID #%d', $accountId));
- $account = $this->accountRepository->find($accountId);
+ $account = $this->accountRepository->find($accountId);
if (null !== $account) {
Log::debug(sprintf('Found account with ID #%d ("%s")', $accountId, $account->name));
$collection->push($account);
@@ -629,10 +1601,10 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-account_id':
- $parts = explode(',', $value);
- $collection = new Collection();
+ $parts = explode(',', $value);
+ $collection = new Collection();
foreach ($parts as $accountId) {
- $account = $this->accountRepository->find((int) $accountId);
+ $account = $this->accountRepository->find((int)$accountId);
if (null !== $account) {
$collection->push($account);
}
@@ -647,48 +1619,48 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // cash account
- //
+ //
+ // cash account
+ //
case 'source_is_cash':
- $account = $this->getCashAccount();
+ $account = $this->getCashAccount();
$this->collector->setSourceAccounts(new Collection()->push($account));
break;
case '-source_is_cash':
- $account = $this->getCashAccount();
+ $account = $this->getCashAccount();
$this->collector->excludeSourceAccounts(new Collection()->push($account));
break;
case 'destination_is_cash':
- $account = $this->getCashAccount();
+ $account = $this->getCashAccount();
$this->collector->setDestinationAccounts(new Collection()->push($account));
break;
case '-destination_is_cash':
- $account = $this->getCashAccount();
+ $account = $this->getCashAccount();
$this->collector->excludeDestinationAccounts(new Collection()->push($account));
break;
case 'account_is_cash':
- $account = $this->getCashAccount();
+ $account = $this->getCashAccount();
$this->collector->setAccounts(new Collection()->push($account));
break;
case '-account_is_cash':
- $account = $this->getCashAccount();
+ $account = $this->getCashAccount();
$this->collector->excludeAccounts(new Collection()->push($account));
break;
- //
- // description
- //
+ //
+ // description
+ //
case 'description_starts':
$this->collector->descriptionStarts([$value]);
@@ -710,7 +1682,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'description_contains':
- $this->words[] = $value;
+ $this->words[] = $value;
return false;
@@ -729,11 +1701,11 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // currency
- //
+ //
+ // currency
+ //
case 'currency_is':
- $currency = $this->findCurrency($value);
+ $currency = $this->findCurrency($value);
if ($currency instanceof TransactionCurrency) {
$this->collector->setCurrency($currency);
}
@@ -745,7 +1717,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-currency_is':
- $currency = $this->findCurrency($value);
+ $currency = $this->findCurrency($value);
if ($currency instanceof TransactionCurrency) {
$this->collector->excludeCurrency($currency);
}
@@ -757,7 +1729,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'foreign_currency_is':
- $currency = $this->findCurrency($value);
+ $currency = $this->findCurrency($value);
if ($currency instanceof TransactionCurrency) {
$this->collector->setForeignCurrency($currency);
}
@@ -769,7 +1741,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-foreign_currency_is':
- $currency = $this->findCurrency($value);
+ $currency = $this->findCurrency($value);
if ($currency instanceof TransactionCurrency) {
$this->collector->excludeForeignCurrency($currency);
}
@@ -780,9 +1752,9 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // attachments
- //
+ //
+ // attachments
+ //
case 'has_attachments':
case '-has_no_attachments':
Log::debug('Set collector to filter on attachments.');
@@ -797,8 +1769,8 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // categories
+ //
+ // categories
case '-has_any_category':
case 'has_no_category':
$this->collector->withoutCategory();
@@ -812,7 +1784,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'category_is':
- $category = $this->categoryRepository->findByName($value);
+ $category = $this->categoryRepository->findByName($value);
if (null !== $category) {
$this->collector->setCategory($category);
@@ -824,7 +1796,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-category_is':
- $category = $this->categoryRepository->findByName($value);
+ $category = $this->categoryRepository->findByName($value);
if (null !== $category) {
$this->collector->excludeCategory($category);
@@ -834,7 +1806,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'category_ends':
- $result = $this->categoryRepository->categoryEndsWith($value, 1337);
+ $result = $this->categoryRepository->categoryEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->setCategories($result);
}
@@ -846,7 +1818,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-category_ends':
- $result = $this->categoryRepository->categoryEndsWith($value, 1337);
+ $result = $this->categoryRepository->categoryEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeCategories($result);
}
@@ -858,7 +1830,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'category_starts':
- $result = $this->categoryRepository->categoryStartsWith($value, 1337);
+ $result = $this->categoryRepository->categoryStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->setCategories($result);
}
@@ -870,7 +1842,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-category_starts':
- $result = $this->categoryRepository->categoryStartsWith($value, 1337);
+ $result = $this->categoryRepository->categoryStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeCategories($result);
}
@@ -882,7 +1854,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'category_contains':
- $result = $this->categoryRepository->searchCategory($value, 1337);
+ $result = $this->categoryRepository->searchCategory($value, 1337);
if ($result->count() > 0) {
$this->collector->setCategories($result);
}
@@ -894,7 +1866,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-category_contains':
- $result = $this->categoryRepository->searchCategory($value, 1337);
+ $result = $this->categoryRepository->searchCategory($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeCategories($result);
}
@@ -905,9 +1877,9 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // budgets
- //
+ //
+ // budgets
+ //
case '-has_any_budget':
case 'has_no_budget':
$this->collector->withoutBudget();
@@ -921,7 +1893,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'budget_contains':
- $result = $this->budgetRepository->searchBudget($value, 1337);
+ $result = $this->budgetRepository->searchBudget($value, 1337);
if ($result->count() > 0) {
$this->collector->setBudgets($result);
}
@@ -933,7 +1905,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-budget_contains':
- $result = $this->budgetRepository->searchBudget($value, 1337);
+ $result = $this->budgetRepository->searchBudget($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBudgets($result);
}
@@ -945,7 +1917,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'budget_is':
- $budget = $this->budgetRepository->findByName($value);
+ $budget = $this->budgetRepository->findByName($value);
if (null !== $budget) {
$this->collector->setBudget($budget);
@@ -957,7 +1929,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-budget_is':
- $budget = $this->budgetRepository->findByName($value);
+ $budget = $this->budgetRepository->findByName($value);
if (null !== $budget) {
$this->collector->excludeBudget($budget);
@@ -969,7 +1941,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'budget_ends':
- $result = $this->budgetRepository->budgetEndsWith($value, 1337);
+ $result = $this->budgetRepository->budgetEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->setBudgets($result);
}
@@ -981,7 +1953,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-budget_ends':
- $result = $this->budgetRepository->budgetEndsWith($value, 1337);
+ $result = $this->budgetRepository->budgetEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBudgets($result);
}
@@ -993,7 +1965,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'budget_starts':
- $result = $this->budgetRepository->budgetStartsWith($value, 1337);
+ $result = $this->budgetRepository->budgetStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->setBudgets($result);
}
@@ -1005,7 +1977,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-budget_starts':
- $result = $this->budgetRepository->budgetStartsWith($value, 1337);
+ $result = $this->budgetRepository->budgetStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBudgets($result);
}
@@ -1016,9 +1988,9 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // bill
- //
+ //
+ // bill
+ //
case '-has_any_bill':
case 'has_no_bill':
$this->collector->withoutBill();
@@ -1032,7 +2004,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'bill_contains':
- $result = $this->billRepository->searchBill($value, 1337);
+ $result = $this->billRepository->searchBill($value, 1337);
if ($result->count() > 0) {
$this->collector->setBills($result);
@@ -1044,7 +2016,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-bill_contains':
- $result = $this->billRepository->searchBill($value, 1337);
+ $result = $this->billRepository->searchBill($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBills($result);
@@ -1056,7 +2028,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'bill_is':
- $bill = $this->billRepository->findByName($value);
+ $bill = $this->billRepository->findByName($value);
if (null !== $bill) {
$this->collector->setBill($bill);
@@ -1068,7 +2040,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-bill_is':
- $bill = $this->billRepository->findByName($value);
+ $bill = $this->billRepository->findByName($value);
if (null !== $bill) {
$this->collector->excludeBills(new Collection()->push($bill));
@@ -1080,7 +2052,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'bill_ends':
- $result = $this->billRepository->billEndsWith($value, 1337);
+ $result = $this->billRepository->billEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->setBills($result);
}
@@ -1092,7 +2064,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-bill_ends':
- $result = $this->billRepository->billEndsWith($value, 1337);
+ $result = $this->billRepository->billEndsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBills($result);
}
@@ -1104,7 +2076,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'bill_starts':
- $result = $this->billRepository->billStartsWith($value, 1337);
+ $result = $this->billRepository->billStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->setBills($result);
}
@@ -1116,7 +2088,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-bill_starts':
- $result = $this->billRepository->billStartsWith($value, 1337);
+ $result = $this->billRepository->billStartsWith($value, 1337);
if ($result->count() > 0) {
$this->collector->excludeBills($result);
}
@@ -1127,9 +2099,9 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // tags
- //
+ //
+ // tags
+ //
case '-has_any_tag':
case 'has_no_tag':
$this->collector->withoutTags();
@@ -1144,7 +2116,7 @@ class OperatorQuerySearch implements SearchInterface
case '-tag_is_not':
case 'tag_is':
- $result = $this->tagRepository->findByTag($value);
+ $result = $this->tagRepository->findByTag($value);
if (null !== $result) {
$this->includeTags[] = $result->id;
$this->includeTags = array_unique($this->includeTags);
@@ -1159,7 +2131,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'tag_contains':
- $tags = $this->tagRepository->searchTag($value);
+ $tags = $this->tagRepository->searchTag($value);
if (0 === $tags->count()) {
Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
Log::warning(sprintf('Call to findNothing() from %s.', $operator));
@@ -1174,7 +2146,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'tag_starts':
- $tags = $this->tagRepository->tagStartsWith($value);
+ $tags = $this->tagRepository->tagStartsWith($value);
if (0 === $tags->count()) {
Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
Log::warning(sprintf('Call to findNothing() from %s.', $operator));
@@ -1189,7 +2161,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-tag_starts':
- $tags = $this->tagRepository->tagStartsWith($value);
+ $tags = $this->tagRepository->tagStartsWith($value);
if (0 === $tags->count()) {
Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
Log::warning(sprintf('Call to findNothing() from %s.', $operator));
@@ -1203,7 +2175,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case 'tag_ends':
- $tags = $this->tagRepository->tagEndsWith($value);
+ $tags = $this->tagRepository->tagEndsWith($value);
if (0 === $tags->count()) {
Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
Log::warning(sprintf('Call to findNothing() from %s.', $operator));
@@ -1217,7 +2189,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-tag_ends':
- $tags = $this->tagRepository->tagEndsWith($value);
+ $tags = $this->tagRepository->tagEndsWith($value);
if (0 === $tags->count()) {
Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
Log::warning(sprintf('Call to findNothing() from %s.', $operator));
@@ -1231,7 +2203,7 @@ class OperatorQuerySearch implements SearchInterface
break;
case '-tag_contains':
- $tags = $this->tagRepository->searchTag($value)->keyBy('id');
+ $tags = $this->tagRepository->searchTag($value)->keyBy('id');
if (0 === $tags->count()) {
Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator));
@@ -1247,7 +2219,7 @@ class OperatorQuerySearch implements SearchInterface
case '-tag_is':
case 'tag_is_not':
- $result = $this->tagRepository->findByTag($value);
+ $result = $this->tagRepository->findByTag($value);
if (null !== $result) {
$this->excludeTags[] = $result->id;
$this->excludeTags = array_unique($this->excludeTags);
@@ -1255,9 +2227,9 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // notes
- //
+ //
+ // notes
+ //
case 'notes_contains':
$this->collector->notesContain($value);
@@ -1320,14 +2292,14 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // amount
- //
+ //
+ // amount
+ //
case 'amount_is':
// strip comma's, make dots.
Log::debug(sprintf('Original value "%s"', $value));
- $value = str_replace(',', '.', $value);
- $amount = app('steam')->positive($value);
+ $value = str_replace(',', '.', $value);
+ $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->amountIs($amount);
@@ -1336,8 +2308,8 @@ class OperatorQuerySearch implements SearchInterface
case '-amount_is':
// strip comma's, make dots.
Log::debug(sprintf('Original value "%s"', $value));
- $value = str_replace(',', '.', $value);
- $amount = app('steam')->positive($value);
+ $value = str_replace(',', '.', $value);
+ $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->amountIsNot($amount);
@@ -1345,9 +2317,9 @@ class OperatorQuerySearch implements SearchInterface
case 'foreign_amount_is':
// strip comma's, make dots.
- $value = str_replace(',', '.', $value);
+ $value = str_replace(',', '.', $value);
- $amount = app('steam')->positive($value);
+ $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->foreignAmountIs($amount);
@@ -1355,9 +2327,9 @@ class OperatorQuerySearch implements SearchInterface
case '-foreign_amount_is':
// strip comma's, make dots.
- $value = str_replace(',', '.', $value);
+ $value = str_replace(',', '.', $value);
- $amount = app('steam')->positive($value);
+ $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->foreignAmountIsNot($amount);
@@ -1366,9 +2338,9 @@ class OperatorQuerySearch implements SearchInterface
case '-amount_more':
case 'amount_less':
// strip comma's, make dots.
- $value = str_replace(',', '.', $value);
+ $value = str_replace(',', '.', $value);
- $amount = app('steam')->positive($value);
+ $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->amountLess($amount);
@@ -1377,9 +2349,9 @@ class OperatorQuerySearch implements SearchInterface
case '-foreign_amount_more':
case 'foreign_amount_less':
// strip comma's, make dots.
- $value = str_replace(',', '.', $value);
+ $value = str_replace(',', '.', $value);
- $amount = app('steam')->positive($value);
+ $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->foreignAmountLess($amount);
@@ -1389,8 +2361,8 @@ class OperatorQuerySearch implements SearchInterface
case 'amount_more':
Log::debug(sprintf('Now handling operator "%s"', $operator));
// strip comma's, make dots.
- $value = str_replace(',', '.', $value);
- $amount = app('steam')->positive($value);
+ $value = str_replace(',', '.', $value);
+ $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->amountMore($amount);
@@ -1400,16 +2372,16 @@ class OperatorQuerySearch implements SearchInterface
case 'foreign_amount_more':
Log::debug(sprintf('Now handling operator "%s"', $operator));
// strip comma's, make dots.
- $value = str_replace(',', '.', $value);
- $amount = app('steam')->positive($value);
+ $value = str_replace(',', '.', $value);
+ $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->foreignAmountMore($amount);
break;
- //
- // transaction type
- //
+ //
+ // transaction type
+ //
case 'transaction_type':
$this->collector->setTypes([ucfirst($value)]);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
@@ -1422,152 +2394,152 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // dates
- //
+ //
+ // dates
+ //
case '-date_on':
case 'date_on':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactDateParams($range, $prohibited);
return false;
case 'date_before':
case '-date_after':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setDateBeforeParams($range);
return false;
case 'date_after':
case '-date_before':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setDateAfterParams($range);
return false;
case 'interest_date_on':
case '-interest_date_on':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactMetaDateParams('interest_date', $range, $prohibited);
return false;
case 'interest_date_before':
case '-interest_date_after':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateBeforeParams('interest_date', $range);
return false;
case 'interest_date_after':
case '-interest_date_before':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateAfterParams('interest_date', $range);
return false;
case 'book_date_on':
case '-book_date_on':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactMetaDateParams('book_date', $range, $prohibited);
return false;
case 'book_date_before':
case '-book_date_after':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateBeforeParams('book_date', $range);
return false;
case 'book_date_after':
case '-book_date_before':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateAfterParams('book_date', $range);
return false;
case 'process_date_on':
case '-process_date_on':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactMetaDateParams('process_date', $range, $prohibited);
return false;
case 'process_date_before':
case '-process_date_after':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateBeforeParams('process_date', $range);
return false;
case 'process_date_after':
case '-process_date_before':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateAfterParams('process_date', $range);
return false;
case 'due_date_on':
case '-due_date_on':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactMetaDateParams('due_date', $range, $prohibited);
return false;
case 'due_date_before':
case '-due_date_after':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateBeforeParams('due_date', $range);
return false;
case 'due_date_after':
case '-due_date_before':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateAfterParams('due_date', $range);
return false;
case 'payment_date_on':
case '-payment_date_on':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactMetaDateParams('payment_date', $range, $prohibited);
return false;
case 'payment_date_before':
case '-payment_date_after':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateBeforeParams('payment_date', $range);
return false;
case 'payment_date_after':
case '-payment_date_before':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateAfterParams('payment_date', $range);
return false;
case 'invoice_date_on':
case '-invoice_date_on':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactMetaDateParams('invoice_date', $range, $prohibited);
return false;
case 'invoice_date_before':
case '-invoice_date_after':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateBeforeParams('invoice_date', $range);
return false;
case 'invoice_date_after':
case '-invoice_date_before':
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setMetaDateAfterParams('invoice_date', $range);
return false;
@@ -1575,7 +2547,7 @@ class OperatorQuerySearch implements SearchInterface
case 'created_at_on':
case '-created_at_on':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactObjectDateParams('created_at', $range, $prohibited);
return false;
@@ -1583,7 +2555,7 @@ class OperatorQuerySearch implements SearchInterface
case 'created_at_before':
case '-created_at_after':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setObjectDateBeforeParams('created_at', $range);
return false;
@@ -1591,7 +2563,7 @@ class OperatorQuerySearch implements SearchInterface
case 'created_at_after':
case '-created_at_before':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setObjectDateAfterParams('created_at', $range);
return false;
@@ -1599,7 +2571,7 @@ class OperatorQuerySearch implements SearchInterface
case 'updated_at_on':
case '-updated_at_on':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setExactObjectDateParams('updated_at', $range, $prohibited);
return false;
@@ -1607,7 +2579,7 @@ class OperatorQuerySearch implements SearchInterface
case 'updated_at_before':
case '-updated_at_after':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setObjectDateBeforeParams('updated_at', $range);
return false;
@@ -1615,14 +2587,14 @@ class OperatorQuerySearch implements SearchInterface
case 'updated_at_after':
case '-updated_at_before':
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
- $range = $this->parseDateRange($operator, $value);
+ $range = $this->parseDateRange($operator, $value);
$this->setObjectDateAfterParams('updated_at', $range);
return false;
- //
- // external URL
- //
+ //
+ // external URL
+ //
case '-any_external_url':
case 'no_external_url':
$this->collector->withoutExternalUrl();
@@ -1687,9 +2659,9 @@ class OperatorQuerySearch implements SearchInterface
break;
- //
- // other fields
- //
+ //
+ // other fields
+ //
case 'external_id_is':
$this->collector->setExternalId($value);
@@ -1947,976 +2919,4 @@ class OperatorQuerySearch implements SearchInterface
return true;
}
-
- /**
- * @throws FireflyException
- */
- public static function getRootOperator(string $operator): string
- {
- $original = $operator;
- // if the string starts with "-" (not), we can remove it and recycle
- // the configuration from the original operator.
- if (str_starts_with($operator, '-')) {
- $operator = substr($operator, 1);
- }
-
- $config = config(sprintf('search.operators.%s', $operator));
- if (null === $config) {
- throw new FireflyException(sprintf('No configuration for search operator "%s"', $operator));
- }
- if (true === $config['alias']) {
- $return = $config['alias_for'];
- if (str_starts_with($original, '-')) {
- $return = sprintf('-%s', $config['alias_for']);
- }
- Log::debug(sprintf('"%s" is an alias for "%s", so return that instead.', $original, $return));
-
- return $return;
- }
- Log::debug(sprintf('"%s" is not an alias.', $operator));
-
- return $original;
- }
-
- /**
- * searchDirection: 1 = source (default), 2 = destination, 3 = both
- * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- * @SuppressWarnings("PHPMD.NPathComplexity")
- */
- private function searchAccount(string $value, SearchDirection $searchDirection, StringPosition $stringPosition, bool $prohibited = false): void
- {
- Log::debug(sprintf('searchAccount("%s", %s, %s)', $value, $stringPosition->name, $searchDirection->name));
-
- // search direction (default): for source accounts
- $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::REVENUE->value];
- $collectorMethod = 'setSourceAccounts';
- if ($prohibited) {
- $collectorMethod = 'excludeSourceAccounts';
- }
-
- // search direction: for destination accounts
- if (SearchDirection::DESTINATION === $searchDirection) { // destination
- // destination can be
- $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::EXPENSE->value];
- $collectorMethod = 'setDestinationAccounts';
- if ($prohibited) {
- $collectorMethod = 'excludeDestinationAccounts';
- }
- }
- // either account could be:
- if (SearchDirection::BOTH === $searchDirection) {
- $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::REVENUE->value];
- $collectorMethod = 'setAccounts';
- if ($prohibited) {
- $collectorMethod = 'excludeAccounts';
- }
- }
- // string position (default): starts with:
- $stringMethod = 'str_starts_with';
-
- // string position: ends with:
- if (StringPosition::ENDS === $stringPosition) {
- $stringMethod = 'str_ends_with';
- }
- if (StringPosition::CONTAINS === $stringPosition) {
- $stringMethod = 'str_contains';
- }
- if (StringPosition::IS === $stringPosition) {
- $stringMethod = 'stringIsEqual';
- }
-
- // get accounts:
- $accounts = $this->accountRepository->searchAccount($value, $searchTypes, 1337);
- if (0 === $accounts->count() && false === $prohibited) {
- Log::warning('Found zero accounts, search for non existing account, NO results will be returned.');
- $this->collector->findNothing();
-
- return;
- }
- if (0 === $accounts->count() && true === $prohibited) {
- Log::debug('Found zero accounts, but the search is negated, so effectively we ignore the search parameter.');
-
- return;
- }
- Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count()));
- $filtered = $accounts->filter(
- static fn (Account $account) => $stringMethod(strtolower($account->name), strtolower($value))
- );
-
- if (0 === $filtered->count()) {
- Log::warning('Left with zero accounts, so cannot find anything, NO results will be returned.');
- $this->collector->findNothing();
-
- return;
- }
- Log::debug(sprintf('Left with %d, set as %s().', $filtered->count(), $collectorMethod));
- $this->collector->{$collectorMethod}($filtered); // @phpstan-ignore-line
- }
-
- /**
- * TODO make enums
- * searchDirection: 1 = source (default), 2 = destination, 3 = both
- * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- * @SuppressWarnings("PHPMD.NPathComplexity")
- */
- private function searchAccountNr(string $value, SearchDirection $searchDirection, StringPosition $stringPosition, bool $prohibited = false): void
- {
- Log::debug(sprintf('searchAccountNr(%s, %d, %d)', $value, $searchDirection->name, $stringPosition->name));
-
- // search direction (default): for source accounts
- $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::REVENUE->value];
- $collectorMethod = 'setSourceAccounts';
- if (true === $prohibited) {
- $collectorMethod = 'excludeSourceAccounts';
- }
-
- // search direction: for destination accounts
- if (SearchDirection::DESTINATION === $searchDirection) {
- // destination can be
- $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::EXPENSE->value];
- $collectorMethod = 'setDestinationAccounts';
- if (true === $prohibited) {
- $collectorMethod = 'excludeDestinationAccounts';
- }
- }
-
- // either account could be:
- if (SearchDirection::BOTH === $searchDirection) {
- $searchTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::REVENUE->value];
- $collectorMethod = 'setAccounts';
- if (true === $prohibited) {
- $collectorMethod = 'excludeAccounts';
- }
- }
-
- // string position (default): starts with:
- $stringMethod = 'str_starts_with';
-
- // string position: ends with:
- if (StringPosition::ENDS === $stringPosition) {
- $stringMethod = 'str_ends_with';
- }
- if (StringPosition::CONTAINS === $stringPosition) {
- $stringMethod = 'str_contains';
- }
- if (StringPosition::IS === $stringPosition) {
- $stringMethod = 'stringIsEqual';
- }
-
- // search for accounts:
- $accounts = $this->accountRepository->searchAccountNr($value, $searchTypes, 1337);
- if (0 === $accounts->count()) {
- Log::debug('Found zero accounts, search for invalid account.');
- Log::warning('Call to findNothing() from searchAccountNr().');
- $this->collector->findNothing();
-
- return;
- }
-
- // if found, do filter
- Log::debug(sprintf('Found %d accounts, will filter.', $accounts->count()));
- $filtered = $accounts->filter(
- static function (Account $account) use ($value, $stringMethod) {
- // either IBAN or account number
- $ibanMatch = $stringMethod(strtolower((string) $account->iban), strtolower($value));
- $accountNrMatch = false;
-
- /** @var AccountMeta $meta */
- foreach ($account->accountMeta as $meta) {
- if ('account_number' === $meta->name && $stringMethod(strtolower((string) $meta->data), strtolower($value))) {
- $accountNrMatch = true;
- }
- }
-
- return $ibanMatch || $accountNrMatch;
- }
- );
-
- if (0 === $filtered->count()) {
- Log::debug('Left with zero, search for invalid account');
- Log::warning('Call to findNothing() from searchAccountNr().');
- $this->collector->findNothing();
-
- return;
- }
- Log::debug(sprintf('Left with %d, set as %s().', $filtered->count(), $collectorMethod));
- $this->collector->{$collectorMethod}($filtered); // @phpstan-ignore-line
- }
-
- private function getCashAccount(): Account
- {
- return $this->accountRepository->getCashAccount();
- }
-
- private function findCurrency(string $value): ?TransactionCurrency
- {
- if (str_contains($value, '(') && str_contains($value, ')')) {
- // bad method to split and get the currency code:
- $parts = explode(' ', $value);
- $value = trim($parts[count($parts) - 1], "() \t\n\r\0\x0B");
- }
- $result = $this->currencyRepository->findByCode($value);
- if (null === $result) {
- return $this->currencyRepository->findByName($value);
- }
-
- return $result;
- }
-
- /**
- * @throws FireflyException
- */
- private function parseDateRange(string $type, string $value): array
- {
- $parser = new ParseDateString();
- if ($parser->isDateRange($value)) {
- return $parser->parseRange($value);
- }
-
- try {
- $parsedDate = $parser->parseDate($value);
- } catch (FireflyException) {
- Log::debug(sprintf('Could not parse date "%s", will return empty array.', $value));
- $this->invalidOperators[] = [
- 'type' => $type,
- 'value' => $value,
- ];
-
- return [];
- }
-
- return [
- 'exact' => $parsedDate,
- ];
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setExactDateParams(array $range, bool $prohibited = false): void
- {
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setExactParameters()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- Log::debug(sprintf('Set date_is_exact value "%s"', $value->format('Y-m-d')));
- $this->collector->setRange($value, $value);
- $this->operators->push(['type' => 'date_on', 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'exact_not':
- if ($value instanceof Carbon) {
- $this->collector->excludeRange($value, $value);
- $this->operators->push(['type' => 'not_date_on', 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_exact YEAR value "%s"', $value));
- $this->collector->yearIs($value);
- $this->operators->push(['type' => 'date_on_year', 'value' => $value]);
- }
-
- break;
-
- case 'year_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_exact_not YEAR value "%s"', $value));
- $this->collector->yearIsNot($value);
- $this->operators->push(['type' => 'not_date_on_year', 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_exact MONTH value "%s"', $value));
- $this->collector->monthIs($value);
- $this->operators->push(['type' => 'date_on_month', 'value' => $value]);
- }
-
- break;
-
- case 'month_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_exact not MONTH value "%s"', $value));
- $this->collector->monthIsNot($value);
- $this->operators->push(['type' => 'not_date_on_month', 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_exact DAY value "%s"', $value));
- $this->collector->dayIs($value);
- $this->operators->push(['type' => 'date_on_day', 'value' => $value]);
- }
-
- break;
-
- case 'day_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set not date_is_exact DAY value "%s"', $value));
- $this->collector->dayIsNot($value);
- $this->operators->push(['type' => 'not_date_on_day', 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setDateBeforeParams(array $range, bool $prohibited = false): void
- {
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setDateBeforeParams()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- $this->collector->setBefore($value);
- $this->operators->push(['type' => 'date_before', 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value));
- $this->collector->yearBefore($value);
- $this->operators->push(['type' => 'date_before_year', 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value));
- $this->collector->monthBefore($value);
- $this->operators->push(['type' => 'date_before_month', 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_before DAY value "%s"', $value));
- $this->collector->dayBefore($value);
- $this->operators->push(['type' => 'date_before_day', 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setDateAfterParams(array $range, bool $prohibited = false): void
- {
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setDateAfterParams()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- $this->collector->setAfter($value);
- $this->operators->push(['type' => 'date_after', 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value));
- $this->collector->yearAfter($value);
- $this->operators->push(['type' => 'date_after_year', 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value));
- $this->collector->monthAfter($value);
- $this->operators->push(['type' => 'date_after_month', 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_after DAY value "%s"', $value));
- $this->collector->dayAfter($value);
- $this->operators->push(['type' => 'date_after_day', 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setExactMetaDateParams(string $field, array $range, bool $prohibited = false): void
- {
- Log::debug('Now in setExactMetaDateParams()');
-
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setExactMetaDateParams()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- Log::debug(sprintf('Set %s_is_exact value "%s"', $field, $value->format('Y-m-d')));
- $this->collector->setMetaDateRange($value, $value, $field);
- $this->operators->push(['type' => sprintf('%s_on', $field), 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'exact_not':
- if ($value instanceof Carbon) {
- Log::debug(sprintf('Set NOT %s_is_exact value "%s"', $field, $value->format('Y-m-d')));
- $this->collector->excludeMetaDateRange($value, $value, $field);
- $this->operators->push(['type' => sprintf('not_%s_on', $field), 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_exact YEAR value "%s"', $field, $value));
- $this->collector->metaYearIs($value, $field);
- $this->operators->push(['type' => sprintf('%s_on_year', $field), 'value' => $value]);
- }
-
- break;
-
- case 'year_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set NOT %s_is_exact YEAR value "%s"', $field, $value));
- $this->collector->metaYearIsNot($value, $field);
- $this->operators->push(['type' => sprintf('not_%s_on_year', $field), 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_exact MONTH value "%s"', $field, $value));
- $this->collector->metaMonthIs($value, $field);
- $this->operators->push(['type' => sprintf('%s_on_month', $field), 'value' => $value]);
- }
-
- break;
-
- case 'month_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set NOT %s_is_exact MONTH value "%s"', $field, $value));
- $this->collector->metaMonthIsNot($value, $field);
- $this->operators->push(['type' => sprintf('not_%s_on_month', $field), 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_exact DAY value "%s"', $field, $value));
- $this->collector->metaDayIs($value, $field);
- $this->operators->push(['type' => sprintf('%s_on_day', $field), 'value' => $value]);
- }
-
- break;
-
- case 'day_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set NOT %s_is_exact DAY value "%s"', $field, $value));
- $this->collector->metaDayIsNot($value, $field);
- $this->operators->push(['type' => sprintf('not_%s_on_day', $field), 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setMetaDateBeforeParams(string $field, array $range, bool $prohibited = false): void
- {
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setMetaDateBeforeParams()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- $this->collector->setMetaBefore($value, $field);
- $this->operators->push(['type' => sprintf('%s_before', $field), 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_before YEAR value "%s"', $field, $value));
- $this->collector->metaYearBefore($value, $field);
- $this->operators->push(['type' => sprintf('%s_before_year', $field), 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_before MONTH value "%s"', $field, $value));
- $this->collector->metaMonthBefore($value, $field);
- $this->operators->push(['type' => sprintf('%s_before_month', $field), 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_before DAY value "%s"', $field, $value));
- $this->collector->metaDayBefore($value, $field);
- $this->operators->push(['type' => sprintf('%s_before_day', $field), 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setMetaDateAfterParams(string $field, array $range, bool $prohibited = false): void
- {
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setMetaDateAfterParams()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- $this->collector->setMetaAfter($value, $field);
- $this->operators->push(['type' => sprintf('%s_after', $field), 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_after YEAR value "%s"', $field, $value));
- $this->collector->metaYearAfter($value, $field);
- $this->operators->push(['type' => sprintf('%s_after_year', $field), 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_after MONTH value "%s"', $field, $value));
- $this->collector->metaMonthAfter($value, $field);
- $this->operators->push(['type' => sprintf('%s_after_month', $field), 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_after DAY value "%s"', $field, $value));
- $this->collector->metaDayAfter($value, $field);
- $this->operators->push(['type' => sprintf('%s_after_day', $field), 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setExactObjectDateParams(string $field, array $range, bool $prohibited = false): void
- {
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setExactObjectDateParams()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- Log::debug(sprintf('Set %s_is_exact value "%s"', $field, $value->format('Y-m-d')));
- $this->collector->setObjectRange($value, clone $value, $field);
- $this->operators->push(['type' => sprintf('%s_on', $field), 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'exact_not':
- if ($value instanceof Carbon) {
- Log::debug(sprintf('Set NOT %s_is_exact value "%s"', $field, $value->format('Y-m-d')));
- $this->collector->excludeObjectRange($value, clone $value, $field);
- $this->operators->push(['type' => sprintf('not_%s_on', $field), 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_exact YEAR value "%s"', $field, $value));
- $this->collector->objectYearIs($value, $field);
- $this->operators->push(['type' => sprintf('%s_on_year', $field), 'value' => $value]);
- }
-
- break;
-
- case 'year_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set NOT %s_is_exact YEAR value "%s"', $field, $value));
- $this->collector->objectYearIsNot($value, $field);
- $this->operators->push(['type' => sprintf('not_%s_on_year', $field), 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_exact MONTH value "%s"', $field, $value));
- $this->collector->objectMonthIs($value, $field);
- $this->operators->push(['type' => sprintf('%s_on_month', $field), 'value' => $value]);
- }
-
- break;
-
- case 'month_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set NOT %s_is_exact MONTH value "%s"', $field, $value));
- $this->collector->objectMonthIsNot($value, $field);
- $this->operators->push(['type' => sprintf('not_%s_on_month', $field), 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set %s_is_exact DAY value "%s"', $field, $value));
- $this->collector->objectDayIs($value, $field);
- $this->operators->push(['type' => sprintf('%s_on_day', $field), 'value' => $value]);
- }
-
- break;
-
- case 'day_not':
- if (is_string($value)) {
- Log::debug(sprintf('Set NOT %s_is_exact DAY value "%s"', $field, $value));
- $this->collector->objectDayIsNot($value, $field);
- $this->operators->push(['type' => sprintf('not_%s_on_day', $field), 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setObjectDateBeforeParams(string $field, array $range, bool $prohibited = false): void
- {
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setObjectDateBeforeParams()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- $this->collector->setObjectBefore($value, $field);
- $this->operators->push(['type' => sprintf('%s_before', $field), 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value));
- $this->collector->objectYearBefore($value, $field);
- $this->operators->push(['type' => sprintf('%s_before_year', $field), 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value));
- $this->collector->objectMonthBefore($value, $field);
- $this->operators->push(['type' => sprintf('%s_before_month', $field), 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_before DAY value "%s"', $value));
- $this->collector->objectDayBefore($value, $field);
- $this->operators->push(['type' => sprintf('%s_before_day', $field), 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- /**
- * @throws FireflyException
- *
- * @SuppressWarnings("PHPMD.BooleanArgumentFlag")
- */
- private function setObjectDateAfterParams(string $field, array $range, bool $prohibited = false): void
- {
- /**
- * @var string $key
- * @var Carbon|string $value
- */
- foreach ($range as $key => $value) {
- $key = $prohibited ? sprintf('%s_not', $key) : $key;
-
- switch ($key) {
- default:
- throw new FireflyException(sprintf('Cannot handle key "%s" in setObjectDateAfterParams()', $key));
-
- case 'exact':
- if ($value instanceof Carbon) {
- $this->collector->setObjectAfter($value, $field);
- $this->operators->push(['type' => sprintf('%s_after', $field), 'value' => $value->format('Y-m-d')]);
- }
-
- break;
-
- case 'year':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value));
- $this->collector->objectYearAfter($value, $field);
- $this->operators->push(['type' => sprintf('%s_after_year', $field), 'value' => $value]);
- }
-
- break;
-
- case 'month':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value));
- $this->collector->objectMonthAfter($value, $field);
- $this->operators->push(['type' => sprintf('%s_after_month', $field), 'value' => $value]);
- }
-
- break;
-
- case 'day':
- if (is_string($value)) {
- Log::debug(sprintf('Set date_is_after DAY value "%s"', $value));
- $this->collector->objectDayAfter($value, $field);
- $this->operators->push(['type' => sprintf('%s_after_day', $field), 'value' => $value]);
- }
-
- break;
- }
- }
- }
-
- private function handleNodeGroup(NodeGroup $node, bool $flipProhibitedFlag): void
- {
- $prohibited = $node->isProhibited($flipProhibitedFlag);
-
- foreach ($node->getNodes() as $subNode) {
- $this->handleSearchNode($subNode, $prohibited);
- }
- }
-
- public function searchTime(): float
- {
- return microtime(true) - $this->startTime;
- }
-
- public function searchTransactions(): LengthAwarePaginator
- {
- $this->parseTagInstructions();
- if (0 === count($this->getWords()) && 0 === count($this->getExcludedWords()) && 0 === count($this->getOperators())) {
- return new LengthAwarePaginator([], 0, 5, 1);
- }
-
- return $this->collector->getPaginatedGroups();
- }
-
- private function parseTagInstructions(): void
- {
- Log::debug('Now in parseTagInstructions()');
- // if exclude tags, remove excluded tags.
- if (count($this->excludeTags) > 0) {
- Log::debug(sprintf('%d exclude tag(s)', count($this->excludeTags)));
- $collection = new Collection();
- foreach ($this->excludeTags as $tagId) {
- $tag = $this->tagRepository->find($tagId);
- if (null !== $tag) {
- Log::debug(sprintf('Exclude tag "%s"', $tag->tag));
- $collection->push($tag);
- }
- }
- Log::debug(sprintf('Selecting all tags except %d excluded tag(s).', $collection->count()));
- $this->collector->setWithoutSpecificTags($collection);
- }
- // if include tags, include them:
- if (count($this->includeTags) > 0) {
- Log::debug(sprintf('%d include tag(s)', count($this->includeTags)));
- $collection = new Collection();
- foreach ($this->includeTags as $tagId) {
- $tag = $this->tagRepository->find($tagId);
- if (null !== $tag) {
- Log::debug(sprintf('Include tag "%s"', $tag->tag));
- $collection->push($tag);
- }
- }
- $this->collector->setAllTags($collection);
- }
- // if include ANY tags, include them: (see #8632)
- if (count($this->includeAnyTags) > 0) {
- Log::debug(sprintf('%d include ANY tag(s)', count($this->includeAnyTags)));
- $collection = new Collection();
- foreach ($this->includeAnyTags as $tagId) {
- $tag = $this->tagRepository->find($tagId);
- if (null !== $tag) {
- Log::debug(sprintf('Include ANY tag "%s"', $tag->tag));
- $collection->push($tag);
- }
- }
- $this->collector->setTags($collection);
- }
- }
-
- public function getWords(): array
- {
- return $this->words;
- }
-
- public function getExcludedWords(): array
- {
- return $this->prohibitedWords;
- }
-
- public function setDate(Carbon $date): void
- {
- $this->date = $date;
- }
-
- public function setPage(int $page): void
- {
- $this->page = $page;
- $this->collector->setPage($this->page);
- }
-
- public function setUser(User $user): void
- {
- $this->accountRepository->setUser($user);
- $this->billRepository->setUser($user);
- $this->categoryRepository->setUser($user);
- $this->budgetRepository->setUser($user);
- $this->tagRepository->setUser($user);
- $this->collector = app(GroupCollectorInterface::class);
- $this->collector->setUser($user);
- $this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation();
-
- $this->setLimit((int) app('preferences')->getForUser($user, 'listPageSize', 50)->data);
- }
-
- public function setLimit(int $limit): void
- {
- $this->limit = $limit;
- $this->collector->setLimit($this->limit);
- }
}
diff --git a/app/Support/Search/QueryParser/GdbotsQueryParser.php b/app/Support/Search/QueryParser/GdbotsQueryParser.php
index a402013e48..0e670a21df 100644
--- a/app/Support/Search/QueryParser/GdbotsQueryParser.php
+++ b/app/Support/Search/QueryParser/GdbotsQueryParser.php
@@ -32,7 +32,6 @@ use Gdbots\QueryParser\QueryParser as BaseQueryParser;
use Illuminate\Support\Facades\Log;
use LogicException;
use TypeError;
-
use function Safe\fwrite;
class GdbotsQueryParser implements QueryParserInterface
@@ -52,12 +51,12 @@ class GdbotsQueryParser implements QueryParserInterface
try {
$result = $this->parser->parse($query);
$nodes = array_map(
- fn (GdbotsNode\Node $node) => $this->convertNode($node),
+ fn(GdbotsNode\Node $node) => $this->convertNode($node),
$result->getNodes()
);
return new NodeGroup($nodes);
- } catch (LogicException|TypeError $e) {
+ } catch (LogicException | TypeError $e) {
fwrite(STDERR, "Setting up GdbotsQueryParserTest\n");
app('log')->error($e->getMessage());
app('log')->error(sprintf('Could not parse search: "%s".', $query));
@@ -76,7 +75,7 @@ class GdbotsQueryParser implements QueryParserInterface
case $node instanceof GdbotsNode\Field:
return new FieldNode(
$node->getValue(),
- (string) $node->getNode()->getValue(),
+ (string)$node->getNode()->getValue(),
BoolOperator::PROHIBITED === $node->getBoolOperator()
);
@@ -85,7 +84,7 @@ class GdbotsQueryParser implements QueryParserInterface
return new NodeGroup(
array_map(
- fn (GdbotsNode\Node $subNode) => $this->convertNode($subNode),
+ fn(GdbotsNode\Node $subNode) => $this->convertNode($subNode),
$node->getNodes()
)
);
@@ -98,7 +97,7 @@ class GdbotsQueryParser implements QueryParserInterface
case $node instanceof GdbotsNode\Mention:
case $node instanceof GdbotsNode\Emoticon:
case $node instanceof GdbotsNode\Emoji:
- return new StringNode((string) $node->getValue(), BoolOperator::PROHIBITED === $node->getBoolOperator());
+ return new StringNode((string)$node->getValue(), BoolOperator::PROHIBITED === $node->getBoolOperator());
default:
throw new FireflyException(
diff --git a/app/Support/Search/QueryParser/QueryParser.php b/app/Support/Search/QueryParser/QueryParser.php
index c9072f970b..2533bcc3a8 100644
--- a/app/Support/Search/QueryParser/QueryParser.php
+++ b/app/Support/Search/QueryParser/QueryParser.php
@@ -46,22 +46,6 @@ class QueryParser implements QueryParserInterface
return $this->buildNodeGroup(false);
}
- private function buildNodeGroup(bool $isSubquery, bool $prohibited = false): NodeGroup
- {
- $nodes = [];
- $nodeResult = $this->buildNextNode($isSubquery);
-
- while ($nodeResult->node instanceof Node) {
- $nodes[] = $nodeResult->node;
- if ($nodeResult->isSubqueryEnd) {
- break;
- }
- $nodeResult = $this->buildNextNode($isSubquery);
- }
-
- return new NodeGroup($nodes, $prohibited);
- }
-
private function buildNextNode(bool $isSubquery): NodeResult
{
$tokenUnderConstruction = '';
@@ -155,7 +139,7 @@ class QueryParser implements QueryParserInterface
if ('' === $tokenUnderConstruction) {
// In any other location, it's just a normal character
$tokenUnderConstruction .= $char;
- $skipNext = true;
+ $skipNext = true;
}
if ('' !== $tokenUnderConstruction && !$skipNext) { // @phpstan-ignore-line
Log::debug(sprintf('Turns out that "%s" is a field name. Reset the token.', $tokenUnderConstruction));
@@ -187,13 +171,29 @@ class QueryParser implements QueryParserInterface
++$this->position;
}
- $finalNode = '' !== $tokenUnderConstruction || '' !== $fieldName
+ $finalNode = '' !== $tokenUnderConstruction || '' !== $fieldName
? $this->createNode($tokenUnderConstruction, $fieldName, $prohibited)
: null;
return new NodeResult($finalNode, true);
}
+ private function buildNodeGroup(bool $isSubquery, bool $prohibited = false): NodeGroup
+ {
+ $nodes = [];
+ $nodeResult = $this->buildNextNode($isSubquery);
+
+ while ($nodeResult->node instanceof Node) {
+ $nodes[] = $nodeResult->node;
+ if ($nodeResult->isSubqueryEnd) {
+ break;
+ }
+ $nodeResult = $this->buildNextNode($isSubquery);
+ }
+
+ return new NodeGroup($nodes, $prohibited);
+ }
+
private function createNode(string $token, string $fieldName, bool $prohibited): Node
{
if ('' !== $fieldName) {
diff --git a/app/Support/Singleton/PreferencesSingleton.php b/app/Support/Singleton/PreferencesSingleton.php
index 32b9bb94f6..e8ff779c5e 100644
--- a/app/Support/Singleton/PreferencesSingleton.php
+++ b/app/Support/Singleton/PreferencesSingleton.php
@@ -29,7 +29,7 @@ class PreferencesSingleton
{
private static ?PreferencesSingleton $instance = null;
- private array $preferences = [];
+ private array $preferences = [];
private function __construct()
{
@@ -45,6 +45,11 @@ class PreferencesSingleton
return self::$instance;
}
+ public function getPreference(string $key): mixed
+ {
+ return $this->preferences[$key] ?? null;
+ }
+
public function resetPreferences(): void
{
$this->preferences = [];
@@ -54,9 +59,4 @@ class PreferencesSingleton
{
$this->preferences[$key] = $value;
}
-
- public function getPreference(string $key): mixed
- {
- return $this->preferences[$key] ?? null;
- }
}
diff --git a/app/Support/Steam.php b/app/Support/Steam.php
index e103f19ece..c9a13be8b7 100644
--- a/app/Support/Steam.php
+++ b/app/Support/Steam.php
@@ -38,7 +38,6 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use ValueError;
-
use function Safe\parse_url;
use function Safe\preg_replace;
@@ -47,6 +46,81 @@ use function Safe\preg_replace;
*/
class Steam
{
+ public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
+ {
+ Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String()));
+ $result = [];
+ $convertToPrimary ??= Amount::convertToPrimary();
+ $primary ??= Amount::getPrimaryCurrency();
+ $currencies = $this->getCurrencies($accounts);
+
+ // balance(s) in all currencies for ALL accounts.
+ $arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
+ ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
+ ->groupBy(['transactions.account_id', 'transaction_currencies.code'])
+ ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray();
+
+ /** @var Account $account */
+ foreach ($accounts as $account) {
+ // this array is PER account, so we wait a bit before we change code here.
+ $return = [
+ 'pc_balance' => '0',
+ 'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
+ ];
+ $currency = $currencies[$account->id];
+
+ // second array
+ $accountSum = array_filter($arrayOfSums, fn($entry) => $entry['account_id'] === $account->id);
+ if (0 === count($accountSum)) {
+ $result[$account->id] = $return;
+
+ continue;
+ }
+ $accountSum = array_values($accountSum)[0];
+ $sumOfAmount = (string)$accountSum['sum_of_amount'];
+ $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
+ $sumsByCode = [
+ $accountSum['code'] => $sumOfAmount,
+ ];
+
+ // Log::debug('All balances are (joined)', $others);
+ // if there is no request to convert, take this as "balance" and "pc_balance".
+ $return['balance'] = $sumsByCode[$currency->code] ?? '0';
+ if (!$convertToPrimary) {
+ unset($return['pc_balance']);
+ // Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
+ }
+ // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
+ if ($convertToPrimary) {
+ $return['pc_balance'] = $this->convertAllBalances($sumsByCode, $primary, $date);
+ // Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
+ }
+
+ // either way, the balance is always combined with the virtual balance:
+ $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
+
+ if ($convertToPrimary) {
+ // the primary currency balance is combined with a converted virtual_balance:
+ $converter = new ExchangeRateConverter();
+ $pcVirtualBalance = $converter->convert($currency, $primary, $date, $virtualBalance);
+ $return['pc_balance'] = bcadd($pcVirtualBalance, $return['pc_balance']);
+ // Log::debug(sprintf('Primary virtual balance makes the primary total %s', $return['pc_balance']));
+ }
+ if (!$convertToPrimary) {
+ // if not, also increase the balance + primary balance for consistency.
+ $return['balance'] = bcadd($return['balance'], $virtualBalance);
+ // Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
+ }
+ $final = array_merge($return, $sumsByCode);
+ $result[$account->id] = $final;
+ // Log::debug('Final balance is', $final);
+ }
+
+ return $result;
+ }
+
/**
* https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers
*/
@@ -66,27 +140,15 @@ class Steam
// Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision));
if (str_contains($number, '.')) {
if ('-' !== $number[0]) {
- return bcadd($number, '0.'.str_repeat('0', $precision).'5', $precision);
+ return bcadd($number, '0.' . str_repeat('0', $precision) . '5', $precision);
}
- return bcsub($number, '0.'.str_repeat('0', $precision).'5', $precision);
+ return bcsub($number, '0.' . str_repeat('0', $precision) . '5', $precision);
}
return $number;
}
- public function filterAccountBalances(array $total, Account $account, bool $convertToPrimary, ?TransactionCurrency $currency = null): array
- {
- Log::debug(sprintf('filterAccountBalances(#%d)', $account->id));
- $return = [];
- foreach ($total as $key => $value) {
- $return[$key] = $this->filterAccountBalance($value, $account, $convertToPrimary, $currency);
- }
- Log::debug(sprintf('end of filterAccountBalances(#%d)', $account->id));
-
- return $return;
- }
-
public function filterAccountBalance(array $set, Account $account, bool $convertToPrimary, ?TransactionCurrency $currency = null): array
{
Log::debug(sprintf('filterAccountBalance(#%d)', $account->id), $set);
@@ -138,6 +200,18 @@ class Steam
return $set;
}
+ public function filterAccountBalances(array $total, Account $account, bool $convertToPrimary, ?TransactionCurrency $currency = null): array
+ {
+ Log::debug(sprintf('filterAccountBalances(#%d)', $account->id));
+ $return = [];
+ foreach ($total as $key => $value) {
+ $return[$key] = $this->filterAccountBalance($value, $account, $convertToPrimary, $currency);
+ }
+ Log::debug(sprintf('end of filterAccountBalances(#%d)', $account->id));
+
+ return $return;
+ }
+
public function filterSpaces(string $string): string
{
$search = [
@@ -197,6 +271,94 @@ class Steam
return str_replace($search, '', $string);
}
+ /**
+ * Returns smaller than or equal to, so be careful with END OF DAY.
+ *
+ * Returns the balance of an account at exact moment given. Array with at least one value.
+ * Always returns:
+ * "balance": balance in the account's currency OR user's primary currency if the account has no currency
+ * "EUR": balance in EUR (or whatever currencies the account has balance in)
+ *
+ * If the user has $convertToPrimary:
+ * "balance": balance in the account's currency OR user's primary currency if the account has no currency
+ * --> "pc_balance": balance in the user's primary currency, with all amounts converted to the primary currency.
+ * "EUR": balance in EUR (or whatever currencies the account has balance in)
+ */
+ public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
+ {
+
+ $cache = new CacheProperties();
+ $cache->addProperty($account->id);
+ $cache->addProperty($date);
+ if ($cache->has()) {
+ Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
+
+ // return $cache->get();
+ }
+ // Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
+ if (null === $convertToPrimary) {
+ $convertToPrimary = Amount::convertToPrimary($account->user);
+ }
+ if (!$primary instanceof TransactionCurrency) {
+ $primary = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
+ }
+ // account balance thing.
+ $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
+ if ($currencyPresent) {
+ $accountCurrency = $account->meta['currency'];
+ }
+ if (!$currencyPresent) {
+
+ $accountCurrency = $this->getAccountCurrency($account);
+ }
+ $hasCurrency = null !== $accountCurrency;
+ $currency = $hasCurrency ? $accountCurrency : $primary;
+ $return = [
+ 'pc_balance' => '0',
+ 'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
+ ];
+ // balance(s) in all currencies.
+ $array = $account->transactions()
+ ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
+ ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
+ ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
+ ->get(['transaction_currencies.code', 'transactions.amount'])->toArray();
+ $others = $this->groupAndSumTransactions($array, 'code', 'amount');
+ // Log::debug('All balances are (joined)', $others);
+ // if there is no request to convert, take this as "balance" and "pc_balance".
+ $return['balance'] = $others[$currency->code] ?? '0';
+ if (!$convertToPrimary) {
+ unset($return['pc_balance']);
+ // Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
+ }
+ // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
+ if ($convertToPrimary) {
+ $return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert.
+ // Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
+ }
+
+ // either way, the balance is always combined with the virtual balance:
+ $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
+
+ if ($convertToPrimary) {
+ // the primary currency balance is combined with a converted virtual_balance:
+ $converter = new ExchangeRateConverter();
+ $pcVirtualBalance = $converter->convert($currency, $primary, $date, $virtualBalance);
+ $return['pc_balance'] = bcadd($pcVirtualBalance, $return['pc_balance']);
+ // Log::debug(sprintf('Primary virtual balance makes the primary total %s', $return['pc_balance']));
+ }
+ if (!$convertToPrimary) {
+ // if not, also increase the balance + primary balance for consistency.
+ $return['balance'] = bcadd($return['balance'], $virtualBalance);
+ // Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
+ }
+ $final = array_merge($return, $others);
+ // Log::debug('Final balance is', $final);
+ $cache->store($final);
+
+ return $final;
+ }
+
public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end, bool $convertToPrimary): array
{
// expand period.
@@ -205,7 +367,7 @@ class Steam
Log::debug(sprintf('finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
// set up cache
- $cache = new CacheProperties();
+ $cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('final-balance-in-range');
$cache->addProperty($start);
@@ -215,22 +377,22 @@ class Steam
return $cache->get();
}
- $balances = [];
- $formatted = $start->format('Y-m-d');
+ $balances = [];
+ $formatted = $start->format('Y-m-d');
/*
* To make sure the start balance is correct, we need to get the balance at the exact end of the previous day.
* Since we just did "startOfDay" we can do subDay()->endOfDay() to get the correct moment.
* THAT will be the start balance.
*/
- $request = clone $start;
+ $request = clone $start;
$request->subDay()->endOfDay();
Log::debug('Get first balance to start.');
Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String()));
- $startBalance = $this->finalAccountBalance($account, $request);
- $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
- $accountCurrency = $this->getAccountCurrency($account);
- $hasCurrency = $accountCurrency instanceof TransactionCurrency;
- $currency = $accountCurrency ?? $primaryCurrency;
+ $startBalance = $this->finalAccountBalance($account, $request);
+ $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
+ $accountCurrency = $this->getAccountCurrency($account);
+ $hasCurrency = $accountCurrency instanceof TransactionCurrency;
+ $currency = $accountCurrency ?? $primaryCurrency;
Log::debug(sprintf('Currency is %s', $currency->code));
@@ -243,7 +405,7 @@ class Steam
Log::debug(sprintf('Also set start balance in %s', $primaryCurrency->code));
$startBalance[$primaryCurrency->code] ??= '0';
}
- $currencies = [
+ $currencies = [
$currency->id => $currency,
$primaryCurrency->id => $primaryCurrency,
];
@@ -253,48 +415,47 @@ class Steam
// sums up the balance changes per day.
Log::debug(sprintf('Date >= %s and <= %s', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s')));
- $set = $account->transactions()
- ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
- ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
- ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
- ->groupBy('transaction_journals.date')
- ->groupBy('transactions.transaction_currency_id')
- ->orderBy('transaction_journals.date', 'ASC')
- ->whereNull('transaction_journals.deleted_at')
- ->get(
- [ // @phpstan-ignore-line
- 'transaction_journals.date',
- 'transactions.transaction_currency_id',
- DB::raw('SUM(transactions.amount) AS sum_of_day'),
- ]
- )
- ;
+ $set = $account->transactions()
+ ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
+ ->where('transaction_journals.date', '>=', $start->format('Y-m-d H:i:s'))
+ ->where('transaction_journals.date', '<=', $end->format('Y-m-d H:i:s'))
+ ->groupBy('transaction_journals.date')
+ ->groupBy('transactions.transaction_currency_id')
+ ->orderBy('transaction_journals.date', 'ASC')
+ ->whereNull('transaction_journals.deleted_at')
+ ->get(
+ [ // @phpstan-ignore-line
+ 'transaction_journals.date',
+ 'transactions.transaction_currency_id',
+ DB::raw('SUM(transactions.amount) AS sum_of_day'),
+ ]
+ );
- $currentBalance = $startBalance;
- $converter = new ExchangeRateConverter();
+ $currentBalance = $startBalance;
+ $converter = new ExchangeRateConverter();
/** @var Transaction $entry */
foreach ($set as $entry) {
// get date object
- $carbon = new Carbon($entry->date, $entry->date_tz);
- $carbonKey = $carbon->format('Y-m-d');
+ $carbon = new Carbon($entry->date, $entry->date_tz);
+ $carbonKey = $carbon->format('Y-m-d');
// make sure sum is a string:
- $sumOfDay = (string)($entry->sum_of_day ?? '0');
+ $sumOfDay = (string)($entry->sum_of_day ?? '0');
// #10426 make sure sum is not in scientific notation.
- $sumOfDay = $this->floatalize($sumOfDay);
+ $sumOfDay = $this->floatalize($sumOfDay);
// find currency of this entry, does not have to exist.
$currencies[$entry->transaction_currency_id] ??= Amount::getTransactionCurrencyById($entry->transaction_currency_id);
// make sure this $entry has its own $entryCurrency
/** @var TransactionCurrency $entryCurrency */
- $entryCurrency = $currencies[$entry->transaction_currency_id];
+ $entryCurrency = $currencies[$entry->transaction_currency_id];
Log::debug(sprintf('Processing transaction(s) on moment %s', $carbon->format('Y-m-d H:i:s')));
// add amount to current balance in currency code.
- $currentBalance[$entryCurrency->code] ??= '0';
+ $currentBalance[$entryCurrency->code] ??= '0';
$currentBalance[$entryCurrency->code] = bcadd($sumOfDay, (string)$currentBalance[$entryCurrency->code]);
// if not requested to convert to primary currency, add the amount to "balance", do nothing else.
@@ -312,7 +473,7 @@ class Steam
}
}
// add to final array.
- $balances[$carbonKey] = $currentBalance;
+ $balances[$carbonKey] = $currentBalance;
Log::debug(sprintf('Updated entry [%s]', $carbonKey), $currentBalance);
}
$cache->store($balances);
@@ -321,175 +482,40 @@ class Steam
return $balances;
}
- public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
- {
- Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String()));
- $result = [];
- $convertToPrimary ??= Amount::convertToPrimary();
- $primary ??= Amount::getPrimaryCurrency();
- $currencies = $this->getCurrencies($accounts);
-
- // balance(s) in all currencies for ALL accounts.
- $arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray())
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
- ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
- ->groupBy(['transactions.account_id', 'transaction_currencies.code'])
- ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray()
- ;
-
- /** @var Account $account */
- foreach ($accounts as $account) {
- // this array is PER account, so we wait a bit before we change code here.
- $return = [
- 'pc_balance' => '0',
- 'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
- ];
- $currency = $currencies[$account->id];
-
- // second array
- $accountSum = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id);
- if (0 === count($accountSum)) {
- $result[$account->id] = $return;
-
- continue;
- }
- $accountSum = array_values($accountSum)[0];
- $sumOfAmount = (string)$accountSum['sum_of_amount'];
- $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount);
- $sumsByCode = [
- $accountSum['code'] => $sumOfAmount,
- ];
-
- // Log::debug('All balances are (joined)', $others);
- // if there is no request to convert, take this as "balance" and "pc_balance".
- $return['balance'] = $sumsByCode[$currency->code] ?? '0';
- if (!$convertToPrimary) {
- unset($return['pc_balance']);
- // Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
- }
- // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
- if ($convertToPrimary) {
- $return['pc_balance'] = $this->convertAllBalances($sumsByCode, $primary, $date);
- // Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
- }
-
- // either way, the balance is always combined with the virtual balance:
- $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
-
- if ($convertToPrimary) {
- // the primary currency balance is combined with a converted virtual_balance:
- $converter = new ExchangeRateConverter();
- $pcVirtualBalance = $converter->convert($currency, $primary, $date, $virtualBalance);
- $return['pc_balance'] = bcadd($pcVirtualBalance, $return['pc_balance']);
- // Log::debug(sprintf('Primary virtual balance makes the primary total %s', $return['pc_balance']));
- }
- if (!$convertToPrimary) {
- // if not, also increase the balance + primary balance for consistency.
- $return['balance'] = bcadd($return['balance'], $virtualBalance);
- // Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
- }
- $final = array_merge($return, $sumsByCode);
- $result[$account->id] = $final;
- // Log::debug('Final balance is', $final);
- }
-
- return $result;
- }
-
/**
- * Returns smaller than or equal to, so be careful with END OF DAY.
+ * https://framework.zend.com/downloads/archives
*
- * Returns the balance of an account at exact moment given. Array with at least one value.
- * Always returns:
- * "balance": balance in the account's currency OR user's primary currency if the account has no currency
- * "EUR": balance in EUR (or whatever currencies the account has balance in)
- *
- * If the user has $convertToPrimary:
- * "balance": balance in the account's currency OR user's primary currency if the account has no currency
- * --> "pc_balance": balance in the user's primary currency, with all amounts converted to the primary currency.
- * "EUR": balance in EUR (or whatever currencies the account has balance in)
+ * Convert a scientific notation to float
+ * Additionally fixed a problem with PHP <= 5.2.x with big integers
*/
- public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array
+ public function floatalize(string $value): string
{
+ $value = strtoupper($value);
+ if (!str_contains($value, 'E')) {
+ return $value;
+ }
+ Log::debug(sprintf('Floatalizing %s', $value));
- $cache = new CacheProperties();
- $cache->addProperty($account->id);
- $cache->addProperty($date);
- if ($cache->has()) {
- Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
+ $number = substr($value, 0, (int)strpos($value, 'E'));
+ if (str_contains($number, '.')) {
+ $post = strlen(substr($number, (int)strpos($number, '.') + 1));
+ $mantis = substr($value, (int)strpos($value, 'E') + 1);
+ if ($mantis < 0) {
+ $post += abs((int)$mantis);
+ }
- // return $cache->get();
- }
- // Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s')));
- if (null === $convertToPrimary) {
- $convertToPrimary = Amount::convertToPrimary($account->user);
- }
- if (!$primary instanceof TransactionCurrency) {
- $primary = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup);
- }
- // account balance thing.
- $currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
- if ($currencyPresent) {
- $accountCurrency = $account->meta['currency'];
- }
- if (!$currencyPresent) {
-
- $accountCurrency = $this->getAccountCurrency($account);
- }
- $hasCurrency = null !== $accountCurrency;
- $currency = $hasCurrency ? $accountCurrency : $primary;
- $return = [
- 'pc_balance' => '0',
- 'balance' => '0', // this key is overwritten right away, but I must remember it is always created.
- ];
- // balance(s) in all currencies.
- $array = $account->transactions()
- ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
- ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id')
- ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'))
- ->get(['transaction_currencies.code', 'transactions.amount'])->toArray()
- ;
- $others = $this->groupAndSumTransactions($array, 'code', 'amount');
- // Log::debug('All balances are (joined)', $others);
- // if there is no request to convert, take this as "balance" and "pc_balance".
- $return['balance'] = $others[$currency->code] ?? '0';
- if (!$convertToPrimary) {
- unset($return['pc_balance']);
- // Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance']));
- }
- // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency.
- if ($convertToPrimary) {
- $return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert.
- // Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance']));
+ // TODO careless float could break financial math.
+ return number_format((float)$value, $post, '.', '');
}
- // either way, the balance is always combined with the virtual balance:
- $virtualBalance = (string)('' === (string)$account->virtual_balance ? '0' : $account->virtual_balance);
-
- if ($convertToPrimary) {
- // the primary currency balance is combined with a converted virtual_balance:
- $converter = new ExchangeRateConverter();
- $pcVirtualBalance = $converter->convert($currency, $primary, $date, $virtualBalance);
- $return['pc_balance'] = bcadd($pcVirtualBalance, $return['pc_balance']);
- // Log::debug(sprintf('Primary virtual balance makes the primary total %s', $return['pc_balance']));
- }
- if (!$convertToPrimary) {
- // if not, also increase the balance + primary balance for consistency.
- $return['balance'] = bcadd($return['balance'], $virtualBalance);
- // Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance']));
- }
- $final = array_merge($return, $others);
- // Log::debug('Final balance is', $final);
- $cache->store($final);
-
- return $final;
+ // TODO careless float could break financial math.
+ return number_format((float)$value, 0, '.', '');
}
public function getAccountCurrency(Account $account): ?TransactionCurrency
{
- $type = $account->accountType->type;
- $list = config('firefly.valid_currency_account_types');
+ $type = $account->accountType->type;
+ $list = config('firefly.valid_currency_account_types');
// return null if not in this list.
if (!in_array($type, $list, true)) {
@@ -503,45 +529,6 @@ class Steam
return Amount::getTransactionCurrencyById((int)$result->data);
}
- private function groupAndSumTransactions(array $array, string $group, string $field): array
- {
- $return = [];
-
- foreach ($array as $item) {
- $groupKey = $item[$group] ?? 'unknown';
- $return[$groupKey] = bcadd($return[$groupKey] ?? '0', (string)$item[$field]);
- }
-
- return $return;
- }
-
- private function convertAllBalances(array $others, TransactionCurrency $primary, Carbon $date): string
- {
- $total = '0';
- $converter = new ExchangeRateConverter();
- $singleton = PreferencesSingleton::getInstance();
- foreach ($others as $key => $amount) {
- $preference = $singleton->getPreference($key);
-
- try {
- $currency = $preference ?? Amount::getTransactionCurrencyByCode($key);
- } catch (FireflyException) {
- continue;
- }
- if (null === $preference) {
- $singleton->setPreference($key, $currency);
- }
- $current = $amount;
- if ($currency->id !== $primary->id) {
- $current = $converter->convert($currency, $primary, $date, $amount);
- Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current));
- }
- $total = bcadd($current, $total);
- }
-
- return $total;
- }
-
/**
* @throws FireflyException
*/
@@ -563,19 +550,34 @@ class Steam
return (string)$host;
}
+ /**
+ * Get user's language.
+ *
+ * @throws FireflyException
+ */
+ public function getLanguage(): string // get preference
+ {
+ $preference = app('preferences')->get('language', config('firefly.default_language', 'en_US'))->data;
+ if (!is_string($preference)) {
+ throw new FireflyException(sprintf('Preference "language" must be a string, but is unexpectedly a "%s".', gettype($preference)));
+ }
+
+ return str_replace('-', '_', $preference);
+ }
+
public function getLastActivities(array $accounts): array
{
$list = [];
- $set = auth()->user()->transactions()
- ->whereIn('transactions.account_id', $accounts)
- ->groupBy(['transactions.account_id', 'transaction_journals.user_id'])
- ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
+ $set = auth()->user()->transactions()
+ ->whereIn('transactions.account_id', $accounts)
+ ->groupBy(['transactions.account_id', 'transaction_journals.user_id'])
+ ->get(['transactions.account_id', DB::raw('MAX(transaction_journals.date) AS max_date')]) // @phpstan-ignore-line
;
/** @var Transaction $entry */
foreach ($set as $entry) {
- $date = new Carbon($entry->max_date, config('app.timezone'));
+ $date = new Carbon($entry->max_date, config('app.timezone'));
$date->setTimezone(config('app.timezone'));
$list[(int)$entry->account_id] = $date;
}
@@ -605,21 +607,6 @@ class Steam
return $locale;
}
- /**
- * Get user's language.
- *
- * @throws FireflyException
- */
- public function getLanguage(): string // get preference
- {
- $preference = app('preferences')->get('language', config('firefly.default_language', 'en_US'))->data;
- if (!is_string($preference)) {
- throw new FireflyException(sprintf('Preference "language" must be a string, but is unexpectedly a "%s".', gettype($preference)));
- }
-
- return str_replace('-', '_', $preference);
- }
-
public function getLocaleArray(string $locale): array
{
return [
@@ -650,9 +637,9 @@ class Steam
public function getSafeUrl(string $unknownUrl, string $safeUrl): string
{
// Log::debug(sprintf('getSafeUrl(%s, %s)', $unknownUrl, $safeUrl));
- $returnUrl = $safeUrl;
- $unknownHost = parse_url($unknownUrl, PHP_URL_HOST);
- $safeHost = parse_url($safeUrl, PHP_URL_HOST);
+ $returnUrl = $safeUrl;
+ $unknownHost = parse_url($unknownUrl, PHP_URL_HOST);
+ $safeHost = parse_url($safeUrl, PHP_URL_HOST);
if (null !== $unknownHost && $unknownHost === $safeHost) {
$returnUrl = $unknownUrl;
@@ -681,36 +668,6 @@ class Steam
return $amount;
}
- /**
- * https://framework.zend.com/downloads/archives
- *
- * Convert a scientific notation to float
- * Additionally fixed a problem with PHP <= 5.2.x with big integers
- */
- public function floatalize(string $value): string
- {
- $value = strtoupper($value);
- if (!str_contains($value, 'E')) {
- return $value;
- }
- Log::debug(sprintf('Floatalizing %s', $value));
-
- $number = substr($value, 0, (int)strpos($value, 'E'));
- if (str_contains($number, '.')) {
- $post = strlen(substr($number, (int)strpos($number, '.') + 1));
- $mantis = substr($value, (int)strpos($value, 'E') + 1);
- if ($mantis < 0) {
- $post += abs((int)$mantis);
- }
-
- // TODO careless float could break financial math.
- return number_format((float)$value, $post, '.', '');
- }
-
- // TODO careless float could break financial math.
- return number_format((float)$value, 0, '.', '');
- }
-
public function opposite(?string $amount = null): ?string
{
if (null === $amount) {
@@ -768,6 +725,33 @@ class Steam
return $amount;
}
+ private function convertAllBalances(array $others, TransactionCurrency $primary, Carbon $date): string
+ {
+ $total = '0';
+ $converter = new ExchangeRateConverter();
+ $singleton = PreferencesSingleton::getInstance();
+ foreach ($others as $key => $amount) {
+ $preference = $singleton->getPreference($key);
+
+ try {
+ $currency = $preference ?? Amount::getTransactionCurrencyByCode($key);
+ } catch (FireflyException) {
+ continue;
+ }
+ if (null === $preference) {
+ $singleton->setPreference($key, $currency);
+ }
+ $current = $amount;
+ if ($currency->id !== $primary->id) {
+ $current = $converter->convert($currency, $primary, $date, $amount);
+ Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current));
+ }
+ $total = bcadd($current, $total);
+ }
+
+ return $total;
+ }
+
private function getCurrencies(Collection $accounts): array
{
$currencies = [];
@@ -776,8 +760,8 @@ class Steam
$primary = Amount::getPrimaryCurrency();
$currencies[$primary->id] = $primary;
- $ids = $accounts->pluck('id')->toArray();
- $result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
+ $ids = $accounts->pluck('id')->toArray();
+ $result = AccountMeta::whereIn('account_id', $ids)->where('name', 'currency_id')->get();
/** @var AccountMeta $item */
foreach ($result as $item) {
@@ -787,7 +771,7 @@ class Steam
}
}
// collect those currencies, skip primary because we already have it.
- $set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get();
+ $set = TransactionCurrency::whereIn('id', $accountPreferences)->where('id', '!=', $primary->id)->get();
foreach ($set as $item) {
$currencies[$item->id] = $item;
}
@@ -798,7 +782,7 @@ class Steam
$currencyPresent = isset($account->meta) && array_key_exists('currency', $account->meta) && null !== $account->meta['currency'];
if ($currencyPresent) {
$currencyId = $account->meta['currency']->id;
- $currencies[$currencyId] ??= $account->meta['currency'];
+ $currencies[$currencyId] ??= $account->meta['currency'];
$accountCurrencies[$accountId] = $account->meta['currency'];
}
if (!$currencyPresent && !array_key_exists($accountId, $accountPreferences)) {
@@ -811,4 +795,16 @@ class Steam
return $accountCurrencies;
}
+
+ private function groupAndSumTransactions(array $array, string $group, string $field): array
+ {
+ $return = [];
+
+ foreach ($array as $item) {
+ $groupKey = $item[$group] ?? 'unknown';
+ $return[$groupKey] = bcadd($return[$groupKey] ?? '0', (string)$item[$field]);
+ }
+
+ return $return;
+ }
}
diff --git a/app/Support/System/GeneratesInstallationId.php b/app/Support/System/GeneratesInstallationId.php
index 20cd0a303c..732237214f 100644
--- a/app/Support/System/GeneratesInstallationId.php
+++ b/app/Support/System/GeneratesInstallationId.php
@@ -49,7 +49,7 @@ trait GeneratesInstallationId
if (null === $config) {
$uuid4 = Uuid::uuid4();
- $uniqueId = (string) $uuid4;
+ $uniqueId = (string)$uuid4;
app('log')->info(sprintf('Created Firefly III installation ID %s', $uniqueId));
app('fireflyconfig')->set('installation_id', $uniqueId);
}
diff --git a/app/Support/System/OAuthKeys.php b/app/Support/System/OAuthKeys.php
index 53e353481f..1c1f2276cf 100644
--- a/app/Support/System/OAuthKeys.php
+++ b/app/Support/System/OAuthKeys.php
@@ -31,7 +31,6 @@ use Illuminate\Support\Facades\Crypt;
use Laravel\Passport\Console\KeysCommand;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
-
use function Safe\file_get_contents;
use function Safe\file_put_contents;
@@ -43,6 +42,78 @@ class OAuthKeys
private const string PRIVATE_KEY = 'oauth_private_key';
private const string PUBLIC_KEY = 'oauth_public_key';
+ public static function generateKeys(): void
+ {
+ Artisan::registerCommand(new KeysCommand());
+ Artisan::call('firefly-iii:laravel-passport-keys');
+ }
+
+ public static function hasKeyFiles(): bool
+ {
+ $private = storage_path('oauth-private.key');
+ $public = storage_path('oauth-public.key');
+
+ return file_exists($private) && file_exists($public);
+ }
+
+ public static function keysInDatabase(): bool
+ {
+ $privateKey = '';
+ $publicKey = '';
+ // better check if keys are in the database:
+ if (app('fireflyconfig')->has(self::PRIVATE_KEY) && app('fireflyconfig')->has(self::PUBLIC_KEY)) {
+ try {
+ $privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
+ $publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
+ } catch (ContainerExceptionInterface | FireflyException | NotFoundExceptionInterface $e) {
+ app('log')->error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage()));
+ app('log')->error($e->getTraceAsString());
+ }
+ }
+ if ('' !== $privateKey && '' !== $publicKey) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * @throws FireflyException
+ */
+ public static function restoreKeysFromDB(): bool
+ {
+ $privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
+ $publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
+
+ try {
+ $privateContent = Crypt::decrypt($privateKey);
+ $publicContent = Crypt::decrypt($publicKey);
+ } catch (DecryptException $e) {
+ app('log')->error('Could not decrypt pub/private keypair.');
+ app('log')->error($e->getMessage());
+
+ // delete config vars from DB:
+ app('fireflyconfig')->delete(self::PRIVATE_KEY);
+ app('fireflyconfig')->delete(self::PUBLIC_KEY);
+
+ return false;
+ }
+ $private = storage_path('oauth-private.key');
+ $public = storage_path('oauth-public.key');
+ file_put_contents($private, $privateContent);
+ file_put_contents($public, $publicContent);
+
+ return true;
+ }
+
+ public static function storeKeysInDB(): void
+ {
+ $private = storage_path('oauth-private.key');
+ $public = storage_path('oauth-public.key');
+ app('fireflyconfig')->set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private)));
+ app('fireflyconfig')->set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public)));
+ }
+
public static function verifyKeysRoutine(): void
{
if (!self::keysInDatabase() && !self::hasKeyFiles()) {
@@ -60,76 +131,4 @@ class OAuthKeys
self::storeKeysInDB();
}
}
-
- public static function keysInDatabase(): bool
- {
- $privateKey = '';
- $publicKey = '';
- // better check if keys are in the database:
- if (app('fireflyconfig')->has(self::PRIVATE_KEY) && app('fireflyconfig')->has(self::PUBLIC_KEY)) {
- try {
- $privateKey = (string) app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
- $publicKey = (string) app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
- } catch (ContainerExceptionInterface|FireflyException|NotFoundExceptionInterface $e) {
- app('log')->error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage()));
- app('log')->error($e->getTraceAsString());
- }
- }
- if ('' !== $privateKey && '' !== $publicKey) {
- return true;
- }
-
- return false;
- }
-
- public static function hasKeyFiles(): bool
- {
- $private = storage_path('oauth-private.key');
- $public = storage_path('oauth-public.key');
-
- return file_exists($private) && file_exists($public);
- }
-
- public static function generateKeys(): void
- {
- Artisan::registerCommand(new KeysCommand());
- Artisan::call('firefly-iii:laravel-passport-keys');
- }
-
- public static function storeKeysInDB(): void
- {
- $private = storage_path('oauth-private.key');
- $public = storage_path('oauth-public.key');
- app('fireflyconfig')->set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private)));
- app('fireflyconfig')->set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public)));
- }
-
- /**
- * @throws FireflyException
- */
- public static function restoreKeysFromDB(): bool
- {
- $privateKey = (string) app('fireflyconfig')->get(self::PRIVATE_KEY)?->data;
- $publicKey = (string) app('fireflyconfig')->get(self::PUBLIC_KEY)?->data;
-
- try {
- $privateContent = Crypt::decrypt($privateKey);
- $publicContent = Crypt::decrypt($publicKey);
- } catch (DecryptException $e) {
- app('log')->error('Could not decrypt pub/private keypair.');
- app('log')->error($e->getMessage());
-
- // delete config vars from DB:
- app('fireflyconfig')->delete(self::PRIVATE_KEY);
- app('fireflyconfig')->delete(self::PUBLIC_KEY);
-
- return false;
- }
- $private = storage_path('oauth-private.key');
- $public = storage_path('oauth-public.key');
- file_put_contents($private, $privateContent);
- file_put_contents($public, $publicContent);
-
- return true;
- }
}
diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php
index 39cc1594db..49c9a6d11e 100644
--- a/app/Support/Twig/AmountFormat.php
+++ b/app/Support/Twig/AmountFormat.php
@@ -29,10 +29,10 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Support\Facades\Log;
+use Override;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
-use Override;
/**
* Contains all amount formatting routines.
@@ -48,6 +48,17 @@ class AmountFormat extends AbstractExtension
];
}
+ #[Override]
+ public function getFunctions(): array
+ {
+ return [
+ $this->formatAmountByAccount(),
+ $this->formatAmountBySymbol(),
+ $this->formatAmountByCurrency(),
+ $this->formatAmountByCode(),
+ ];
+ }
+
protected function formatAmount(): TwigFilter
{
return new TwigFilter(
@@ -61,30 +72,6 @@ class AmountFormat extends AbstractExtension
);
}
- protected function formatAmountPlain(): TwigFilter
- {
- return new TwigFilter(
- 'formatAmountPlain',
- static function (string $string): string {
- $currency = Amount::getPrimaryCurrency();
-
- return Amount::formatAnything($currency, $string, false);
- },
- ['is_safe' => ['html']]
- );
- }
-
- #[Override]
- public function getFunctions(): array
- {
- return [
- $this->formatAmountByAccount(),
- $this->formatAmountBySymbol(),
- $this->formatAmountByCurrency(),
- $this->formatAmountByCode(),
- ];
- }
-
/**
* Will format the amount by the currency related to the given account.
*
@@ -107,50 +94,6 @@ class AmountFormat extends AbstractExtension
);
}
- /**
- * Will format the amount by the currency related to the given account.
- */
- protected function formatAmountBySymbol(): TwigFunction
- {
- return new TwigFunction(
- 'formatAmountBySymbol',
- static function (string $amount, ?string $symbol = null, ?int $decimalPlaces = null, ?bool $coloured = null): string {
-
- if (null === $symbol) {
- $message = sprintf('formatAmountBySymbol("%s", %s, %d, %s) was called without a symbol. Please browse to /flush to clear your cache.', $amount, var_export($symbol, true), $decimalPlaces, var_export($coloured, true));
- Log::error($message);
- $currency = Amount::getPrimaryCurrency();
- }
- if (null !== $symbol) {
- $decimalPlaces ??= 2;
- $coloured ??= true;
- $currency = new TransactionCurrency();
- $currency->symbol = $symbol;
- $currency->decimal_places = $decimalPlaces;
- }
-
- return Amount::formatAnything($currency, $amount, $coloured);
- },
- ['is_safe' => ['html']]
- );
- }
-
- /**
- * Will format the amount by the currency related to the given account.
- */
- protected function formatAmountByCurrency(): TwigFunction
- {
- return new TwigFunction(
- 'formatAmountByCurrency',
- static function (TransactionCurrency $currency, string $amount, ?bool $coloured = null): string {
- $coloured ??= true;
-
- return Amount::formatAnything($currency, $amount, $coloured);
- },
- ['is_safe' => ['html']]
- );
- }
-
/**
* Use the code to format a currency.
*/
@@ -175,4 +118,61 @@ class AmountFormat extends AbstractExtension
['is_safe' => ['html']]
);
}
+
+ /**
+ * Will format the amount by the currency related to the given account.
+ */
+ protected function formatAmountByCurrency(): TwigFunction
+ {
+ return new TwigFunction(
+ 'formatAmountByCurrency',
+ static function (TransactionCurrency $currency, string $amount, ?bool $coloured = null): string {
+ $coloured ??= true;
+
+ return Amount::formatAnything($currency, $amount, $coloured);
+ },
+ ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * Will format the amount by the currency related to the given account.
+ */
+ protected function formatAmountBySymbol(): TwigFunction
+ {
+ return new TwigFunction(
+ 'formatAmountBySymbol',
+ static function (string $amount, ?string $symbol = null, ?int $decimalPlaces = null, ?bool $coloured = null): string {
+
+ if (null === $symbol) {
+ $message = sprintf('formatAmountBySymbol("%s", %s, %d, %s) was called without a symbol. Please browse to /flush to clear your cache.', $amount, var_export($symbol, true), $decimalPlaces, var_export($coloured, true));
+ Log::error($message);
+ $currency = Amount::getPrimaryCurrency();
+ }
+ if (null !== $symbol) {
+ $decimalPlaces ??= 2;
+ $coloured ??= true;
+ $currency = new TransactionCurrency();
+ $currency->symbol = $symbol;
+ $currency->decimal_places = $decimalPlaces;
+ }
+
+ return Amount::formatAnything($currency, $amount, $coloured);
+ },
+ ['is_safe' => ['html']]
+ );
+ }
+
+ protected function formatAmountPlain(): TwigFilter
+ {
+ return new TwigFilter(
+ 'formatAmountPlain',
+ static function (string $string): string {
+ $currency = Amount::getPrimaryCurrency();
+
+ return Amount::formatAnything($currency, $string, false);
+ },
+ ['is_safe' => ['html']]
+ );
+ }
}
diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php
index 6f71d6f578..337e832312 100644
--- a/app/Support/Twig/General.php
+++ b/app/Support/Twig/General.php
@@ -37,7 +37,6 @@ use Override;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
-
use function Safe\parse_url;
/**
@@ -57,144 +56,6 @@ class General extends AbstractExtension
];
}
- /**
- * Show account balance. Only used on the front page of Firefly III.
- */
- protected function balance(): TwigFilter
- {
- return new TwigFilter(
- 'balance',
- static function (?Account $account): string {
- if (!$account instanceof Account) {
- return '0';
- }
-
- /** @var Carbon $date */
- $date = session('end', today(config('app.timezone'))->endOfMonth());
- Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
- $info = Steam::finalAccountBalance($account, $date);
- $currency = Steam::getAccountCurrency($account);
- $primary = Amount::getPrimaryCurrency();
- $convertToPrimary = Amount::convertToPrimary();
- $usePrimary = $convertToPrimary && $primary->id !== $currency->id;
- $currency ??= $primary;
- $strings = [];
- foreach ($info as $key => $balance) {
- if ('balance' === $key) {
- // balance in account currency.
- if (!$usePrimary) {
- $strings[] = app('amount')->formatAnything($currency, $balance, false);
- }
-
- continue;
- }
- if ('pc_balance' === $key) {
- // balance in primary currency.
- if ($usePrimary) {
- $strings[] = app('amount')->formatAnything($primary, $balance, false);
- }
-
- continue;
- }
- // for multi currency accounts.
- if ($usePrimary && $key !== $primary->code) {
- $strings[] = app('amount')->formatAnything(Amount::getTransactionCurrencyByCode($key), $balance, false);
- }
- }
-
- return implode(', ', $strings);
- // return app('steam')->balance($account, $date);
- }
- );
- }
-
- /**
- * Used to convert 1024 to 1kb etc.
- */
- protected function formatFilesize(): TwigFilter
- {
- return new TwigFilter(
- 'filesize',
- static function (int $size): string {
- // less than one GB, more than one MB
- if ($size < (1024 * 1024 * 2014) && $size >= (1024 * 1024)) {
- return round($size / (1024 * 1024), 2).' MB';
- }
-
- // less than one MB
- if ($size < (1024 * 1024)) {
- return round($size / 1024, 2).' KB';
- }
-
- return $size.' bytes';
- }
- );
- }
-
- /**
- * Show icon with attachment.
- *
- * @SuppressWarnings("PHPMD.CyclomaticComplexity")
- */
- protected function mimeIcon(): TwigFilter
- {
- return new TwigFilter(
- 'mimeIcon',
- static fn (string $string): string => match ($string) {
- 'application/pdf' => 'fa-file-pdf-o',
- 'image/webp', 'image/png', 'image/jpeg', 'image/svg+xml', 'image/heic', 'image/heic-sequence', 'application/vnd.oasis.opendocument.image' => 'fa-file-image-o',
- 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'application/x-iwork-pages-sffpages', 'application/vnd.sun.xml.writer', 'application/vnd.sun.xml.writer.template', 'application/vnd.sun.xml.writer.global', 'application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.text-template', 'application/vnd.oasis.opendocument.text-web', 'application/vnd.oasis.opendocument.text-master' => 'fa-file-word-o',
- 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'application/vnd.sun.xml.calc', 'application/vnd.sun.xml.calc.template', 'application/vnd.stardivision.calc', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.spreadsheet-template' => 'fa-file-excel-o',
- 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.openxmlformats-officedocument.presentationml.template', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'application/vnd.sun.xml.impress', 'application/vnd.sun.xml.impress.template', 'application/vnd.stardivision.impress', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.presentation-template' => 'fa-file-powerpoint-o',
- 'application/vnd.sun.xml.draw', 'application/vnd.sun.xml.draw.template', 'application/vnd.stardivision.draw', 'application/vnd.oasis.opendocument.chart' => 'fa-paint-brush',
- 'application/vnd.oasis.opendocument.graphics', 'application/vnd.oasis.opendocument.graphics-template', 'application/vnd.sun.xml.math', 'application/vnd.stardivision.math', 'application/vnd.oasis.opendocument.formula', 'application/vnd.oasis.opendocument.database' => 'fa-calculator',
- default => 'fa-file-o',
- },
- ['is_safe' => ['html']]
- );
- }
-
- protected function markdown(): TwigFilter
- {
- return new TwigFilter(
- 'markdown',
- static function (string $text): string {
- $converter = new GithubFlavoredMarkdownConverter(
- [
- 'allow_unsafe_links' => false,
- 'max_nesting_level' => 5,
- 'html_input' => 'escape',
- ]
- );
-
- return (string)$converter->convert($text);
- },
- ['is_safe' => ['html']]
- );
- }
-
- /**
- * Show URL host name
- */
- protected function phpHostName(): TwigFilter
- {
- return new TwigFilter(
- 'phphost',
- static function (string $string): string {
- $proto = parse_url($string, PHP_URL_SCHEME);
- $host = parse_url($string, PHP_URL_HOST);
- if (is_array($host)) {
- $host = implode(' ', $host);
- }
- if (is_array($proto)) {
- $proto = implode(' ', $proto);
- }
-
- return e(sprintf('%s://%s', $proto, $host));
- }
- );
- }
-
#[Override]
public function getFunctions(): array
{
@@ -212,38 +73,6 @@ class General extends AbstractExtension
];
}
- /**
- * Basic example thing for some views.
- */
- protected function phpdate(): TwigFunction
- {
- return new TwigFunction(
- 'phpdate',
- static fn (string $str): string => date($str)
- );
- }
-
- /**
- * Will return "active" when the current route matches the given argument
- * exactly.
- */
- protected function activeRouteStrict(): TwigFunction
- {
- return new TwigFunction(
- 'activeRouteStrict',
- static function (): string {
- $args = func_get_args();
- $route = $args[0]; // name of the route.
-
- if (\Route::getCurrentRoute()->getName() === $route) {
- return 'active';
- }
-
- return '';
- }
- );
- }
-
/**
* Will return "active" when a part of the route matches the argument.
* ie. "accounts" will match "accounts.index".
@@ -275,7 +104,7 @@ class General extends AbstractExtension
'activeRoutePartialObjectType',
static function ($context): string {
[, $route, $objectType] = func_get_args();
- $activeObjectType = $context['objectType'] ?? false;
+ $activeObjectType = $context['objectType'] ?? false;
if ($objectType === $activeObjectType
&& false !== stripos(
@@ -291,6 +120,193 @@ class General extends AbstractExtension
);
}
+ /**
+ * Will return "active" when the current route matches the given argument
+ * exactly.
+ */
+ protected function activeRouteStrict(): TwigFunction
+ {
+ return new TwigFunction(
+ 'activeRouteStrict',
+ static function (): string {
+ $args = func_get_args();
+ $route = $args[0]; // name of the route.
+
+ if (\Route::getCurrentRoute()->getName() === $route) {
+ return 'active';
+ }
+
+ return '';
+ }
+ );
+ }
+
+ /**
+ * Show account balance. Only used on the front page of Firefly III.
+ */
+ protected function balance(): TwigFilter
+ {
+ return new TwigFilter(
+ 'balance',
+ static function (?Account $account): string {
+ if (!$account instanceof Account) {
+ return '0';
+ }
+
+ /** @var Carbon $date */
+ $date = session('end', today(config('app.timezone'))->endOfMonth());
+ Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
+ $info = Steam::finalAccountBalance($account, $date);
+ $currency = Steam::getAccountCurrency($account);
+ $primary = Amount::getPrimaryCurrency();
+ $convertToPrimary = Amount::convertToPrimary();
+ $usePrimary = $convertToPrimary && $primary->id !== $currency->id;
+ $currency ??= $primary;
+ $strings = [];
+ foreach ($info as $key => $balance) {
+ if ('balance' === $key) {
+ // balance in account currency.
+ if (!$usePrimary) {
+ $strings[] = app('amount')->formatAnything($currency, $balance, false);
+ }
+
+ continue;
+ }
+ if ('pc_balance' === $key) {
+ // balance in primary currency.
+ if ($usePrimary) {
+ $strings[] = app('amount')->formatAnything($primary, $balance, false);
+ }
+
+ continue;
+ }
+ // for multi currency accounts.
+ if ($usePrimary && $key !== $primary->code) {
+ $strings[] = app('amount')->formatAnything(Amount::getTransactionCurrencyByCode($key), $balance, false);
+ }
+ }
+
+ return implode(', ', $strings);
+ // return app('steam')->balance($account, $date);
+ }
+ );
+ }
+
+ protected function carbonize(): TwigFunction
+ {
+ return new TwigFunction(
+ 'carbonize',
+ static fn(string $date): Carbon => new Carbon($date, config('app.timezone'))
+ );
+ }
+
+ /**
+ * Formats a string as a thing by converting it to a Carbon first.
+ */
+ protected function formatDate(): TwigFunction
+ {
+ return new TwigFunction(
+ 'formatDate',
+ static function (string $date, string $format): string {
+ $carbon = new Carbon($date);
+
+ return $carbon->isoFormat($format);
+ }
+ );
+ }
+
+ /**
+ * Used to convert 1024 to 1kb etc.
+ */
+ protected function formatFilesize(): TwigFilter
+ {
+ return new TwigFilter(
+ 'filesize',
+ static function (int $size): string {
+ // less than one GB, more than one MB
+ if ($size < (1024 * 1024 * 2014) && $size >= (1024 * 1024)) {
+ return round($size / (1024 * 1024), 2) . ' MB';
+ }
+
+ // less than one MB
+ if ($size < (1024 * 1024)) {
+ return round($size / 1024, 2) . ' KB';
+ }
+
+ return $size . ' bytes';
+ }
+ );
+ }
+
+ /**
+ * TODO Remove me when v2 hits.
+ */
+ protected function getMetaField(): TwigFunction
+ {
+ return new TwigFunction(
+ 'accountGetMetaField',
+ static function (Account $account, string $field): string {
+ /** @var AccountRepositoryInterface $repository */
+ $repository = app(AccountRepositoryInterface::class);
+ $result = $repository->getMetaValue($account, $field);
+ if (null === $result) {
+ return '';
+ }
+
+ return $result;
+ }
+ );
+ }
+
+ protected function getRootSearchOperator(): TwigFunction
+ {
+ return new TwigFunction(
+ 'getRootSearchOperator',
+ static function (string $operator): string {
+ $result = OperatorQuerySearch::getRootOperator($operator);
+
+ return str_replace('-', 'not_', $result);
+ }
+ );
+ }
+
+ /**
+ * Will return true if the user is of role X.
+ */
+ protected function hasRole(): TwigFunction
+ {
+ return new TwigFunction(
+ 'hasRole',
+ static function (string $role): bool {
+ $repository = app(UserRepositoryInterface::class);
+ if ($repository->hasRole(auth()->user(), $role)) {
+ return true;
+ }
+
+ return false;
+ }
+ );
+ }
+
+ protected function markdown(): TwigFilter
+ {
+ return new TwigFilter(
+ 'markdown',
+ static function (string $text): string {
+ $converter = new GithubFlavoredMarkdownConverter(
+ [
+ 'allow_unsafe_links' => false,
+ 'max_nesting_level' => 5,
+ 'html_input' => 'escape',
+ ]
+ );
+
+ return (string)$converter->convert($text);
+ },
+ ['is_safe' => ['html']]
+ );
+ }
+
/**
* Will return "menu-open" when a part of the route matches the argument.
* ie. "accounts" will match "accounts.index".
@@ -313,75 +329,58 @@ class General extends AbstractExtension
}
/**
- * Formats a string as a thing by converting it to a Carbon first.
+ * Show icon with attachment.
+ *
+ * @SuppressWarnings("PHPMD.CyclomaticComplexity")
*/
- protected function formatDate(): TwigFunction
+ protected function mimeIcon(): TwigFilter
{
- return new TwigFunction(
- 'formatDate',
- static function (string $date, string $format): string {
- $carbon = new Carbon($date);
+ return new TwigFilter(
+ 'mimeIcon',
+ static fn(string $string): string => match ($string) {
+ 'application/pdf' => 'fa-file-pdf-o',
+ 'image/webp', 'image/png', 'image/jpeg', 'image/svg+xml', 'image/heic', 'image/heic-sequence', 'application/vnd.oasis.opendocument.image' => 'fa-file-image-o',
+ 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'application/x-iwork-pages-sffpages', 'application/vnd.sun.xml.writer', 'application/vnd.sun.xml.writer.template', 'application/vnd.sun.xml.writer.global', 'application/vnd.stardivision.writer', 'application/vnd.stardivision.writer-global', 'application/vnd.oasis.opendocument.text', 'application/vnd.oasis.opendocument.text-template', 'application/vnd.oasis.opendocument.text-web', 'application/vnd.oasis.opendocument.text-master' => 'fa-file-word-o',
+ 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'application/vnd.sun.xml.calc', 'application/vnd.sun.xml.calc.template', 'application/vnd.stardivision.calc', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.spreadsheet-template' => 'fa-file-excel-o',
+ 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/vnd.openxmlformats-officedocument.presentationml.template', 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'application/vnd.sun.xml.impress', 'application/vnd.sun.xml.impress.template', 'application/vnd.stardivision.impress', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.presentation-template' => 'fa-file-powerpoint-o',
+ 'application/vnd.sun.xml.draw', 'application/vnd.sun.xml.draw.template', 'application/vnd.stardivision.draw', 'application/vnd.oasis.opendocument.chart' => 'fa-paint-brush',
+ 'application/vnd.oasis.opendocument.graphics', 'application/vnd.oasis.opendocument.graphics-template', 'application/vnd.sun.xml.math', 'application/vnd.stardivision.math', 'application/vnd.oasis.opendocument.formula', 'application/vnd.oasis.opendocument.database' => 'fa-calculator',
+ default => 'fa-file-o',
+ },
+ ['is_safe' => ['html']]
+ );
+ }
- return $carbon->isoFormat($format);
+ /**
+ * Show URL host name
+ */
+ protected function phpHostName(): TwigFilter
+ {
+ return new TwigFilter(
+ 'phphost',
+ static function (string $string): string {
+ $proto = parse_url($string, PHP_URL_SCHEME);
+ $host = parse_url($string, PHP_URL_HOST);
+ if (is_array($host)) {
+ $host = implode(' ', $host);
+ }
+ if (is_array($proto)) {
+ $proto = implode(' ', $proto);
+ }
+
+ return e(sprintf('%s://%s', $proto, $host));
}
);
}
/**
- * TODO Remove me when v2 hits.
+ * Basic example thing for some views.
*/
- protected function getMetaField(): TwigFunction
+ protected function phpdate(): TwigFunction
{
return new TwigFunction(
- 'accountGetMetaField',
- static function (Account $account, string $field): string {
- /** @var AccountRepositoryInterface $repository */
- $repository = app(AccountRepositoryInterface::class);
- $result = $repository->getMetaValue($account, $field);
- if (null === $result) {
- return '';
- }
-
- return $result;
- }
- );
- }
-
- /**
- * Will return true if the user is of role X.
- */
- protected function hasRole(): TwigFunction
- {
- return new TwigFunction(
- 'hasRole',
- static function (string $role): bool {
- $repository = app(UserRepositoryInterface::class);
- if ($repository->hasRole(auth()->user(), $role)) {
- return true;
- }
-
- return false;
- }
- );
- }
-
- protected function getRootSearchOperator(): TwigFunction
- {
- return new TwigFunction(
- 'getRootSearchOperator',
- static function (string $operator): string {
- $result = OperatorQuerySearch::getRootOperator($operator);
-
- return str_replace('-', 'not_', $result);
- }
- );
- }
-
- protected function carbonize(): TwigFunction
- {
- return new TwigFunction(
- 'carbonize',
- static fn (string $date): Carbon => new Carbon($date, config('app.timezone'))
+ 'phpdate',
+ static fn(string $str): string => date($str)
);
}
}
diff --git a/app/Support/Twig/Rule.php b/app/Support/Twig/Rule.php
index c833745d40..7ed872df8b 100644
--- a/app/Support/Twig/Rule.php
+++ b/app/Support/Twig/Rule.php
@@ -23,34 +23,43 @@ declare(strict_types=1);
namespace FireflyIII\Support\Twig;
-use Twig\Extension\AbstractExtension;
-use Twig\TwigFunction;
use Config;
use Override;
+use Twig\Extension\AbstractExtension;
+use Twig\TwigFunction;
/**
* Class Rule.
*/
class Rule extends AbstractExtension
{
- #[Override]
- public function getFunctions(): array
+ public function allActionTriggers(): TwigFunction
{
- return [
- $this->allJournalTriggers(),
- $this->allRuleTriggers(),
- $this->allActionTriggers(),
- ];
+ return new TwigFunction(
+ 'allRuleActions',
+ static function () {
+ // array of valid values for actions
+ $ruleActions = array_keys(Config::get('firefly.rule-actions'));
+ $possibleActions = [];
+ foreach ($ruleActions as $key) {
+ $possibleActions[$key] = (string)trans('firefly.rule_action_' . $key . '_choice');
+ }
+ unset($ruleActions);
+ asort($possibleActions);
+
+ return $possibleActions;
+ }
+ );
}
public function allJournalTriggers(): TwigFunction
{
return new TwigFunction(
'allJournalTriggers',
- static fn () => [
- 'store-journal' => (string) trans('firefly.rule_trigger_store_journal'),
- 'update-journal' => (string) trans('firefly.rule_trigger_update_journal'),
- 'manual-activation' => (string) trans('firefly.rule_trigger_manual'),
+ static fn() => [
+ 'store-journal' => (string)trans('firefly.rule_trigger_store_journal'),
+ 'update-journal' => (string)trans('firefly.rule_trigger_update_journal'),
+ 'manual-activation' => (string)trans('firefly.rule_trigger_manual'),
]
);
}
@@ -64,7 +73,7 @@ class Rule extends AbstractExtension
$possibleTriggers = [];
foreach ($ruleTriggers as $key) {
if ('user_action' !== $key) {
- $possibleTriggers[$key] = (string) trans('firefly.rule_trigger_'.$key.'_choice');
+ $possibleTriggers[$key] = (string)trans('firefly.rule_trigger_' . $key . '_choice');
}
}
unset($ruleTriggers);
@@ -75,22 +84,13 @@ class Rule extends AbstractExtension
);
}
- public function allActionTriggers(): TwigFunction
+ #[Override]
+ public function getFunctions(): array
{
- return new TwigFunction(
- 'allRuleActions',
- static function () {
- // array of valid values for actions
- $ruleActions = array_keys(Config::get('firefly.rule-actions'));
- $possibleActions = [];
- foreach ($ruleActions as $key) {
- $possibleActions[$key] = (string) trans('firefly.rule_action_'.$key.'_choice');
- }
- unset($ruleActions);
- asort($possibleActions);
-
- return $possibleActions;
- }
- );
+ return [
+ $this->allJournalTriggers(),
+ $this->allRuleTriggers(),
+ $this->allActionTriggers(),
+ ];
}
}
diff --git a/app/Support/Twig/TransactionGroupTwig.php b/app/Support/Twig/TransactionGroupTwig.php
index 81cf8db231..e2957ec3a1 100644
--- a/app/Support/Twig/TransactionGroupTwig.php
+++ b/app/Support/Twig/TransactionGroupTwig.php
@@ -31,10 +31,9 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalMeta;
use Illuminate\Support\Facades\DB;
+use Override;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
-use Override;
-
use function Safe\json_decode;
/**
@@ -76,6 +75,141 @@ class TransactionGroupTwig extends AbstractExtension
);
}
+ public function journalGetMetaDate(): TwigFunction
+ {
+ return new TwigFunction(
+ 'journalGetMetaDate',
+ static function (int $journalId, string $metaField) {
+ /** @var null|TransactionJournalMeta $entry */
+ $entry = DB::table('journal_meta')
+ ->where('name', $metaField)
+ ->where('transaction_journal_id', $journalId)
+ ->whereNull('deleted_at')
+ ->first();
+ if (null === $entry) {
+ return today(config('app.timezone'));
+ }
+
+ return new Carbon(json_decode((string)$entry->data, false));
+ }
+ );
+ }
+
+ public function journalGetMetaField(): TwigFunction
+ {
+ return new TwigFunction(
+ 'journalGetMetaField',
+ static function (int $journalId, string $metaField) {
+ /** @var null|TransactionJournalMeta $entry */
+ $entry = DB::table('journal_meta')
+ ->where('name', $metaField)
+ ->where('transaction_journal_id', $journalId)
+ ->whereNull('deleted_at')
+ ->first();
+ if (null === $entry) {
+ return '';
+ }
+
+ return json_decode((string)$entry->data, true);
+ }
+ );
+ }
+
+ public function journalHasMeta(): TwigFunction
+ {
+ return new TwigFunction(
+ 'journalHasMeta',
+ static function (int $journalId, string $metaField) {
+ $count = DB::table('journal_meta')
+ ->where('name', $metaField)
+ ->where('transaction_journal_id', $journalId)
+ ->whereNull('deleted_at')
+ ->count();
+
+ return 1 === $count;
+ }
+ );
+ }
+
+ /**
+ * Shows the amount for a single journal object.
+ */
+ public function journalObjectAmount(): TwigFunction
+ {
+ return new TwigFunction(
+ 'journalObjectAmount',
+ function (TransactionJournal $journal): string {
+ $result = $this->normalJournalObjectAmount($journal);
+ // now append foreign amount, if any.
+ if ($this->journalObjectHasForeign($journal)) {
+ $foreign = $this->foreignJournalObjectAmount($journal);
+ $result = sprintf('%s (%s)', $result, $foreign);
+ }
+
+ return $result;
+ },
+ ['is_safe' => ['html']]
+ );
+ }
+
+ /**
+ * Generate foreign amount for transaction from a transaction group.
+ */
+ private function foreignJournalArrayAmount(array $array): string
+ {
+ $type = $array['transaction_type_type'] ?? TransactionTypeEnum::WITHDRAWAL->value;
+ $amount = $array['foreign_amount'] ?? '0';
+ $colored = true;
+
+ $sourceType = $array['source_account_type'] ?? 'invalid';
+ $amount = $this->signAmount($amount, $type, $sourceType);
+
+ if (TransactionTypeEnum::TRANSFER->value === $type) {
+ $colored = false;
+ }
+ $result = app('amount')->formatFlat($array['foreign_currency_symbol'], (int)$array['foreign_currency_decimal_places'], $amount, $colored);
+ if (TransactionTypeEnum::TRANSFER->value === $type) {
+ return sprintf('%s', $result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generate foreign amount for journal from a transaction group.
+ */
+ private function foreignJournalObjectAmount(TransactionJournal $journal): string
+ {
+ $type = $journal->transactionType->type;
+
+ /** @var Transaction $first */
+ $first = $journal->transactions()->where('amount', '<', 0)->first();
+ $currency = $first->foreignCurrency;
+ $amount = '' === $first->foreign_amount ? '0' : $first->foreign_amount;
+ $colored = true;
+ $sourceType = $first->account->accountType()->first()->type;
+
+ $amount = $this->signAmount($amount, $type, $sourceType);
+
+ if (TransactionTypeEnum::TRANSFER->value === $type) {
+ $colored = false;
+ }
+ $result = app('amount')->formatFlat($currency->symbol, $currency->decimal_places, $amount, $colored);
+ if (TransactionTypeEnum::TRANSFER->value === $type) {
+ return sprintf('%s', $result);
+ }
+
+ return $result;
+ }
+
+ private function journalObjectHasForeign(TransactionJournal $journal): bool
+ {
+ /** @var Transaction $first */
+ $first = $journal->transactions()->where('amount', '<', 0)->first();
+
+ return '' !== $first->foreign_amount;
+ }
+
/**
* Generate normal amount for transaction from a transaction group.
*/
@@ -91,7 +225,34 @@ class TransactionGroupTwig extends AbstractExtension
$colored = false;
}
- $result = app('amount')->formatFlat($array['currency_symbol'], (int) $array['currency_decimal_places'], $amount, $colored);
+ $result = app('amount')->formatFlat($array['currency_symbol'], (int)$array['currency_decimal_places'], $amount, $colored);
+ if (TransactionTypeEnum::TRANSFER->value === $type) {
+ return sprintf('%s', $result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generate normal amount for transaction from a transaction group.
+ */
+ private function normalJournalObjectAmount(TransactionJournal $journal): string
+ {
+ $type = $journal->transactionType->type;
+
+ /** @var Transaction $first */
+ $first = $journal->transactions()->where('amount', '<', 0)->first();
+ $currency = $journal->transactionCurrency;
+ $amount = $first->amount ?? '0';
+ $colored = true;
+ $sourceType = $first->account->accountType()->first()->type;
+
+ $amount = $this->signAmount($amount, $type, $sourceType);
+
+ if (TransactionTypeEnum::TRANSFER->value === $type) {
+ $colored = false;
+ }
+ $result = app('amount')->formatFlat($currency->symbol, $currency->decimal_places, $amount, $colored);
if (TransactionTypeEnum::TRANSFER->value === $type) {
return sprintf('%s', $result);
}
@@ -118,169 +279,4 @@ class TransactionGroupTwig extends AbstractExtension
return $amount;
}
-
- /**
- * Generate foreign amount for transaction from a transaction group.
- */
- private function foreignJournalArrayAmount(array $array): string
- {
- $type = $array['transaction_type_type'] ?? TransactionTypeEnum::WITHDRAWAL->value;
- $amount = $array['foreign_amount'] ?? '0';
- $colored = true;
-
- $sourceType = $array['source_account_type'] ?? 'invalid';
- $amount = $this->signAmount($amount, $type, $sourceType);
-
- if (TransactionTypeEnum::TRANSFER->value === $type) {
- $colored = false;
- }
- $result = app('amount')->formatFlat($array['foreign_currency_symbol'], (int) $array['foreign_currency_decimal_places'], $amount, $colored);
- if (TransactionTypeEnum::TRANSFER->value === $type) {
- return sprintf('%s', $result);
- }
-
- return $result;
- }
-
- /**
- * Shows the amount for a single journal object.
- */
- public function journalObjectAmount(): TwigFunction
- {
- return new TwigFunction(
- 'journalObjectAmount',
- function (TransactionJournal $journal): string {
- $result = $this->normalJournalObjectAmount($journal);
- // now append foreign amount, if any.
- if ($this->journalObjectHasForeign($journal)) {
- $foreign = $this->foreignJournalObjectAmount($journal);
- $result = sprintf('%s (%s)', $result, $foreign);
- }
-
- return $result;
- },
- ['is_safe' => ['html']]
- );
- }
-
- /**
- * Generate normal amount for transaction from a transaction group.
- */
- private function normalJournalObjectAmount(TransactionJournal $journal): string
- {
- $type = $journal->transactionType->type;
-
- /** @var Transaction $first */
- $first = $journal->transactions()->where('amount', '<', 0)->first();
- $currency = $journal->transactionCurrency;
- $amount = $first->amount ?? '0';
- $colored = true;
- $sourceType = $first->account->accountType()->first()->type;
-
- $amount = $this->signAmount($amount, $type, $sourceType);
-
- if (TransactionTypeEnum::TRANSFER->value === $type) {
- $colored = false;
- }
- $result = app('amount')->formatFlat($currency->symbol, $currency->decimal_places, $amount, $colored);
- if (TransactionTypeEnum::TRANSFER->value === $type) {
- return sprintf('%s', $result);
- }
-
- return $result;
- }
-
- private function journalObjectHasForeign(TransactionJournal $journal): bool
- {
- /** @var Transaction $first */
- $first = $journal->transactions()->where('amount', '<', 0)->first();
-
- return '' !== $first->foreign_amount;
- }
-
- /**
- * Generate foreign amount for journal from a transaction group.
- */
- private function foreignJournalObjectAmount(TransactionJournal $journal): string
- {
- $type = $journal->transactionType->type;
-
- /** @var Transaction $first */
- $first = $journal->transactions()->where('amount', '<', 0)->first();
- $currency = $first->foreignCurrency;
- $amount = '' === $first->foreign_amount ? '0' : $first->foreign_amount;
- $colored = true;
- $sourceType = $first->account->accountType()->first()->type;
-
- $amount = $this->signAmount($amount, $type, $sourceType);
-
- if (TransactionTypeEnum::TRANSFER->value === $type) {
- $colored = false;
- }
- $result = app('amount')->formatFlat($currency->symbol, $currency->decimal_places, $amount, $colored);
- if (TransactionTypeEnum::TRANSFER->value === $type) {
- return sprintf('%s', $result);
- }
-
- return $result;
- }
-
- public function journalHasMeta(): TwigFunction
- {
- return new TwigFunction(
- 'journalHasMeta',
- static function (int $journalId, string $metaField) {
- $count = DB::table('journal_meta')
- ->where('name', $metaField)
- ->where('transaction_journal_id', $journalId)
- ->whereNull('deleted_at')
- ->count()
- ;
-
- return 1 === $count;
- }
- );
- }
-
- public function journalGetMetaDate(): TwigFunction
- {
- return new TwigFunction(
- 'journalGetMetaDate',
- static function (int $journalId, string $metaField) {
- /** @var null|TransactionJournalMeta $entry */
- $entry = DB::table('journal_meta')
- ->where('name', $metaField)
- ->where('transaction_journal_id', $journalId)
- ->whereNull('deleted_at')
- ->first()
- ;
- if (null === $entry) {
- return today(config('app.timezone'));
- }
-
- return new Carbon(json_decode((string) $entry->data, false));
- }
- );
- }
-
- public function journalGetMetaField(): TwigFunction
- {
- return new TwigFunction(
- 'journalGetMetaField',
- static function (int $journalId, string $metaField) {
- /** @var null|TransactionJournalMeta $entry */
- $entry = DB::table('journal_meta')
- ->where('name', $metaField)
- ->where('transaction_journal_id', $journalId)
- ->whereNull('deleted_at')
- ->first()
- ;
- if (null === $entry) {
- return '';
- }
-
- return json_decode((string) $entry->data, true);
- }
- );
- }
}
diff --git a/app/Support/Twig/Translation.php b/app/Support/Twig/Translation.php
index bb19890ff9..e4f429f07e 100644
--- a/app/Support/Twig/Translation.php
+++ b/app/Support/Twig/Translation.php
@@ -23,10 +23,10 @@ declare(strict_types=1);
namespace FireflyIII\Support\Twig;
+use Override;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;
-use Override;
/**
* Class Budget.
@@ -39,7 +39,7 @@ class Translation extends AbstractExtension
return [
new TwigFilter(
'_',
- static fn ($name) => (string) trans(sprintf('firefly.%s', $name)),
+ static fn($name) => (string)trans(sprintf('firefly.%s', $name)),
['is_safe' => ['html']]
),
];