diff --git a/app/Console/Commands/Upgrade/RenameAccountMeta.php b/app/Console/Commands/Upgrade/RenameAccountMeta.php index 1a184daeee..8d99cd552c 100644 --- a/app/Console/Commands/Upgrade/RenameAccountMeta.php +++ b/app/Console/Commands/Upgrade/RenameAccountMeta.php @@ -61,6 +61,7 @@ class RenameAccountMeta extends Command $array = [ 'accountRole' => 'account_role', 'ccType' => 'cc_type', + 'accountNumber' => 'account_number', 'ccMonthlyPaymentDate' => 'cc_monthly_payment_date', ]; $count = 0; @@ -71,6 +72,9 @@ class RenameAccountMeta extends Command */ foreach ($array as $old => $new) { $count += AccountMeta::where('name', $old)->update(['name' => $new]); + + // delete empty entries while we're at it. + AccountMeta::where('name', $new)->where('data','""')->delete(); } $this->markAsExecuted(); diff --git a/app/Console/Commands/Upgrade/UpgradeDatabase.php b/app/Console/Commands/Upgrade/UpgradeDatabase.php index f41d5791f6..deebd307fa 100644 --- a/app/Console/Commands/Upgrade/UpgradeDatabase.php +++ b/app/Console/Commands/Upgrade/UpgradeDatabase.php @@ -55,6 +55,7 @@ class UpgradeDatabase extends Command $commands = [ // there are 12 upgrade commands. 'firefly-iii:transaction-identifiers', + 'firefly-iii:migrate-to-groups', 'firefly-iii:account-currencies', 'firefly-iii:transfer-currencies', 'firefly-iii:other-currencies', @@ -63,7 +64,6 @@ class UpgradeDatabase extends Command 'firefly-iii:bills-to-rules', 'firefly-iii:bl-currency', 'firefly-iii:cc-liabilities', - 'firefly-iii:migrate-to-groups', 'firefly-iii:back-to-journals', 'firefly-iii:rename-account-meta' ]; diff --git a/app/Http/Controllers/Account/EditController.php b/app/Http/Controllers/Account/EditController.php index 8b6b73321e..117f515dca 100644 --- a/app/Http/Controllers/Account/EditController.php +++ b/app/Http/Controllers/Account/EditController.php @@ -100,17 +100,12 @@ class EditController extends Controller $openingBalanceAmount = (string)$repository->getOpeningBalanceAmount($account); $openingBalanceDate = $repository->getOpeningBalanceDate($account); - $default = app('amount')->getDefaultCurrency(); - $currency = $this->currencyRepos->findNull((int)$repository->getMetaValue($account, 'currency_id')); + $currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); // include this account in net-worth charts? $includeNetWorth = $repository->getMetaValue($account, 'include_net_worth'); $includeNetWorth = null === $includeNetWorth ? true : '1' === $includeNetWorth; - if (null === $currency) { - $currency = $default; - } - // code to handle active-checkboxes $hasOldInput = null !== $request->old('_token'); $preFilled = [ diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php index 2ab0f03d16..0fbb0be443 100644 --- a/app/Http/Controllers/Account/ReconcileController.php +++ b/app/Http/Controllers/Account/ReconcileController.php @@ -25,15 +25,18 @@ namespace FireflyIII\Http\Controllers\Account; use Carbon\Carbon; use Exception; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionGroupFactory; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\ReconciliationStoreRequest; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Support\Http\Controllers\UserNavigation; +use FireflyIII\User; use Log; /** @@ -83,13 +86,12 @@ class ReconcileController extends Controller */ public function reconcile(Account $account, Carbon $start = null, Carbon $end = null) { - if (AccountType::INITIAL_BALANCE === $account->accountType->type) { - return $this->redirectToOriginalAccount($account); - } if (AccountType::ASSET !== $account->accountType->type) { + // @codeCoverageIgnoreStart session()->flash('error', (string)trans('firefly.must_be_asset_account')); return redirect(route('accounts.index', [config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type))])); + // @codeCoverageIgnoreEnd } $currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); @@ -97,16 +99,20 @@ class ReconcileController extends Controller $range = app('preferences')->get('viewRange', '1M')->data; // get start and end + // @codeCoverageIgnoreStart if (null === $start && null === $end) { + /** @var Carbon $start */ $start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range)); /** @var Carbon $end */ $end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range)); + } if (null === $end) { /** @var Carbon $end */ $end = app('navigation')->endOfPeriod($start, $range); } + // @codeCoverageIgnoreEnd $startDate = clone $start; $startDate->subDay(); @@ -142,70 +148,78 @@ class ReconcileController extends Controller Log::debug('In ReconcileController::submit()'); $data = $request->getAll(); - /** @var Transaction $transaction */ - foreach ($data['transactions'] as $transactionId) { - $this->repository->reconcileById((int)$transactionId); + /** @var string $journalId */ + foreach ($data['journals'] as $journalId) { + $this->repository->reconcileById((int)$journalId); } Log::debug('Reconciled all transactions.'); // create reconciliation transaction (if necessary): + $result = ''; if ('create' === $data['reconcile']) { - // get "opposing" account. - $reconciliation = $this->accountRepos->getReconciliation($account); - $difference = $data['difference']; - $source = $reconciliation; - $destination = $account; - if (1 === bccomp($difference, '0')) { - // amount is positive. Add it to reconciliation? - $source = $account; - $destination = $reconciliation; - } - - // data for journal - $description = trans( - 'firefly.reconcilliation_transaction_title', - ['from' => $start->formatLocalized($this->monthAndDayFormat), 'to' => $end->formatLocalized($this->monthAndDayFormat)] - ); - $journalData = [ - 'type' => 'Reconciliation', - 'description' => $description, - 'user' => auth()->user()->id, - 'date' => $data['end'], - 'bill_id' => null, - 'bill_name' => null, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'tags' => null, - 'interest_date' => null, - 'transactions' => [[ - 'currency_id' => (int)$this->accountRepos->getMetaValue($account, 'currency_id'), - 'currency_code' => null, - 'description' => null, - 'amount' => app('steam')->positive($difference), - 'source_id' => $source->id, - 'source_name' => null, - 'destination_id' => $destination->id, - 'destination_name' => null, - 'reconciled' => true, - 'identifier' => 0, - 'foreign_currency_id' => null, - 'foreign_currency_code' => null, - 'foreign_amount' => null, - 'budget_id' => null, - 'budget_name' => null, - 'category_id' => null, - 'category_name' => null, - ], - ], - 'notes' => implode(', ', $data['transactions']), - ]; - - $this->repository->store($journalData); + $result = $this->createReconciliation($account, $start, $end, $data['difference']); } Log::debug('End of routine.'); app('preferences')->mark(); - session()->flash('success', (string)trans('firefly.reconciliation_stored')); + if ('' === $result) { + session()->flash('success', (string)trans('firefly.reconciliation_stored')); + } + if ('' !== $result) { + session()->flash('error', (string)trans('firefly.reconciliation_error', ['error' => $result])); + } return redirect(route('accounts.show', [$account->id])); } + + /** + * Creates a reconciliation group. + * @return string + */ + private function createReconciliation(Account $account, Carbon $start, Carbon $end, string $difference): string + { + $reconciliation = $this->accountRepos->getReconciliation($account); + $currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); + $source = $reconciliation; + $destination = $account; + if (1 === bccomp($difference, '0')) { + $source = $account; + $destination = $reconciliation; + } + + // title: + $description = trans('firefly.reconciliation_transaction_title', + ['from' => $start->formatLocalized($this->monthAndDayFormat), 'to' => $end->formatLocalized($this->monthAndDayFormat)]); + $submission = [ + 'user' => auth()->user()->id, + 'group_title' => null, + 'transactions' => [ + [ + 'user' => auth()->user()->id, + 'type' => strtolower(TransactionType::RECONCILIATION), + 'date' => $end, + 'order' => 0, + 'currency_id' => $currency->id, + 'foreign_currency_id' => null, + 'amount' => $difference, + 'foreign_amount' => null, + 'description' => $description, + 'source_id' => $source->id, + 'destination_id' => $destination->id, + 'reconciled' => true, + ], + ], + ]; + /** @var TransactionGroupFactory $factory */ + $factory = app(TransactionGroupFactory::class); + /** @var User $user */ + $user = auth()->user(); + $factory->setUser($user); + try { + $factory->create($submission); + } catch (FireflyException $e) { + return $e->getMessage(); + } + + return ''; + } } diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php index 91dad191a2..b2a32c327d 100644 --- a/app/Http/Controllers/Account/ShowController.php +++ b/app/Http/Controllers/Account/ShowController.php @@ -36,6 +36,7 @@ use FireflyIII\Support\Http\Controllers\UserNavigation; use Illuminate\Http\Request; use Illuminate\Support\Collection; use View; +use Exception; /** * Class ShowController @@ -53,6 +54,7 @@ class ShowController extends Controller /** * ShowController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -82,43 +84,34 @@ class ShowController extends Controller * @param Carbon|null $end * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View - * - * @throws FireflyException - * + * @throws Exception */ public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null) { - if (AccountType::INITIAL_BALANCE === $account->accountType->type) { - return $this->redirectToOriginalAccount($account); - } - // a basic thing to determin if this account is a liability: - if ($this->repository->isLiability($account)) { - return redirect(route('accounts.show.all', [$account->id])); + if (in_array($account->accountType->type, [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION], true)) { + return $this->redirectToOriginalAccount($account); // @codeCoverageIgnore } /** @var Carbon $start */ $start = $start ?? session('start'); /** @var Carbon $end */ $end = $end ?? session('end'); + if ($end < $start) { - throw new FireflyException('End is after start!'); // @codeCoverageIgnore + [$start, $end] = [$end, $start]; // @codeCoverageIgnore } - $what = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); // used for menu + $objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); $today = new Carbon; $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); $page = (int)$request->get('page'); $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $currencyId = (int)$this->repository->getMetaValue($account, 'currency_id'); - $currency = $this->currencyRepos->findNull($currencyId); - if (0 === $currencyId) { - $currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore - } - $fStart = $start->formatLocalized($this->monthAndDayFormat); - $fEnd = $end->formatLocalized($this->monthAndDayFormat); - $subTitle = (string)trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]); - $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); - $periods = $this->getAccountPeriodOverview($account, $end); + $currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); + $fStart = $start->formatLocalized($this->monthAndDayFormat); + $fEnd = $end->formatLocalized($this->monthAndDayFormat); + $subTitle = (string)trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]); + $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); + $periods = $this->getAccountPeriodOverview($account, $end); /** @var GroupCollectorInterface $collector */ $collector = app(GroupCollectorInterface::class); @@ -134,7 +127,7 @@ class ShowController extends Controller return view( 'accounts.show', compact( - 'account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'groups', 'subTitle', 'start', 'end', + 'account', 'showAll', 'objectType', 'currency', 'today', 'periods', 'subTitleIcon', 'groups', 'subTitle', 'start', 'end', 'chartUri' ) ); @@ -145,7 +138,7 @@ class ShowController extends Controller * * @param Request $request * @param Account $account - * + * @throws Exception * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View * * @@ -156,19 +149,16 @@ class ShowController extends Controller return $this->redirectToOriginalAccount($account); // @codeCoverageIgnore } $isLiability = $this->repository->isLiability($account); + $objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); $end = new Carbon; $today = new Carbon; $start = $this->repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth(); $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type); $page = (int)$request->get('page'); $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $currencyId = (int)$this->repository->getMetaValue($account, 'currency_id'); - $currency = $this->currencyRepos->findNull($currencyId); - if (0 === $currencyId) { - $currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore - } - $subTitle = (string)trans('firefly.all_journals_for_account', ['name' => $account->name]); - $periods = new Collection; + $currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); + $subTitle = (string)trans('firefly.all_journals_for_account', ['name' => $account->name]); + $periods = new Collection; /** @var GroupCollectorInterface $collector */ $collector = app(GroupCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation(); @@ -179,7 +169,8 @@ class ShowController extends Controller return view( 'accounts.show', - compact('account', 'showAll', 'isLiability', 'currency', 'today', 'chartUri', 'periods', 'subTitleIcon', 'groups', 'subTitle', 'start', 'end') + compact('account', 'showAll', 'objectType', 'isLiability', 'currency', 'today', + 'chartUri', 'periods', 'subTitleIcon', 'groups', 'subTitle', 'start', 'end') ); } diff --git a/app/Http/Controllers/Json/ReconcileController.php b/app/Http/Controllers/Json/ReconcileController.php index 722612e9b4..d111379fdf 100644 --- a/app/Http/Controllers/Json/ReconcileController.php +++ b/app/Http/Controllers/Json/ReconcileController.php @@ -139,7 +139,7 @@ class ReconcileController extends Controller 'accounts.reconcile.overview', compact( 'account', 'start', 'diffCompare', 'difference', 'end', 'clearedAmount', 'startBalance', 'endBalance', 'amount', - 'route', 'countCleared', 'reconSum' + 'route', 'countCleared', 'reconSum', 'selectedIds' ) )->render(); // @codeCoverageIgnoreStart diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index e6f9c431b2..b3eb297850 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -65,29 +65,6 @@ class TransactionController extends Controller ); } - - /** - * Do a reconciliation. - * - * @param Request $request - * - * @return JsonResponse - */ - public function reconcile(Request $request): JsonResponse - { - $transactionIds = $request->get('transactions'); - foreach ($transactionIds as $transactionId) { - $transactionId = (int)$transactionId; - $transaction = $this->repository->findTransaction($transactionId); - if (null !== $transaction) { - Log::debug(sprintf('Transaction ID is %d', $transaction->id)); - $this->repository->reconcile($transaction); - } - } - - return response()->json(['ok' => 'reconciled']); - } - /** * Reorder transactions. * diff --git a/app/Http/Requests/ReconciliationStoreRequest.php b/app/Http/Requests/ReconciliationStoreRequest.php index 67765f6728..1113266f04 100644 --- a/app/Http/Requests/ReconciliationStoreRequest.php +++ b/app/Http/Requests/ReconciliationStoreRequest.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; -use FireflyIII\Rules\ValidTransactions; +use FireflyIII\Rules\ValidJournals; use Log; /** @@ -59,7 +59,7 @@ class ReconciliationStoreRequest extends Request 'start_balance' => $this->string('startBalance'), 'end_balance' => $this->string('endBalance'), 'difference' => $this->string('difference'), - 'journals' => $transactions, + 'journals' => $transactions, 'reconcile' => $this->string('reconcile'), ]; Log::debug('In ReconciliationStoreRequest::getAll(). Will now return data.'); @@ -80,7 +80,7 @@ class ReconciliationStoreRequest extends Request 'startBalance' => 'numeric', 'endBalance' => 'numeric', 'difference' => 'required|numeric', - 'journals' => [new ValidJournals], + 'journals' => [new ValidJournals], 'reconcile' => 'required|in:create,nothing', ]; } diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index adef41cd10..58bdd4aeb2 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -230,6 +230,7 @@ class AccountRepository implements AccountRepositoryInterface if (count($accountIds) > 0) { $query->whereIn('accounts.id', $accountIds); } + $query->orderBy('accounts.active', 'DESC'); $query->orderBy('accounts.name', 'ASC'); $result = $query->get(['accounts.*']); @@ -249,6 +250,7 @@ class AccountRepository implements AccountRepositoryInterface if (count($types) > 0) { $query->accountTypeIn($types); } + $query->orderBy('accounts.active', 'DESC'); $query->orderBy('accounts.name', 'ASC'); $result = $query->get(['accounts.*']); diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 103b7d86c3..5340c32f0e 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -688,18 +688,14 @@ class JournalRepository implements JournalRepositoryInterface /** * @param int $transactionId - * - * @return bool */ - public function reconcileById(int $transactionId): bool + public function reconcileById(int $journalId): void { - /** @var Transaction $transaction */ - $transaction = $this->user->transactions()->find($transactionId); - if (null !== $transaction) { - return $this->reconcile($transaction); + /** @var TransactionJournal $journal */ + $journal = $this->user->transactionJournals()->find($journalId); + if (null !== $journal) { + $journal->transactions()->update(['reconciled' => true]); } - - return false; } /** diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index e0671daa1c..5a3b6a5cf0 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -309,11 +309,9 @@ interface JournalRepositoryInterface public function reconcile(Transaction $transaction): bool; /** - * @param int $transactionId - * - * @return bool + * @param int $journalId */ - public function reconcileById(int $transactionId): bool; + public function reconcileById(int $journalId): void; /** * @param TransactionJournal $journal diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php index 507b9d2b7b..c9be6b5258 100644 --- a/app/Support/Twig/AmountFormat.php +++ b/app/Support/Twig/AmountFormat.php @@ -87,7 +87,7 @@ class AmountFormat extends Twig_Extension static function (AccountModel $account, string $amount, bool $coloured = null): string { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + Log::warning('AmountFormat::formatAmountByAccount should NOT be called in the TEST environment!'); } $coloured = $coloured ?? true; diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 7d5772219c..bbab73358f 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -153,10 +153,6 @@ class General extends Twig_Extension /** @var Carbon $date */ $date = session('end', Carbon::now()->endOfMonth()); - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); - } - return app('steam')->balance($account, $date); } ); @@ -213,7 +209,7 @@ class General extends Twig_Extension 'accountGetMetaField', static function (Account $account, string $field): string { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + Log::warning('Twig General::getMetaField should NOT be called in the TEST environment!'); } /** @var AccountRepositoryInterface $repository */ @@ -238,9 +234,6 @@ class General extends Twig_Extension return new Twig_SimpleFunction( 'hasRole', static function (string $role): bool { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); - } $repository = app(UserRepositoryInterface::class); if ($repository->hasRole(auth()->user(), $role)) { return true; @@ -258,7 +251,7 @@ class General extends Twig_Extension { return new Twig_SimpleFilter( 'markdown', - function (string $text): string { + static function (string $text): string { $converter = new CommonMarkConverter; return $converter->convertToHtml($text); @@ -275,7 +268,7 @@ class General extends Twig_Extension { return new Twig_SimpleFilter( 'mimeIcon', - function (string $string): string { + static function (string $string): string { switch ($string) { default: return 'fa-file-o'; @@ -354,7 +347,7 @@ class General extends Twig_Extension { return new Twig_SimpleFunction( 'phpdate', - function (string $str): string { + static function (string $str): string { return date($str); } ); diff --git a/app/Support/Twig/TransactionGroupTwig.php b/app/Support/Twig/TransactionGroupTwig.php index 5bdd2e1a2a..52d00ddabb 100644 --- a/app/Support/Twig/TransactionGroupTwig.php +++ b/app/Support/Twig/TransactionGroupTwig.php @@ -96,7 +96,7 @@ class TransactionGroupTwig extends Twig_Extension 'journalGetMetaDate', static function (int $journalId, string $metaField) { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + Log::warning('Twig TransactionGroup::journalGetMetaDate should NOT be called in the TEST environment!'); } $entry = DB::table('journal_meta') ->where('name', $metaField) @@ -121,7 +121,7 @@ class TransactionGroupTwig extends Twig_Extension 'journalGetMetaField', static function (int $journalId, string $metaField) { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + Log::warning('Twig TransactionGroup::journalGetMetaField should NOT be called in the TEST environment!'); } $entry = DB::table('journal_meta') ->where('name', $metaField) @@ -146,7 +146,7 @@ class TransactionGroupTwig extends Twig_Extension 'journalHasMeta', static function (int $journalId, string $metaField) { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + Log::warning('Twig TransactionGroup::journalHasMeta should NOT be called in the TEST environment!'); } $count = DB::table('journal_meta') ->where('name', $metaField) diff --git a/app/Validation/AccountValidator.php b/app/Validation/AccountValidator.php index dd629f01bf..e91e90ef19 100644 --- a/app/Validation/AccountValidator.php +++ b/app/Validation/AccountValidator.php @@ -124,6 +124,9 @@ class AccountValidator case TransactionType::OPENING_BALANCE: $result = $this->validateOBDestination($destinationId, $destinationName); break; + case TransactionType::RECONCILIATION: + $result = $this->validateReconciliationDestination($destinationId); + break; //case TransactionType::OPENING_BALANCE: //case TransactionType::RECONCILIATION: // die(sprintf('Cannot handle type "%s"', $this->transactionType)); @@ -159,6 +162,9 @@ class AccountValidator case TransactionType::OPENING_BALANCE: $result = $this->validateOBSource($accountId, $accountName); break; + case TransactionType::RECONCILIATION: + $result = $this->validateReconciliationSource($accountId); + break; //case TransactionType::OPENING_BALANCE: //case TransactionType::RECONCILIATION: // die(sprintf('Cannot handle type "%s"', $this->transactionType)); @@ -582,5 +588,51 @@ class AccountValidator return true; } + /** + * @param int|null $accountId + * @return bool + */ + private function validateReconciliationSource(?int $accountId): bool + { + if (null === $accountId) { + return false; + } + $result = $this->accountRepository->findNull($accountId); + $types = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::RECONCILIATION]; + if (null === $result) { + return false; + } + if (in_array($result->accountType->type, $types, true)) { + $this->source = $result; + + return true; + } + + return false; + } + + /** + * @param int|null $accountId + * @return bool + */ + private function validateReconciliationDestination(?int $accountId): bool + { + if (null === $accountId) { + return false; + } + $result = $this->accountRepository->findNull($accountId); + $types = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::RECONCILIATION]; + if (null === $result) { + return false; + } + if (in_array($result->accountType->type, $types, true)) { + $this->destination = $result; + + return true; + } + + return false; + } + } \ No newline at end of file diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 5cadee6a94..93a41e4b73 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -780,7 +780,9 @@ return [ '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', - 'reconcilliation_transaction_title' => 'Reconciliation (:from to :to)', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => 'Reconcile this account', 'confirm_reconciliation' => 'Confirm reconciliation', 'submitted_start_balance' => 'Submitted start balance', @@ -1381,13 +1383,13 @@ return [ 'recurrence_deleted' => 'Recurring transaction ":title" deleted', // new lines for summary controller. - 'box_balance_in_currency' => 'Balance (:currency)', - 'box_spent_in_currency' => 'Spent (:currency)', - 'box_earned_in_currency' => 'Earned (:currency)', - 'box_bill_paid_in_currency' => 'Bills paid (:currency)', - 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', - 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', - 'box_net_worth_in_currency' => 'Net worth (:currency)', - 'box_spend_per_day' => 'Left to spend per day: :amount', + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', ]; diff --git a/resources/views/v1/accounts/reconcile/overview.twig b/resources/views/v1/accounts/reconcile/overview.twig index 33758ecf6b..7116cf6ed4 100644 --- a/resources/views/v1/accounts/reconcile/overview.twig +++ b/resources/views/v1/accounts/reconcile/overview.twig @@ -15,8 +15,8 @@ - {% for id in transactionIds %} - + {% for id in selectedIds %} + {% endfor %} @@ -25,7 +25,7 @@ - + diff --git a/resources/views/v1/accounts/reconcile/transactions.twig b/resources/views/v1/accounts/reconcile/transactions.twig index 7851342972..999d3d16d2 100644 --- a/resources/views/v1/accounts/reconcile/transactions.twig +++ b/resources/views/v1/accounts/reconcile/transactions.twig @@ -102,7 +102,6 @@ {% for group in groups %} {% if group.count > 1 %} - - - - - - - {% endif %} - {% for index, transaction in group.transactions %} - {% set style="" %} - {% if group.transactions|length == loop.index and group.count > 1 %} - {% set style="style='border-bottom:1px #aaa solid;'" %} - {% endif %} - - - - - - - - + + + + - - {% endfor %} + + + {% endif %} + {% for index, transaction in group.transactions %} + {% set style="" %} + {% if group.transactions|length == loop.index and group.count > 1 %} + {% set style="style='border-bottom:1px #aaa solid;'" %} + {% endif %} + + + + + + + + + + {% endfor %} {% endfor %}
{{ formatAmountByAccount(account, startBalance) }}
{{ trans('firefly.selected_transactions', {count: transactionIds|length}) }}{{ trans('firefly.selected_transactions', {count: selectedIds|length}) }} {{ formatAmountByAccount(account, amount) }}
- {% if journal.date >= start and journal.date <= end %} {% if journal.reconciled %} diff --git a/resources/views/v1/list/groups.twig b/resources/views/v1/list/groups.twig index d1800f7048..f62c16fa32 100644 --- a/resources/views/v1/list/groups.twig +++ b/resources/views/v1/list/groups.twig @@ -13,76 +13,19 @@ TODO: hide and show columns
- - {{ group.title }} - - - {% for sum in group.sums %} - {{ formatAmountBySymbol(sum.amount, sum.currency_symbol, sum.currency_symbol_decimal_places) }}{% if loop.index != group.sums|length %},{% endif %} - {% endfor %} -   -
- - -
-
- {% if journal.transaction_type_type == 'Withdrawal' %} - - {% endif %} - - {% if journal.transaction_type_type == 'Deposit' %} - - {% endif %} - - {% if journal.transaction_type_type == 'Transfer' %} - - {% endif %} - - {% if journal.transaction_type_type == 'Reconciliation' %} - - {% endif %} - {% if journal.transaction_type_type == 'Opening balance' %} - - {% endif %} - - - {{ transaction.description }} - - {{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_symbol_decimal_places) }} - {% if null != transaction.foreign_amount %} - ({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places) }}) - {% endif %} - {{ transaction.date.formatLocalized(monthAndDayFormat) }} - {{ transaction.source_account_name }} - - {{ transaction.destination_account_name }} - - {% if group.count == 1 %} +
+ + {{ group.title }} + + + {% for sum in group.sums %} + {{ formatAmountBySymbol(sum.amount, sum.currency_symbol, sum.currency_symbol_decimal_places) }}{% if loop.index != group.sums|length %},{% endif %} + {% endfor %} +  
@@ -92,10 +35,72 @@ TODO: hide and show columns
  • {{ 'clone'|_ }}
  • - {% endif %} -
    + {% if journal.transaction_type_type == 'Withdrawal' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Deposit' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Transfer' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Reconciliation' %} + + {% endif %} + {% if journal.transaction_type_type == 'Opening balance' %} + + {% endif %} + + + {% if transaction.reconciled %} + + {% endif %} + {{ transaction.description }} + + {{ formatAmountBySymbol(transaction.amount, transaction.currency_symbol, transaction.currency_symbol_decimal_places) }} + {% if null != transaction.foreign_amount %} + ({{ formatAmountBySymbol(transaction.foreign_amount, transaction.foreign_currency_symbol, transaction.foreign_currency_symbol_decimal_places) }}) + {% endif %} + {{ transaction.date.formatLocalized(monthAndDayFormat) }} + {{ transaction.source_account_name }} + + {{ transaction.destination_account_name }} + + {% if group.count == 1 %} +
    + + +
    + {% endif %} +
    diff --git a/tests/Feature/Controllers/Account/EditControllerTest.php b/tests/Feature/Controllers/Account/EditControllerTest.php index 3de317bf0e..12d6b233b8 100644 --- a/tests/Feature/Controllers/Account/EditControllerTest.php +++ b/tests/Feature/Controllers/Account/EditControllerTest.php @@ -74,17 +74,18 @@ class EditControllerTest extends TestCase // mock preferences: $this->mockDefaultPreferences(); - $repository->shouldReceive('findNull')->withArgs([1])->andReturn($euro)->atLeast()->once(); + //$repository->shouldReceive('findNull')->withArgs([1])->andReturn($euro)->atLeast()->once(); // mock hasRole for user repository: $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->andReturn(true)->atLeast()->once(); $repository->shouldReceive('get')->andReturn(new Collection)->atLeast()->once(); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); + $accountRepos->shouldReceive('getAccountCurrency')->andReturn($euro)->once(); $accountRepos->shouldReceive('getNoteText')->andReturn('Some text')->once(); $accountRepos->shouldReceive('getOpeningBalanceAmount')->andReturnNull()->atLeast()->once(); $accountRepos->shouldReceive('getOpeningBalanceDate')->andReturnNull()->atLeast()->once(); - $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1')->atLeast()->once(); + //$accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1')->atLeast()->once(); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'account_number'])->andReturn('123')->atLeast()->once(); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'account_role'])->andReturn('defaultAsset')->atLeast()->once(); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'cc_type'])->andReturn('')->atLeast()->once(); @@ -128,13 +129,13 @@ class EditControllerTest extends TestCase // mock hasRole for user repository: $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->andReturn(true)->atLeast()->once(); - $repository->shouldReceive('findNull')->once()->andReturn($euro); + //$repository->shouldReceive('findNull')->once()->andReturn($euro); $repository->shouldReceive('get')->andReturn(new Collection); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $accountRepos->shouldReceive('getNoteText')->andReturn('Some text')->once(); $accountRepos->shouldReceive('getOpeningBalanceAmount')->andReturnNull(); $accountRepos->shouldReceive('getOpeningBalanceDate')->andReturnNull(); - $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); + //$accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'currency_id'])->andReturn('1'); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'account_number'])->andReturn('123'); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'account_role'])->andReturn('defaultAsset'); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'cc_type'])->andReturn(''); @@ -143,6 +144,7 @@ class EditControllerTest extends TestCase $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'BIC'])->andReturn('BIC'); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'interest'])->andReturn('1'); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'interest_period'])->andReturn('monthly'); + $accountRepos->shouldReceive('getAccountCurrency')->andReturn($euro)->once(); // get all types: $accountRepos->shouldReceive('getAccountTypeByType')->withArgs(['Debt'])->andReturn(AccountType::find(11))->once(); @@ -179,12 +181,12 @@ class EditControllerTest extends TestCase $repository = $this->mock(CurrencyRepositoryInterface::class); $accountRepos = $this->mock(AccountRepositoryInterface::class); $userRepos = $this->mock(UserRepositoryInterface::class); - + $euro = $this->getEuro(); // mock hasRole for user repository: $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->andReturn(true)->atLeast()->once(); Amount::shouldReceive('getDefaultCurrency')->andReturn(TransactionCurrency::find(2)); - $repository->shouldReceive('findNull')->once()->andReturn(null); + //$repository->shouldReceive('findNull')->once()->andReturn(null); $repository->shouldReceive('get')->andReturn(new Collection); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $accountRepos->shouldReceive('getNoteText')->andReturn('Some text')->once(); @@ -199,6 +201,7 @@ class EditControllerTest extends TestCase $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'BIC'])->andReturn('BIC'); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'interest'])->andReturn('1'); $accountRepos->shouldReceive('getMetaValue')->withArgs([Mockery::any(), 'interest_period'])->andReturn('monthly'); + $accountRepos->shouldReceive('getAccountCurrency')->andReturn($euro)->once(); // mock calls to Preferences: $this->mockDefaultPreferences(); diff --git a/tests/Feature/Controllers/Account/ReconcileControllerTest.php b/tests/Feature/Controllers/Account/ReconcileControllerTest.php index 7b9f6549b1..cf8d547681 100644 --- a/tests/Feature/Controllers/Account/ReconcileControllerTest.php +++ b/tests/Feature/Controllers/Account/ReconcileControllerTest.php @@ -23,20 +23,21 @@ declare(strict_types=1); namespace Tests\Feature\Controllers\Account; +use Amount; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\FiscalHelperInterface; -use FireflyIII\Models\Account; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionGroupFactory; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Helpers\Fiscal\FiscalHelperInterface; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; -use Illuminate\Support\Collection; use Log; use Mockery; +use Preferences; +use Steam; use Tests\TestCase; /** @@ -53,64 +54,6 @@ class ReconcileControllerTest extends TestCase Log::info(sprintf('Now in %s.', get_class($this))); } - /** - * Test editing a reconciliation. - * - * @covers \FireflyIII\Http\Controllers\Account\ReconcileController - */ - public function testEdit(): void - { - $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); - - return; - $repository = $this->mock(JournalRepositoryInterface::class); - $userRepos = $this->mock(UserRepositoryInterface::class); - $accountRepos = $this->mock(AccountRepositoryInterface::class); - $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); - $fiscalHelper = $this->mock(FiscalHelperInterface::class); - $collector = $this->mock(TransactionCollectorInterface::class); - - // mock hasRole for user repository: - $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->andReturn(true)->atLeast()->once(); - - $journal = $this->user()->transactionJournals()->where('transaction_type_id', 5)->first(); - $transaction = $journal->transactions()->where('amount', '>', 0)->first(); - $repository->shouldReceive('firstNull')->andReturn($journal); - $repository->shouldReceive('getFirstPosTransaction')->andReturn($transaction); - $repository->shouldReceive('getJournalDate')->andReturn('2018-01-01'); - $repository->shouldReceive('getJournalCategoryName')->andReturn(''); - $repository->shouldReceive('getJournalBudgetid')->andReturn(0); - - $this->be($this->user()); - $response = $this->get(route('accounts.reconcile.edit', [$journal->id])); - $response->assertStatus(200); - - // has bread crumb - $response->assertSee('