diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php index fb38bcedb1..f615d61c92 100644 --- a/app/Http/Controllers/Account/ReconcileController.php +++ b/app/Http/Controllers/Account/ReconcileController.php @@ -23,11 +23,13 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Account; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Http\Request; @@ -35,6 +37,7 @@ use Illuminate\Support\Collection; use Navigation; use Preferences; use Response; +use Session; use View; /** @@ -67,9 +70,13 @@ class ReconcileController extends Controller * @param Carbon $end * * @return \Illuminate\Http\JsonResponse + * @throws FireflyException */ public function overview(Request $request, Account $account, Carbon $start, Carbon $end) { + if ($account->accountType->type !== AccountType::ASSET) { + throw new FireflyException(sprintf('Account %s is not an asset account.', $account->name)); + } $startBalance = $request->get('startBalance'); $endBalance = $request->get('endBalance'); $transactionIds = $request->get('transactions') ?? []; @@ -93,6 +100,11 @@ class ReconcileController extends Controller $clearedAmount = bcadd($clearedAmount, $transaction->amount); } + // final difference: + //{% set diff = (startBalance - endBalance) + clearedAmount + amount %} + $difference = bcadd(bcadd(bcsub($startBalance, $endBalance), $clearedAmount), $amount); + $diffCompare = bccomp($difference, '0'); + $return = [ 'is_zero' => false, 'post_uri' => $route, @@ -100,7 +112,10 @@ class ReconcileController extends Controller ]; $return['html'] = view( 'accounts.reconcile.overview', - compact('account', 'start', 'end', 'clearedIds', 'transactionIds', 'clearedAmount', 'startBalance', 'endBalance', 'amount', 'route') + compact( + 'account', 'start', 'diffCompare', 'difference', 'end', 'clearedIds', 'transactionIds', 'clearedAmount', 'startBalance', 'endBalance', 'amount', + 'route' + ) )->render(); return Response::json($return); @@ -118,6 +133,11 @@ class ReconcileController extends Controller if (AccountType::INITIAL_BALANCE === $account->accountType->type) { return $this->redirectToOriginalAccount($account); } + if (AccountType::ASSET !== $account->accountType->type) { + Session::flash('error', trans('firefly.must_be_asset_account')); + + return redirect(route('accounts.index', [config('firefly.shortNamesByFullName.' . $account->accountType->type)])); + } /** @var CurrencyRepositoryInterface $currencyRepos */ $currencyRepos = app(CurrencyRepositoryInterface::class); $currencyId = intval($account->getMeta('currency_id')); @@ -170,6 +190,52 @@ class ReconcileController extends Controller ); } + /** + * @param Account $account + * @param Carbon $start + * @param Carbon $end + */ + public function submit(Request $request, Account $account, Carbon $start, Carbon $end) + { + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $transactions = $repository->getTransactionsById($request->get('transactions')); + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $repository->reconcile($transaction); // mark as reconciled. + } + + // create reconciliation transaction (if necessary): + if ($request->get('reconcile') === 'create') { + /** @var AccountRepositoryInterface $accountRepos */ + $accountRepos = app(AccountRepositoryInterface::class); + $reconciliation = $accountRepos->getReconciliation($account); + $difference = $request->get('difference'); + + // store journal between these two. + $data = [ + 'what' => 'Reconciliation', + 'source' => $account, + 'destination' => $reconciliation, + 'category' => '', + 'budget_id' => 0, + 'amount' => $difference, + 'currency_id' => $account->getMeta('currency_id'), + 'description' => 'Reconciliation [period]', + 'date' => $request->get('end'), + ]; + $journal = $repository->store($data); + // reconcile this transaction too: + $transaction = $journal->transactions()->first(); + $repository->reconcile($transaction); + } + Session::flash('success', trans('firefly.reconciliation_stored')); + + return redirect(route('accounts.show', [$account->id])); + + + } + /** * @param Account $account * @param Carbon $start @@ -199,13 +265,4 @@ class ReconcileController extends Controller return Response::json(['html' => $html]); } - - /** - * @param Account $account - * @param Carbon $start - * @param Carbon $end - */ - public function submit(Request $request, Account $account, Carbon $start, Carbon $end) { - var_dump($request->all()); - } } diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 05b4ae3aa0..c2d90a2225 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -109,6 +109,15 @@ interface AccountRepositoryInterface */ public function getCashAccount(): Account; + /** + * Find or create the opposing reconciliation account. + * + * @param Account $account + * + * @return Account|null + */ + public function getReconciliation(Account $account): ?Account; + /** * Returns the date of the very last transaction in this account. * diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php index 7a06298410..b8609b7990 100644 --- a/app/Repositories/Account/FindAccountsTrait.php +++ b/app/Repositories/Account/FindAccountsTrait.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Account; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\User; @@ -222,4 +223,37 @@ trait FindAccountsTrait return $account; } + + /** + * @param Account $account + * + * @return Account|null + * @throws FireflyException + */ + public function getReconciliation(Account $account): ?Account + { + if ($account->accountType->type !== AccountType::ASSET) { + throw new FireflyException(sprintf('%s is not an asset account.', $account->name)); + } + $name = $account->name . ' reconciliation'; + $type = AccountType::where('type', AccountType::RECONCILIATION)->first(); + $accounts = $this->user->accounts()->where('account_type_id', $type->id)->get(); + /** @var Account $account */ + foreach ($accounts as $account) { + if ($account->name === $name) { + return $account; + } + } + // assume nothing was found. create it! + $data = [ + 'accountType' => 'reconcile', + 'name' => $name, + 'iban' => null, + 'virtualBalance' => null, + 'active' => true, + ]; + $account = $this->storeAccount($data); + + return $account; + } } diff --git a/app/Repositories/Journal/SupportJournalsTrait.php b/app/Repositories/Journal/SupportJournalsTrait.php index a357a316bc..25a8a02d69 100644 --- a/app/Repositories/Journal/SupportJournalsTrait.php +++ b/app/Repositories/Journal/SupportJournalsTrait.php @@ -67,6 +67,11 @@ trait SupportJournalsTrait $accounts['source'] = Account::where('user_id', $user->id)->where('id', $data['source_account_id'])->first(); $accounts['destination'] = Account::where('user_id', $user->id)->where('id', $data['destination_account_id'])->first(); break; + case TransactionType::RECONCILIATION: + $accounts['source'] = $data['source']; + $accounts['destination'] = $data['destination']; + unset($data['source'], $data['destination']); + break; default: throw new FireflyException(sprintf('Did not recognise transaction type "%s".', $type->type)); } @@ -229,6 +234,9 @@ trait SupportJournalsTrait $check = 'destination'; } switch ($transactionType->type) { + case TransactionType::RECONCILIATION: + // do nothing. + break; case TransactionType::DEPOSIT: case TransactionType::WITHDRAWAL: // continue: diff --git a/config/firefly.php b/config/firefly.php index 7fad830168..8e5e52fb05 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -107,12 +107,13 @@ return [ ], 'accountTypeByIdentifier' => [ - 'asset' => 'Asset account', - 'expense' => 'Expense account', - 'revenue' => 'Revenue account', - 'opening' => 'Initial balance account', - 'initial' => 'Initial balance account', - 'import' => 'Import account', + 'asset' => 'Asset account', + 'expense' => 'Expense account', + 'revenue' => 'Revenue account', + 'opening' => 'Initial balance account', + 'initial' => 'Initial balance account', + 'import' => 'Import account', + 'reconcile' => 'Reconciliation account', ], 'shortNamesByFullName' => [ diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 992cb1d2f9..e23103c1f3 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -654,6 +654,9 @@ return [ 'create_neg_reconcile_transaction' => 'Clear the selected transactions, and create a correction removing :amount from this asset account.', 'reconcile_do_nothing' => 'Clear the selected transactions, but do not correct.', 'reconcile_go_back' => 'You can always edit or delete a correction later.', + 'must_be_asset_account' => 'You can only reconcile asset accounts', + 'reconciliation_stored' => 'Reconciliation stored', + 'reconcile_this_account' => 'Reconcile this account', 'confirm_reconciliation' => 'Confirm reconciliation', 'submitted_start_balance' => 'Submitted start balance', 'selected_transactions' => 'Selected transactions (:count)', diff --git a/resources/views/accounts/reconcile/overview.twig b/resources/views/accounts/reconcile/overview.twig index 1fc087fa1d..b7beb0cd72 100644 --- a/resources/views/accounts/reconcile/overview.twig +++ b/resources/views/accounts/reconcile/overview.twig @@ -35,38 +35,42 @@ {{ 'submitted_end_balance'|_ }} (date) {{ endBalance|formatAmount }} - {% set diff = (startBalance - endBalance) + clearedAmount + amount %} + {{ 'difference'|_ }} - {{ diff|formatAmount }} + + {{ difference|formatAmount }} + + +

- {% if diff > 0 %} + {% if diffCompare > 0 %} {{ 'reconcile_has_more'|_ }} {% endif %} - {% if diff < 0 %} + {% if diffCompare < 0 %} {{ 'reconcile_has_less'|_ }} {% endif %}

- {% if diff == 0 %} + {% if diffCompare == 0 %}

{{ 'reconcile_is_equal'|_ }}

{% endif %} - {% if diff != 0 %} + {% if diffCompare != 0 %}
diff --git a/resources/views/list/accounts.twig b/resources/views/list/accounts.twig index 1352d8a315..35c46b239b 100644 --- a/resources/views/list/accounts.twig +++ b/resources/views/list/accounts.twig @@ -18,7 +18,7 @@ {% for account in accounts %} -
+
{% if what == 'asset' %}{% endif %}
{{ account.name }} {% if what == "asset" %}