From 5bbe1eab7ce0110416ffbe1c7c47338f988dfbf3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 1 Jul 2019 20:22:35 +0200 Subject: [PATCH] Expand test coverage and improve transaction management code. --- .../Chart/BudgetReportController.php | 2 +- .../Json/AutoCompleteController.php | 91 +++- .../Recurring/CreateController.php | 1 + .../Recurring/DeleteController.php | 5 +- .../Controllers/Recurring/EditController.php | 16 +- .../Controllers/Recurring/IndexController.php | 7 +- .../Controllers/Report/ExpenseController.php | 29 +- .../Report/OperationsController.php | 1 + .../Controllers/Rule/CreateController.php | 1 + .../Controllers/Rule/DeleteController.php | 1 + app/Http/Controllers/Rule/EditController.php | 1 + app/Http/Controllers/Rule/IndexController.php | 1 + .../Controllers/Rule/SelectController.php | 11 +- .../Transaction/IndexController.php | 41 +- app/Http/Middleware/StartFireflySession.php | 4 +- app/Http/Requests/RecurrenceFormRequest.php | 83 +++ app/Repositories/Budget/BudgetRepository.php | 8 +- .../Category/CategoryRepository.php | 233 ++++---- .../Currency/CurrencyRepository.php | 22 +- .../Currency/CurrencyRepositoryInterface.php | 6 + .../Journal/JournalRepository.php | 20 + .../Journal/JournalRepositoryInterface.php | 8 + .../Recurring/RecurringRepository.php | 14 +- .../RecurringRepositoryInterface.php | 4 +- .../TransactionTypeRepository.php | 16 +- .../TransactionTypeRepositoryInterface.php | 7 + app/Support/Binder/AccountList.php | 10 +- app/Support/Binder/BudgetList.php | 14 +- app/Support/ExpandedForm.php | 44 +- .../Http/Controllers/PeriodOverview.php | 96 ++-- .../Http/Controllers/RequestInformation.php | 2 +- .../Http/Controllers/UserNavigation.php | 18 +- app/TransactionRules/TransactionMatcher.php | 16 +- app/TransactionRules/Triggers/CurrencyIs.php | 11 +- app/Validation/TransactionValidation.php | 2 +- public/v1/css/firefly.css | 4 + public/v1/js/ff/recurring/create.js | 9 +- public/v1/js/ff/recurring/edit.js | 24 +- public/v1/js/ff/rules/create-edit.js | 10 +- resources/lang/en_US/validation.php | 3 + resources/views/v1/list/groups.twig | 70 ++- .../views/v1/list/journals-array-tiny.twig | 10 + resources/views/v1/recurring/edit.twig | 10 +- resources/views/v1/recurring/show.twig | 2 +- .../v1/reports/partials/exp-categories.twig | 2 +- .../v1/reports/partials/exp-not-grouped.twig | 2 +- .../v1/reports/partials/top-transactions.twig | 14 +- resources/views/v1/transactions/index.twig | 10 +- routes/web.php | 2 + .../Chart/BudgetReportControllerTest.php | 2 + .../Recurring/CreateControllerTest.php | 511 +++++++++++------- .../Recurring/DeleteControllerTest.php | 8 +- .../Recurring/EditControllerTest.php | 120 ++-- .../Recurring/IndexControllerTest.php | 23 +- .../Report/ExpenseControllerTest.php | 262 +++------ .../Controllers/Rule/CreateControllerTest.php | 21 +- .../Controllers/Rule/DeleteControllerTest.php | 11 +- .../Controllers/Rule/EditControllerTest.php | 15 +- .../Controllers/Rule/IndexControllerTest.php | 18 +- .../Controllers/Rule/SelectControllerTest.php | 15 +- tests/TestCase.php | 3 +- .../TransferCurrenciesCorrectionsTest.php | 26 +- tests/Unit/Factory/RecurrenceFactoryTest.php | 10 +- 63 files changed, 1251 insertions(+), 812 deletions(-) create mode 100644 resources/views/v1/list/journals-array-tiny.twig diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php index 2d0ee59b49..439c8414dd 100644 --- a/app/Http/Controllers/Chart/BudgetReportController.php +++ b/app/Http/Controllers/Chart/BudgetReportController.php @@ -153,7 +153,7 @@ class BudgetReportController extends Controller $cache->addProperty($start); $cache->addProperty($end); if ($cache->has()) { - //return response()->json($cache->get()); // @codeCoverageIgnore + return response()->json($cache->get()); // @codeCoverageIgnore } $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); $function = app('navigation')->preferredEndOfPeriod($start, $end); diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php index e5f87f3b68..10fbc664e4 100644 --- a/app/Http/Controllers/Json/AutoCompleteController.php +++ b/app/Http/Controllers/Json/AutoCompleteController.php @@ -30,8 +30,10 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Log; @@ -54,7 +56,7 @@ class AutoCompleteController extends Controller public function accounts(Request $request): JsonResponse { $accountTypes = explode(',', $request->get('types') ?? ''); - $search = $request->get('query'); + $search = $request->get('search'); /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); @@ -92,15 +94,45 @@ class AutoCompleteController extends Controller } /** + * Searches in the titles of all transaction journals. + * The result is limited to the top 15 unique results. + * + * @param Request $request * @return JsonResponse - * @codeCoverageIgnore */ - public function budgets(): JsonResponse + public function allJournals(Request $request): JsonResponse { + $search = (string)$request->get('search'); + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $result = $repository->searchJournalDescriptions($search); + + // limit and unique + $filtered = $result->unique('description'); + $limited = $filtered->slice(0, 15); + $array = $limited->toArray(); + foreach ($array as $index => $item) { + // give another key for consistency + $array[$index]['name'] = $item['description']; + } + + + return response()->json($array); + + } + + /** + * @param Request $request + * @return JsonResponse + */ + public function budgets(Request $request): JsonResponse + { + $search = (string)$request->get('search'); /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class); + $result = $repository->searchBudget($search); - return response()->json($repository->getActiveBudgets()->toArray()); + return response()->json($result->toArray()); } /** @@ -111,7 +143,7 @@ class AutoCompleteController extends Controller */ public function categories(Request $request): JsonResponse { - $query = (string)$request->get('query'); + $query = (string)$request->get('search'); /** @var CategoryRepositoryInterface $repository */ $repository = app(CategoryRepositoryInterface::class); $result = $repository->searchCategory($query); @@ -119,6 +151,44 @@ class AutoCompleteController extends Controller return response()->json($result->toArray()); } + /** + * @param Request $request + * @return JsonResponse + */ + public function currencyNames(Request $request): JsonResponse + { + $query = (string)$request->get('search'); + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $result = $repository->searchCurrency($query)->toArray(); + foreach ($result as $index => $item) { + $result[$index]['name'] = sprintf('%s (%s)', $item['name'], $item['code']); + } + + return response()->json($result); + } + + /** + * @param Request $request + * + * @return JsonResponse + * @codeCoverageIgnore + */ + public function transactionTypes(Request $request): JsonResponse + { + $query = (string)$request->get('search'); + /** @var TransactionTypeRepositoryInterface $repository */ + $repository = app(TransactionTypeRepositoryInterface::class); + $array = $repository->searchTypes($query)->toArray(); + + foreach ($array as $index => $item) { + // different key for consistency. + $array[$index]['name'] = $item['type']; + } + + return response()->json($array); + } + /** * @return JsonResponse * @codeCoverageIgnore @@ -164,13 +234,18 @@ class AutoCompleteController extends Controller */ public function tags(Request $request): JsonResponse { - $query = (string)$request->get('query'); + $search = (string)$request->get('search'); /** @var TagRepositoryInterface $repository */ $repository = app(TagRepositoryInterface::class); - $result = $repository->searchTags($query); + $result = $repository->searchTags($search); + $array = $result->toArray(); + foreach ($array as $index => $item) { + // rename field for consistency. + $array[$index]['name'] = $item['tag']; + } - return response()->json($result->toArray()); + return response()->json($array); } } diff --git a/app/Http/Controllers/Recurring/CreateController.php b/app/Http/Controllers/Recurring/CreateController.php index 4a946fadc3..4606a6d6ac 100644 --- a/app/Http/Controllers/Recurring/CreateController.php +++ b/app/Http/Controllers/Recurring/CreateController.php @@ -46,6 +46,7 @@ class CreateController extends Controller /** * CreateController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Recurring/DeleteController.php b/app/Http/Controllers/Recurring/DeleteController.php index 8b7b548541..5ad24fce6c 100644 --- a/app/Http/Controllers/Recurring/DeleteController.php +++ b/app/Http/Controllers/Recurring/DeleteController.php @@ -39,6 +39,7 @@ class DeleteController extends Controller /** * DeleteController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -79,8 +80,8 @@ class DeleteController extends Controller * Destroy the recurring transaction. * * @param RecurringRepositoryInterface $repository - * @param Request $request - * @param Recurrence $recurrence + * @param Request $request + * @param Recurrence $recurrence * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ diff --git a/app/Http/Controllers/Recurring/EditController.php b/app/Http/Controllers/Recurring/EditController.php index 02ce761a85..e99b900814 100644 --- a/app/Http/Controllers/Recurring/EditController.php +++ b/app/Http/Controllers/Recurring/EditController.php @@ -47,6 +47,7 @@ class EditController extends Controller /** * EditController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -70,7 +71,7 @@ class EditController extends Controller /** * Edit a recurring transaction. * - * @param Request $request + * @param Request $request * @param Recurrence $recurrence * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View @@ -92,7 +93,7 @@ class EditController extends Controller $repetition = $recurrence->recurrenceRepetitions()->first(); $currentRepType = $repetition->repetition_type; if ('' !== $repetition->repetition_moment) { - $currentRepType .= ',' . $repetition->repetition_moment; + $currentRepType .= ',' . $repetition->repetition_moment; // @codeCoverageIgnore } // put previous url in session if not redirect from store (not "return_to_edit"). @@ -123,9 +124,12 @@ class EditController extends Controller $hasOldInput = null !== $request->old('_token'); $preFilled = [ - 'transaction_type' => strtolower($recurrence->transactionType->type), - 'active' => $hasOldInput ? (bool)$request->old('active') : $recurrence->active, - 'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : $recurrence->apply_rules, + 'transaction_type' => strtolower($recurrence->transactionType->type), + 'active' => $hasOldInput ? (bool)$request->old('active') : $recurrence->active, + 'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : $recurrence->apply_rules, + 'deposit_source_id' => $array['transactions'][0]['source_id'], + 'withdrawal_destination_id' => $array['transactions'][0]['destination_id'], + ]; return view( @@ -138,7 +142,7 @@ class EditController extends Controller * Update the recurring transaction. * * @param RecurrenceFormRequest $request - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws \FireflyIII\Exceptions\FireflyException diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index a4f66c5fa1..b5c7af9859 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -48,6 +48,7 @@ class IndexController extends Controller /** * IndexController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -121,8 +122,8 @@ class IndexController extends Controller $transformer = app(RecurrenceTransformer::class); $transformer->setParameters(new ParameterBag); - $array = $transformer->transform($recurrence); - $transactions = $this->recurring->getTransactions($recurrence); + $array = $transformer->transform($recurrence); + $groups = $this->recurring->getTransactions($recurrence); // transform dates back to Carbon objects: foreach ($array['recurrence_repetitions'] as $index => $repetition) { @@ -133,7 +134,7 @@ class IndexController extends Controller $subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]); - return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'transactions')); + return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups')); } } diff --git a/app/Http/Controllers/Report/ExpenseController.php b/app/Http/Controllers/Report/ExpenseController.php index 4a09328033..ef8e3d4d2a 100644 --- a/app/Http/Controllers/Report/ExpenseController.php +++ b/app/Http/Controllers/Report/ExpenseController.php @@ -47,6 +47,7 @@ class ExpenseController extends Controller /** * Constructor for ExpenseController + * @codeCoverageIgnore */ public function __construct() { @@ -252,11 +253,11 @@ class ExpenseController extends Controller $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); - $cache->addProperty('expense-budget'); + $cache->addProperty('top-expense'); $cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($expense->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + //return $cache->get(); // @codeCoverageIgnore } $combined = $this->combineAccounts($expense); $all = new Collection; @@ -268,11 +269,11 @@ class ExpenseController extends Controller $collector = app(GroupCollectorInterface::class); $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($accounts); - $collector->setAccounts($all); - $set = $collector->getExtractedJournals(); + $collector->setAccounts($all)->withAccountInformation(); + $sorted = $collector->getExtractedJournals(); - usort($set, function ($a, $b) { - return $a['amount'] <=> $b['amount']; + usort($sorted, function ($a, $b) { + return $a['amount'] <=> $b['amount']; // @codeCoverageIgnore }); try { @@ -304,11 +305,11 @@ class ExpenseController extends Controller $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); - $cache->addProperty('expense-budget'); + $cache->addProperty('top-income'); $cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($expense->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + //return $cache->get(); // @codeCoverageIgnore } $combined = $this->combineAccounts($expense); $all = new Collection; @@ -321,11 +322,15 @@ class ExpenseController extends Controller $collector = app(GroupCollectorInterface::class); $total = $accounts->merge($all); - $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total); - $journals = $collector->getExtractedJournals(); + $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total)->withAccountInformation(); + $sorted = $collector->getExtractedJournals(); - usort($journals, function ($a, $b) { - return $a['amount'] <=> $b['amount']; + foreach (array_keys($sorted) as $key) { + $sorted[$key]['amount'] = bcmul($sorted[$key]['amount'], '-1'); + } + + usort($sorted, function ($a, $b) { + return $a['amount'] <=> $b['amount']; // @codeCoverageIgnore }); try { diff --git a/app/Http/Controllers/Report/OperationsController.php b/app/Http/Controllers/Report/OperationsController.php index 8b8eca74c1..c5bffbba86 100644 --- a/app/Http/Controllers/Report/OperationsController.php +++ b/app/Http/Controllers/Report/OperationsController.php @@ -41,6 +41,7 @@ class OperationsController extends Controller /** * OperationsController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Rule/CreateController.php b/app/Http/Controllers/Rule/CreateController.php index 155ed5205f..b8030dfda4 100644 --- a/app/Http/Controllers/Rule/CreateController.php +++ b/app/Http/Controllers/Rule/CreateController.php @@ -45,6 +45,7 @@ class CreateController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Rule/DeleteController.php b/app/Http/Controllers/Rule/DeleteController.php index a568f2dd91..61f605ef2f 100644 --- a/app/Http/Controllers/Rule/DeleteController.php +++ b/app/Http/Controllers/Rule/DeleteController.php @@ -39,6 +39,7 @@ class DeleteController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Rule/EditController.php b/app/Http/Controllers/Rule/EditController.php index b474878cf6..d136c175ee 100644 --- a/app/Http/Controllers/Rule/EditController.php +++ b/app/Http/Controllers/Rule/EditController.php @@ -45,6 +45,7 @@ class EditController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Rule/IndexController.php b/app/Http/Controllers/Rule/IndexController.php index 53831990c1..4593b67de6 100644 --- a/app/Http/Controllers/Rule/IndexController.php +++ b/app/Http/Controllers/Rule/IndexController.php @@ -45,6 +45,7 @@ class IndexController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php index c8edd0cabd..02a26ca0e8 100644 --- a/app/Http/Controllers/Rule/SelectController.php +++ b/app/Http/Controllers/Rule/SelectController.php @@ -76,7 +76,7 @@ class SelectController extends Controller * Execute the given rule on a set of existing transactions. * * @param SelectTransactionsRequest $request - * @param Rule $rule + * @param Rule $rule * * @return RedirectResponse */ @@ -181,11 +181,12 @@ class SelectController extends Controller // Return json response $view = 'ERROR, see logs.'; try { - $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render(); + $view = view('list.journals-array-tiny', ['journals' => $matchingTransactions])->render(); // @codeCoverageIgnoreStart } catch (Throwable $exception) { Log::error(sprintf('Could not render view in testTriggers(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); + $view = sprintf('Could not render list.journals-tiny: %s', $exception->getMessage()); } // @codeCoverageIgnoreEnd @@ -236,17 +237,17 @@ class SelectController extends Controller // Warn the user if only a subset of transactions is returned $warning = ''; - if ($matchingTransactions->count() === $limit) { + if (count($matchingTransactions) === $limit) { $warning = (string)trans('firefly.warning_transaction_subset', ['max_num_transactions' => $limit]); // @codeCoverageIgnore } - if (0 === $matchingTransactions->count()) { + if (0 === count($matchingTransactions)) { $warning = (string)trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]); // @codeCoverageIgnore } // Return json response $view = 'ERROR, see logs.'; try { - $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render(); + $view = view('list.journals-array-tiny', ['journals' => $matchingTransactions])->render(); // @codeCoverageIgnoreStart } catch (Throwable $exception) { Log::error(sprintf('Could not render view in testTriggersByRule(): %s', $exception->getMessage())); diff --git a/app/Http/Controllers/Transaction/IndexController.php b/app/Http/Controllers/Transaction/IndexController.php index 479186ca23..fc03aaf7af 100644 --- a/app/Http/Controllers/Transaction/IndexController.php +++ b/app/Http/Controllers/Transaction/IndexController.php @@ -36,7 +36,31 @@ use Illuminate\Http\Request; */ class IndexController extends Controller { - use PeriodOverview; + use PeriodOverview; + + /** @var JournalRepositoryInterface */ + private $repository; + + /** + * IndexController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-credit-card'); + app('view')->share('title', (string)trans('firefly.accounts')); + + $this->repository = app(JournalRepositoryInterface::class); + + return $next($request); + } + ); + } /** * Index for a range of transactions. @@ -54,6 +78,7 @@ class IndexController extends Controller $types = config('firefly.transactionTypesByType.' . $objectType); $page = (int)$request->get('page'); $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $pageSize =3; if (null === $start) { $start = session('start'); $end = session('end'); @@ -62,16 +87,16 @@ class IndexController extends Controller $end = session('end'); } - if ($end < $start) { - [$start, $end] = [$end, $start]; - } - - $path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]); - + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; + $path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]); $startStr = $start->formatLocalized($this->monthAndDayFormat); $endStr = $end->formatLocalized($this->monthAndDayFormat); $subTitle = (string)trans(sprintf('firefly.title_%s_between', $objectType), ['start' => $startStr, 'end' => $endStr]); - $periods = $this->getTransactionPeriodOverview($objectType, $end); + + $firstJournal = $this->repository->firstNull(); + $startPeriod = null === $firstJournal ? new Carbon : $firstJournal->date; + $endPeriod = clone $end; + $periods = $this->getTransactionPeriodOverview($objectType, $startPeriod, $endPeriod); /** @var GroupCollectorInterface $collector */ $collector = app(GroupCollectorInterface::class); diff --git a/app/Http/Middleware/StartFireflySession.php b/app/Http/Middleware/StartFireflySession.php index 4b6ece87cc..513f49b74c 100644 --- a/app/Http/Middleware/StartFireflySession.php +++ b/app/Http/Middleware/StartFireflySession.php @@ -53,10 +53,10 @@ class StartFireflySession extends StartSession && 'GET' === $request->method() && !$request->ajax()) { $session->setPreviousUrl($uri); - Log::debug(sprintf('Will set previous URL to %s', $uri)); + //Log::debug(sprintf('Will set previous URL to %s', $uri)); return; } - Log::debug(sprintf('Will NOT set previous URL to %s', $uri)); + //Log::debug(sprintf('Will NOT set previous URL to %s', $uri)); } } diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php index 24cd086e7e..5863128db3 100644 --- a/app/Http/Requests/RecurrenceFormRequest.php +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -29,6 +29,9 @@ use FireflyIII\Models\Recurrence; use FireflyIII\Models\TransactionType; use FireflyIII\Rules\ValidRecurrenceRepetitionType; use FireflyIII\Rules\ValidRecurrenceRepetitionValue; +use FireflyIII\Validation\AccountValidator; +use Illuminate\Validation\Validator; +use Log; /** * Class RecurrenceFormRequest @@ -136,6 +139,86 @@ class RecurrenceFormRequest extends Request return $return; } + + /** + * Configure the validator instance with special rules for after the basic validation rules. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + // validate all account info + $this->validateAccountInformation($validator); + } + ); + } + + /** + * Validates the given account information. Switches on given transaction type. + * + * @param Validator $validator + * @throws FireflyException + */ + public function validateAccountInformation(Validator $validator): void + { + Log::debug('Now in validateAccountInformation()'); + /** @var AccountValidator $accountValidator */ + $accountValidator = app(AccountValidator::class); + $data = $validator->getData(); + $transactionType = $data['transaction_type'] ?? 'invalid'; + + $accountValidator->setTransactionType($transactionType); + + // default values: + $sourceId = null; + $destinationId = null; + + switch ($this->string('transaction_type')) { + default: + throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->string('transaction_type'))); // @codeCoverageIgnore + case 'withdrawal': + $sourceId = (int)$data['source_id']; + $destinationId = (int)$data['withdrawal_destination_id']; + break; + case 'deposit': + $sourceId = (int)$data['deposit_source_id']; + $destinationId = (int)$data['destination_id']; + break; + case 'transfer': + $sourceId = (int)$data['source_id']; + $destinationId = (int)$data['destination_id']; + break; + } + + + // validate source account. + $validSource = $accountValidator->validateSource($sourceId, null); + + // do something with result: + if (false === $validSource) { + $message = (string)trans('validation.generic_invalid_source'); + $validator->errors()->add('source_id', $message); + $validator->errors()->add('deposit_source_id', $message); + + return; + } + + // validate destination account + $validDestination = $accountValidator->validateDestination($destinationId, null); + // do something with result: + if (false === $validDestination) { + $message = (string)trans('validation.generic_invalid_destination'); + $validator->errors()->add('destination_id', $message); + $validator->errors()->add('withdrawal_destination_id', $message); + + return; + } + } + /** * The rules for this request. * diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index a6352a596e..03e51e17b5 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -647,9 +647,13 @@ class BudgetRepository implements BudgetRepositoryInterface */ public function searchBudget(string $query): Collection { - $query = sprintf('%%%s%%', $query); - return $this->user->budgets()->where('name', 'LIKE', $query)->get(); + $search = $this->user->budgets(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%%%s%%', $query)); + } + + return $search->get(); } /** diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 89c91acfb0..771482a788 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -111,22 +111,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $collector->getExtractedJournals(); } - /** - * @param array $journals - * @return string - */ - private function sumJournals(array $journals): string - { - $sum = '0'; - /** @var array $journal */ - foreach ($journals as $journal) { - $amount = (string)$journal['amount']; - $sum = bcadd($sum, $amount); - } - - return $sum; - } - /** * A very cryptic method name that means: * @@ -156,7 +140,7 @@ class CategoryRepository implements CategoryRepositoryInterface $currencyId = (int)$journal['currency_id']; if (!isset($return[$currencyId])) { $return[$currencyId] = [ - 'earned' => '0', + 'earned' => '0', 'currency_id' => $currencyId, 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code'], @@ -264,8 +248,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $result; } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * Find a category or return NULL * @@ -278,6 +260,8 @@ class CategoryRepository implements CategoryRepositoryInterface return $this->user->categories()->find($categoryId); } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Find a category. * @@ -297,8 +281,6 @@ class CategoryRepository implements CategoryRepositoryInterface return null; } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * @param array $data * @@ -313,6 +295,8 @@ class CategoryRepository implements CategoryRepositoryInterface return $factory->findOrCreate(null, $data['name']); } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Category $category * @@ -341,47 +325,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $firstJournalDate; } - /** - * @param Category $category - * - * @return Carbon|null - */ - private function getFirstJournalDate(Category $category): ?Carbon - { - $query = $category->transactionJournals()->orderBy('date', 'ASC'); - $result = $query->first(['transaction_journals.*']); - - if (null !== $result) { - return $result->date; - } - - return null; - } - - /** @noinspection MoreThanThreeArgumentsInspection */ - - /** - * @param Category $category - * - * @return Carbon|null - */ - private function getFirstTransactionDate(Category $category): ?Carbon - { - // check transactions: - $query = $category->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->orderBy('transaction_journals.date', 'ASC'); - - $lastTransaction = $query->first(['transaction_journals.*']); - if (null !== $lastTransaction) { - return new Carbon($lastTransaction->date); - } - - return null; - } - - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * Get all categories with ID's. * @@ -394,8 +337,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $this->user->categories()->whereIn('id', $categoryIds)->get(); } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * @param Category $category * @param Collection $accounts @@ -426,55 +367,7 @@ class CategoryRepository implements CategoryRepositoryInterface return $lastJournalDate; } - /** - * @param Category $category - * @param Collection $accounts - * - * @return Carbon|null - */ - private function getLastJournalDate(Category $category, Collection $accounts): ?Carbon - { - $query = $category->transactionJournals()->orderBy('date', 'DESC'); - - if ($accounts->count() > 0) { - $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id'); - $query->whereIn('t.account_id', $accounts->pluck('id')->toArray()); - } - - $result = $query->first(['transaction_journals.*']); - - if (null !== $result) { - return $result->date; - } - - return null; - } - - /** - * @param Category $category - * @param Collection $accounts - * - * @return Carbon|null - * @throws \Exception - */ - private function getLastTransactionDate(Category $category, Collection $accounts): ?Carbon - { - // check transactions: - $query = $category->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->orderBy('transaction_journals.date', 'DESC'); - if ($accounts->count() > 0) { - // filter journals: - $query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray()); - } - - $lastTransaction = $query->first(['transaction_journals.*']); - if (null !== $lastTransaction) { - return new Carbon($lastTransaction->date); - } - - return null; - } + /** @noinspection MoreThanThreeArgumentsInspection */ /** * @param Collection $categories @@ -518,6 +411,8 @@ class CategoryRepository implements CategoryRepositoryInterface return $data; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Collection $accounts * @param Carbon $start @@ -555,6 +450,8 @@ class CategoryRepository implements CategoryRepositoryInterface return $result; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Collection $categories * @param Collection $accounts @@ -644,9 +541,12 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function searchCategory(string $query): Collection { - $query = sprintf('%%%s%%', $query); + $search = $this->user->categories(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%%%s%%', $query)); + } - return $this->user->categories()->where('name', 'LIKE', $query)->get(); + return $search->get(); } /** @@ -831,4 +731,107 @@ class CategoryRepository implements CategoryRepositoryInterface return $service->update($category, $data); } + + /** + * @param array $journals + * @return string + */ + private function sumJournals(array $journals): string + { + $sum = '0'; + /** @var array $journal */ + foreach ($journals as $journal) { + $amount = (string)$journal['amount']; + $sum = bcadd($sum, $amount); + } + + return $sum; + } + + /** + * @param Category $category + * + * @return Carbon|null + */ + private function getFirstJournalDate(Category $category): ?Carbon + { + $query = $category->transactionJournals()->orderBy('date', 'ASC'); + $result = $query->first(['transaction_journals.*']); + + if (null !== $result) { + return $result->date; + } + + return null; + } + + /** + * @param Category $category + * + * @return Carbon|null + */ + private function getFirstTransactionDate(Category $category): ?Carbon + { + // check transactions: + $query = $category->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'ASC'); + + $lastTransaction = $query->first(['transaction_journals.*']); + if (null !== $lastTransaction) { + return new Carbon($lastTransaction->date); + } + + return null; + } + + /** + * @param Category $category + * @param Collection $accounts + * + * @return Carbon|null + */ + private function getLastJournalDate(Category $category, Collection $accounts): ?Carbon + { + $query = $category->transactionJournals()->orderBy('date', 'DESC'); + + if ($accounts->count() > 0) { + $query->leftJoin('transactions as t', 't.transaction_journal_id', '=', 'transaction_journals.id'); + $query->whereIn('t.account_id', $accounts->pluck('id')->toArray()); + } + + $result = $query->first(['transaction_journals.*']); + + if (null !== $result) { + return $result->date; + } + + return null; + } + + /** + * @param Category $category + * @param Collection $accounts + * + * @return Carbon|null + * @throws \Exception + */ + private function getLastTransactionDate(Category $category, Collection $accounts): ?Carbon + { + // check transactions: + $query = $category->transactions() + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->orderBy('transaction_journals.date', 'DESC'); + if ($accounts->count() > 0) { + // filter journals: + $query->whereIn('transactions.account_id', $accounts->pluck('id')->toArray()); + } + + $lastTransaction = $query->first(['transaction_journals.*']); + if (null !== $lastTransaction) { + return new Carbon($lastTransaction->date); + } + + return null; + } } diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index f17d874fd2..4256266dd6 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -258,7 +258,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface /** * Find by object, ID or code. Returns user default or system default. * - * @param int|null $currencyId + * @param int|null $currencyId * @param string|null $currencyCode * * @return TransactionCurrency|null @@ -288,7 +288,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface /** * Find by object, ID or code. Returns NULL if nothing found. * - * @param int|null $currencyId + * @param int|null $currencyId * @param string|null $currencyCode * * @return TransactionCurrency|null @@ -369,7 +369,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface * * @param TransactionCurrency $fromCurrency * @param TransactionCurrency $toCurrency - * @param Carbon $date + * @param Carbon $date * * @return CurrencyExchangeRate|null */ @@ -438,7 +438,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface /** * @param TransactionCurrency $currency - * @param array $data + * @param array $data * * @return TransactionCurrency */ @@ -449,4 +449,18 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $service->update($currency, $data); } + + /** + * @param string $search + * @return Collection + */ + public function searchCurrency(string $search): Collection + { + $query = TransactionCurrency::where('enabled', 1); + if ('' !== $search) { + $query->where('name', 'LIKE', sprintf('%%%s%%', $search)); + } + + return $query->get(); + } } diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 5122588827..9dc3105287 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -34,6 +34,12 @@ use Illuminate\Support\Collection; */ interface CurrencyRepositoryInterface { + /** + * @param string $search + * @return Collection + */ + public function searchCurrency(string $search): Collection; + /** * @param TransactionCurrency $currency * diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 5340c32f0e..6b1d3a30be 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -54,6 +54,8 @@ use stdClass; */ class JournalRepository implements JournalRepositoryInterface { + + /** @var User */ private $user; @@ -67,7 +69,25 @@ class JournalRepository implements JournalRepositoryInterface } } + /** + * Search in journal descriptions. + * + * @param string $search + * @return Collection + */ + public function searchJournalDescriptions(string $search): Collection + { + $query = $this->user->transactionJournals() + ->orderBy('date', 'DESC'); + if ('' !== $query) { + $query->where('description', 'LIKE', sprintf('%%%s%%', $search)); + } + + return $query->get(); + } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param TransactionJournal $journal * @param TransactionType $type diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 5a3b6a5cf0..0fe12b0ce1 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -41,6 +41,14 @@ use Illuminate\Support\MessageBag; interface JournalRepositoryInterface { + /** + * Search in journal descriptions. + * + * @param string $search + * @return Collection + */ + public function searchJournalDescriptions(string $search): Collection; + /** * Get all transaction journals with a specific type, regardless of user. * diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index e6074daad6..4248272e2e 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -303,13 +303,11 @@ class RecurringRepository implements RecurringRepositoryInterface } /** - * TODO check usage and verify it still works. - * * @param Recurrence $recurrence * * @return Collection */ - public function getTransactions(Recurrence $recurrence): array + public function getTransactions(Recurrence $recurrence): Collection { $journalMeta = TransactionJournalMeta ::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') @@ -319,9 +317,17 @@ class RecurringRepository implements RecurringRepositoryInterface ->where('data', json_encode((string)$recurrence->id)) ->get()->pluck('transaction_journal_id')->toArray(); $search = []; + + + foreach ($journalMeta as $journalId) { $search[] = (int)$journalId; } + if (0 === count($search)) { + + return []; + } + /** @var GroupCollectorInterface $collector */ $collector = app(GroupCollectorInterface::class); @@ -330,7 +336,7 @@ class RecurringRepository implements RecurringRepositoryInterface // filter on specific journals. $collector->setJournalIds($search); - return $collector->getExtractedJournals(); + return $collector->getGroups(); } /** diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index 15e89f53d3..ee609f685b 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -141,9 +141,9 @@ interface RecurringRepositoryInterface /** * @param Recurrence $recurrence * - * @return array + * @return Collection */ - public function getTransactions(Recurrence $recurrence): array; + public function getTransactions(Recurrence $recurrence): Collection; /** * Calculate the next X iterations starting on the date given in $date. diff --git a/app/Repositories/TransactionType/TransactionTypeRepository.php b/app/Repositories/TransactionType/TransactionTypeRepository.php index bb7cab684a..0e139c6787 100644 --- a/app/Repositories/TransactionType/TransactionTypeRepository.php +++ b/app/Repositories/TransactionType/TransactionTypeRepository.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\TransactionType; use FireflyIII\Models\TransactionType; +use Illuminate\Support\Collection; use Log; /** @@ -46,7 +47,7 @@ class TransactionTypeRepository implements TransactionTypeRepositoryInterface /** * @param TransactionType|null $type - * @param string|null $typeString + * @param string|null $typeString * * @return TransactionType */ @@ -67,4 +68,17 @@ class TransactionTypeRepository implements TransactionTypeRepositoryInterface return $search; } + + /** + * @param string $query + * @return Collection + */ + public function searchTypes(string $query): Collection + { + if ('' === $query) { + return TransactionType::get(); + } + + return TransactionType::where('type', 'LIKE', sprintf('%%%s%%', $query))->get(); + } } \ No newline at end of file diff --git a/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php b/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php index d6e4f2679f..0300889cf5 100644 --- a/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php +++ b/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\TransactionType; use FireflyIII\Models\TransactionType; +use Illuminate\Support\Collection; /** * Interface TransactionTypeRepositoryInterface @@ -44,4 +45,10 @@ interface TransactionTypeRepositoryInterface * @return TransactionType|null */ public function findByType(string $type): ?TransactionType; + + /** + * @param string $query + * @return Collection + */ + public function searchTypes(string $query): Collection; } \ No newline at end of file diff --git a/app/Support/Binder/AccountList.php b/app/Support/Binder/AccountList.php index 500d6e1aed..2ef02c9de8 100644 --- a/app/Support/Binder/AccountList.php +++ b/app/Support/Binder/AccountList.php @@ -36,7 +36,7 @@ class AccountList implements BinderInterface /** * @param string $value - * @param Route $route + * @param Route $route * * @return Collection * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException @@ -44,25 +44,29 @@ class AccountList implements BinderInterface */ public static function routeBinder(string $value, Route $route): Collection { + Log::debug(sprintf('Now in AccountList::routeBinder("%s")', $value)); if (auth()->check()) { + Log::debug('User is logged in.'); $collection = new Collection; if ('allAssetAccounts' === $value) { - /** @var \Illuminate\Support\Collection $collection */ + /** @var Collection $collection */ $collection = auth()->user()->accounts() ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->where('account_types.type', AccountType::ASSET) ->orderBy('accounts.name', 'ASC') ->get(['accounts.*']); + Log::debug(sprintf('Collection length is %d', $collection->count())); } if ('allAssetAccounts' !== $value) { $incoming = array_map('\intval', explode(',', $value)); $list = array_merge(array_unique($incoming), [0]); - /** @var \Illuminate\Support\Collection $collection */ + /** @var Collection $collection */ $collection = auth()->user()->accounts() ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->whereIn('accounts.id', $list) ->orderBy('accounts.name', 'ASC') ->get(['accounts.*']); + Log::debug(sprintf('Collection length is %d', $collection->count())); } if ($collection->count() > 0) { diff --git a/app/Support/Binder/BudgetList.php b/app/Support/Binder/BudgetList.php index 731cf20b24..629b282879 100644 --- a/app/Support/Binder/BudgetList.php +++ b/app/Support/Binder/BudgetList.php @@ -25,6 +25,7 @@ namespace FireflyIII\Support\Binder; use FireflyIII\Models\Budget; use Illuminate\Routing\Route; use Illuminate\Support\Collection; +use Log; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -34,7 +35,7 @@ class BudgetList implements BinderInterface { /** * @param string $value - * @param Route $route + * @param Route $route * * @return Collection * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException @@ -42,27 +43,36 @@ class BudgetList implements BinderInterface */ public static function routeBinder(string $value, Route $route): Collection { + Log::debug(sprintf('Now in BudgetList::routeBinder("%s")', $value)); if (auth()->check()) { $list = array_unique(array_map('\intval', explode(',', $value))); + Log::debug('List is now', $list); if (0 === count($list)) { + Log::warning('List count is zero, return 404.'); throw new NotFoundHttpException; // @codeCoverageIgnore } - /** @var \Illuminate\Support\Collection $collection */ + /** @var Collection $collection */ $collection = auth()->user()->budgets() ->where('active', 1) ->whereIn('id', $list) ->get(); + Log::debug(sprintf('Found %d active budgets', $collection->count()), $list); // add empty budget if applicable. if (in_array(0, $list, true)) { + Log::debug('Add empty budget because $list contains 0.'); $collection->push(new Budget); } if ($collection->count() > 0) { + Log::debug(sprintf('List length is > 0 (%d), so return it.', $collection->count())); + return $collection; } + Log::debug('List length is zero, fall back to 404.'); } + Log::debug('Final fallback to 404.'); throw new NotFoundHttpException; } } diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index 6bd00d27a2..e04eeedd8d 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -102,10 +102,7 @@ class ExpandedForm { // make repositories /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); - + $repository = app(AccountRepositoryInterface::class); $accountList = $repository->getActiveAccountsByType( [AccountType::ASSET, AccountType::DEFAULT, AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN,] ); @@ -113,12 +110,15 @@ class ExpandedForm $defaultCurrency = app('amount')->getDefaultCurrency(); $grouped = []; // group accounts: + /** @var Account $account */ foreach ($accountList as $account) { - $balance = app('steam')->balance($account, new Carbon); - $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); - $currency = $currencyRepos->findNull($currencyId); - $role = $repository->getMetaValue($account, 'account_role'); + $balance = app('steam')->balance($account, new Carbon); + + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + + $role = $repository->getMetaValue($account, 'account_role'); + if ('' === $role && !in_array($account->accountType->type, $liabilityTypes, true)) { $role = 'no_account_type'; // @codeCoverageIgnore } @@ -126,15 +126,11 @@ class ExpandedForm if (in_array($account->accountType->type, $liabilityTypes, true)) { $role = 'l_' . $account->accountType->type; // @codeCoverageIgnore } - - if (null === $currency) { - $currency = $defaultCurrency; - } - $key = (string)trans('firefly.opt_group_' . $role); $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; } + return $this->select($name, $grouped, $value, $options); } @@ -152,8 +148,6 @@ class ExpandedForm // make repositories /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); $accountList = $repository->getActiveAccountsByType( [ @@ -176,10 +170,9 @@ class ExpandedForm // group accounts: /** @var Account $account */ foreach ($accountList as $account) { - $balance = app('steam')->balance($account, new Carbon); - $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); - $currency = $currencyRepos->findNull($currencyId); - $role = (string)$repository->getMetaValue($account, 'account_role'); + $balance = app('steam')->balance($account, new Carbon); + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + $role = (string)$repository->getMetaValue($account, 'account_role'); if ('' === $role && !in_array($account->accountType->type, $liabilityTypes, true)) { $role = 'no_account_type'; // @codeCoverageIgnore } @@ -217,8 +210,6 @@ class ExpandedForm // make repositories /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); $accountList = $repository->getActiveAccountsByType( [ @@ -241,10 +232,9 @@ class ExpandedForm // group accounts: /** @var Account $account */ foreach ($accountList as $account) { - $balance = app('steam')->balance($account, new Carbon); - $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); - $currency = $currencyRepos->findNull($currencyId); - $role = (string)$repository->getMetaValue($account, 'account_role'); + $balance = app('steam')->balance($account, new Carbon); + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + $role = (string)$repository->getMetaValue($account, 'account_role'); if ('' === $role && !in_array($account->accountType->type, $liabilityTypes, true)) { $role = 'no_account_type'; // @codeCoverageIgnore } @@ -257,10 +247,6 @@ class ExpandedForm $role = 'l_' . $account->accountType->type; // @codeCoverageIgnore } - if (null === $currency) { - $currency = $defaultCurrency; - } - $key = (string)trans('firefly.opt_group_' . $role); $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; } diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index d6a1f36286..fb842b02cf 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -30,7 +30,6 @@ use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; @@ -81,10 +80,7 @@ trait PeriodOverview protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array { $range = app('preferences')->get('viewRange', '1M')->data; - - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; // properties for cache $cache = new CacheProperties; @@ -159,10 +155,7 @@ trait PeriodOverview protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array { $range = app('preferences')->get('viewRange', '1M')->data; - - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; // properties for entries with their amounts. $cache = new CacheProperties(); @@ -173,7 +166,7 @@ trait PeriodOverview $cache->addProperty($category->id); if ($cache->has()) { - //return $cache->get(); // @codeCoverageIgnore + return $cache->get(); // @codeCoverageIgnore } /** @var array $dates */ $dates = app('navigation')->blockPeriods($start, $end, $range); @@ -240,9 +233,7 @@ trait PeriodOverview { $range = app('preferences')->get('viewRange', '1M')->data; - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; $cache = new CacheProperties; $cache->addProperty($start); @@ -250,7 +241,7 @@ trait PeriodOverview $cache->addProperty('no-budget-period-entries'); if ($cache->has()) { - //return $cache->get(); // @codeCoverageIgnore + return $cache->get(); // @codeCoverageIgnore } /** @var array $dates */ @@ -284,6 +275,8 @@ trait PeriodOverview } /** + * TODO fix date. + * * Show period overview for no category view. * * @param Carbon $theDate @@ -309,7 +302,7 @@ trait PeriodOverview $cache->addProperty('no-category-period-entries'); if ($cache->has()) { - //return $cache->get(); // @codeCoverageIgnore + return $cache->get(); // @codeCoverageIgnore } $dates = app('navigation')->blockPeriods($start, $end, $range); @@ -379,11 +372,7 @@ trait PeriodOverview /** @var Carbon $end */ $start = clone $date; $end = $repository->firstUseDate($tag) ?? new Carbon; - - - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; // properties for entries with their amounts. $cache = new CacheProperties; @@ -431,63 +420,60 @@ trait PeriodOverview * @param string $transactionType * @param Carbon $endDate * - * @return Collection + * @return array */ - protected function getTransactionPeriodOverview(string $transactionType, Carbon $endDate): Collection + protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array { - die('not yet complete'); - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $range = app('preferences')->get('viewRange', '1M')->data; - $endJournal = $repository->firstNull(); - $end = null === $endJournal ? new Carbon : $endJournal->date; - $start = clone $endDate; - $types = config('firefly.transactionTypesByType.' . $transactionType); + $range = app('preferences')->get('viewRange', '1M')->data; + $types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType)); + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore + // properties for cache + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty('transactions-period-entries'); + $cache->addProperty($transactionType); + if ($cache->has()) { + // return $cache->get(); // @codeCoverageIgnore } - /** @var array $dates */ $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; + $entries = []; + + // collect all journals in this period (regardless of type) + $collector = app(GroupCollectorInterface::class); + $collector->setTypes($types)->setRange($start, $end); + $genericSet = $collector->getExtractedJournals(); foreach ($dates as $currentDate) { - /** @var GroupCollectorInterface $collector */ - $collector = app(GroupCollectorInterface::class); - $collector->setTypes($types)->setRange($currentDate['start'], $currentDate['end']); - $journals = $collector->getExtractedJournals(); - $amounts = $this->getJournalsSum($journals); - $spent = []; $earned = []; $transferred = []; + $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); // set to correct array if ('expenses' === $transactionType || 'withdrawal' === $transactionType) { - $spent = $amounts; + $spent = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']); } if ('revenue' === $transactionType || 'deposit' === $transactionType) { - $earned = $amounts; + $earned = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']); } if ('transfer' === $transactionType || 'transfers' === $transactionType) { - $transferred = $amounts; + $transferred = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']); } - $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); - $entries->push( + $entries[] = [ - 'transactions' => $amounts['count'], - 'title' => $title, - 'spent' => $spent, - 'earned' => $earned, - 'transferred' => $transferred, - 'route' => route( - 'transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')] - ), - ] - ); + 'title' => $title, + 'route' => + route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } return $entries; diff --git a/app/Support/Http/Controllers/RequestInformation.php b/app/Support/Http/Controllers/RequestInformation.php index 062d79f1fc..af803a9047 100644 --- a/app/Support/Http/Controllers/RequestInformation.php +++ b/app/Support/Http/Controllers/RequestInformation.php @@ -178,7 +178,7 @@ trait RequestInformation // both must be array and either must be > 0 if (count($intro) > 0 || count($specialIntro) > 0) { $shownDemo = app('preferences')->get($key, false)->data; - Log::debug(sprintf('Check if user has already seen intro with key "%s". Result is %s', $key, var_export($shownDemo, true))); + //Log::debug(sprintf('Check if user has already seen intro with key "%s". Result is %s', $key, var_export($shownDemo, true))); } if (!is_bool($shownDemo)) { $shownDemo = true; diff --git a/app/Support/Http/Controllers/UserNavigation.php b/app/Support/Http/Controllers/UserNavigation.php index dc135e6133..2152c8d4d7 100644 --- a/app/Support/Http/Controllers/UserNavigation.php +++ b/app/Support/Http/Controllers/UserNavigation.php @@ -50,7 +50,7 @@ trait UserNavigation */ protected function getPreviousUri(string $identifier): string { - Log::debug(sprintf('Trying to retrieve URL stored under "%s"', $identifier)); + // Log::debug(sprintf('Trying to retrieve URL stored under "%s"', $identifier)); // "forbidden" words for specific identifiers: // if these are in the previous URI, don't refer back there. $array = [ @@ -67,24 +67,24 @@ trait UserNavigation 'transactions.mass-delete.uri' => '/transactions/show/', ]; $forbidden = $array[$identifier] ?? '/show/'; - Log::debug(sprintf('The forbidden word for %s is "%s"', $identifier, $forbidden)); + //Log::debug(sprintf('The forbidden word for %s is "%s"', $identifier, $forbidden)); $uri = (string)session($identifier); - Log::debug(sprintf('The URI is %s', $uri)); + //Log::debug(sprintf('The URI is %s', $uri)); if ( !(false === strpos($identifier, 'delete')) && !(false === strpos($uri, $forbidden))) { $uri = $this->redirectUri; - Log::debug(sprintf('URI is now %s (identifier contains "delete")', $uri)); + //Log::debug(sprintf('URI is now %s (identifier contains "delete")', $uri)); } if (!(false === strpos($uri, 'jscript'))) { $uri = $this->redirectUri; // @codeCoverageIgnore - Log::debug(sprintf('URI is now %s (uri contains jscript)', $uri)); + //Log::debug(sprintf('URI is now %s (uri contains jscript)', $uri)); } // more debug notes: - Log::debug(sprintf('strpos($identifier, "delete"): %s', var_export(strpos($identifier, 'delete'), true))); - Log::debug(sprintf('strpos($uri, $forbidden): %s', var_export(strpos($uri, $forbidden), true))); + //Log::debug(sprintf('strpos($identifier, "delete"): %s', var_export(strpos($identifier, 'delete'), true))); + //Log::debug(sprintf('strpos($uri, $forbidden): %s', var_export(strpos($uri, $forbidden), true))); return $uri; } @@ -154,10 +154,10 @@ trait UserNavigation if (null === $errors || (null !== $errors && 0 === $errors->count())) { $url = app('url')->previous(); session()->put($identifier, $url); - Log::debug(sprintf('Will put previous URI in cache under key %s: %s', $identifier, $url)); + //Log::debug(sprintf('Will put previous URI in cache under key %s: %s', $identifier, $url)); return; } - Log::debug(sprintf('The users session contains errors somehow so we will not remember the URI!: %s', var_export($errors, true))); + //Log::debug(sprintf('The users session contains errors somehow so we will not remember the URI!: %s', var_export($errors, true))); } } diff --git a/app/TransactionRules/TransactionMatcher.php b/app/TransactionRules/TransactionMatcher.php index ac01df0d57..f54cd3dc23 100644 --- a/app/TransactionRules/TransactionMatcher.php +++ b/app/TransactionRules/TransactionMatcher.php @@ -265,10 +265,10 @@ class TransactionMatcher // - all transactions have been fetched from the database // - the maximum number of transactions to return has been found // - the maximum number of transactions to search in have been searched - $pageSize = min($this->searchLimit, min($this->triggeredLimit, 50)); - $processed = 0; - $page = 1; - $result = []; + $pageSize = min($this->searchLimit, min($this->triggeredLimit, 50)); + $processed = 0; + $page = 1; + $totalResult = []; Log::debug(sprintf('Search limit is %d, triggered limit is %d, so page size is %d', $this->searchLimit, $this->triggeredLimit, $pageSize)); @@ -322,8 +322,8 @@ class TransactionMatcher Log::debug(sprintf('Found %d journals that match.', count($filtered))); // merge: - $result = $result + $filtered; - Log::debug(sprintf('Total count is now %d', count($result))); + $totalResult = $totalResult + $filtered; + Log::debug(sprintf('Total count is now %d', count($totalResult))); // Update counters ++$page; @@ -333,7 +333,7 @@ class TransactionMatcher // Check for conditions to finish the loop $reachedEndOfList = count($journals) < 1; - $foundEnough = count($result) >= $this->triggeredLimit; + $foundEnough = count($totalResult) >= $this->triggeredLimit; $searchedEnough = ($processed >= $this->searchLimit); Log::debug(sprintf('reachedEndOfList: %s', var_export($reachedEndOfList, true))); @@ -342,6 +342,6 @@ class TransactionMatcher } while (!$reachedEndOfList && !$foundEnough && !$searchedEnough); Log::debug('End of do-loop'); - return $result; + return $totalResult; } } diff --git a/app/TransactionRules/Triggers/CurrencyIs.php b/app/TransactionRules/Triggers/CurrencyIs.php index d936a47a5e..03fdc9b5b6 100644 --- a/app/TransactionRules/Triggers/CurrencyIs.php +++ b/app/TransactionRules/Triggers/CurrencyIs.php @@ -70,8 +70,15 @@ final class CurrencyIs extends AbstractTrigger implements TriggerInterface { /** @var CurrencyRepositoryInterface $repository */ $repository = app(CurrencyRepositoryInterface::class); - $currency = $repository->findByNameNull($this->triggerValue); - $hit = true; + + // if currency name contains " (" + if (0 === strpos($this->triggerValue, ' (')) { + $parts = explode(' (', $this->triggerValue); + $this->triggerValue = $parts[0]; + } + + $currency = $repository->findByNameNull($this->triggerValue); + $hit = true; if (null !== $currency) { /** @var Transaction $transaction */ foreach ($journal->transactions as $transaction) { diff --git a/app/Validation/TransactionValidation.php b/app/Validation/TransactionValidation.php index 7e523879e6..62dd43259a 100644 --- a/app/Validation/TransactionValidation.php +++ b/app/Validation/TransactionValidation.php @@ -41,7 +41,7 @@ trait TransactionValidation */ public function validateAccountInformation(Validator $validator): void { - Log::debug('Now in validateAccountInformation()'); + //Log::debug('Now in validateAccountInformation()'); $data = $validator->getData(); $transactionType = $data['type'] ?? 'invalid'; diff --git a/public/v1/css/firefly.css b/public/v1/css/firefly.css index 03eb004b8a..92ef770452 100644 --- a/public/v1/css/firefly.css +++ b/public/v1/css/firefly.css @@ -19,6 +19,10 @@ */ +.no-margin-pagination {padding-bottom:0;padding-top:0;} +.no-margin-pagination ul.pagination {margin:0 !important;} + + input.ti-new-tag-input { font-size: 14px !important; line-height: 1.42857143; diff --git a/public/v1/js/ff/recurring/create.js b/public/v1/js/ff/recurring/create.js index 8eb77f162c..da87215c5a 100644 --- a/public/v1/js/ff/recurring/create.js +++ b/public/v1/js/ff/recurring/create.js @@ -189,13 +189,13 @@ function updateFormFields() { // show source account ID: $('#source_id_holder').show(); + // show destination name: + // $('#destination_name_holder').show(); // old one + $('#withdrawal_destination_id_holder').show(); + // hide destination ID: $('#destination_id_holder').hide(); - // show destination name: - //$('#destination_name_holder').show(); // old one - $('#withdrawal_destination_id_holder').show(); - // show budget $('#budget_id_holder').show(); @@ -212,7 +212,6 @@ function updateFormFields() { // $('#destination_name_holder').hide(); // old one $('#withdrawal_destination_id_holder').hide(); - $('#destination_id_holder').show(); $('#budget_id_holder').hide(); $('#piggy_bank_id_holder').hide(); diff --git a/public/v1/js/ff/recurring/edit.js b/public/v1/js/ff/recurring/edit.js index 8127059ed4..bc5ea5d8e7 100644 --- a/public/v1/js/ff/recurring/edit.js +++ b/public/v1/js/ff/recurring/edit.js @@ -184,13 +184,15 @@ function updateFormFields() { if (transactionType === 'withdrawal') { // hide source account name: - $('#source_name_holder').hide(); + // $('#source_name_holder').hide(); // no longer used + $('#deposit_source_id_holder').hide(); // show source account ID: $('#source_id_holder').show(); // show destination name: - $('#destination_name_holder').show(); + // $('#destination_name_holder').show(); // no longer used. + $('#withdrawal_destination_id_holder').show(); // hide destination ID: $('#destination_id_holder').hide(); @@ -203,18 +205,28 @@ function updateFormFields() { } if (transactionType === 'deposit') { - $('#source_name_holder').show(); + // $('#source_name_holder').show(); // no longer used + $('#deposit_source_id_holder').show(); + $('#source_id_holder').hide(); - $('#destination_name_holder').hide(); + + // $('#destination_name_holder').hide(); // no longer used + $('#withdrawal_destination_id_holder').hide(); + $('#destination_id_holder').show(); $('#budget_id_holder').hide(); $('#piggy_bank_id_holder').hide(); } if (transactionType === 'transfer') { - $('#source_name_holder').hide(); + // $('#source_name_holder').hide(); // no longer used + $('#deposit_source_id_holder').hide(); + $('#source_id_holder').show(); - $('#destination_name_holder').hide(); + + // $('#destination_name_holder').hide(); // no longer used + $('#withdrawal_destination_id_holder').show(); + $('#destination_id_holder').show(); $('#budget_id_holder').hide(); $('#piggy_bank_id_holder').show(); diff --git a/public/v1/js/ff/rules/create-edit.js b/public/v1/js/ff/rules/create-edit.js index 3a62d58b16..a7939c9248 100644 --- a/public/v1/js/ff/rules/create-edit.js +++ b/public/v1/js/ff/rules/create-edit.js @@ -307,7 +307,7 @@ function updateTriggerInput(selectList) { case 'to_account_is': case 'to_account_contains': console.log('Select list value is ' + selectList.val() + ', so input needs auto complete.'); - createAutoComplete(inputResult, 'json/all-accounts'); + createAutoComplete(inputResult, 'json/accounts'); break; case 'tag_is': console.log('Select list value is ' + selectList.val() + ', so input needs auto complete.'); @@ -377,8 +377,8 @@ function createAutoComplete(input, URI) { prefetch: { url: URI + '?uid=' + uid, filter: function (list) { - return $.map(list, function (name) { - return {name: name}; + return $.map(list, function (item) { + return {name: item.name}; }); } }, @@ -386,8 +386,8 @@ function createAutoComplete(input, URI) { url: URI + '?search=%QUERY&uid=' + uid, wildcard: '%QUERY', filter: function (list) { - return $.map(list, function (name) { - return {name: name}; + return $.map(list, function (item) { + return {name: item.name}; }); } } diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index c28e44520c..a88bd5a43b 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -189,4 +189,7 @@ return [ 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/views/v1/list/groups.twig b/resources/views/v1/list/groups.twig index f62c16fa32..1a44199aa5 100644 --- a/resources/views/v1/list/groups.twig +++ b/resources/views/v1/list/groups.twig @@ -5,9 +5,12 @@ TODO: hide and show columns - + + @@ -16,7 +19,7 @@ TODO: hide and show columns - - - - - - + - - + {% endfor %} {% endfor %} + + + + +
- {{ groups.render|raw }} - {{ groups.render|raw }} +
+ +
+
- {{ group.title }} + {{ group.title }} @@ -25,7 +28,7 @@ TODO: hide and show columns {% endfor %}   +
@@ -41,52 +44,54 @@ TODO: hide and show columns {% 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;'" %} + {% set style="border-bottom:1px #aaa solid;" %} {% endif %}
- {% if journal.transaction_type_type == 'Withdrawal' %} + + {% if transaction.transaction_type_type == 'Withdrawal' %} {% endif %} - {% if journal.transaction_type_type == 'Deposit' %} + {% if transaction.transaction_type_type == 'Deposit' %} {% endif %} - {% if journal.transaction_type_type == 'Transfer' %} + {% if transaction.transaction_type_type == 'Transfer' %} {% endif %} - {% if journal.transaction_type_type == 'Reconciliation' %} + {% if transaction.transaction_type_type == 'Reconciliation' %} {% endif %} - {% if journal.transaction_type_type == 'Opening balance' %} + {% if transaction.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.date.formatLocalized(monthAndDayFormat) }} + {{ transaction.source_account_name }} + {{ transaction.destination_account_name }} + {% if group.count == 1 %}
{% endif %}
+
+ +
+
+
+ +
+ + +
+
+
- - diff --git a/resources/views/v1/list/journals-array-tiny.twig b/resources/views/v1/list/journals-array-tiny.twig new file mode 100644 index 0000000000..f3fa763136 --- /dev/null +++ b/resources/views/v1/list/journals-array-tiny.twig @@ -0,0 +1,10 @@ +
+ {% for journal in journals %} + + {{ journal.description }} + + {{ journalArrayAmount(journal) }} + + + {% endfor %} +
\ No newline at end of file diff --git a/resources/views/v1/recurring/edit.twig b/resources/views/v1/recurring/edit.twig index 7d91a0d45e..50d4a4e2a3 100644 --- a/resources/views/v1/recurring/edit.twig +++ b/resources/views/v1/recurring/edit.twig @@ -91,13 +91,19 @@ {{ ExpandedForm.longAccountList('source_id', array.transactions[0].source_id, {label: trans('form.asset_source_account')}) }} {# source account name for deposits: #} - {{ ExpandedForm.text('source_name', array.transactions[0].source_name, {label: trans('form.revenue_account')}) }} + {#{{ ExpandedForm.text('source_name', array.transactions[0].source_name, {label: trans('form.revenue_account')}) }}#} + + {# NEW for deposits, a drop down with revenue accounts, loan debt cash and mortgage #} + {{ ExpandedForm.activeDepositDestinations('deposit_source_id', preFilled.deposit_source_id, {label: trans('form.deposit_source_id')}) }} {# destination if deposit or transfer: #} {{ ExpandedForm.longAccountList('destination_id', array.transactions[0].destination_id, {label: trans('form.asset_destination_account')} ) }} {# destination account name for withdrawals #} - {{ ExpandedForm.text('destination_name', array.transactions[0].destination_name, {label: trans('form.expense_account')}) }} + {# {{ ExpandedForm.text('destination_name', array.transactions[0].destination_name, {label: trans('form.expense_account')}) }}#} + + {# NEW for withdrawals, also a drop down with expense accounts, loans, debts, mortgages or (cash). #} + {{ ExpandedForm.activeWithdrawalDestinations('withdrawal_destination_id', preFilled.withdrawal_destination_id, {label: trans('form.withdrawal_destination_id')}) }} diff --git a/resources/views/v1/recurring/show.twig b/resources/views/v1/recurring/show.twig index 046b33d3a9..ad603c6769 100644 --- a/resources/views/v1/recurring/show.twig +++ b/resources/views/v1/recurring/show.twig @@ -198,7 +198,7 @@
- {% include 'list.transactions' with {showBudgets:true, showCategories:true} %} + {% include 'list.groups' %}
diff --git a/resources/views/v1/reports/partials/exp-categories.twig b/resources/views/v1/reports/partials/exp-categories.twig index 1ff99b927d..bfa38f242b 100644 --- a/resources/views/v1/reports/partials/exp-categories.twig +++ b/resources/views/v1/reports/partials/exp-categories.twig @@ -31,7 +31,7 @@ {{ '0'|formatAmount }} {% else %} {% for income in entry.earned.per_currency %} - {{ formatAmountBySymbol(income.sum, income.currency.symbol, income.currency.dp) }}
+ {{ formatAmountBySymbol(income.sum * -1, income.currency.symbol, income.currency.dp) }}
{% endfor %} {% endif %} diff --git a/resources/views/v1/reports/partials/exp-not-grouped.twig b/resources/views/v1/reports/partials/exp-not-grouped.twig index ab4250937c..ec26868ce6 100644 --- a/resources/views/v1/reports/partials/exp-not-grouped.twig +++ b/resources/views/v1/reports/partials/exp-not-grouped.twig @@ -23,7 +23,7 @@ {{ '0'|formatAmount }} {% endif %} {% for income in amounts.earned.per_currency %} - {{ formatAmountBySymbol(income.sum, income.currency.symbol, income.currency.dp) }}
+ {{ formatAmountBySymbol(income.sum * -1, income.currency.symbol, income.currency.dp) }}
{% endfor %} diff --git a/resources/views/v1/reports/partials/top-transactions.twig b/resources/views/v1/reports/partials/top-transactions.twig index f384049535..c52c4fa895 100644 --- a/resources/views/v1/reports/partials/top-transactions.twig +++ b/resources/views/v1/reports/partials/top-transactions.twig @@ -10,15 +10,21 @@ {% for transaction in sorted %} - - {{ transaction.opposing_account_name }} + + {{ transaction.destination_account_name }} {{ transaction.description }} {{ transaction.date.formatLocalized(monthAndDayFormat) }} - {{ transaction|transactionAmount }} + + + {{ 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 %} + {% endfor %} diff --git a/resources/views/v1/transactions/index.twig b/resources/views/v1/transactions/index.twig index 921f16da60..9ce51a3103 100644 --- a/resources/views/v1/transactions/index.twig +++ b/resources/views/v1/transactions/index.twig @@ -7,7 +7,7 @@ {% block content %} {# upper show-all instruction #} - {% if periods.count > 0 %} + {% if periods|length > 0 %}

{{ 'showEverything'|_ }}

@@ -17,7 +17,7 @@ {# list with journals #}
-
+

{{ subTitle }}

@@ -31,7 +31,7 @@
{# boxes with info #} - {% if periods.count > 0 %} + {% if periods|length > 0 %}
{% include 'list.periods' %}
@@ -56,7 +56,7 @@
{# lower show-all instruction #} - {% if periods.count > 0 %} + {% if periods|length > 0 %}

{{ 'showEverything'|_ }}

diff --git a/routes/web.php b/routes/web.php index 050373120a..72008dfe25 100644 --- a/routes/web.php +++ b/routes/web.php @@ -537,6 +537,8 @@ Route::group( Route::get('currencies', ['uses' => 'Json\AutoCompleteController@currencies', 'as' => 'autocomplete.currencies']); Route::get('piggy-banks', ['uses' => 'Json\AutoCompleteController@piggyBanks', 'as' => 'autocomplete.piggy-banks']); Route::get('tags', ['uses' => 'Json\AutoCompleteController@tags', 'as' => 'autocomplete.tags']); + Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allJournals', 'as' => 'autocomplete.all-journals']); + Route::get('currency-names', ['uses' => 'Json\AutoCompleteController@currencyNames', 'as' => 'autocomplete.currency-names']); diff --git a/tests/Feature/Controllers/Chart/BudgetReportControllerTest.php b/tests/Feature/Controllers/Chart/BudgetReportControllerTest.php index 1f5db9a55e..cd3ccc8ed1 100644 --- a/tests/Feature/Controllers/Chart/BudgetReportControllerTest.php +++ b/tests/Feature/Controllers/Chart/BudgetReportControllerTest.php @@ -133,6 +133,8 @@ class BudgetReportControllerTest extends TestCase $limit3->budget_id = $budget->id; $limit3->start_date = new Carbon('2012-01-01'); $limit3->end_date = new Carbon('2012-01-31'); + $limit3->amount = '100'; + $limit3->save(); $fiscalHelper->shouldReceive('endOfFiscalYear')->atLeast()->once()->andReturn($date); diff --git a/tests/Feature/Controllers/Recurring/CreateControllerTest.php b/tests/Feature/Controllers/Recurring/CreateControllerTest.php index 73658580cf..f1d58d61ee 100644 --- a/tests/Feature/Controllers/Recurring/CreateControllerTest.php +++ b/tests/Feature/Controllers/Recurring/CreateControllerTest.php @@ -24,8 +24,9 @@ declare(strict_types=1); namespace Tests\Feature\Controllers\Recurring; +use Amount; use Carbon\Carbon; -use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; @@ -33,14 +34,18 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Validation\AccountValidator; use Illuminate\Support\Collection; use Log; use Mockery; +use Preferences; +use Steam; use Tests\TestCase; /** * * Class CreateControllerTest + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ class CreateControllerTest extends TestCase { @@ -58,76 +63,100 @@ class CreateControllerTest extends TestCase */ public function testCreate(): void { - $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); + // mock repositories, even if not used. + $this->mock(RecurringRepositoryInterface::class); + $this->mock(CurrencyRepositoryInterface::class); + $this->mock(PiggyBankRepositoryInterface::class); + + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $budgetRepos = $this->mock(BudgetRepositoryInterface::class); + $userRepos = $this->mock(UserRepositoryInterface::class); + + $euro = $this->getEuro(); + $asset = $this->getRandomAsset(); + $cash = $this->getRandomAsset(); + $this->mockDefaultSession(); - return; - $recurringRepos = $this->mock(RecurringRepositoryInterface::class); - $budgetRepos = $this->mock(BudgetRepositoryInterface::class); - $userRepos = $this->mock(UserRepositoryInterface::class); - $currencyRepos = $this->mock(CurrencyRepositoryInterface::class); - $accountRepos = $this->mock(AccountRepositoryInterface::class); - $piggyRepos = $this->mock(PiggyBankRepositoryInterface::class); $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true); - $budgetRepos->shouldReceive('getActiveBudgets')->andReturn(new Collection)->once(); - \Amount::shouldReceive('getDefaultCurrency')->andReturn(TransactionCurrency::find(1)); + + + // for view: + $accountRepos->shouldReceive('getActiveAccountsByType')->atLeast()->once()->andReturn(new Collection([$asset])); + Steam::shouldReceive('balance')->andReturn('100')->atLeast()->once(); + $accountRepos->shouldReceive('getAccountCurrency')->atLeast()->once()->andReturn($euro); + $accountRepos->shouldReceive('getMetaValue')->atLeast()->once()->andReturnNull(); + $accountRepos->shouldReceive('getCashAccount')->atLeast()->once()->andReturn($cash); + //Amount::shouldReceive('getDefaultCurrency')->andReturn($euro)->atLeast()->once(); + Amount::shouldReceive('formatAnything')->atLeast()->once()->andReturn('100'); $this->be($this->user()); $response = $this->get(route('recurring.create')); $response->assertStatus(200); $response->assertSee('