mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Basic code for tracking liabilities.
This commit is contained in:
parent
f0d3ca5d53
commit
8dbc846314
@ -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();
|
||||
|
@ -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]));
|
||||
|
@ -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'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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'));
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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' => [
|
||||
|
@ -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',
|
||||
|
@ -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)',
|
||||
|
@ -17,9 +17,17 @@
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ 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 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -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')}) }}
|
||||
|
||||
</div>
|
||||
|
@ -33,7 +33,7 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if account.transactions.count > 0 %}
|
||||
{% if account.transactions.count > 0 and account.accountType.type == 'Asset account' %}
|
||||
<p class="text-success">
|
||||
{{ 'save_transactions_by_moving'|_ }}
|
||||
</p>
|
||||
|
12
resources/views/form/percentage.twig
Normal file
12
resources/views/form/percentage.twig
Normal file
@ -0,0 +1,12 @@
|
||||
<div class="{{ classes }}" id="{{ name }}_holder">
|
||||
<label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
{{ Form.input('number', name, value, options) }}
|
||||
<div class="input-group-addon">%</div>
|
||||
</div>
|
||||
{% include 'form/help' %}
|
||||
{% include 'form/feedback' %}
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user