chore: code cleanup.

This commit is contained in:
James Cole
2023-05-29 13:56:55 +02:00
parent 7f7644c92f
commit 1b52147a05
295 changed files with 12418 additions and 12324 deletions
+101 -101
View File
@@ -40,6 +40,80 @@ use Psr\Container\NotFoundExceptionInterface;
*/
class Amount
{
/**
* bool $sepBySpace is $localeconv['n_sep_by_space']
* int $signPosn = $localeconv['n_sign_posn']
* string $sign = $localeconv['negative_sign']
* bool $csPrecedes = $localeconv['n_cs_precedes'].
*
* @param bool $sepBySpace
* @param int $signPosn
* @param string $sign
* @param bool $csPrecedes
*
* @return string
*
*/
public static function getAmountJsConfig(bool $sepBySpace, int $signPosn, string $sign, bool $csPrecedes): string
{
// negative first:
$space = ' ';
// require space between symbol and amount?
if (false === $sepBySpace) {
$space = ''; // no
}
// 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
// format would be (currency before amount)
// AB%sC_D%vE
// or:
// AD%v_B%sCE (amount before currency)
// the _ is the optional space
// switch on how to display amount:
switch ($signPosn) {
default:
case 0:
// ( and ) around the whole thing
$posA = '(';
$posE = ')';
break;
case 1:
// The sign string precedes the quantity and currency_symbol
$posA = $sign;
break;
case 2:
// The sign string succeeds the quantity and currency_symbol
$posE = $sign;
break;
case 3:
// The sign string immediately precedes the currency_symbol
$posB = $sign;
break;
case 4:
// The sign string immediately succeeds the currency_symbol
$posC = $sign;
}
// default is amount before currency
$format = $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE;
if ($csPrecedes) {
// alternative is currency before amount
$format = $posA.$posB.'%s'.$posC.$space.$posD.'%v'.$posE;
}
return $format;
}
/**
* 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.
@@ -181,22 +255,6 @@ class Amount
return $currency;
}
/**
* @param string $value
*
* @return string
*/
private function tryDecrypt(string $value): string
{
try {
$value = Crypt::decrypt($value); // verified
} catch (DecryptException $e) {
// @ignoreException
}
return $value;
}
/**
* This method returns the correct format rules required by accounting.js,
* the library used to format amounts in charts.
@@ -223,6 +281,26 @@ class Amount
];
}
/**
* @return TransactionCurrency
*/
public function getSystemCurrency(): TransactionCurrency
{
return TransactionCurrency::where('code', 'EUR')->first();
}
/**
* @param array $info
* @param string $field
*
* @return bool
*/
private function getLocaleField(array $info, string $field): bool
{
return (is_bool($info[$field]) && true === $info[$field])
|| (is_int($info[$field]) && 1 === $info[$field]);
}
/**
* @return array
* @throws FireflyException
@@ -252,96 +330,18 @@ class Amount
}
/**
* @param array $info
* @param string $field
*
* @return bool
*/
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']
* string $sign = $localeconv['negative_sign']
* bool $csPrecedes = $localeconv['n_cs_precedes'].
*
* @param bool $sepBySpace
* @param int $signPosn
* @param string $sign
* @param bool $csPrecedes
* @param string $value
*
* @return string
*
*/
public static function getAmountJsConfig(bool $sepBySpace, int $signPosn, string $sign, bool $csPrecedes): string
private function tryDecrypt(string $value): string
{
// negative first:
$space = ' ';
// require space between symbol and amount?
if (false === $sepBySpace) {
$space = ''; // no
try {
$value = Crypt::decrypt($value); // verified
} catch (DecryptException $e) {
// @ignoreException
}
// 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
// format would be (currency before amount)
// AB%sC_D%vE
// or:
// AD%v_B%sCE (amount before currency)
// the _ is the optional space
// switch on how to display amount:
switch ($signPosn) {
default:
case 0:
// ( and ) around the whole thing
$posA = '(';
$posE = ')';
break;
case 1:
// The sign string precedes the quantity and currency_symbol
$posA = $sign;
break;
case 2:
// The sign string succeeds the quantity and currency_symbol
$posE = $sign;
break;
case 3:
// The sign string immediately precedes the currency_symbol
$posB = $sign;
break;
case 4:
// The sign string immediately succeeds the currency_symbol
$posC = $sign;
}
// default is amount before currency
$format = $posA.$posD.'%v'.$space.$posB.'%s'.$posC.$posE;
if ($csPrecedes) {
// alternative is currency before amount
$format = $posA.$posB.'%s'.$posC.$space.$posD.'%v'.$posE;
}
return $format;
}
/**
* @return TransactionCurrency
*/
public function getSystemCurrency(): TransactionCurrency
{
return TransactionCurrency::where('code', 'EUR')->first();
return $value;
}
}
+17 -17
View File
@@ -112,15 +112,6 @@ class RemoteUserGuard implements Guard
$this->user = $retrievedUser;
}
/**
* @inheritDoc
*/
public function guest(): bool
{
Log::debug(sprintf('Now at %s', __METHOD__));
return !$this->check();
}
/**
* @inheritDoc
*/
@@ -133,16 +124,10 @@ class RemoteUserGuard implements Guard
/**
* @inheritDoc
*/
public function user(): ?User
public function guest(): bool
{
Log::debug(sprintf('Now at %s', __METHOD__));
$user = $this->user;
if (null === $user) {
Log::debug('User is NULL');
return null;
}
return $user;
return !$this->check();
}
/**
@@ -172,6 +157,21 @@ class RemoteUserGuard implements Guard
$this->user = $user;
}
/**
* @inheritDoc
*/
public function user(): ?User
{
Log::debug(sprintf('Now at %s', __METHOD__));
$user = $this->user;
if (null === $user) {
Log::debug('User is NULL');
return null;
}
return $user;
}
/**
* @inheritDoc
*/
+8 -8
View File
@@ -86,6 +86,14 @@ class CacheProperties
return Cache::has($this->hash);
}
/**
* @param mixed $data
*/
public function store($data): void
{
Cache::forever($this->hash, $data);
}
/**
*/
private function hash(): void
@@ -101,12 +109,4 @@ class CacheProperties
}
$this->hash = substr(hash('sha256', $content), 0, 16);
}
/**
* @param mixed $data
*/
public function store($data): void
{
Cache::forever($this->hash, $data);
}
}
@@ -79,26 +79,53 @@ class FrontpageChartGenerator
}
/**
* 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.
* If limits are present, each limit is processed individually.
* @param Carbon $end
*/
public function setEnd(Carbon $end): void
{
$this->end = $end;
}
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
/**
* A basic setter for the user. Also updates the repositories with the right user.
*
* @param User $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.
*
* @param array $data
* @param Budget $budget
* @param Collection $limits
*
* @return array
*/
private function processBudget(array $data, Budget $budget): array
private function budgetLimits(array $data, Budget $budget, Collection $limits): array
{
// get all limits:
$limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end);
// if no limits
if (0 === $limits->count()) {
return $this->noBudgetLimits($data, $budget);
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
$data = $this->processLimit($data, $budget, $limit);
}
return $this->budgetLimits($data, $budget, $limits);
return $data;
}
/**
@@ -125,22 +152,26 @@ class FrontpageChartGenerator
}
/**
* If a budget has budget limit, each limit is processed individually.
* 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.
* If limits are present, each limit is processed individually.
*
* @param array $data
* @param Budget $budget
* @param Collection $limits
*
* @return array
*/
private function budgetLimits(array $data, Budget $budget, Collection $limits): array
private function processBudget(array $data, Budget $budget): array
{
/** @var BudgetLimit $limit */
foreach ($limits as $limit) {
$data = $this->processLimit($data, $budget, $limit);
// get all limits:
$limits = $this->blRepository->getBudgetLimits($budget, $this->start, $this->end);
// if no limits
if (0 === $limits->count()) {
return $this->noBudgetLimits($data, $budget);
}
return $data;
return $this->budgetLimits($data, $budget, $limits);
}
/**
@@ -199,35 +230,4 @@ class FrontpageChartGenerator
return $data;
}
/**
* @param Carbon $end
*/
public function setEnd(Carbon $end): void
{
$this->end = $end;
}
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
/**
* A basic setter for the user. Also updates the repositories with the right user.
*
* @param User $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);
}
}
@@ -98,6 +98,22 @@ class FrontpageChartGenerator
return $this->insertValues($currencyData, $tempData);
}
/**
* @param array $currency
*/
private function addCurrency(array $currency): void
{
$currencyId = (int)$currency['currency_id'];
$this->currencies[$currencyId] = $this->currencies[$currencyId] ?? [
'currency_id' => $currencyId,
'currency_name' => $currency['currency_name'],
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_decimal_places' => $currency['currency_decimal_places'],
];
}
/**
* @param Category $category
* @param Collection $accounts
@@ -121,22 +137,6 @@ class FrontpageChartGenerator
return $tempData;
}
/**
* @param array $currency
*/
private function addCurrency(array $currency): void
{
$currencyId = (int)$currency['currency_id'];
$this->currencies[$currencyId] = $this->currencies[$currencyId] ?? [
'currency_id' => $currencyId,
'currency_name' => $currency['currency_name'],
'currency_symbol' => $currency['currency_symbol'],
'currency_code' => $currency['currency_code'],
'currency_decimal_places' => $currency['currency_decimal_places'],
];
}
/**
* @param Collection $accounts
*
+120 -120
View File
@@ -140,6 +140,126 @@ class ExportDataGenerator
return $return;
}
/**
* @inheritDoc
*/
public function get(string $key, mixed $default = null): mixed
{
return null;
}
/**
* @inheritDoc
*/
public function has(mixed $key): mixed
{
return null;
}
/**
* @param Collection $accounts
*/
public function setAccounts(Collection $accounts): void
{
$this->accounts = $accounts;
}
/**
* @param Carbon $end
*/
public function setEnd(Carbon $end): void
{
$this->end = $end;
}
/**
* @param bool $exportAccounts
*/
public function setExportAccounts(bool $exportAccounts): void
{
$this->exportAccounts = $exportAccounts;
}
/**
* @param bool $exportBills
*/
public function setExportBills(bool $exportBills): void
{
$this->exportBills = $exportBills;
}
/**
* @param bool $exportBudgets
*/
public function setExportBudgets(bool $exportBudgets): void
{
$this->exportBudgets = $exportBudgets;
}
/**
* @param bool $exportCategories
*/
public function setExportCategories(bool $exportCategories): void
{
$this->exportCategories = $exportCategories;
}
/**
* @param bool $exportPiggies
*/
public function setExportPiggies(bool $exportPiggies): void
{
$this->exportPiggies = $exportPiggies;
}
/**
* @param bool $exportRecurring
*/
public function setExportRecurring(bool $exportRecurring): void
{
$this->exportRecurring = $exportRecurring;
}
/**
* @param bool $exportRules
*/
public function setExportRules(bool $exportRules): void
{
$this->exportRules = $exportRules;
}
/**
* @param bool $exportTags
*/
public function setExportTags(bool $exportTags): void
{
$this->exportTags = $exportTags;
}
/**
* @param bool $exportTransactions
*/
public function setExportTransactions(bool $exportTransactions): void
{
$this->exportTransactions = $exportTransactions;
}
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
/**
* @return string
* @throws FireflyException
@@ -217,14 +337,6 @@ class ExportDataGenerator
return $string;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
/**
* @return string
* @throws FireflyException
@@ -828,14 +940,6 @@ class ExportDataGenerator
return $string;
}
/**
* @inheritDoc
*/
public function get(string $key, mixed $default = null): mixed
{
return null;
}
/**
* @return string
* @throws FireflyException
@@ -979,14 +1083,6 @@ class ExportDataGenerator
return $string;
}
/**
* @param Collection $accounts
*/
public function setAccounts(Collection $accounts): void
{
$this->accounts = $accounts;
}
/**
* @param array $tags
*
@@ -1004,100 +1100,4 @@ class ExportDataGenerator
return implode(',', $smol);
}
/**
* @inheritDoc
*/
public function has(mixed $key): mixed
{
return null;
}
/**
* @param Carbon $end
*/
public function setEnd(Carbon $end): void
{
$this->end = $end;
}
/**
* @param bool $exportAccounts
*/
public function setExportAccounts(bool $exportAccounts): void
{
$this->exportAccounts = $exportAccounts;
}
/**
* @param bool $exportBills
*/
public function setExportBills(bool $exportBills): void
{
$this->exportBills = $exportBills;
}
/**
* @param bool $exportBudgets
*/
public function setExportBudgets(bool $exportBudgets): void
{
$this->exportBudgets = $exportBudgets;
}
/**
* @param bool $exportCategories
*/
public function setExportCategories(bool $exportCategories): void
{
$this->exportCategories = $exportCategories;
}
/**
* @param bool $exportPiggies
*/
public function setExportPiggies(bool $exportPiggies): void
{
$this->exportPiggies = $exportPiggies;
}
/**
* @param bool $exportRecurring
*/
public function setExportRecurring(bool $exportRecurring): void
{
$this->exportRecurring = $exportRecurring;
}
/**
* @param bool $exportRules
*/
public function setExportRules(bool $exportRules): void
{
$this->exportRules = $exportRules;
}
/**
* @param bool $exportTags
*/
public function setExportTags(bool $exportTags): void
{
$this->exportTags = $exportTags;
}
/**
* @param bool $exportTransactions
*/
public function setExportTransactions(bool $exportTransactions): void
{
$this->exportTransactions = $exportTransactions;
}
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
}
+41 -41
View File
@@ -49,16 +49,6 @@ class FireflyConfig
Configuration::where('name', $name)->forceDelete();
}
/**
* @param string $name
*
* @return bool
*/
public function has(string $name): bool
{
return Configuration::where('name', $name)->count() === 1;
}
/**
* @param string $name
* @param bool|string|int|null $default
@@ -93,6 +83,47 @@ class FireflyConfig
return $this->set($name, $default);
}
/**
* @param string $name
* @param mixed $default
*
* @return Configuration|null
*/
public function getFresh(string $name, $default = null): ?Configuration
{
$config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
if ($config) {
return $config;
}
// no preference found and default is null:
if (null === $default) {
return null;
}
return $this->set($name, $default);
}
/**
* @param string $name
*
* @return bool
*/
public function has(string $name): bool
{
return Configuration::where('name', $name)->count() === 1;
}
/**
* @param string $name
* @param mixed $value
*
* @return Configuration
*/
public function put(string $name, $value): Configuration
{
return $this->set($name, $value);
}
/**
* @param string $name
* @param mixed $value
@@ -127,35 +158,4 @@ class FireflyConfig
return $config;
}
/**
* @param string $name
* @param mixed $default
*
* @return Configuration|null
*/
public function getFresh(string $name, $default = null): ?Configuration
{
$config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
if ($config) {
return $config;
}
// no preference found and default is null:
if (null === $default) {
return null;
}
return $this->set($name, $default);
}
/**
* @param string $name
* @param mixed $value
*
* @return Configuration
*/
public function put(string $name, $value): Configuration
{
return $this->set($name, $value);
}
}
+30 -30
View File
@@ -63,36 +63,6 @@ class AccountForm
return $this->select($name, $grouped, $value, $options);
}
private function getAccountsGrouped(array $types, AccountRepositoryInterface $repository = null): array
{
if (null === $repository) {
$repository = $this->getAccountRepository();
}
$accountList = $repository->getActiveAccountsByType($types);
$liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN,];
$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);
} elseif ('' === $role) {
if (AccountType::EXPENSE === $account->accountType->type) {
$role = 'expense_account';
} elseif (AccountType::REVENUE === $account->accountType->type) {
$role = 'revenue_account';
} else {
$role = 'no_account_type';
}
}
$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.
*
@@ -181,4 +151,34 @@ class AccountForm
return $this->select($name, $grouped, $value, $options);
}
private function getAccountsGrouped(array $types, AccountRepositoryInterface $repository = null): array
{
if (null === $repository) {
$repository = $this->getAccountRepository();
}
$accountList = $repository->getActiveAccountsByType($types);
$liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN,];
$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);
} elseif ('' === $role) {
if (AccountType::EXPENSE === $account->accountType->type) {
$role = 'expense_account';
} elseif (AccountType::REVENUE === $account->accountType->type) {
$role = 'revenue_account';
} else {
$role = 'no_account_type';
}
}
$key = (string)trans(sprintf('firefly.opt_group_%s', $role));
$grouped[$key][$account->id] = $account->name;
}
return $grouped;
}
}
+92 -92
View File
@@ -53,61 +53,6 @@ class CurrencyForm
return $this->currencyField($name, 'amount', $value, $options);
}
/**
* @param string $name
* @param string $view
* @param mixed $value
* @param array|null $options
*
* @return string
* @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';
$defaultCurrency = $options['currency'] ?? Amt::getDefaultCurrency();
/** @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] : $defaultCurrency->id;
Log::debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
// find this currency in set of currencies:
foreach ($currencies as $currency) {
if ($currency->id === $sentCurrencyId) {
$defaultCurrency = $currency;
Log::debug(sprintf('default currency is now %s', $defaultCurrency->code));
break;
}
}
// make sure value is formatted nicely:
if (null !== $value && '' !== $value) {
$value = app('steam')->bcround($value, $defaultCurrency->decimal_places);
}
try {
$html = view('form.'.$view, compact('defaultCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
} catch (Throwable $e) {
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.
*
@@ -123,6 +68,58 @@ class CurrencyForm
return $this->allCurrencyField($name, 'balance', $value, $options);
}
/**
* TODO cleanup and describe
*
* @param string $name
* @param mixed $value
* @param array|null $options
*
* @return string
*/
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 string $name
* @param mixed $value
* @param array|null $options
*
* @return string
*/
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
*
@@ -181,54 +178,57 @@ class CurrencyForm
}
/**
* TODO cleanup and describe
*
* @param string $name
* @param string $view
* @param mixed $value
* @param array|null $options
*
* @return string
* @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';
$defaultCurrency = $options['currency'] ?? Amt::getDefaultCurrency();
/** @var Collection $currencies */
$currencies = app('amount')->getCurrencies();
unset($options['currency'], $options['placeholder']);
// get all currencies:
$list = $currencyRepos->get();
$array = [];
/** @var TransactionCurrency $currency */
foreach ($list as $currency) {
$array[$currency->id] = $currency->name.' ('.$currency->symbol.')';
// 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] : $defaultCurrency->id;
Log::debug(sprintf('Sent currency ID is %d', $sentCurrencyId));
// find this currency in set of currencies:
foreach ($currencies as $currency) {
if ($currency->id === $sentCurrencyId) {
$defaultCurrency = $currency;
Log::debug(sprintf('default currency is now %s', $defaultCurrency->code));
break;
}
}
return $this->select($name, $array, $value, $options);
}
/**
* TODO cleanup and describe
*
* @param string $name
* @param mixed $value
* @param array|null $options
*
* @return string
*/
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, $defaultCurrency->decimal_places);
}
try {
$html = view('form.'.$view, compact('defaultCurrency', 'currencies', 'classes', 'name', 'label', 'value', 'options'))->render();
} catch (Throwable $e) {
Log::debug(sprintf('Could not render currencyField(): %s', $e->getMessage()));
$html = 'Could not render currencyField.';
throw new FireflyException($html, 0, $e);
}
return $this->select($name, $array, $value, $options);
return $html;
}
}
+37 -37
View File
@@ -26,8 +26,8 @@ namespace FireflyIII\Support\Form;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Support\MessageBag;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\MessageBag;
use Throwable;
/**
@@ -61,23 +61,6 @@ trait FormSupport
return $html;
}
/**
* @param string $name
* @param array|null $options
*
* @return string
*/
protected function label(string $name, array $options = null): string
{
$options = $options ?? [];
if (array_key_exists('label', $options)) {
return $options['label'];
}
$name = str_replace('[]', '', $name);
return (string)trans('form.'.$name);
}
/**
* @param string $name
* @param mixed $label
@@ -97,25 +80,6 @@ trait FormSupport
return $options;
}
/**
* @param string $name
*
* @return string
*/
protected function getHolderClasses(string $name): string
{
// Get errors from session:
/** @var MessageBag $errors */
$errors = session('errors');
$classes = 'form-group';
if (null !== $errors && $errors->has($name)) {
$classes = 'form-group has-error has-feedback';
}
return $classes;
}
/**
* @param string $name
* @param mixed|null $value
@@ -163,4 +127,40 @@ trait FormSupport
return $date;
}
/**
* @param string $name
*
* @return string
*/
protected function getHolderClasses(string $name): string
{
// Get errors from session:
/** @var MessageBag $errors */
$errors = session('errors');
$classes = 'form-group';
if (null !== $errors && $errors->has($name)) {
$classes = 'form-group has-error has-feedback';
}
return $classes;
}
/**
* @param string $name
* @param array|null $options
*
* @return string
*/
protected function label(string $name, array $options = null): string
{
$options = $options ?? [];
if (array_key_exists('label', $options)) {
return $options['label'];
}
$name = str_replace('[]', '', $name);
return (string)trans('form.'.$name);
}
}
+124 -124
View File
@@ -74,130 +74,6 @@ trait ConvertsExchangeRates
return $set;
}
/**
* @return void
*/
private function getPreference(): void
{
$this->enabled = true;
}
/**
* @param int $currencyId
* @return TransactionCurrency
*/
private function getCurrency(int $currencyId): TransactionCurrency
{
$result = TransactionCurrency::find($currencyId);
if (null === $result) {
return app('amount')->getDefaultCurrency();
}
return $result;
}
/**
* @param TransactionCurrency $from
* @param TransactionCurrency $to
* @param Carbon $date
* @return string
*/
private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{
Log::debug(sprintf('getRate(%s, %s, "%s")', $from->code, $to->code, $date->format('Y-m-d')));
/** @var CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
->where('date', '<=', $date->format('Y-m-d'))
->orderBy('date', 'DESC')
->first();
if (null !== $result) {
$rate = (string)$result->rate;
Log::debug(sprintf('Rate is %s', $rate));
return $rate;
}
// no result. perhaps the other way around?
/** @var CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
->where('from_currency_id', $to->id)
->where('to_currency_id', $from->id)
->where('date', '<=', $date->format('Y-m-d'))
->orderBy('date', 'DESC')
->first();
if (null !== $result) {
$rate = bcdiv('1', (string)$result->rate);
Log::debug(sprintf('Reversed rate is %s', $rate));
return $rate;
}
// try euro rates
$result1 = $this->getEuroRate($from, $date);
if ('0' === $result1) {
Log::debug(sprintf('No exchange rate between EUR and %s', $from->code));
return '0';
}
$result2 = $this->getEuroRate($to, $date);
if ('0' === $result2) {
Log::debug(sprintf('No exchange rate between EUR and %s', $to->code));
return '0';
}
// still need to inverse rate 2:
$result2 = bcdiv('1', $result2);
$rate = bcmul($result1, $result2);
Log::debug(sprintf('Rate %s to EUR is %s', $from->code, $result1));
Log::debug(sprintf('Rate EUR to %s is %s', $to->code, $result2));
Log::debug(sprintf('Rate for %s to %s is %s', $from->code, $to->code, $rate));
return $rate;
}
/**
* @param TransactionCurrency $currency
* @param Carbon $date
* @return string
*/
private function getEuroRate(TransactionCurrency $currency, Carbon $date): string
{
Log::debug(sprintf('Find rate for %s to Euro', $currency->code));
$euro = TransactionCurrency::whereCode('EUR')->first();
if (null === $euro) {
app('log')->warning('Cannot do indirect conversion without EUR.');
return '0';
}
// try one way:
/** @var CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
->where('from_currency_id', $currency->id)
->where('to_currency_id', $euro->id)
->where('date', '<=', $date->format('Y-m-d'))
->orderBy('date', 'DESC')
->first();
if (null !== $result) {
$rate = (string)$result->rate;
Log::debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate));
return $rate;
}
// try the other way around and inverse it.
/** @var CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
->where('from_currency_id', $euro->id)
->where('to_currency_id', $currency->id)
->where('date', '<=', $date->format('Y-m-d'))
->orderBy('date', 'DESC')
->first();
if (null !== $result) {
$rate = bcdiv('1', (string)$result->rate);
Log::debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate));
return $rate;
}
Log::debug(sprintf('No rate for %s to EUR.', $currency->code));
return '0';
}
/**
* For a sum of entries, get the exchange rate to the native currency of
* the user.
@@ -260,4 +136,128 @@ trait ConvertsExchangeRates
return bcmul($amount, $rate);
}
/**
* @param int $currencyId
* @return TransactionCurrency
*/
private function getCurrency(int $currencyId): TransactionCurrency
{
$result = TransactionCurrency::find($currencyId);
if (null === $result) {
return app('amount')->getDefaultCurrency();
}
return $result;
}
/**
* @param TransactionCurrency $currency
* @param Carbon $date
* @return string
*/
private function getEuroRate(TransactionCurrency $currency, Carbon $date): string
{
Log::debug(sprintf('Find rate for %s to Euro', $currency->code));
$euro = TransactionCurrency::whereCode('EUR')->first();
if (null === $euro) {
app('log')->warning('Cannot do indirect conversion without EUR.');
return '0';
}
// try one way:
/** @var CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
->where('from_currency_id', $currency->id)
->where('to_currency_id', $euro->id)
->where('date', '<=', $date->format('Y-m-d'))
->orderBy('date', 'DESC')
->first();
if (null !== $result) {
$rate = (string)$result->rate;
Log::debug(sprintf('Rate for %s to EUR is %s.', $currency->code, $rate));
return $rate;
}
// try the other way around and inverse it.
/** @var CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
->where('from_currency_id', $euro->id)
->where('to_currency_id', $currency->id)
->where('date', '<=', $date->format('Y-m-d'))
->orderBy('date', 'DESC')
->first();
if (null !== $result) {
$rate = bcdiv('1', (string)$result->rate);
Log::debug(sprintf('Inverted rate for %s to EUR is %s.', $currency->code, $rate));
return $rate;
}
Log::debug(sprintf('No rate for %s to EUR.', $currency->code));
return '0';
}
/**
* @return void
*/
private function getPreference(): void
{
$this->enabled = true;
}
/**
* @param TransactionCurrency $from
* @param TransactionCurrency $to
* @param Carbon $date
* @return string
*/
private function getRate(TransactionCurrency $from, TransactionCurrency $to, Carbon $date): string
{
Log::debug(sprintf('getRate(%s, %s, "%s")', $from->code, $to->code, $date->format('Y-m-d')));
/** @var CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
->where('date', '<=', $date->format('Y-m-d'))
->orderBy('date', 'DESC')
->first();
if (null !== $result) {
$rate = (string)$result->rate;
Log::debug(sprintf('Rate is %s', $rate));
return $rate;
}
// no result. perhaps the other way around?
/** @var CurrencyExchangeRate $result */
$result = auth()->user()
->currencyExchangeRates()
->where('from_currency_id', $to->id)
->where('to_currency_id', $from->id)
->where('date', '<=', $date->format('Y-m-d'))
->orderBy('date', 'DESC')
->first();
if (null !== $result) {
$rate = bcdiv('1', (string)$result->rate);
Log::debug(sprintf('Reversed rate is %s', $rate));
return $rate;
}
// try euro rates
$result1 = $this->getEuroRate($from, $date);
if ('0' === $result1) {
Log::debug(sprintf('No exchange rate between EUR and %s', $from->code));
return '0';
}
$result2 = $this->getEuroRate($to, $date);
if ('0' === $result2) {
Log::debug(sprintf('No exchange rate between EUR and %s', $to->code));
return '0';
}
// still need to inverse rate 2:
$result2 = bcdiv('1', $result2);
$rate = bcmul($result1, $result2);
Log::debug(sprintf('Rate %s to EUR is %s', $from->code, $result1));
Log::debug(sprintf('Rate EUR to %s is %s', $to->code, $result2));
Log::debug(sprintf('Rate for %s to %s is %s', $from->code, $to->code, $rate));
return $rate;
}
}
@@ -31,8 +31,8 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Illuminate\Support\Collection;
use JsonException;
use Illuminate\Support\Facades\Log;
use JsonException;
/**
* Trait ChartGeneration
+1 -1
View File
@@ -28,8 +28,8 @@ use FireflyIII\Http\Requests\NewUserFormRequest;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\User;
use Laravel\Passport\Passport;
use Illuminate\Support\Facades\Log;
use Laravel\Passport\Passport;
use phpseclib3\Crypt\RSA;
/**
+64 -66
View File
@@ -37,6 +37,70 @@ use Psr\Container\NotFoundExceptionInterface;
*/
trait CronRunner
{
/**
* @param bool $force
* @param Carbon $date
*
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function billWarningCronJob(bool $force, Carbon $date): array
{
/** @var BillWarningCronjob $billWarning */
$billWarning = app(BillWarningCronjob::class);
$billWarning->setForce($force);
$billWarning->setDate($date);
try {
$billWarning->fire();
} catch (FireflyException $e) {
return [
'job_fired' => false,
'job_succeeded' => false,
'job_errored' => true,
'message' => $e->getMessage(),
];
}
return [
'job_fired' => $billWarning->jobFired,
'job_succeeded' => $billWarning->jobSucceeded,
'job_errored' => $billWarning->jobErrored,
'message' => $billWarning->message,
];
}
/**
* @param bool $force
* @param Carbon $date
*
* @return array
*/
protected function exchangeRatesCronJob(bool $force, Carbon $date): array
{
/** @var ExchangeRatesCronjob $exchangeRates */
$exchangeRates = app(ExchangeRatesCronjob::class);
$exchangeRates->setForce($force);
$exchangeRates->setDate($date);
try {
$exchangeRates->fire();
} catch (FireflyException $e) {
return [
'job_fired' => false,
'job_succeeded' => false,
'job_errored' => true,
'message' => $e->getMessage(),
];
}
return [
'job_fired' => $exchangeRates->jobFired,
'job_succeeded' => $exchangeRates->jobSucceeded,
'job_errored' => $exchangeRates->jobErrored,
'message' => $exchangeRates->message,
];
}
/**
* @param bool $force
* @param Carbon $date
@@ -100,70 +164,4 @@ trait CronRunner
'message' => $recurring->message,
];
}
/**
* @param bool $force
* @param Carbon $date
*
* @return array
*/
protected function exchangeRatesCronJob(bool $force, Carbon $date): array
{
/** @var ExchangeRatesCronjob $exchangeRates */
$exchangeRates = app(ExchangeRatesCronjob::class);
$exchangeRates->setForce($force);
$exchangeRates->setDate($date);
try {
$exchangeRates->fire();
} catch (FireflyException $e) {
return [
'job_fired' => false,
'job_succeeded' => false,
'job_errored' => true,
'message' => $e->getMessage(),
];
}
return [
'job_fired' => $exchangeRates->jobFired,
'job_succeeded' => $exchangeRates->jobSucceeded,
'job_errored' => $exchangeRates->jobErrored,
'message' => $exchangeRates->message,
];
}
/**
* @param bool $force
* @param Carbon $date
*
* @return array
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function billWarningCronJob(bool $force, Carbon $date): array
{
/** @var BillWarningCronjob $billWarning */
$billWarning = app(BillWarningCronjob::class);
$billWarning->setForce($force);
$billWarning->setDate($date);
try {
$billWarning->fire();
} catch (FireflyException $e) {
return [
'job_fired' => false,
'job_succeeded' => false,
'job_errored' => true,
'message' => $e->getMessage(),
];
}
return [
'job_fired' => $billWarning->jobFired,
'job_succeeded' => $billWarning->jobSucceeded,
'job_errored' => $billWarning->jobErrored,
'message' => $billWarning->message,
];
}
}
+110 -110
View File
@@ -150,116 +150,6 @@ trait PeriodOverview
return $entries;
}
/**
* Filter a list of journals by a set of dates, and then group them by currency.
*
* @param array $array
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
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;
}
/**
* Return only transactions where $account is the source.
*
* @param Account $account
* @param array $journals
*
* @return array
*/
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.
*
* @param Account $account
* @param array $journals
*
* @return array
*/
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;
}
/**
* @param array $journals
*
* @return array
*/
private function groupByCurrency(array $journals): array
{
$return = [];
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$foreignCurrencyId = $journal['foreign_currency_id'];
if (!array_key_exists($currencyId, $return)) {
$return[$currencyId] = [
'amount' => '0',
'count' => 0,
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_code' => $journal['currency_code'],
'currency_symbol' => $journal['currency_symbol'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
}
$return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $journal['amount'] ?? '0');
$return[$currencyId]['count']++;
if (null !== $foreignCurrencyId && null !== $journal['foreign_amount']) {
if (!array_key_exists($foreignCurrencyId, $return)) {
$return[$foreignCurrencyId] = [
'amount' => '0',
'count' => 0,
'currency_id' => (int)$foreignCurrencyId,
'currency_name' => $journal['foreign_currency_name'],
'currency_code' => $journal['foreign_currency_code'],
'currency_symbol' => $journal['foreign_currency_symbol'],
'currency_decimal_places' => $journal['foreign_currency_decimal_places'],
];
}
$return[$foreignCurrencyId]['count']++;
$return[$foreignCurrencyId]['amount'] = bcadd($return[$foreignCurrencyId]['amount'], $journal['foreign_amount']);
}
}
return $return;
}
/**
* Overview for single category. Has been refactored recently.
*
@@ -608,4 +498,114 @@ trait PeriodOverview
return $entries;
}
/**
* Filter a list of journals by a set of dates, and then group them by currency.
*
* @param array $array
* @param Carbon $start
* @param Carbon $end
*
* @return array
*/
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;
}
/**
* Return only transactions where $account is the source.
*
* @param Account $account
* @param array $journals
*
* @return array
*/
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.
*
* @param Account $account
* @param array $journals
*
* @return array
*/
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;
}
/**
* @param array $journals
*
* @return array
*/
private function groupByCurrency(array $journals): array
{
$return = [];
/** @var array $journal */
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$foreignCurrencyId = $journal['foreign_currency_id'];
if (!array_key_exists($currencyId, $return)) {
$return[$currencyId] = [
'amount' => '0',
'count' => 0,
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_code' => $journal['currency_code'],
'currency_symbol' => $journal['currency_symbol'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
}
$return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $journal['amount'] ?? '0');
$return[$currencyId]['count']++;
if (null !== $foreignCurrencyId && null !== $journal['foreign_amount']) {
if (!array_key_exists($foreignCurrencyId, $return)) {
$return[$foreignCurrencyId] = [
'amount' => '0',
'count' => 0,
'currency_id' => (int)$foreignCurrencyId,
'currency_name' => $journal['foreign_currency_name'],
'currency_code' => $journal['foreign_currency_code'],
'currency_symbol' => $journal['foreign_currency_symbol'],
'currency_decimal_places' => $journal['foreign_currency_decimal_places'],
];
}
$return[$foreignCurrencyId]['count']++;
$return[$foreignCurrencyId]['amount'] = bcadd($return[$foreignCurrencyId]['amount'], $journal['foreign_amount']);
}
}
return $return;
}
}
@@ -59,6 +59,24 @@ trait RequestInformation
return $parts['host'];
}
/**
* @return string
*/
final protected function getPageName(): string // get request info
{
return str_replace('.', '_', RouteFacade::currentRouteName());
}
/**
* Get the specific name of a page for intro.
*
* @return string
*/
final protected function getSpecificPageName(): string // get request info
{
return null === RouteFacade::current()->parameter('objectType') ? '' : '_'.RouteFacade::current()->parameter('objectType');
}
/**
* Get a list of triggers.
*
@@ -115,24 +133,6 @@ trait RequestInformation
return $shownDemo;
}
/**
* @return string
*/
final protected function getPageName(): string // get request info
{
return str_replace('.', '_', RouteFacade::currentRouteName());
}
/**
* Get the specific name of a page for intro.
*
* @return string
*/
final protected function getSpecificPageName(): string // get request info
{
return null === RouteFacade::current()->parameter('objectType') ? '' : '_'.RouteFacade::current()->parameter('objectType');
}
/**
* Check if date is outside session range.
*
+1 -1
View File
@@ -41,7 +41,7 @@ class AuditProcessor
public function __invoke(LogRecord $record): LogRecord
{
if (auth()->check()) {
$message = sprintf(
$message = sprintf(
'AUDIT: %s (%s (%s) -> %s:%s)',
$record['message'],
app('request')->ip(),
+88 -88
View File
@@ -173,71 +173,6 @@ class Navigation
return $periods;
}
/**
* @param Carbon $theDate
* @param string $repeatFreq
*
* @return Carbon
*/
public function startOfPeriod(Carbon $theDate, string $repeatFreq): Carbon
{
$date = clone $theDate;
$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',
];
if (array_key_exists($repeatFreq, $functionMap)) {
$function = $functionMap[$repeatFreq];
$date->$function();
return $date;
}
if ('half-year' === $repeatFreq || '6M' === $repeatFreq) {
$month = $date->month;
$date->startOfYear();
if ($month >= 7) {
$date->addMonths(6);
}
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) {
return $result;
}
if ('custom' === $repeatFreq) {
return $date; // the date is already at the start.
}
Log::error(sprintf('Cannot do startOfPeriod for $repeat_freq "%s"', $repeatFreq));
return $theDate;
}
/**
* @param Carbon $end
* @param string $repeatFreq
@@ -441,29 +376,6 @@ class Navigation
return $entries;
}
/**
* 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".
*
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function preferredCarbonFormat(Carbon $start, Carbon $end): string
{
$format = 'Y-m-d';
if ($start->diffInMonths($end) > 1) {
$format = 'Y-m';
}
if ($start->diffInMonths($end) > 12) {
$format = 'Y';
}
return $format;
}
/**
* @param Carbon $theDate
* @param string $repeatFrequency
@@ -504,6 +416,29 @@ class Navigation
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".
*
* @param Carbon $start
* @param Carbon $end
*
* @return string
*/
public function preferredCarbonFormat(Carbon $start, Carbon $end): string
{
$format = 'Y-m-d';
if ($start->diffInMonths($end) > 1) {
$format = 'Y-m';
}
if ($start->diffInMonths($end) > 12) {
$format = 'Y';
}
return $format;
}
/**
* If the date difference between start and end is less than a month, method returns trans(config.month_and_day). If the difference is less than a year,
* method returns "config.month". If the date difference is larger, method returns "config.year".
@@ -597,6 +532,71 @@ class Navigation
return $format;
}
/**
* @param Carbon $theDate
* @param string $repeatFreq
*
* @return Carbon
*/
public function startOfPeriod(Carbon $theDate, string $repeatFreq): Carbon
{
$date = clone $theDate;
$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',
];
if (array_key_exists($repeatFreq, $functionMap)) {
$function = $functionMap[$repeatFreq];
$date->$function();
return $date;
}
if ('half-year' === $repeatFreq || '6M' === $repeatFreq) {
$month = $date->month;
$date->startOfYear();
if ($month >= 7) {
$date->addMonths(6);
}
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) {
return $result;
}
if ('custom' === $repeatFreq) {
return $date; // the date is already at the start.
}
Log::error(sprintf('Cannot do startOfPeriod for $repeat_freq "%s"', $repeatFreq));
return $theDate;
}
/**
* @param Carbon $theDate
* @param string $repeatFreq
+209 -209
View File
@@ -121,6 +121,183 @@ class ParseDateString
throw new FireflyException(sprintf('[d] Not a recognised date format: "%s"', $date));
}
/**
* @param string $date
*
* @return array
*/
public function parseRange(string $date): array
{
// several types of range can be submitted
$result = [
'exact' => new Carbon('1984-09-17'),
];
switch (true) {
default:
break;
case $this->isDayRange($date):
$result = $this->parseDayRange($date);
break;
case $this->isMonthRange($date):
$result = $this->parseMonthRange($date);
break;
case $this->isYearRange($date):
$result = $this->parseYearRange($date);
break;
case $this->isMonthDayRange($date):
$result = $this->parseMonthDayRange($date);
break;
case $this->isDayYearRange($date):
$result = $this->parseDayYearRange($date);
break;
case $this->isMonthYearRange($date):
$result = $this->parseMonthYearRange($date);
break;
}
return $result;
}
/**
* @param string $date
*
* @return bool
*/
protected function isDayRange(string $date): bool
{
// if regex for xxxx-xx-DD:
$pattern = '/^xxxx-xx-(0[1-9]|[12]\d|3[01])$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a day range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a day range.', $date));
return false;
}
/**
* @param string $date
*
* @return bool
*/
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])$/';
if (preg_match($pattern, $date)) {
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;
}
/**
* @param string $date
*
* @return bool
*/
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])$/';
if (preg_match($pattern, $date)) {
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;
}
/**
* @param string $date
*
* @return bool
*/
protected function isMonthRange(string $date): bool
{
// if regex for xxxx-MM-xx:
$pattern = '/^xxxx-(0[1-9]|1[012])-xx$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a month range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a month range.', $date));
return false;
}
/**
* @param string $date
*
* @return bool
*/
protected function isMonthYearRange(string $date): bool
{
// if regex for YYYY-MM-xx:
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/';
if (preg_match($pattern, $date)) {
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;
}
/**
* @param string $date
*
* @return bool
*/
protected function isYearRange(string $date): bool
{
// if regex for YYYY-xx-xx:
$pattern = '/^(19|20)\d\d-xx-xx$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a year range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a year range.', $date));
return false;
}
/**
* format of string is xxxx-xx-DD
*
* @param string $date
*
* @return array
*/
protected function parseDayRange(string $date): array
{
$parts = explode('-', $date);
return [
'day' => $parts[2],
];
}
/**
* @param string $date
*
* @return Carbon
*/
protected function parseDefaultDate(string $date): Carbon
{
return Carbon::createFromFormat('Y-m-d', $date);
}
/**
* @param string $keyword
*
@@ -146,13 +323,38 @@ class ParseDateString
}
/**
* format of string is xxxx-MM-xx
*
* @param string $date
*
* @return Carbon
* @return array
*/
protected function parseDefaultDate(string $date): Carbon
protected function parseMonthRange(string $date): array
{
return Carbon::createFromFormat('Y-m-d', $date);
Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
'month' => $parts[1],
];
}
/**
* format of string is YYYY-MM-xx
*
* @param string $date
*
* @return array
*/
protected function parseMonthYearRange(string $date): array
{
Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
'year' => $parts[0],
'month' => $parts[1],
];
}
/**
@@ -209,133 +411,6 @@ class ParseDateString
return $today;
}
/**
* @param string $date
*
* @return array
*/
public function parseRange(string $date): array
{
// several types of range can be submitted
$result = [
'exact' => new Carbon('1984-09-17'),
];
switch (true) {
default:
break;
case $this->isDayRange($date):
$result = $this->parseDayRange($date);
break;
case $this->isMonthRange($date):
$result = $this->parseMonthRange($date);
break;
case $this->isYearRange($date):
$result = $this->parseYearRange($date);
break;
case $this->isMonthDayRange($date):
$result = $this->parseMonthDayRange($date);
break;
case $this->isDayYearRange($date):
$result = $this->parseDayYearRange($date);
break;
case $this->isMonthYearRange($date):
$result = $this->parseMonthYearRange($date);
break;
}
return $result;
}
/**
* @param string $date
*
* @return bool
*/
protected function isDayRange(string $date): bool
{
// if regex for xxxx-xx-DD:
$pattern = '/^xxxx-xx-(0[1-9]|[12]\d|3[01])$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a day range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a day range.', $date));
return false;
}
/**
* format of string is xxxx-xx-DD
*
* @param string $date
*
* @return array
*/
protected function parseDayRange(string $date): array
{
$parts = explode('-', $date);
return [
'day' => $parts[2],
];
}
/**
* @param string $date
*
* @return bool
*/
protected function isMonthRange(string $date): bool
{
// if regex for xxxx-MM-xx:
$pattern = '/^xxxx-(0[1-9]|1[012])-xx$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a month range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a month range.', $date));
return false;
}
/**
* format of string is xxxx-MM-xx
*
* @param string $date
*
* @return array
*/
protected function parseMonthRange(string $date): array
{
Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
'month' => $parts[1],
];
}
/**
* @param string $date
*
* @return bool
*/
protected function isYearRange(string $date): bool
{
// if regex for YYYY-xx-xx:
$pattern = '/^(19|20)\d\d-xx-xx$/';
if (preg_match($pattern, $date)) {
Log::debug(sprintf('"%s" is a year range.', $date));
return true;
}
Log::debug(sprintf('"%s" is not a year range.', $date));
return false;
}
/**
* format of string is YYYY-xx-xx
*
@@ -353,62 +428,6 @@ class ParseDateString
];
}
/**
* @param string $date
*
* @return bool
*/
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])$/';
if (preg_match($pattern, $date)) {
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
*
* @param string $date
*
* @return array
*/
private function parseMonthDayRange(string $date): array
{
Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date));
$parts = explode('-', $date);
return [
'month' => $parts[1],
'day' => $parts[2],
];
}
/**
* @param string $date
*
* @return bool
*/
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])$/';
if (preg_match($pattern, $date)) {
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
*
@@ -428,39 +447,20 @@ class ParseDateString
}
/**
* @param string $date
*
* @return bool
*/
protected function isMonthYearRange(string $date): bool
{
// if regex for YYYY-MM-xx:
$pattern = '/^(19|20)\d\d-(0[1-9]|1[012])-xx$/';
if (preg_match($pattern, $date)) {
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
*
* @param string $date
*
* @return array
*/
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],
];
}
}
+111 -111
View File
@@ -51,6 +51,55 @@ class Preferences
return Preference::where('user_id', $user->id)->get();
}
/**
* @param User $user
* @param string $search
*
* @return Collection
*/
public function beginsWith(User $user, string $search): Collection
{
return Preference::where('user_id', $user->id)->where('name', 'LIKE', $search.'%')->get();
}
/**
* @param string $name
*
* @return bool
* @throws FireflyException
*/
public function delete(string $name): bool
{
$fullName = sprintf('preference%s%s', auth()->user()->id, $name);
if (Cache::has($fullName)) {
Cache::forget($fullName);
}
Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete();
return true;
}
/**
* @param string $name
*
* @return Collection
*/
public function findByName(string $name): Collection
{
return Preference::where('name', $name)->get();
}
/**
* @param User $user
* @param string $name
*/
public function forget(User $user, string $name): void
{
$key = sprintf('preference%s%s', $user->id, $name);
Cache::forget($key);
Cache::put($key, '', 5);
}
/**
* @param string $name
* @param mixed $default
@@ -72,6 +121,29 @@ class Preferences
return $this->getForUser($user, $name, $default);
}
/**
* @param User $user
* @param array $list
*
* @return array
*/
public function getArrayForUser(User $user, array $list): array
{
$result = [];
$preferences = Preference::where('user_id', $user->id)->whereIn('name', $list)->get(['id', 'name', 'data']);
/** @var Preference $preference */
foreach ($preferences as $preference) {
$result[$preference->name] = $preference->data;
}
foreach ($list as $name) {
if (!array_key_exists($name, $result)) {
$result[$name] = null;
}
}
return $result;
}
/**
* @param User $user
* @param string $name
@@ -100,117 +172,6 @@ class Preferences
return $this->setForUser($user, $name, $default);
}
/**
* @param string $name
*
* @return bool
* @throws FireflyException
*/
public function delete(string $name): bool
{
$fullName = sprintf('preference%s%s', auth()->user()->id, $name);
if (Cache::has($fullName)) {
Cache::forget($fullName);
}
Preference::where('user_id', auth()->user()->id)->where('name', $name)->delete();
return true;
}
/**
* @param User $user
* @param string $name
*/
public function forget(User $user, string $name): void
{
$key = sprintf('preference%s%s', $user->id, $name);
Cache::forget($key);
Cache::put($key, '', 5);
}
/**
* @param User $user
* @param string $name
* @param mixed $value
*
* @return Preference
* @throws FireflyException
*/
public function setForUser(User $user, string $name, $value): Preference
{
$fullName = sprintf('preference%s%s', $user->id, $name);
Cache::forget($fullName);
/** @var Preference|null $pref */
$pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']);
if (null !== $pref && null === $value) {
$pref->delete();
return new Preference();
}
if (null === $value) {
return new Preference();
}
if (null === $pref) {
$pref = new Preference();
$pref->user_id = $user->id;
$pref->name = $name;
}
$pref->data = $value;
try {
$pref->save();
} catch (PDOException $e) {
throw new FireflyException(sprintf('Could not save preference: %s', $e->getMessage()), 0, $e);
}
Cache::forever($fullName, $pref);
return $pref;
}
/**
* @param User $user
* @param string $search
*
* @return Collection
*/
public function beginsWith(User $user, string $search): Collection
{
return Preference::where('user_id', $user->id)->where('name', 'LIKE', $search.'%')->get();
}
/**
* @param string $name
*
* @return Collection
*/
public function findByName(string $name): Collection
{
return Preference::where('name', $name)->get();
}
/**
* @param User $user
* @param array $list
*
* @return array
*/
public function getArrayForUser(User $user, array $list): array
{
$result = [];
$preferences = Preference::where('user_id', $user->id)->whereIn('name', $list)->get(['id', 'name', 'data']);
/** @var Preference $preference */
foreach ($preferences as $preference) {
$result[$preference->name] = $preference->data;
}
foreach ($list as $name) {
if (!array_key_exists($name, $result)) {
$result[$name] = null;
}
}
return $result;
}
/**
* @param string $name
* @param mixed $default
@@ -295,4 +256,43 @@ class Preferences
return $this->setForUser(auth()->user(), $name, $value);
}
/**
* @param User $user
* @param string $name
* @param mixed $value
*
* @return Preference
* @throws FireflyException
*/
public function setForUser(User $user, string $name, $value): Preference
{
$fullName = sprintf('preference%s%s', $user->id, $name);
Cache::forget($fullName);
/** @var Preference|null $pref */
$pref = Preference::where('user_id', $user->id)->where('name', $name)->first(['id', 'name', 'data', 'updated_at', 'created_at']);
if (null !== $pref && null === $value) {
$pref->delete();
return new Preference();
}
if (null === $value) {
return new Preference();
}
if (null === $pref) {
$pref = new Preference();
$pref->user_id = $user->id;
$pref->name = $name;
}
$pref->data = $value;
try {
$pref->save();
} catch (PDOException $e) {
throw new FireflyException(sprintf('Could not save preference: %s', $e->getMessage()), 0, $e);
}
Cache::forever($fullName, $pref);
return $pref;
}
}
@@ -91,48 +91,6 @@ class BudgetReportGenerator
}
}
/**
* Process each row of expenses collected for the "Account per budget" partial
*
* @param array $expenses
*/
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.
*
* @param array $expenses
* @param array $budget
*/
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]
= $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]
= $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId] ?? '0';
$this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId]
= bcadd($this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId], $journal['amount']);
}
}
/**
* Generates the data necessary to create the card that displays
* the budget overview in the general report.
@@ -149,6 +107,60 @@ class BudgetReportGenerator
$this->percentageReport();
}
/**
* @return array
*/
public function getReport(): array
{
return $this->report;
}
/**
* @param Collection $accounts
*/
public function setAccounts(Collection $accounts): void
{
$this->accounts = $accounts;
}
/**
* @param Collection $budgets
*/
public function setBudgets(Collection $budgets): void
{
$this->budgets = $budgets;
}
/**
* @param Carbon $end
*/
public function setEnd(Carbon $end): void
{
$this->end = $end;
}
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
/**
* @param User $user
* @throws FireflyException
* @throws JsonException
*/
public function setUser(User $user): void
{
$this->repository->setUser($user);
$this->blRepository->setUser($user);
$this->opsRepository->setUser($user);
$this->nbRepository->setUser($user);
$this->currency = app('amount')->getDefaultCurrencyByUser($user);
}
/**
* Start the budgets block on the default report by processing every budget.
*/
@@ -161,82 +173,6 @@ class BudgetReportGenerator
}
}
/**
* Process expenses etc. for a single budget for the budgets block on the default report.
*
* @param Budget $budget
*/
private function processBudget(Budget $budget): void
{
$budgetId = (int)$budget->id;
$this->report['budgets'][$budgetId] = $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.
*
* @param Budget $budget
* @param BudgetLimit $limit
*/
private function processLimit(Budget $budget, BudgetLimit $limit): void
{
$budgetId = (int)$budget->id;
$limitId = (int)$limit->id;
$limitCurrency = $limit->transactionCurrency ?? $this->currency;
$currencyId = (int)$limitCurrency->id;
$expenses = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, $this->accounts, new Collection([$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] = $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]
= $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($this->report['sums'][$currencyId]['budgeted'], $limit->amount);
$this->report['sums'][$currencyId]['spent'] = bcadd($this->report['sums'][$currencyId]['spent'], $spent);
$this->report['sums'][$currencyId]['left'] = bcadd($this->report['sums'][$currencyId]['left'], bcadd($limit->amount, $spent));
$this->report['sums'][$currencyId]['overspent'] = bcadd($this->report['sums'][$currencyId]['overspent'], $overspent);
}
/**
* Calculate the expenses for transactions without a budget. Part of the "budgets" block of the default report.
*/
@@ -322,56 +258,120 @@ class BudgetReportGenerator
}
/**
* @return array
* Process expenses etc. for a single budget for the budgets block on the default report.
*
* @param Budget $budget
*/
public function getReport(): array
private function processBudget(Budget $budget): void
{
return $this->report;
$budgetId = (int)$budget->id;
$this->report['budgets'][$budgetId] = $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);
}
}
/**
* @param Collection $accounts
* Process each set of transactions for each row of expenses.
*
* @param array $expenses
* @param array $budget
*/
public function setAccounts(Collection $accounts): void
private function processBudgetExpenses(array $expenses, array $budget): void
{
$this->accounts = $accounts;
$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]
= $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]
= $this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId] ?? '0';
$this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId]
= bcadd($this->report[$sourceAccountId]['currencies'][$currencyId]['budgets'][$budgetId], $journal['amount']);
}
}
/**
* @param Collection $budgets
* Process each row of expenses collected for the "Account per budget" partial
*
* @param array $expenses
*/
public function setBudgets(Collection $budgets): void
private function processExpenses(array $expenses): void
{
$this->budgets = $budgets;
foreach ($expenses['budgets'] as $budget) {
$this->processBudgetExpenses($expenses, $budget);
}
}
/**
* @param Carbon $end
* Process a single budget limit for the budgets block on the default report.
*
* @param Budget $budget
* @param BudgetLimit $limit
*/
public function setEnd(Carbon $end): void
private function processLimit(Budget $budget, BudgetLimit $limit): void
{
$this->end = $end;
}
$budgetId = (int)$budget->id;
$limitId = (int)$limit->id;
$limitCurrency = $limit->transactionCurrency ?? $this->currency;
$currencyId = (int)$limitCurrency->id;
$expenses = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, $this->accounts, new Collection([$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';
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
$this->report['budgets'][$budgetId]['budget_limits'][$limitId] = $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,
];
/**
* @param User $user
* @throws FireflyException
* @throws JsonException
*/
public function setUser(User $user): void
{
$this->repository->setUser($user);
$this->blRepository->setUser($user);
$this->opsRepository->setUser($user);
$this->nbRepository->setUser($user);
$this->currency = app('amount')->getDefaultCurrencyByUser($user);
// make sum information:
$this->report['sums'][$currencyId]
= $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($this->report['sums'][$currencyId]['budgeted'], $limit->amount);
$this->report['sums'][$currencyId]['spent'] = bcadd($this->report['sums'][$currencyId]['spent'], $spent);
$this->report['sums'][$currencyId]['left'] = bcadd($this->report['sums'][$currencyId]['left'], bcadd($limit->amount, $spent));
$this->report['sums'][$currencyId]['overspent'] = bcadd($this->report['sums'][$currencyId]['overspent'], $overspent);
}
}
@@ -86,45 +86,36 @@ class CategoryReportGenerator
}
/**
* Process one of the spent arrays from the operations method.
*
* @param array $data
* @param Collection $accounts
*/
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;
}
/**
* @param int $currencyId
* @param array $currencyRow
* @param Carbon $end
*/
private function processCurrencyArray(int $currencyId, array $currencyRow): void
public function setEnd(Carbon $end): void
{
$this->report['sums'][$currencyId] = $this->report['sums'][$currencyId] ?? [
'spent' => '0',
'earned' => '0',
'sum' => '0',
'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'],
];
$this->end = $end;
}
/**
* @var int $categoryId
* @var array $categoryRow
*/
foreach ($currencyRow['categories'] as $categoryId => $categoryRow) {
$this->processCategoryRow($currencyId, $currencyRow, $categoryId, $categoryRow);
}
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->noCatRepository->setUser($user);
$this->opsRepository->setUser($user);
}
/**
@@ -179,35 +170,44 @@ class CategoryReportGenerator
}
/**
* @param Collection $accounts
* @param int $currencyId
* @param array $currencyRow
*/
public function setAccounts(Collection $accounts): void
private function processCurrencyArray(int $currencyId, array $currencyRow): void
{
$this->accounts = $accounts;
$this->report['sums'][$currencyId] = $this->report['sums'][$currencyId] ?? [
'spent' => '0',
'earned' => '0',
'sum' => '0',
'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'],
];
/**
* @var int $categoryId
* @var array $categoryRow
*/
foreach ($currencyRow['categories'] as $categoryId => $categoryRow) {
$this->processCategoryRow($currencyId, $currencyRow, $categoryId, $categoryRow);
}
}
/**
* @param Carbon $end
* Process one of the spent arrays from the operations method.
*
* @param array $data
*/
public function setEnd(Carbon $end): void
private function processOpsArray(array $data): void
{
$this->end = $end;
}
/**
* @param Carbon $start
*/
public function setStart(Carbon $start): void
{
$this->start = $start;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->noCatRepository->setUser($user);
$this->opsRepository->setUser($user);
/**
* @var int $currencyId
* @var array $currencyRow
*/
foreach ($data as $currencyId => $currencyRow) {
$this->processCurrencyArray($currencyId, $currencyRow);
}
}
}
@@ -34,8 +34,8 @@ use Illuminate\Contracts\Auth\Authenticatable;
*/
trait AdministrationTrait
{
protected User $user;
protected ?int $administrationId = null;
protected User $user;
protected ?UserGroup $userGroup = null;
/**
@@ -56,6 +56,13 @@ trait AdministrationTrait
$this->refreshAdministration();
}
public function setUser(Authenticatable|User|null $user): void
{
if (null !== $user) {
$this->user = $user;
}
}
/**
* @return void
*/
@@ -63,8 +70,8 @@ trait AdministrationTrait
{
if (null !== $this->administrationId) {
$memberships = GroupMembership::where('user_id', $this->user->id)
->where('user_group_id', $this->administrationId)
->count();
->where('user_group_id', $this->administrationId)
->count();
if (0 === $memberships) {
throw new FireflyException(sprintf('User #%d has no access to administration #%d', $this->user->id, $this->administrationId));
}
@@ -73,12 +80,4 @@ trait AdministrationTrait
}
throw new FireflyException(sprintf('Cannot validate administration for user #%d', $this->user->id));
}
public function setUser(Authenticatable|User|null $user): void
{
if (null !== $user) {
$this->user = $user;
}
}
}
+47 -47
View File
@@ -30,6 +30,16 @@ use Illuminate\Support\Facades\Log;
*/
trait AppendsLocationData
{
/**
* Abstract method stolen from "InteractsWithInput".
*
* @param null $key
* @param bool $default
*
* @return mixed
*/
abstract public function boolean($key = null, $default = false);
/**
* Abstract method.
*
@@ -39,6 +49,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.
*
@@ -114,6 +140,27 @@ trait AppendsLocationData
return sprintf('%s_%s', $prefix, $key);
}
/**
* @param string|null $prefix
*
* @return bool
*/
private function isValidEmptyPUT(?string $prefix): bool
{
$longitudeKey = $this->getLocationKey($prefix, 'longitude');
$latitudeKey = $this->getLocationKey($prefix, 'latitude');
$zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
return (
null === $this->get($longitudeKey)
&& null === $this->get($latitudeKey)
&& null === $this->get($zoomLevelKey))
&& (
'PUT' === $this->method()
|| ('POST' === $this->method() && $this->routeIs('*.update'))
);
}
/**
* @param string|null $prefix
*
@@ -157,32 +204,6 @@ trait AppendsLocationData
return false;
}
/**
* 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
*/
abstract public function boolean($key = null, $default = false);
/**
* @param string|null $prefix
*
@@ -227,25 +248,4 @@ trait AppendsLocationData
return false;
}
/**
* @param string|null $prefix
*
* @return bool
*/
private function isValidEmptyPUT(?string $prefix): bool
{
$longitudeKey = $this->getLocationKey($prefix, 'longitude');
$latitudeKey = $this->getLocationKey($prefix, 'latitude');
$zoomLevelKey = $this->getLocationKey($prefix, 'zoom_level');
return (
null === $this->get($longitudeKey)
&& null === $this->get($latitudeKey)
&& null === $this->get($zoomLevelKey))
&& (
'PUT' === $this->method()
|| ('POST' === $this->method() && $this->routeIs('*.update'))
);
}
}
+85 -85
View File
@@ -33,44 +33,6 @@ use Illuminate\Support\Facades\Log;
*/
trait ConvertsDataTypes
{
/**
* Return integer value.
*
* @param string $field
*
* @return int
*/
public function convertInteger(string $field): int
{
return (int)$this->get($field);
}
/**
* 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 string $key
* @param mixed|null $default
* @return mixed
*/
abstract public function get(string $key, mixed $default = null): mixed;
/**
* Return string value.
*
* @param string $field
*
* @return string
*/
public function convertString(string $field): string
{
$entry = $this->get($field);
if (!is_scalar($entry)) {
return '';
}
return $this->clearString((string)$entry, false);
}
/**
* @param string|null $string
* @param bool $keepNewlines
@@ -152,6 +114,53 @@ trait ConvertsDataTypes
return trim($string);
}
/**
* Return integer value.
*
* @param string $field
*
* @return int
*/
public function convertInteger(string $field): int
{
return (int)$this->get($field);
}
/**
* Return string value.
*
* @param string $field
*
* @return string
*/
public function convertString(string $field): string
{
$entry = $this->get($field);
if (!is_scalar($entry)) {
return '';
}
return $this->clearString((string)$entry, false);
}
/**
* 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 string $key
* @param mixed|null $default
* @return mixed
*/
abstract public function get(string $key, mixed $default = null): mixed;
/**
* 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.
*
@@ -210,6 +219,44 @@ trait ConvertsDataTypes
return false;
}
protected function convertDateTime(?string $string): ?Carbon
{
$value = $this->get($string);
if (null === $value) {
return null;
}
if ('' === $value) {
return null;
}
if (10 === strlen($value)) {
// probably a date format.
try {
$carbon = Carbon::createFromFormat('Y-m-d', $value);
} catch (InvalidDateException $e) {
Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage()));
return null;
} catch (InvalidFormatException $e) {
Log::error(sprintf('[2] "%s" is of an invalid format: %s', $value, $e->getMessage()));
return null;
}
return $carbon;
}
// is an atom string, I hope?
try {
$carbon = Carbon::parse($value);
} catch (InvalidDateException $e) {
Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage()));
return null;
} catch (InvalidFormatException $e) {
Log::error(sprintf('[4] "%s" is of an invalid format: %s', $value, $e->getMessage()));
return null;
}
return $carbon;
}
/**
* Return floating value.
*
@@ -256,44 +303,6 @@ trait ConvertsDataTypes
return $carbon;
}
protected function convertDateTime(?string $string): ?Carbon
{
$value = $this->get($string);
if (null === $value) {
return null;
}
if ('' === $value) {
return null;
}
if (10 === strlen($value)) {
// probably a date format.
try {
$carbon = Carbon::createFromFormat('Y-m-d', $value);
} catch (InvalidDateException $e) {
Log::error(sprintf('[1] "%s" is not a valid date: %s', $value, $e->getMessage()));
return null;
} catch (InvalidFormatException $e) {
Log::error(sprintf('[2] "%s" is of an invalid format: %s', $value, $e->getMessage()));
return null;
}
return $carbon;
}
// is an atom string, I hope?
try {
$carbon = Carbon::parse($value);
} catch (InvalidDateException $e) {
Log::error(sprintf('[3] "%s" is not a valid date or time: %s', $value, $e->getMessage()));
return null;
} catch (InvalidFormatException $e) {
Log::error(sprintf('[4] "%s" is of an invalid format: %s', $value, $e->getMessage()));
return null;
}
return $carbon;
}
/**
* Returns all data in the request, or omits the field if not set,
* according to the config from the request. This is the way.
@@ -315,15 +324,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.
*
File diff suppressed because it is too large Load Diff
+131 -131
View File
@@ -32,8 +32,8 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use Illuminate\Support\Collection;
use JsonException;
use Illuminate\Support\Facades\Log;
use JsonException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use stdClass;
@@ -47,6 +47,56 @@ use ValueError;
*/
class Steam
{
/**
* Gets balance at the end of current month by default
*
* @param Account $account
* @param Carbon $date
* @param TransactionCurrency|null $currency
*
* @return string
* @throws FireflyException
*/
public function balance(Account $account, Carbon $date, ?TransactionCurrency $currency = null): string
{
// abuse chart properties:
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance');
$cache->addProperty($date);
$cache->addProperty($currency ? $currency->id : 0);
if ($cache->has()) {
return $cache->get();
}
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
if (null === $currency) {
$currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUser($account->user);
}
// first part: get all balances in own currency:
$transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id)
->get(['transactions.amount'])->toArray();
$nativeBalance = $this->sumTransactions($transactions, 'amount');
// get all balances in foreign currency:
$transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $currency->id)
->where('transactions.transaction_currency_id', '!=', $currency->id)
->get(['transactions.foreign_amount'])->toArray();
$foreignBalance = $this->sumTransactions($transactions, 'foreign_amount');
$balance = bcadd($nativeBalance, $foreignBalance);
$virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance;
$balance = bcadd($balance, $virtual);
$cache->store($balance);
return $balance;
}
/**
* @param Account $account
* @param Carbon $date
@@ -79,25 +129,6 @@ class Steam
return bcadd($nativeBalance, $foreignBalance);
}
/**
* @param array $transactions
* @param string $key
*
* @return string
*/
public function sumTransactions(array $transactions, string $key): string
{
$sum = '0';
/** @var array $transaction */
foreach ($transactions as $transaction) {
$value = (string)($transaction[$key] ?? '0');
$value = '' === $value ? '0' : $value;
$sum = bcadd($sum, $value);
}
return $sum;
}
/**
* Gets the balance for the given account during the whole range, using this format:.
*
@@ -189,53 +220,34 @@ class Steam
}
/**
* Gets balance at the end of current month by default
*
* @param Account $account
* @param Carbon $date
* @param TransactionCurrency|null $currency
*
* @return string
* @throws FireflyException
* @return array
*/
public function balance(Account $account, Carbon $date, ?TransactionCurrency $currency = null): string
public function balancePerCurrency(Account $account, Carbon $date): array
{
// abuse chart properties:
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance');
$cache->addProperty('balance-per-currency');
$cache->addProperty($date);
$cache->addProperty($currency ? $currency->id : 0);
if ($cache->has()) {
return $cache->get();
}
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
if (null === $currency) {
$currency = $repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrencyByUser($account->user);
$query = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->groupBy('transactions.transaction_currency_id');
$balances = $query->get(['transactions.transaction_currency_id', DB::raw('SUM(transactions.amount) as sum_for_currency')]);
$return = [];
/** @var stdClass $entry */
foreach ($balances as $entry) {
$return[(int)$entry->transaction_currency_id] = (string)$entry->sum_for_currency;
}
// first part: get all balances in own currency:
$transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.transaction_currency_id', $currency->id)
->get(['transactions.amount'])->toArray();
$nativeBalance = $this->sumTransactions($transactions, 'amount');
// get all balances in foreign currency:
$transactions = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->where('transactions.foreign_currency_id', $currency->id)
->where('transactions.transaction_currency_id', '!=', $currency->id)
->get(['transactions.foreign_amount'])->toArray();
$foreignBalance = $this->sumTransactions($transactions, 'foreign_amount');
$balance = bcadd($nativeBalance, $foreignBalance);
$virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance;
$balance = bcadd($balance, $virtual);
$cache->store($return);
$cache->store($balance);
return $balance;
return $return;
}
/**
@@ -303,37 +315,6 @@ class Steam
return $result;
}
/**
* @param Account $account
* @param Carbon $date
*
* @return array
*/
public function balancePerCurrency(Account $account, Carbon $date): array
{
// abuse chart properties:
$cache = new CacheProperties();
$cache->addProperty($account->id);
$cache->addProperty('balance-per-currency');
$cache->addProperty($date);
if ($cache->has()) {
return $cache->get();
}
$query = $account->transactions()
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.date', '<=', $date->format('Y-m-d 23:59:59'))
->groupBy('transactions.transaction_currency_id');
$balances = $query->get(['transactions.transaction_currency_id', DB::raw('SUM(transactions.amount) as sum_for_currency')]);
$return = [];
/** @var stdClass $entry */
foreach ($balances as $entry) {
$return[(int)$entry->transaction_currency_id] = (string)$entry->sum_for_currency;
}
$cache->store($return);
return $return;
}
/**
* https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers
*
@@ -428,6 +409,36 @@ class Steam
return str_replace($search, '', $string);
}
/**
* https://framework.zend.com/downloads/archives
*
* Convert a scientific notation to float
* Additionally fixed a problem with PHP <= 5.2.x with big integers
*
* @param string $value
* @return string
*/
public function floatalize(string $value): string
{
$value = strtoupper($value);
if (!str_contains($value, 'E')) {
return $value;
}
$number = substr($value, 0, strpos($value, 'E'));
if (str_contains($number, '.')) {
$post = strlen(substr($number, strpos($number, '.') + 1));
$mantis = substr($value, 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, '.', '');
}
/**
* @param string $ipAddress
* @return string
@@ -443,6 +454,23 @@ class Steam
return $hostName;
}
/**
* Get user's language.
*
* @return string
* @throws FireflyException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
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 $preference;
}
/**
* @param array $accounts
*
@@ -489,23 +517,6 @@ class Steam
return $locale;
}
/**
* Get user's language.
*
* @return string
* @throws FireflyException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
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 $preference;
}
/**
* @param string $locale
*
@@ -584,36 +595,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
*
* @param string $value
* @return string
*/
public function floatalize(string $value): string
{
$value = strtoupper($value);
if (!str_contains($value, 'E')) {
return $value;
}
$number = substr($value, 0, strpos($value, 'E'));
if (str_contains($number, '.')) {
$post = strlen(substr($number, strpos($number, '.') + 1));
$mantis = substr($value, 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, '.', '');
}
/**
* @param string|null $amount
*
@@ -683,4 +664,23 @@ class Steam
return $amount;
}
/**
* @param array $transactions
* @param string $key
*
* @return string
*/
public function sumTransactions(array $transactions, string $key): string
{
$sum = '0';
/** @var array $transaction */
foreach ($transactions as $transaction) {
$value = (string)($transaction[$key] ?? '0');
$value = '' === $value ? '0' : $value;
$sum = bcadd($sum, $value);
}
return $sum;
}
}
+45 -45
View File
@@ -28,8 +28,8 @@ use Artisan;
use Crypt;
use FireflyIII\Exceptions\FireflyException;
use Illuminate\Contracts\Encryption\DecryptException;
use Laravel\Passport\Console\KeysCommand;
use Illuminate\Support\Facades\Log;
use Laravel\Passport\Console\KeysCommand;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
@@ -44,22 +44,21 @@ class OAuthKeys
/**
*
*/
public static function verifyKeysRoutine(): void
public static function generateKeys(): void
{
if (!self::keysInDatabase() && !self::hasKeyFiles()) {
self::generateKeys();
self::storeKeysInDB();
Artisan::registerCommand(new KeysCommand());
Artisan::call('passport:keys');
}
return;
}
if (self::keysInDatabase() && !self::hasKeyFiles()) {
self::restoreKeysFromDB();
/**
* @return bool
*/
public static function hasKeyFiles(): bool
{
$private = storage_path('oauth-private.key');
$public = storage_path('oauth-public.key');
return;
}
if (!self::keysInDatabase() && self::hasKeyFiles()) {
self::storeKeysInDB();
}
return file_exists($private) && file_exists($public);
}
/**
@@ -86,37 +85,6 @@ class OAuthKeys
return false;
}
/**
* @return bool
*/
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('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)));
}
/**
* @return bool
* @throws ContainerExceptionInterface
@@ -146,4 +114,36 @@ class OAuthKeys
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()) {
self::generateKeys();
self::storeKeysInDB();
return;
}
if (self::keysInDatabase() && !self::hasKeyFiles()) {
self::restoreKeysFromDB();
return;
}
if (!self::keysInDatabase() && self::hasKeyFiles()) {
self::storeKeysInDB();
}
}
}
+37 -37
View File
@@ -46,6 +46,18 @@ class AmountFormat extends AbstractExtension
];
}
/**
* {@inheritdoc}
*/
public function getFunctions(): array
{
return [
$this->formatAmountByAccount(),
$this->formatAmountBySymbol(),
$this->formatAmountByCurrency(),
];
}
/**
* @return TwigFilter
*/
@@ -62,34 +74,6 @@ class AmountFormat extends AbstractExtension
);
}
/**
* @return TwigFilter
*/
protected function formatAmountPlain(): TwigFilter
{
return new TwigFilter(
'formatAmountPlain',
static function (string $string): string {
$currency = app('amount')->getDefaultCurrency();
return app('amount')->formatAnything($currency, $string, false);
},
['is_safe' => ['html']]
);
}
/**
* {@inheritdoc}
*/
public function getFunctions(): array
{
return [
$this->formatAmountByAccount(),
$this->formatAmountBySymbol(),
$this->formatAmountByCurrency(),
];
}
/**
* Will format the amount by the currency related to the given account.
*
@@ -112,6 +96,24 @@ class AmountFormat extends AbstractExtension
);
}
/**
* Will format the amount by the currency related to the given account.
*
* @return TwigFunction
*/
protected function formatAmountByCurrency(): TwigFunction
{
return new TwigFunction(
'formatAmountByCurrency',
static function (TransactionCurrency $currency, string $amount, bool $coloured = null): string {
$coloured = $coloured ?? true;
return app('amount')->formatAnything($currency, $amount, $coloured);
},
['is_safe' => ['html']]
);
}
/**
* Will format the amount by the currency related to the given account.
*
@@ -135,18 +137,16 @@ class AmountFormat extends AbstractExtension
}
/**
* Will format the amount by the currency related to the given account.
*
* @return TwigFunction
* @return TwigFilter
*/
protected function formatAmountByCurrency(): TwigFunction
protected function formatAmountPlain(): TwigFilter
{
return new TwigFunction(
'formatAmountByCurrency',
static function (TransactionCurrency $currency, string $amount, bool $coloured = null): string {
$coloured = $coloured ?? true;
return new TwigFilter(
'formatAmountPlain',
static function (string $string): string {
$currency = app('amount')->getDefaultCurrency();
return app('amount')->formatAnything($currency, $amount, $coloured);
return app('amount')->formatAnything($currency, $string, false);
},
['is_safe' => ['html']]
);
+216 -216
View File
@@ -53,6 +53,95 @@ class General extends AbstractExtension
];
}
/**
* {@inheritdoc}
*/
public function getFunctions(): array
{
return [
$this->phpdate(),
$this->activeRouteStrict(),
$this->activeRoutePartial(),
$this->activeRoutePartialObjectType(),
$this->menuOpenRoutePartial(),
$this->formatDate(),
$this->getMetaField(),
$this->hasRole(),
$this->getRootSearchOperator(),
$this->carbonize(),
];
}
/**
* Will return "active" when a part of the route matches the argument.
* ie. "accounts" will match "accounts.index".
*
* @return TwigFunction
*/
protected function activeRoutePartial(): TwigFunction
{
return new TwigFunction(
'activeRoutePartial',
static function (): string {
$args = func_get_args();
$route = $args[0]; // name of the route.
$name = Route::getCurrentRoute()->getName() ?? '';
if (str_contains($name, $route)) {
return 'active';
}
return '';
}
);
}
/**
* This function will return "active" when the current route matches the first argument (even partly)
* but, the variable $objectType has been set and matches the second argument.
*
* @return TwigFunction
*/
protected function activeRoutePartialObjectType(): TwigFunction
{
return new TwigFunction(
'activeRoutePartialObjectType',
static function ($context): string {
[, $route, $objectType] = func_get_args();
$activeObjectType = $context['objectType'] ?? false;
if ($objectType === $activeObjectType && false !== stripos(Route::getCurrentRoute()->getName(), $route)) {
return 'active';
}
return '';
},
['needs_context' => true]
);
}
/**
* Will return "active" when the current route matches the given argument
* exactly.
*
* @return TwigFunction
*/
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.
*
@@ -74,6 +163,36 @@ class General extends AbstractExtension
);
}
/**
* @return TwigFunction
*/
protected function carbonize(): TwigFunction
{
return new TwigFunction(
'carbonize',
static function (string $date): Carbon {
return new Carbon($date);
}
);
}
/**
* Formats a string as a thing by converting it to a Carbon first.
*
* @return TwigFunction
*/
protected function formatDate(): TwigFunction
{
return new TwigFunction(
'formatDate',
function (string $date, string $format): string {
$carbon = new Carbon($date);
return $carbon->isoFormat($format);
}
);
}
/**
* Used to convert 1024 to 1kb etc.
*
@@ -99,6 +218,103 @@ class General extends AbstractExtension
);
}
/**
* @return TwigFunction
* TODO remove me when layout v1 is deprecated.
*/
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.
*
* @return TwigFunction
*/
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;
}
);
}
/**
* @return TwigFilter
*/
protected function markdown(): TwigFilter
{
return new TwigFilter(
'markdown',
static function (string $text): string {
$converter = new GithubFlavoredMarkdownConverter(
[
'allow_unsafe_links' => false,
'max_nesting_level' => 3,
'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".
*
* @return TwigFunction
*/
protected function menuOpenRoutePartial(): TwigFunction
{
return new TwigFunction(
'menuOpenRoutePartial',
static function (): string {
$args = func_get_args();
$route = $args[0]; // name of the route.
$name = Route::getCurrentRoute()->getName() ?? '';
if (str_contains($name, $route)) {
return 'menu-open';
}
return '';
}
);
}
/**
* Show icon with attachment.
*
@@ -177,28 +393,6 @@ class General extends AbstractExtension
);
}
/**
* @return TwigFilter
*/
protected function markdown(): TwigFilter
{
return new TwigFilter(
'markdown',
static function (string $text): string {
$converter = new GithubFlavoredMarkdownConverter(
[
'allow_unsafe_links' => false,
'max_nesting_level' => 3,
'html_input' => 'escape',
]
);
return (string)$converter->convert($text);
},
['is_safe' => ['html']]
);
}
/**
* Show URL host name
*
@@ -217,25 +411,6 @@ class General extends AbstractExtension
);
}
/**
* {@inheritdoc}
*/
public function getFunctions(): array
{
return [
$this->phpdate(),
$this->activeRouteStrict(),
$this->activeRoutePartial(),
$this->activeRoutePartialObjectType(),
$this->menuOpenRoutePartial(),
$this->formatDate(),
$this->getMetaField(),
$this->hasRole(),
$this->getRootSearchOperator(),
$this->carbonize(),
];
}
/**
* Basic example thing for some views.
*
@@ -250,179 +425,4 @@ class General extends AbstractExtension
}
);
}
/**
* Will return "active" when the current route matches the given argument
* exactly.
*
* @return TwigFunction
*/
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".
*
* @return TwigFunction
*/
protected function activeRoutePartial(): TwigFunction
{
return new TwigFunction(
'activeRoutePartial',
static function (): string {
$args = func_get_args();
$route = $args[0]; // name of the route.
$name = Route::getCurrentRoute()->getName() ?? '';
if (str_contains($name, $route)) {
return 'active';
}
return '';
}
);
}
/**
* This function will return "active" when the current route matches the first argument (even partly)
* but, the variable $objectType has been set and matches the second argument.
*
* @return TwigFunction
*/
protected function activeRoutePartialObjectType(): TwigFunction
{
return new TwigFunction(
'activeRoutePartialObjectType',
static function ($context): string {
[, $route, $objectType] = func_get_args();
$activeObjectType = $context['objectType'] ?? false;
if ($objectType === $activeObjectType && false !== stripos(Route::getCurrentRoute()->getName(), $route)) {
return 'active';
}
return '';
},
['needs_context' => true]
);
}
/**
* Will return "menu-open" when a part of the route matches the argument.
* ie. "accounts" will match "accounts.index".
*
* @return TwigFunction
*/
protected function menuOpenRoutePartial(): TwigFunction
{
return new TwigFunction(
'menuOpenRoutePartial',
static function (): string {
$args = func_get_args();
$route = $args[0]; // name of the route.
$name = Route::getCurrentRoute()->getName() ?? '';
if (str_contains($name, $route)) {
return 'menu-open';
}
return '';
}
);
}
/**
* Formats a string as a thing by converting it to a Carbon first.
*
* @return TwigFunction
*/
protected function formatDate(): TwigFunction
{
return new TwigFunction(
'formatDate',
function (string $date, string $format): string {
$carbon = new Carbon($date);
return $carbon->isoFormat($format);
}
);
}
/**
* @return TwigFunction
* TODO remove me when layout v1 is deprecated.
*/
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;
}
);
}
/**
* Will return true if the user is of role X.
*
* @return TwigFunction
*/
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);
}
);
}
/**
* @return TwigFunction
*/
protected function carbonize(): TwigFunction
{
return new TwigFunction(
'carbonize',
static function (string $date): Carbon {
return new Carbon($date);
}
);
}
}
+24 -24
View File
@@ -33,15 +33,25 @@ use Twig\TwigFunction;
class Rule extends AbstractExtension
{
/**
* @return array
* @return TwigFunction
*/
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;
}
);
}
/**
@@ -84,24 +94,14 @@ class Rule extends AbstractExtension
}
/**
* @return TwigFunction
* @return array
*/
public function allActionTriggers(): TwigFunction
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(),
];
}
}
+195 -195
View File
@@ -76,201 +76,6 @@ class TransactionGroupTwig extends AbstractExtension
);
}
/**
* Generate normal amount for transaction from a transaction group.
*
* @param array $array
*
* @return string
*/
private function normalJournalArrayAmount(array $array): string
{
$type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL;
$amount = $array['amount'] ?? '0';
$colored = true;
$sourceType = $array['source_account_type'] ?? 'invalid';
$amount = $this->signAmount($amount, $type, $sourceType);
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
$result = app('amount')->formatFlat($array['currency_symbol'], (int)$array['currency_decimal_places'], $amount, $colored);
if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
}
return $result;
}
/**
* @param string $amount
* @param string $transactionType
* @param string $sourceType
*
* @return string
*/
private function signAmount(string $amount, string $transactionType, string $sourceType): string
{
// withdrawals stay negative
if ($transactionType !== TransactionType::WITHDRAWAL) {
$amount = bcmul($amount, '-1');
}
// opening balance and it comes from initial balance? its expense.
if ($transactionType === TransactionType::OPENING_BALANCE && AccountType::INITIAL_BALANCE !== $sourceType) {
$amount = bcmul($amount, '-1');
}
// reconciliation and it comes from reconciliation?
if ($transactionType === TransactionType::RECONCILIATION && AccountType::RECONCILIATION !== $sourceType) {
$amount = bcmul($amount, '-1');
}
return $amount;
}
/**
* Generate foreign amount for transaction from a transaction group.
*
* @param array $array
*
* @return string
*/
private function foreignJournalArrayAmount(array $array): string
{
$type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL;
$amount = $array['foreign_amount'] ?? '0';
$colored = true;
$sourceType = $array['source_account_type'] ?? 'invalid';
$amount = $this->signAmount($amount, $type, $sourceType);
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
$result = app('amount')->formatFlat($array['foreign_currency_symbol'], (int)$array['foreign_currency_decimal_places'], $amount, $colored);
if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
}
return $result;
}
/**
* Shows the amount for a single journal object.
*
* @return TwigFunction
*/
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.
*
* @param TransactionJournal $journal
*
* @return string
*/
private function normalJournalObjectAmount(TransactionJournal $journal): string
{
$type = $journal->transactionType->type;
$first = $journal->transactions()->where('amount', '<', 0)->first();
$currency = $journal->transactionCurrency;
$amount = $first->amount ?? '0';
$colored = true;
$sourceType = $first->account()->first()->accountType()->first()->type;
$amount = $this->signAmount($amount, $type, $sourceType);
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
$result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored);
if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
}
return $result;
}
/**
* @param TransactionJournal $journal
*
* @return bool
*/
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.
*
* @param TransactionJournal $journal
*
* @return string
*/
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()->first()->accountType()->first()->type;
$amount = $this->signAmount($amount, $type, $sourceType);
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
$result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored);
if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
}
return $result;
}
/**
* @return TwigFunction
*/
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;
}
);
}
/**
* @return TwigFunction
*/
@@ -314,4 +119,199 @@ class TransactionGroupTwig extends AbstractExtension
}
);
}
/**
* @return TwigFunction
*/
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.
*
* @return TwigFunction
*/
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.
*
* @param array $array
*
* @return string
*/
private function foreignJournalArrayAmount(array $array): string
{
$type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL;
$amount = $array['foreign_amount'] ?? '0';
$colored = true;
$sourceType = $array['source_account_type'] ?? 'invalid';
$amount = $this->signAmount($amount, $type, $sourceType);
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
$result = app('amount')->formatFlat($array['foreign_currency_symbol'], (int)$array['foreign_currency_decimal_places'], $amount, $colored);
if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
}
return $result;
}
/**
* Generate foreign amount for journal from a transaction group.
*
* @param TransactionJournal $journal
*
* @return string
*/
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()->first()->accountType()->first()->type;
$amount = $this->signAmount($amount, $type, $sourceType);
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
$result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored);
if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
}
return $result;
}
/**
* @param TransactionJournal $journal
*
* @return bool
*/
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.
*
* @param array $array
*
* @return string
*/
private function normalJournalArrayAmount(array $array): string
{
$type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL;
$amount = $array['amount'] ?? '0';
$colored = true;
$sourceType = $array['source_account_type'] ?? 'invalid';
$amount = $this->signAmount($amount, $type, $sourceType);
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
$result = app('amount')->formatFlat($array['currency_symbol'], (int)$array['currency_decimal_places'], $amount, $colored);
if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
}
return $result;
}
/**
* Generate normal amount for transaction from a transaction group.
*
* @param TransactionJournal $journal
*
* @return string
*/
private function normalJournalObjectAmount(TransactionJournal $journal): string
{
$type = $journal->transactionType->type;
$first = $journal->transactions()->where('amount', '<', 0)->first();
$currency = $journal->transactionCurrency;
$amount = $first->amount ?? '0';
$colored = true;
$sourceType = $first->account()->first()->accountType()->first()->type;
$amount = $this->signAmount($amount, $type, $sourceType);
if ($type === TransactionType::TRANSFER) {
$colored = false;
}
$result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored);
if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
}
return $result;
}
/**
* @param string $amount
* @param string $transactionType
* @param string $sourceType
*
* @return string
*/
private function signAmount(string $amount, string $transactionType, string $sourceType): string
{
// withdrawals stay negative
if ($transactionType !== TransactionType::WITHDRAWAL) {
$amount = bcmul($amount, '-1');
}
// opening balance and it comes from initial balance? its expense.
if ($transactionType === TransactionType::OPENING_BALANCE && AccountType::INITIAL_BALANCE !== $sourceType) {
$amount = bcmul($amount, '-1');
}
// reconciliation and it comes from reconciliation?
if ($transactionType === TransactionType::RECONCILIATION && AccountType::RECONCILIATION !== $sourceType) {
$amount = bcmul($amount, '-1');
}
return $amount;
}
}