Clean up liability overview.

This commit is contained in:
James Cole
2022-12-27 07:01:13 +01:00
parent eff631276e
commit bb11e61034
9 changed files with 139 additions and 88 deletions

View File

@@ -206,7 +206,7 @@ class AccountFactory
if ('' === (string) $databaseData['virtual_balance']) {
$databaseData['virtual_balance'] = null;
}
// remove virtual balance when not an asset account or a liability
// remove virtual balance when not an asset account
if (!in_array($type->type, $this->canHaveVirtual, true)) {
$databaseData['virtual_balance'] = null;
}
@@ -217,14 +217,14 @@ class AccountFactory
$data = $this->cleanMetaDataArray($account, $data);
$this->storeMetaData($account, $data);
// create opening balance
// create opening balance (only asset accounts)
try {
$this->storeOpeningBalance($account, $data);
} catch (FireflyException $e) {
Log::error($e->getMessage());
}
// create credit liability data (if relevant)
// create credit liability data (only liabilities)
try {
$this->storeCreditLiability($account, $data);
} catch (FireflyException $e) {
@@ -352,16 +352,17 @@ class AccountFactory
$accountType = $account->accountType->type;
$direction = $this->accountRepository->getMetaValue($account, 'liability_direction');
$valid = config('firefly.valid_liabilities');
if (in_array($accountType, $valid, true) && 'credit' === $direction) {
Log::debug('Is a liability with credit direction.');
if (in_array($accountType, $valid, true)) {
Log::debug('Is a liability with credit ("i am owed") direction.');
if ($this->validOBData($data)) {
Log::debug('Has valid CL data.');
$openingBalance = $data['opening_balance'];
$openingBalanceDate = $data['opening_balance_date'];
$this->updateCreditTransaction($account, $openingBalance, $openingBalanceDate);
// store credit transaction.
$this->updateCreditTransaction($account, $direction, $openingBalance, $openingBalanceDate);
}
if (!$this->validOBData($data)) {
Log::debug('Has NOT valid CL data.');
Log::debug('Does NOT have valid CL data, deletr any CL transaction.');
$this->deleteCreditTransaction($account);
}
}

View File

@@ -231,7 +231,7 @@ class BoxController extends Controller
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$allAccounts = $accountRepository->getActiveAccountsByType(
[AccountType::DEFAULT, AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]
[AccountType::DEFAULT, AccountType::ASSET]
);
Log::debug(sprintf('Found %d accounts.', $allAccounts->count()));

View File

@@ -364,7 +364,7 @@ class AccountRepository implements AccountRepositoryInterface
{
$journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionType::OPENING_BALANCE])
->transactionTypes([TransactionType::OPENING_BALANCE, TransactionType::LIABILITY_CREDIT])
->first(['transaction_journals.*']);
if (null === $journal) {
return null;
@@ -388,7 +388,7 @@ class AccountRepository implements AccountRepositoryInterface
{
$journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->transactionTypes([TransactionType::OPENING_BALANCE])
->transactionTypes([TransactionType::OPENING_BALANCE, TransactionType::LIABILITY_CREDIT])
->first(['transaction_journals.*']);
if (null === $journal) {
return null;

View File

@@ -51,7 +51,7 @@ trait AccountServiceTrait
protected AccountRepositoryInterface $accountRepository;
/**
* @param null|string $iban
* @param null|string $iban
*
* @return null|string
*/
@@ -76,7 +76,7 @@ trait AccountServiceTrait
/**
* Returns true if the data in the array is submitted but empty.
*
* @param array $data
* @param array $data
*
* @return bool
*/
@@ -104,8 +104,8 @@ trait AccountServiceTrait
*
* TODO this method treats expense accounts and liabilities the same way (tries to save interest)
*
* @param Account $account
* @param array $data
* @param Account $account
* @param array $data
*
*/
public function updateMetaData(Account $account, array $data): void
@@ -155,14 +155,14 @@ trait AccountServiceTrait
$data[$field] = 1;
}
$factory->crud($account, $field, (string) $data[$field]);
$factory->crud($account, $field, (string)$data[$field]);
}
}
}
/**
* @param Account $account
* @param string $note
* @param Account $account
* @param string $note
*
* @codeCoverageIgnore
* @return bool
@@ -195,13 +195,13 @@ trait AccountServiceTrait
/**
* Verify if array contains valid data to possibly store or update the opening balance.
*
* @param array $data
* @param array $data
*
* @return bool
*/
public function validOBData(array $data): bool
{
$data['opening_balance'] = (string) ($data['opening_balance'] ?? '');
$data['opening_balance'] = (string)($data['opening_balance'] ?? '');
if ('' !== $data['opening_balance'] && 0 === bccomp($data['opening_balance'], '0')) {
$data['opening_balance'] = '';
}
@@ -217,8 +217,8 @@ trait AccountServiceTrait
}
/**
* @param Account $account
* @param array $data
* @param Account $account
* @param array $data
*
* @return TransactionGroup
* @throws FireflyException
@@ -311,7 +311,7 @@ trait AccountServiceTrait
/**
* Delete TransactionGroup with liability credit in it.
*
* @param Account $account
* @param Account $account
*/
protected function deleteCreditTransaction(Account $account): void
{
@@ -329,7 +329,7 @@ trait AccountServiceTrait
/**
* Returns the credit transaction group, or NULL if it does not exist.
*
* @param Account $account
* @param Account $account
*
* @return TransactionGroup|null
*/
@@ -343,7 +343,7 @@ trait AccountServiceTrait
/**
* Delete TransactionGroup with opening balance in it.
*
* @param Account $account
* @param Account $account
*/
protected function deleteOBGroup(Account $account): void
{
@@ -362,7 +362,7 @@ trait AccountServiceTrait
/**
* Returns the opening balance group, or NULL if it does not exist.
*
* @param Account $account
* @param Account $account
*
* @return TransactionGroup|null
*/
@@ -372,8 +372,8 @@ trait AccountServiceTrait
}
/**
* @param int $currencyId
* @param string $currencyCode
* @param int $currencyId
* @param string $currencyCode
*
* @return TransactionCurrency
* @throws FireflyException
@@ -400,20 +400,29 @@ trait AccountServiceTrait
/**
* Create the opposing "credit liability" transaction for credit liabilities.
*
* @param Account $account
* @param string $openingBalance
* @param Carbon $openingBalanceDate
* @param Account $account
* @param string $openingBalance
* @param Carbon $openingBalanceDate
*
* @return TransactionGroup
* @throws FireflyException
*/
protected function updateCreditTransaction(Account $account, string $openingBalance, Carbon $openingBalanceDate): TransactionGroup
protected function updateCreditTransaction(Account $account, string $direction, string $openingBalance, Carbon $openingBalanceDate): TransactionGroup
{
Log::debug(sprintf('Now in %s', __METHOD__));
if (0 === bccomp($openingBalance, '0')) {
Log::debug('Amount is zero, so will not update liability credit group.');
throw new FireflyException('Amount for update liability credit was unexpectedly 0.');
Log::debug('Amount is zero, so will not update liability credit/debit group.');
throw new FireflyException('Amount for update liability credit/debit was unexpectedly 0.');
}
// if direction is "debit" (i owe this debt), amount is negative.
// which means the liability will have a negative balance which the user must fill.
$openingBalance = app('steam')->negative($openingBalance);
// if direction is "credit" (I am owed this debt), amount is positive.
// which means the liability will have a positive balance which is drained when its paid back into any asset.
if ('credit' === $direction) {
$openingBalance = app('steam')->positive($openingBalance);
}
// create if not exists:
@@ -451,9 +460,9 @@ trait AccountServiceTrait
}
/**
* @param Account $account
* @param string $openingBalance
* @param Carbon $openingBalanceDate
* @param Account $account
* @param string $openingBalance
* @param Carbon $openingBalanceDate
*
* @return TransactionGroup
* @throws FireflyException
@@ -468,7 +477,24 @@ trait AccountServiceTrait
}
$language = app('preferences')->getForUser($account->user, 'language', 'en_US')->data;
$amount = app('steam')->positive($openingBalance);
// set source and/or destination based on whether the amount is positive or negative.
// first, assume the amount is positive and go from there:
// if amount is positive ("I am owed this debt"), source is special account, destination is the liability.
$sourceId = null;
$sourceName = trans('firefly.liability_credit_description', ['account' => $account->name], $language);
$destId = $account->id;
$destName = null;
if(-1 === bccomp($openingBalance, '0')) {
// amount is negative, reverse it
$sourceId = $account->id;
$sourceName = null;
$destId = null;
$destName = trans('firefly.liability_credit_description', ['account' => $account->name], $language);
}
// amount must be positive for the transaction to work.
$amount = app('steam')->positive($openingBalance);
// get or grab currency:
$currency = $this->accountRepository->getAccountCurrency($account);
@@ -484,10 +510,10 @@ trait AccountServiceTrait
[
'type' => 'Liability credit',
'date' => $openingBalanceDate,
'source_id' => null,
'source_name' => trans('firefly.liability_credit_description', ['account' => $account->name], $language),
'destination_id' => $account->id,
'destination_name' => null,
'source_id' => $sourceId,
'source_name' => $sourceName,
'destination_id' => $destId,
'destination_name' => $destName,
'user' => $account->user_id,
'currency_id' => $currency->id,
'order' => 0,
@@ -526,7 +552,7 @@ trait AccountServiceTrait
/**
* TODO refactor to "getfirstjournal"
*
* @param TransactionGroup $group
* @param TransactionGroup $group
*
* @return TransactionJournal
* @throws FireflyException
@@ -545,8 +571,8 @@ trait AccountServiceTrait
/**
* TODO Rename to getOpposingTransaction
*
* @param TransactionJournal $journal
* @param Account $account
* @param TransactionJournal $journal
* @param Account $account
*
* @return Transaction
* @throws FireflyException
@@ -563,8 +589,8 @@ trait AccountServiceTrait
}
/**
* @param TransactionJournal $journal
* @param Account $account
* @param TransactionJournal $journal
* @param Account $account
*
* @return Transaction
* @throws FireflyException
@@ -584,9 +610,9 @@ trait AccountServiceTrait
* Update or create the opening balance group.
* Since opening balance and date can still be empty strings, it may fail.
*
* @param Account $account
* @param string $openingBalance
* @param Carbon $openingBalanceDate
* @param Account $account
* @param string $openingBalance
* @param Carbon $openingBalanceDate
*
* @return TransactionGroup
* @throws FireflyException
@@ -646,9 +672,9 @@ trait AccountServiceTrait
}
/**
* @param Account $account
* @param string $openingBalance
* @param Carbon $openingBalanceDate
* @param Account $account
* @param string $openingBalance
* @param Carbon $openingBalanceDate
*
* @return TransactionGroup
* @throws FireflyException

View File

@@ -62,9 +62,11 @@ class CreditRecalculateService
return;
}
if (null !== $this->group && null === $this->account) {
Log::debug('Have to handle a group.');
$this->processGroup();
}
if (null !== $this->account && null === $this->group) {
Log::debug('Have to handle an account.');
// work based on account.
$this->processAccount();
}
@@ -213,7 +215,6 @@ class CreditRecalculateService
}
$factory->crud($account, 'current_debt', $leftOfDebt);
Log::debug(sprintf('Done with %s(#%d)', __METHOD__, $account->id));
}
@@ -252,16 +253,16 @@ class CreditRecalculateService
Log::debug(sprintf('Processing group #%d, journal #%d of type "%s"', $journal->id, $groupId, $type));
// it's a withdrawal into this liability (from asset).
// if it's a credit, we don't care, because sending more money
// to a credit-liability doesn't increase the amount (yet)
// if it's a credit ("I am owed"), this increases the amount due,
// because we're lending person X more money
if (
$type === TransactionType::WITHDRAWAL
&& (int)$account->id === (int)$transaction->account_id
&& 1 === bccomp($usedAmount, '0')
&& 'credit' === $direction
) {
Log::debug(sprintf('Is withdrawal into credit liability #%d, does not influence the amount due.', $transaction->account_id));
$amount = bcadd($amount, app('steam')->positive($usedAmount));
Log::debug(sprintf('Is withdrawal (%s) into credit liability #%d, will increase amount due to %s.', $transaction->account_id, $usedAmount, $amount));
return $amount;
}

View File

@@ -324,7 +324,7 @@ class AccountUpdateService
$openingBalance = $data['opening_balance'];
$openingBalanceDate = $data['opening_balance_date'];
if ('credit' === $direction) {
$this->updateCreditTransaction($account, $openingBalance, $openingBalanceDate);
$this->updateCreditTransaction($account, $direction, $openingBalance, $openingBalanceDate);
}
}

View File

@@ -34,58 +34,77 @@ use Log;
trait LiabilityValidation
{
/**
* @param array $array
* @param array $array
*
* @return bool
*/
protected function validateLCDestination(array $array): bool
{
Log::debug('Now in validateLCDestination', $array);
$result = null;
$accountId = array_key_exists('id', $array) ? $array['id'] : null;
$validTypes = config('firefly.valid_liabilities');
$result = null;
$accountId = array_key_exists('id', $array) ? $array['id'] : null;
$accountName = array_key_exists('name', $array) ? $array['name'] : null;
$validTypes = config('firefly.valid_liabilities');
if (null === $accountId) {
$this->sourceError = (string) trans('validation.lc_destination_need_data');
$result = false;
// if the ID is not null the source account should be a dummy account of the type liability credit.
// the ID of the destination must belong to a liability.
if (null !== $accountId) {
if (AccountType::LIABILITY_CREDIT !== $this?->source?->accountType?->type) {
Log::error('Source account is not a liability.');
return false;
}
$result = $this->findExistingAccount($validTypes, $array);
if (null === $result) {
Log::error('Destination account is not a liability.');
return false;
}
return true;
}
Log::debug('Destination ID is not null.');
$search = $this->accountRepository->find($accountId);
// the source resulted in an account, but it's not of a valid type.
if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) {
$message = sprintf('User submitted only an ID (#%d), which is a "%s", so this is not a valid destination.', $accountId, $search->accountType->type);
Log::debug($message);
$this->sourceError = $message;
$result = false;
if (null !== $accountName && '' !== $accountName) {
Log::debug('Destination ID is null, now we can assume the destination is a (new) liability credit account.');
return true;
}
// the source resulted in an account, AND it's of a valid type.
if (null !== $search && in_array($search->accountType->type, $validTypes, true)) {
Log::debug(sprintf('Found account of correct type: #%d, "%s"', $search->id, $search->name));
$this->source = $search;
$result = true;
}
return $result ?? false;
Log::error('Destination ID is null, but destination name is also NULL.');
return false;
}
/**
* Source of an liability credit must be a liability.
* Source of a liability credit must be a liability or liability credit account.
*
* @param array $array
* @param array $array
*
* @return bool
*/
protected function validateLCSource(array $array): bool
{
Log::debug('Now in validateLCSource', $array);
// if the array has an ID and ID is not null, try to find it and check type.
// this account must be a liability
$accountId = array_key_exists('id', $array) ? $array['id'] : null;
if (null !== $accountId) {
Log::debug('Source ID is not null, assume were looking for a liability.');
// find liability credit:
$result = $this->findExistingAccount(config('firefly.valid_liabilities'), $array);
if (null === $result) {
Log::error('Did not find a liability account, return false.');
return false;
}
Log::debug(sprintf('Return true, found #%d ("%s")', $result->id, $result->name));
$this->source = $result;
return true;
}
// if array has name and is not null, return true.
$accountName = array_key_exists('name', $array) ? $array['name'] : null;
$result = true;
Log::debug('Now in validateLCDestination', $array);
$result = true;
if ('' === $accountName || null === $accountName) {
Log::error('Array must have a name, is not the case, return false.');
$result = false;
}
if (true === $result) {
Log::error('Array has a name, return true.');
// set the source to be a (dummy) revenue account.
$account = new Account();
$accountType = AccountType::whereType(AccountType::LIABILITY_CREDIT)->first();

View File

@@ -749,7 +749,7 @@ return [
'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3),
],
'can_have_virtual_amounts' => [AccountType::ASSET],
'can_have_opening_balance' => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD],
'can_have_opening_balance' => [AccountType::ASSET],
'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],

View File

@@ -15,7 +15,9 @@
<th>{{ trans('list.interest') }} ({{ trans('list.interest_period') }})</th>
{% endif %}
<th class="hidden-sm hidden-xs">{{ trans('form.account_number') }}</th>
<th style="text-align: right;">{{ trans('list.currentBalance') }}</th>
{% if objectType != 'liabilities' %}
<th style="text-align: right;">{{ trans('list.currentBalance') }}</th>
{% endif %}
{% if objectType == 'liabilities' %}
<th style="text-align: right;">
{{ trans('firefly.left_in_debt') }}
@@ -61,11 +63,13 @@
<td>{{ account.interest }}% ({{ account.interestPeriod|lower }})</td>
{% endif %}
<td class="hidden-sm hidden-xs">{{ account.iban }}{% if account.iban == '' %}{{ accountGetMetaField(account, 'account_number') }}{% endif %}</td>
{% if objectType != 'liabilities' %}
<td style="text-align: right;">
<span style="margin-right:5px;">
{{ formatAmountByAccount(account, account.endBalance) }}
</span>
</td>
{% endif %}
{% if objectType == 'liabilities' %}
<td style="text-align: right;">
{% if '-' != account.current_debt %}