From 8dbc84631467c7095472ad23f0445ad8c5f1c881 Mon Sep 17 00:00:00 2001
From: James Cole
Date: Sat, 4 Aug 2018 17:30:47 +0200
Subject: [PATCH] Basic code for tracking liabilities.
---
app/Factory/AccountFactory.php | 17 ++++++----
.../Controllers/Account/CreateController.php | 23 ++++++++++++-
.../Controllers/Account/DeleteController.php | 3 +-
.../Account/AccountRepository.php | 13 +++++++
.../Account/AccountRepositoryInterface.php | 15 ++++++--
.../Internal/Support/AccountServiceTrait.php | 2 +-
app/Support/ExpandedForm.php | 29 ++++++++++++++++
app/Support/Twig/Translation.php | 2 ++
app/Validation/FireflyValidator.php | 34 ++++++++++---------
app/Validation/RecurrenceValidation.php | 2 ++
config/firefly.php | 29 ++++++++++------
config/twigbridge.php | 2 +-
resources/lang/en_US/firefly.php | 5 ++-
resources/lang/en_US/form.php | 1 +
resources/views/accounts/create.twig | 13 +++++--
resources/views/accounts/delete.twig | 2 +-
resources/views/form/percentage.twig | 12 +++++++
17 files changed, 161 insertions(+), 43 deletions(-)
create mode 100644 resources/views/form/percentage.twig
diff --git a/app/Factory/AccountFactory.php b/app/Factory/AccountFactory.php
index aeb9b62b3b..8c06e1e63c 100644
--- a/app/Factory/AccountFactory.php
+++ b/app/Factory/AccountFactory.php
@@ -32,6 +32,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Services\Internal\Support\AccountServiceTrait;
use FireflyIII\User;
+use Log;
/**
* Factory to create or return accounts.
@@ -80,8 +81,9 @@ class AccountFactory
'iban' => $data['iban'],
];
- // remove virtual balance when not an asset account:
- if ($type->type !== AccountType::ASSET) {
+ // remove virtual balance when not an asset account or a liability
+ $canHaveVirtual = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD];
+ if (!\in_array($type->type, $canHaveVirtual, true)) {
$databaseData['virtual_balance'] = '0';
}
@@ -93,7 +95,7 @@ class AccountFactory
$return = Account::create($databaseData);
$this->updateMetaData($return, $data);
- if ($type->type === AccountType::ASSET) {
+ if (\in_array($type->type, $canHaveVirtual, true)) {
if ($this->validIBData($data)) {
$this->updateIB($return, $data);
}
@@ -188,9 +190,12 @@ class AccountFactory
$result = AccountType::find($accountTypeId);
}
if (null === $result) {
- /** @var string $type */
- $type = (string)config('firefly.accountTypeByIdentifier.' . $accountType);
- $result = AccountType::whereType($type)->first();
+ Log::debug(sprintf('No account type found by ID, continue search for "%s".', $accountType));
+ /** @var array $types */
+ $types = config('firefly.accountTypeByIdentifier.' . $accountType) ?? [];
+ if (\count($types) > 0) {
+ $result = AccountType::whereIn('types', $types)->first();
+ }
if (null === $result && null !== $accountType) {
// try as full name:
$result = AccountType::whereType($accountType)->first();
diff --git a/app/Http/Controllers/Account/CreateController.php b/app/Http/Controllers/Account/CreateController.php
index 10da6e9c81..f78ac9e166 100644
--- a/app/Http/Controllers/Account/CreateController.php
+++ b/app/Http/Controllers/Account/CreateController.php
@@ -78,6 +78,26 @@ class CreateController extends Controller
$roles[$role] = (string)trans('firefly.account_role_' . $role);
}
+ // types of liability:
+ $debt = $this->repository->getAccountTypeByType(AccountType::DEBT);
+ $loan = $this->repository->getAccountTypeByType(AccountType::LOAN);
+ $mortgage = $this->repository->getAccountTypeByType(AccountType::MORTGAGE);
+ $creditCard = $this->repository->getAccountTypeByType(AccountType::CREDITCARD);
+ $liabilityTypes = [
+ $debt->id => (string)trans('firefly.account_type_' . AccountType::DEBT),
+ $loan->id => (string)trans('firefly.account_type_' . AccountType::LOAN),
+ $mortgage->id => (string)trans('firefly.account_type_' . AccountType::MORTGAGE),
+ $creditCard->id => (string)trans('firefly.account_type_' . AccountType::CREDITCARD),
+ ];
+ asort($liabilityTypes);
+
+ // interest calculation periods:
+ $interestPeriods = [
+ 'daily' => (string)trans('firefly.interest_calc_daily'),
+ 'monthly' => (string)trans('firefly.interest_calc_monthly'),
+ 'yearly' => (string)trans('firefly.interest_calc_yearly'),
+ ];
+
// pre fill some data
$request->session()->flash('preFilled', ['currency_id' => $defaultCurrency->id]);
@@ -87,7 +107,7 @@ class CreateController extends Controller
}
$request->session()->forget('accounts.create.fromStore');
- return view('accounts.create', compact('subTitleIcon', 'what', 'subTitle', 'roles'));
+ return view('accounts.create', compact('subTitleIcon', 'what','interestPeriods', 'subTitle', 'roles', 'liabilityTypes'));
}
@@ -100,6 +120,7 @@ class CreateController extends Controller
*/
public function store(AccountFormRequest $request)
{
+
$data = $request->getAccountData();
$account = $this->repository->store($data);
$request->session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name]));
diff --git a/app/Http/Controllers/Account/DeleteController.php b/app/Http/Controllers/Account/DeleteController.php
index 700a0660c7..3ec08fe917 100644
--- a/app/Http/Controllers/Account/DeleteController.php
+++ b/app/Http/Controllers/Account/DeleteController.php
@@ -69,12 +69,13 @@ class DeleteController extends Controller
$typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$subTitle = (string)trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]);
$accountList = app('expandedform')->makeSelectListWithEmpty($this->repository->getAccountsByType([$account->accountType->type]));
+ $what = $typeName;
unset($accountList[$account->id]);
// put previous url in session
$this->rememberPreviousUri('accounts.delete.uri');
- return view('accounts.delete', compact('account', 'subTitle', 'accountList'));
+ return view('accounts.delete', compact('account', 'subTitle', 'accountList', 'what'));
}
/**
diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php
index e865b10dbc..29616566f1 100644
--- a/app/Repositories/Account/AccountRepository.php
+++ b/app/Repositories/Account/AccountRepository.php
@@ -168,6 +168,18 @@ class AccountRepository implements AccountRepositoryInterface
return $this->user->accounts()->find($accountId);
}
+ /**
+ * Return account type or null if not found.
+ *
+ * @param string $type
+ *
+ * @return AccountType|null
+ */
+ public function getAccountTypeByType(string $type): ?AccountType
+ {
+ return AccountType::whereType($type)->first();
+ }
+
/**
* @param array $accountIds
*
@@ -373,6 +385,7 @@ class AccountRepository implements AccountRepositoryInterface
* Returns the date of the very first transaction in this account.
*
* @param Account $account
+ *
* @return TransactionJournal|null
*/
public function oldestJournal(Account $account): ?TransactionJournal
diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php
index 09a0981209..8f88884e1a 100644
--- a/app/Repositories/Account/AccountRepositoryInterface.php
+++ b/app/Repositories/Account/AccountRepositoryInterface.php
@@ -24,17 +24,18 @@ namespace FireflyIII\Repositories\Account;
use Carbon\Carbon;
use FireflyIII\Models\Account;
-
-
+use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\User;
use Illuminate\Support\Collection;
+
/**
* Interface AccountRepositoryInterface.
*/
interface AccountRepositoryInterface
{
+
/**
* Moved here from account CRUD.
*
@@ -87,6 +88,15 @@ interface AccountRepositoryInterface
*/
public function findNull(int $accountId): ?Account;
+ /**
+ * Return account type or null if not found.
+ *
+ * @param string $type
+ *
+ * @return AccountType|null
+ */
+ public function getAccountTypeByType(string $type): ?AccountType;
+
/**
* @param array $accountIds
*
@@ -164,6 +174,7 @@ interface AccountRepositoryInterface
* Returns the date of the very first transaction in this account.
*
* @param Account $account
+ *
* @return TransactionJournal|null
*/
public function oldestJournal(Account $account): ?TransactionJournal;
diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php
index a5bf55b646..6877ba13bb 100644
--- a/app/Services/Internal/Support/AccountServiceTrait.php
+++ b/app/Services/Internal/Support/AccountServiceTrait.php
@@ -49,7 +49,7 @@ trait AccountServiceTrait
/** @var array */
public $validCCFields = ['accountRole', 'ccMonthlyPaymentDate', 'ccType', 'accountNumber', 'currency_id', 'BIC'];
/** @var array */
- public $validFields = ['accountNumber', 'currency_id', 'BIC'];
+ public $validFields = ['accountNumber', 'currency_id', 'BIC','interest','interest_period'];
/**
* @param Account $account
diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php
index 0370df5f54..51609ca6c1 100644
--- a/app/Support/ExpandedForm.php
+++ b/app/Support/ExpandedForm.php
@@ -528,6 +528,34 @@ class ExpandedForm
return $html;
}
+ /**
+ * Function to render a percentage.
+ *
+ * @param string $name
+ * @param mixed $value
+ * @param array $options
+ *
+ * @return string
+ *
+ */
+ public function percentage(string $name, $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';
+ unset($options['placeholder']);
+ try {
+ $html = view('form.percentage', compact('classes', 'name', 'label', 'value', 'options'))->render();
+ } catch (Throwable $e) {
+ Log::debug(sprintf('Could not render percentage(): %s', $e->getMessage()));
+ $html = 'Could not render percentage.';
+ }
+
+ return $html;
+ }
+
/**
* @param string $type
* @param string $name
@@ -772,6 +800,7 @@ class ExpandedForm
return $html;
}
+ /** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param string $name
* @param string $view
diff --git a/app/Support/Twig/Translation.php b/app/Support/Twig/Translation.php
index 74198da564..e00093a7c9 100644
--- a/app/Support/Twig/Translation.php
+++ b/app/Support/Twig/Translation.php
@@ -69,7 +69,9 @@ class Translation extends Twig_Extension
'journalLinkTranslation',
function (string $direction, string $original) {
$key = sprintf('firefly.%s_%s', $original, $direction);
+ return $key;
$translation = trans($key);
+
if ($key === $translation) {
return $original;
}
diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php
index 1034b1ef63..c44ac55640 100644
--- a/app/Validation/FireflyValidator.php
+++ b/app/Validation/FireflyValidator.php
@@ -38,7 +38,9 @@ use FireflyIII\TransactionRules\Triggers\TriggerInterface;
use FireflyIII\User;
use Google2FA;
use Illuminate\Contracts\Encryption\DecryptException;
+use Illuminate\Support\Collection;
use Illuminate\Validation\Validator;
+use Log;
/**
* Class FireflyValidator.
@@ -195,12 +197,12 @@ class FireflyValidator extends Validator
*
* @return bool
*/
- public function validateMore($attribute, $value, $parameters): bool
+ public function validateLess($attribute, $value, $parameters): bool
{
/** @var mixed $compare */
$compare = $parameters[0] ?? '0';
- return bccomp((string)$value, (string)$compare) > 0;
+ return bccomp((string)$value, (string)$compare) < 0;
}
/**
@@ -211,15 +213,14 @@ class FireflyValidator extends Validator
*
* @return bool
*/
- public function validateLess($attribute, $value, $parameters): bool
+ public function validateMore($attribute, $value, $parameters): bool
{
/** @var mixed $compare */
$compare = $parameters[0] ?? '0';
- return bccomp((string)$value, (string)$compare) < 0;
+ return bccomp((string)$value, (string)$compare) > 0;
}
-
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
@@ -291,7 +292,7 @@ class FireflyValidator extends Validator
case 'link_to_bill':
/** @var BillRepositoryInterface $repository */
$repository = app(BillRepositoryInterface::class);
- $bill = $repository->findByName((string)$value);
+ $bill = $repository->findByName($value);
return null !== $bill;
case 'invalid':
@@ -468,7 +469,7 @@ class FireflyValidator extends Validator
if ((int)$accountId > 0) {
// exclude current account from check.
- $query->where('account_meta.account_id', '!=', (int)$accountId);
+ $query->where('account_meta.account_id', '!=', $accountId);
}
$set = $query->get(['account_meta.*']);
@@ -499,9 +500,7 @@ class FireflyValidator extends Validator
public function validateUniqueObjectForUser($attribute, $value, $parameters): bool
{
$value = $this->tryDecrypt($value);
- // exclude?
- $table = $parameters[0];
- $field = $parameters[1];
+ [$table, $field] = $parameters;
$exclude = (int)($parameters[2] ?? 0.0);
/*
@@ -630,7 +629,7 @@ class FireflyValidator extends Validator
try {
$value = Crypt::decrypt($value);
} catch (DecryptException $e) {
- // do not care.
+ Log::debug(sprintf('Could not decrypt. %s', $e->getMessage()));
}
return $value;
@@ -717,11 +716,14 @@ class FireflyValidator extends Validator
*/
private function validateByAccountTypeString(string $value, array $parameters, string $type): bool
{
- $search = Config::get('firefly.accountTypeByIdentifier.' . $type);
- $accountType = AccountType::whereType($search)->first();
- $ignore = (int)($parameters[0] ?? 0.0);
-
- $set = auth()->user()->accounts()->where('account_type_id', $accountType->id)->where('id', '!=', $ignore)->get();
+ /** @var array $search */
+ $search = Config::get('firefly.accountTypeByIdentifier.' . $type);
+ /** @var Collection $accountTypes */
+ $accountTypes = AccountType::whereIn('type', $search)->get();
+ $ignore = (int)($parameters[0] ?? 0.0);
+ $accountTypeIds = $accountTypes->pluck('id')->toArray();
+ /** @var Collection $set */
+ $set = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore)->get();
/** @var Account $entry */
foreach ($set as $entry) {
if ($entry->name === $value) {
diff --git a/app/Validation/RecurrenceValidation.php b/app/Validation/RecurrenceValidation.php
index 98ad0d0342..ad6614a0db 100644
--- a/app/Validation/RecurrenceValidation.php
+++ b/app/Validation/RecurrenceValidation.php
@@ -27,6 +27,7 @@ use Carbon\Carbon;
use Exception;
use Illuminate\Validation\Validator;
use InvalidArgumentException;
+use Log;
/**
* Trait RecurrenceValidation
@@ -187,6 +188,7 @@ trait RecurrenceValidation
try {
Carbon::createFromFormat('Y-m-d', $moment);
} catch (InvalidArgumentException|Exception $e) {
+ Log::debug(sprintf('Invalid argument for Carbon: %s', $e->getMessage()));
$validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment'));
}
}
diff --git a/config/firefly.php b/config/firefly.php
index c996c32c76..69f9fd000c 100644
--- a/config/firefly.php
+++ b/config/firefly.php
@@ -176,10 +176,11 @@ return [
],
'subTitlesByIdentifier' =>
[
- 'asset' => 'Asset accounts',
- 'expense' => 'Expense accounts',
- 'revenue' => 'Revenue accounts',
- 'cash' => 'Cash accounts',
+ 'asset' => 'Asset accounts',
+ 'expense' => 'Expense accounts',
+ 'revenue' => 'Revenue accounts',
+ 'cash' => 'Cash accounts',
+ 'liabilities' => 'Liabilities',
],
'subIconsByIdentifier' =>
[
@@ -194,6 +195,7 @@ return [
'Revenue account' => 'fa-download',
'import' => 'fa-download',
'Import account' => 'fa-download',
+ 'liabilities' => 'fa-ticket',
],
'accountTypesByIdentifier' =>
[
@@ -205,13 +207,14 @@ return [
],
'accountTypeByIdentifier' =>
[
- 'asset' => 'Asset account',
- 'expense' => 'Expense account',
- 'revenue' => 'Revenue account',
- 'opening' => 'Initial balance account',
- 'initial' => 'Initial balance account',
- 'import' => 'Import account',
- 'reconcile' => 'Reconciliation account',
+ 'asset' => ['Asset account'],
+ 'expense' => ['Expense account'],
+ 'revenue' => ['Revenue account'],
+ 'opening' => ['Initial balance account'],
+ 'initial' => ['Initial balance account'],
+ 'import' => ['Import account'],
+ 'reconcile' => ['Reconciliation account'],
+ 'liabilities' => ['Loan', 'Debt', 'Mortgage', 'Credit card'],
],
'shortNamesByFullName' =>
[
@@ -222,6 +225,10 @@ return [
'Beneficiary account' => 'expense',
'Revenue account' => 'revenue',
'Cash account' => 'cash',
+ 'Credit card' => 'liabilities',
+ 'Loan' => 'liabilities',
+ 'Debt' => 'liabilities',
+ 'Mortgage' => 'liabilities',
],
'languages' => [
// completed languages
diff --git a/config/twigbridge.php b/config/twigbridge.php
index fa0557b7c9..7d3c3f0091 100644
--- a/config/twigbridge.php
+++ b/config/twigbridge.php
@@ -189,7 +189,7 @@ return [
'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location',
'file', 'staticText', 'password', 'nonSelectableAmount',
'number', 'assetAccountList','amountNoCurrency','currencyList','ruleGroupList','assetAccountCheckList','ruleGroupListWithEmpty',
- 'piggyBankList','currencyListEmpty','activeAssetAccountList'
+ 'piggyBankList','currencyListEmpty','activeAssetAccountList','percentage'
],
],
'Form' => [
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
index eaca551c4c..72edf342b2 100644
--- a/resources/lang/en_US/firefly.php
+++ b/resources/lang/en_US/firefly.php
@@ -591,7 +591,6 @@ return [
'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.',
-
// create new stuff:
'create_new_withdrawal' => 'Create new withdrawal',
'create_new_deposit' => 'Create new deposit',
@@ -689,6 +688,7 @@ return [
'delete_asset_account' => 'Delete asset account ":name"',
'delete_expense_account' => 'Delete expense account ":name"',
'delete_revenue_account' => 'Delete revenue account ":name"',
+ 'delete_liabilities_account' => 'Delete liability ":name"',
'asset_deleted' => 'Successfully deleted asset account ":name"',
'expense_deleted' => 'Successfully deleted expense account ":name"',
'revenue_deleted' => 'Successfully deleted revenue account ":name"',
@@ -756,6 +756,9 @@ return [
'already_cleared_transactions' => 'Already cleared transactions (:count)',
'submitted_end_balance' => 'Submitted end balance',
'initial_balance_description' => 'Initial balance for ":account"',
+ 'interest_calc_daily' => 'Per day',
+ 'interest_calc_monthly' => 'Per month',
+ 'interest_calc_yearly' => 'Per year',
// categories:
'new_category' => 'New category',
diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php
index c2dfc104eb..2007f077a9 100644
--- a/resources/lang/en_US/form.php
+++ b/resources/lang/en_US/form.php
@@ -86,6 +86,7 @@ return [
'remember_me' => 'Remember me',
'liability_type_id' => 'Liability type',
'interest' => 'Interest',
+ 'interest_period' => 'Interest period',
'source_account_asset' => 'Source account (asset account)',
'destination_account_expense' => 'Destination account (expense account)',
diff --git a/resources/views/accounts/create.twig b/resources/views/accounts/create.twig
index 0fe42b070c..dc32ab6e77 100644
--- a/resources/views/accounts/create.twig
+++ b/resources/views/accounts/create.twig
@@ -17,9 +17,17 @@
{{ ExpandedForm.text('name') }}
- {% if what == 'asset' %}
+ {% if what == 'asset' or what=='liabilities' %}
{{ ExpandedForm.currencyList('currency_id', null, {helpText:'account_default_currency'|_}) }}
{% endif %}
+ {% if what == 'liabilities' %}
+ {{ ExpandedForm.select('liability_type_id', liabilityTypes) }}
+ {{ ExpandedForm.amountNoCurrency('openingBalance', null, {label:'debt_start_amount'|_, helpText: 'debt_start_amount_help'|_}) }}
+ {{ ExpandedForm.date('openingBalanceDate', null, {label:'debt_start_date'|_}) }}
+ {{ ExpandedForm.percentage('interest') }}
+ {{ ExpandedForm.select('interest_period', interestPeriods) }}
+
+ {% endif %}
@@ -40,9 +48,10 @@
{{ ExpandedForm.amountNoCurrency('openingBalance') }}
{{ ExpandedForm.date('openingBalanceDate') }}
- {{ ExpandedForm.select('accountRole', roles,null,{'helpText' : 'asset_account_role_help'|_}) }}
+ {{ ExpandedForm.select('accountRole', roles,null,{helpText : 'asset_account_role_help'|_}) }}
{{ ExpandedForm.amountNoCurrency('virtualBalance') }}
{% endif %}
+
{{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }}
diff --git a/resources/views/accounts/delete.twig b/resources/views/accounts/delete.twig
index 9e8dcb0868..0466bab036 100644
--- a/resources/views/accounts/delete.twig
+++ b/resources/views/accounts/delete.twig
@@ -33,7 +33,7 @@
{% endif %}
{% endif %}
- {% if account.transactions.count > 0 %}
+ {% if account.transactions.count > 0 and account.accountType.type == 'Asset account' %}
{{ 'save_transactions_by_moving'|_ }}
diff --git a/resources/views/form/percentage.twig b/resources/views/form/percentage.twig
new file mode 100644
index 0000000000..b5e73b7b91
--- /dev/null
+++ b/resources/views/form/percentage.twig
@@ -0,0 +1,12 @@
+
+
+
+
+
+ {% include 'form/help' %}
+ {% include 'form/feedback' %}
+
+