diff --git a/app/Api/V2/Controllers/Summary/BasicController.php b/app/Api/V2/Controllers/Summary/BasicController.php index 9ff50f65a7..9a3dec5fb7 100644 --- a/app/Api/V2/Controllers/Summary/BasicController.php +++ b/app/Api/V2/Controllers/Summary/BasicController.php @@ -35,6 +35,7 @@ use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; +use FireflyIII\Models\UserGroup; use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Administration\Budget\AvailableBudgetRepositoryInterface; @@ -112,17 +113,8 @@ class BasicController extends Controller $balanceData = $this->getBalanceInformation($start, $end); $billData = $this->getBillInformation($start, $end); $spentData = $this->getLeftToSpendInfo($start, $end); - // $netWorthData = $this->getNetWorthInfo($start, $end); - $total = array_merge($balanceData, $billData, $spentData, $netWorthData); - - // give new keys - // $return = []; - // foreach ($total as $entry) { - // if (null === $code || ($code === $entry['currency_code'])) { - // $return[$entry['key']] = $entry; - // } - // } - + $netWorthData = $this->getNetWorthInfo($start, $end); + $total = array_merge($balanceData, $billData, $spentData, $netWorthData); return response()->json($total); } @@ -373,7 +365,7 @@ class BasicController extends Controller // native info: $nativeLeft = [ 'key' => 'left-to-spend-in-native', - 'monetary_value' => '0', + 'value' => '0', 'currency_id' => (int)$default->id, 'currency_code' => $default->code, 'currency_symbol' => $default->symbol, @@ -381,7 +373,7 @@ class BasicController extends Controller ]; $nativePerDay = [ 'key' => 'left-per-day-to-spend-in-native', - 'monetary_value' => '0', + 'value' => '0', 'currency_id' => (int)$default->id, 'currency_code' => $default->code, 'currency_symbol' => $default->symbol, @@ -400,8 +392,7 @@ class BasicController extends Controller foreach ($row['budgets'] as $budget) { /** @var array $journal */ foreach ($budget['transaction_journals'] as $journal) { - $journalCurrencyId = $journal['currency_id']; - + $journalCurrencyId = $journal['currency_id']; $currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId); $currencies[$currencyId] = $currency; $amount = bcmul($journal['amount'], '-1'); @@ -436,19 +427,19 @@ class BasicController extends Controller // left $return[] = [ 'key' => sprintf('left-to-spend-in-%s', $row['currency_code']), - 'monetary_value' => $left, + 'value' => $left, 'currency_id' => $row['currency_id'], 'currency_code' => $row['currency_code'], 'currency_symbol' => $row['currency_symbol'], 'currency_decimal_places' => $row['currency_decimal_places'], ]; // left (native) - $nativeLeft['monetary_value'] = $leftNative; + $nativeLeft['value'] = $leftNative; // left per day: $return[] = [ 'key' => sprintf('left-per-day-to-spend-in-%s', $row['currency_code']), - 'monetary_value' => $perDay, + 'value' => $perDay, 'currency_id' => $row['currency_id'], 'currency_code' => $row['currency_code'], 'currency_symbol' => $row['currency_symbol'], @@ -456,7 +447,7 @@ class BasicController extends Controller ]; // left per day (native) - $nativePerDay['monetary_value'] = $perDayNative; + $nativePerDay['value'] = $perDayNative; } $return[] = $nativeLeft; $return[] = $nativePerDay; @@ -472,9 +463,9 @@ class BasicController extends Controller */ private function getNetWorthInfo(Carbon $start, Carbon $end): array { - /** @var User $user */ - $user = auth()->user(); - $date = today(config('app.timezone'))->startOfDay(); + /** @var UserGroup $userGroup */ + $userGroup = auth()->user()->userGroup; + $date = today(config('app.timezone'))->startOfDay(); // start and end in the future? use $end if ($this->notInDateRange($date, $start, $end)) { /** @var Carbon $date */ @@ -483,7 +474,7 @@ class BasicController extends Controller /** @var NetWorthInterface $netWorthHelper */ $netWorthHelper = app(NetWorthInterface::class); - $netWorthHelper->setUser($user); + $netWorthHelper->setUserGroup($userGroup); $allAccounts = $this->accountRepository->getActiveAccountsByType( [AccountType::ASSET, AccountType::DEFAULT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::DEBT] ); @@ -497,27 +488,28 @@ class BasicController extends Controller } ); - $netWorthSet = $netWorthHelper->getNetWorthByCurrency($filtered, $date); + $netWorthSet = $netWorthHelper->byAccounts($filtered, $date); $return = []; - foreach ($netWorthSet as $data) { - /** @var TransactionCurrency $currency */ - $currency = $data['currency']; - $amount = $data['balance']; - if (0 === bccomp($amount, '0')) { + // in native amount + $return[] = [ + 'key' => 'net-worth-in-native', + 'value' => $netWorthSet['native']['balance'], + 'currency_id' => $netWorthSet['native']['currency_id'], + 'currency_code' => $netWorthSet['native']['currency_code'], + 'currency_symbol' => $netWorthSet['native']['currency_symbol'], + 'currency_decimal_places' => $netWorthSet['native']['currency_decimal_places'], + ]; + foreach ($netWorthSet as $key => $data) { + if ('native' === $key) { continue; } - // return stuff $return[] = [ - 'key' => sprintf('net-worth-in-%s', $currency->code), - 'title' => trans('firefly.box_net_worth_in_currency', ['currency' => $currency->symbol]), - 'monetary_value' => $amount, - 'currency_id' => $currency->id, - 'currency_code' => $currency->code, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => $currency->decimal_places, - 'value_parsed' => app('amount')->formatAnything($currency, $data['balance'], false), - 'local_icon' => 'line-chart', - 'sub_title' => '', + 'key' => sprintf('net-worth-in-%s', $data['currency_code']), + 'value' => $data['balance'], + 'currency_id' => $data['currency_id'], + 'currency_code' => $data['currency_code'], + 'currency_symbol' => $data['currency_symbol'], + 'currency_decimal_places' => $data['currency_decimal_places'], ]; } diff --git a/app/Helpers/Report/NetWorth.php b/app/Helpers/Report/NetWorth.php index 149e62f882..e98ae422b5 100644 --- a/app/Helpers/Report/NetWorth.php +++ b/app/Helpers/Report/NetWorth.php @@ -27,9 +27,12 @@ use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; +use FireflyIII\Models\UserGroup; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\CacheProperties; +use FireflyIII\Support\Http\Api\ExchangeRateConverter; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Collection; @@ -41,10 +44,98 @@ use JsonException; */ class NetWorth implements NetWorthInterface { - private AccountRepositoryInterface $accountRepository; + private AccountRepositoryInterface $accountRepository; + private AdminAccountRepositoryInterface $adminAccountRepository; private CurrencyRepositoryInterface $currencyRepos; private User $user; + private UserGroup $userGroup; + + /** + * @param Collection $accounts + * @param Carbon $date + * + * @return array + * @throws FireflyException + */ + public function byAccounts(Collection $accounts, Carbon $date): array + { + // start in the past, end in the future? use $date + $ids = implode(',', $accounts->pluck('id')->toArray()); + $cache = new CacheProperties(); + $cache->addProperty($date); + $cache->addProperty('net-worth-by-accounts'); + $cache->addProperty($ids); + if ($cache->has()) { + //return $cache->get(); + } + app('log')->debug(sprintf('Now in byAccounts("%s", "%s")', $ids, $date->format('Y-m-d'))); + + $default = app('amount')->getDefaultCurrency(); + $converter = new ExchangeRateConverter(); + + // default "native" currency has everything twice, for consistency. + $netWorth = [ + 'native' => [ + 'balance' => '0', + 'native_balance' => '0', + 'currency_id' => (int)$default->id, + 'currency_code' => $default->code, + 'currency_name' => $default->name, + 'currency_symbol' => $default->symbol, + 'currency_decimal_places' => (int)$default->decimal_places, + 'native_id' => (int)$default->id, + 'native_code' => $default->code, + 'native_name' => $default->name, + 'native_symbol' => $default->symbol, + 'native_decimal_places' => (int)$default->decimal_places, + ], + ]; + $balances = app('steam')->balancesByAccountsConverted($accounts, $date); + + /** @var Account $account */ + foreach ($accounts as $account) { + app('log')->debug(sprintf('Now at account #%d ("%s")', $account->id, $account->name)); + $currency = $this->adminAccountRepository->getAccountCurrency($account); + $currencyId = (int)$currency->id; + $balance = '0'; + $nativeBalance = '0'; + if (array_key_exists((int)$account->id, $balances)) { + $balance = $balances[(int)$account->id]['balance'] ?? '0'; + $nativeBalance = $balances[(int)$account->id]['native_balance'] ?? '0'; + } + app('log')->debug(sprintf('Balance is %s, native balance is %s', $balance, $nativeBalance)); + // always subtract virtual balance + $virtualBalance = (string)$account->virtual_balance; + if ('' !== $virtualBalance) { + $balance = bcsub($balance, $virtualBalance); + $nativeVirtualBalance = $converter->convert($default, $currency, $account->created_at, $virtualBalance); + $nativeBalance = bcsub($nativeBalance, $nativeVirtualBalance); + } + $netWorth[$currencyId] = $netWorth[$currencyId] ?? [ + 'balance' => '0', + 'native_balance' => '0', + 'currency_id' => $currencyId, + 'currency_code' => $currency->code, + 'currency_name' => $currency->name, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => (int)$currency->decimal_places, + 'native_id' => (int)$default->id, + 'native_code' => $default->code, + 'native_name' => $default->name, + 'native_symbol' => $default->symbol, + 'native_decimal_places' => (int)$default->decimal_places, + ]; + + $netWorth[$currencyId]['balance'] = bcadd($balance, $netWorth[$currencyId]['balance']); + $netWorth[$currencyId]['native_balance'] = bcadd($nativeBalance, $netWorth[$currencyId]['native_balance']); + $netWorth['native']['balance'] = bcadd($nativeBalance, $netWorth['native']['balance']); + $netWorth['native']['native_balance'] = bcadd($nativeBalance, $netWorth['native']['native_balance']); + } + $cache->store($netWorth); + + return $netWorth; + } /** * Returns the user's net worth in an array with the following layout: @@ -146,6 +237,17 @@ class NetWorth implements NetWorthInterface $this->currencyRepos->setUser($this->user); } + /** + * @inheritDoc + * @throws FireflyException + */ + public function setUserGroup(UserGroup $userGroup): void + { + $this->userGroup = $userGroup; + $this->adminAccountRepository = app(AdminAccountRepositoryInterface::class); + $this->adminAccountRepository->setAdministrationId($userGroup->id); + } + /** * @inheritDoc */ diff --git a/app/Helpers/Report/NetWorthInterface.php b/app/Helpers/Report/NetWorthInterface.php index 5145b4d426..61f4c9455c 100644 --- a/app/Helpers/Report/NetWorthInterface.php +++ b/app/Helpers/Report/NetWorthInterface.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Helpers\Report; use Carbon\Carbon; +use FireflyIII\Models\UserGroup; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Collection; @@ -34,6 +35,21 @@ use Illuminate\Support\Collection; */ interface NetWorthInterface { + /** + * Collect net worth based on the given set of accounts. + * + * Returns X arrays with the net worth in each given currency, and the net worth in + * of that amount in the native currency. + * + * Includes extra array with the total(!) net worth in the native currency. + * + * @param Collection $accounts + * @param Carbon $date + * + * @return array + */ + public function byAccounts(Collection $accounts, Carbon $date): array; + /** * TODO unsure why this is deprecated. * @@ -60,6 +76,11 @@ interface NetWorthInterface */ public function setUser(User | Authenticatable | null $user): void; + /** + * @param UserGroup $userGroup + */ + public function setUserGroup(UserGroup $userGroup): void; + /** * TODO move to repository * diff --git a/app/Repositories/Administration/Account/AccountRepository.php b/app/Repositories/Administration/Account/AccountRepository.php index cc0c58281d..220747f663 100644 --- a/app/Repositories/Administration/Account/AccountRepository.php +++ b/app/Repositories/Administration/Account/AccountRepository.php @@ -30,6 +30,7 @@ use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Support\Repositories\Administration\AdministrationTrait; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; /** @@ -147,6 +148,25 @@ class AccountRepository implements AccountRepositoryInterface return $query->get(['accounts.*']); } + /** + * @param array $types + * + * @return Collection + */ + public function getActiveAccountsByType(array $types): Collection + { + $query = $this->userGroup->accounts(); + if (0 !== count($types)) { + $query->accountTypeIn($types); + } + $query->where('active', true); + $query->orderBy('accounts.account_type_id', 'ASC'); + $query->orderBy('accounts.order', 'ASC'); + $query->orderBy('accounts.name', 'ASC'); + + return $query->get(['accounts.*']); + } + /** * @inheritDoc */ diff --git a/app/Repositories/Administration/Account/AccountRepositoryInterface.php b/app/Repositories/Administration/Account/AccountRepositoryInterface.php index a209c05355..678f6f4595 100644 --- a/app/Repositories/Administration/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Administration/Account/AccountRepositoryInterface.php @@ -63,6 +63,13 @@ interface AccountRepositoryInterface */ public function getAccountsByType(array $types, ?array $sort = []): Collection; + /** + * @param array $types + * + * @return Collection + */ + public function getActiveAccountsByType(array $types): Collection; + /** * Return meta value for account. Null if not found. * diff --git a/app/Support/Steam.php b/app/Support/Steam.php index b499d5c44e..e824840c24 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -360,9 +360,9 @@ class Steam $cache->addProperty($account->id); $cache->addProperty('balance'); $cache->addProperty($date); - $cache->addProperty($native ? $native->id : 0); + $cache->addProperty($native->id); if ($cache->has()) { - return $cache->get(); + // return $cache->get(); } /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); @@ -370,7 +370,9 @@ class Steam if (null === $currency) { throw new FireflyException('Cannot get converted account balance: no currency found for account.'); } - + if ((int)$native->id === (int)$currency->id) { + return $this->balance($account, $date); + } /** * selection of transactions * 1: all normal transactions. No foreign currency info. In $currency. Need conversion. @@ -392,7 +394,7 @@ class Steam ->where('transactions.transaction_currency_id', $currency->id) ->whereNull('transactions.foreign_currency_id') ->get(['transaction_journals.date', 'transactions.amount'])->toArray(); - app('log')->debug(sprintf('%d transactions in set #1', count($new[0]))); + app('log')->debug(sprintf('%d transaction(s) in set #1', count($new[0]))); // 2 $existing[] = $account->transactions() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') @@ -400,7 +402,7 @@ class Steam ->where('transactions.transaction_currency_id', $native->id) ->whereNull('transactions.foreign_currency_id') ->get(['transactions.amount'])->toArray(); - app('log')->debug(sprintf('%d transactions in set #2', count($existing[0]))); + app('log')->debug(sprintf('%d transaction(s) in set #2', count($existing[0]))); // 3 $new[] = $account->transactions() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') @@ -464,8 +466,9 @@ class Steam //app('log')->debug(sprintf('Balance from new set #%d is %f', $index, $balance)); } - // add virtual balance + // add virtual balance (also needs conversion) $virtual = null === $account->virtual_balance ? '0' : (string)$account->virtual_balance; + $virtual = $converter->convert($currency, $native, $account->created_at, $virtual); $balance = bcadd($balance, $virtual); $cache->store($balance); @@ -506,6 +509,45 @@ class Steam return $result; } + /** + * This method always ignores the virtual balance. + * + * @param Collection $accounts + * @param Carbon $date + * + * @return array + * @throws FireflyException + */ + public function balancesByAccountsConverted(Collection $accounts, Carbon $date): array + { + $ids = $accounts->pluck('id')->toArray(); + // cache this property. + $cache = new CacheProperties(); + $cache->addProperty($ids); + $cache->addProperty('balances-converted'); + $cache->addProperty($date); + if ($cache->has()) { + // return $cache->get(); + } + + + // need to do this per account. + $result = []; + /** @var Account $account */ + foreach ($accounts as $account) { + $default = app('amount')->getDefaultCurrencyByUser($account->user); + $result[(int)$account->id] + = [ + 'balance' => $this->balance($account, $date), + 'native_balance' => $this->balanceConverted($account, $date, $default), + ]; + } + + $cache->store($result); + + return $result; + } + /** * Same as above, but also groups per currency. * diff --git a/config/cors.php b/config/cors.php new file mode 100644 index 0000000000..47942ff920 --- /dev/null +++ b/config/cors.php @@ -0,0 +1,36 @@ + ['api/*', 'sanctum/csrf-cookie'], + + 'allowed_methods' => ['*'], + + 'allowed_origins' => ['*'], + + 'allowed_origins_patterns' => ['*'], + + 'allowed_headers' => ['*'], + + 'exposed_headers' => [], + + 'max_age' => 0, + + 'supports_credentials' => false, + +]; diff --git a/config/hashing.php b/config/hashing.php index c3615a8a3a..fddaa42238 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -1,28 +1,9 @@ . - */ - declare(strict_types=1); return [ + /* |-------------------------------------------------------------------------- | Default Hash Driver