From eb0da038fb4434f18375d61d169d74acfa44e08c Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 18 Feb 2018 10:31:15 +0100 Subject: [PATCH] Expand tests and API code. --- app/Api/V1/Controllers/BillController.php | 8 +- app/Api/V1/Controllers/Controller.php | 1 + .../V1/Controllers/TransactionController.php | 26 +- app/Api/V1/Requests/BillRequest.php | 23 + app/Api/V1/Requests/TransactionRequest.php | 133 +++-- app/Factory/PiggyBankEventFactory.php | 88 +++ app/Factory/TransactionFactory.php | 94 +-- app/Factory/TransactionJournalFactory.php | 95 ++- .../Events/StoredJournalEventHandler.php | 2 +- .../Transaction/SingleController.php | 2 +- .../Journal/JournalRepository.php | 2 +- .../Journal/JournalRepositoryInterface.php | 2 +- .../PiggyBank/PiggyBankRepository.php | 20 + .../PiggyBankRepositoryInterface.php | 9 + app/Rules/BelongsUser.php | 4 +- app/Transformers/AccountTransformer.php | 3 +- app/Transformers/BillTransformer.php | 3 +- app/Transformers/BudgetTransformer.php | 3 +- app/Transformers/CategoryTransformer.php | 3 +- app/Transformers/JournalMetaTransformer.php | 3 +- .../PiggyBankEventTransformer.php | 3 +- app/Transformers/TagTransformer.php | 3 +- database/factories/ModelFactory.php | 5 +- resources/lang/en_US/validation.php | 1 + .../V1/Controllers/AboutControllerTest.php | 2 +- .../V1/Controllers/AccountControllerTest.php | 13 +- .../Api/V1/Controllers/BillControllerTest.php | 243 ++++++++ .../Controllers/TransactionControllerTest.php | 549 ++++++++++++++++++ 28 files changed, 1171 insertions(+), 172 deletions(-) create mode 100644 app/Factory/PiggyBankEventFactory.php create mode 100644 tests/Api/V1/Controllers/BillControllerTest.php create mode 100644 tests/Api/V1/Controllers/TransactionControllerTest.php diff --git a/app/Api/V1/Controllers/BillController.php b/app/Api/V1/Controllers/BillController.php index 767d5ead1f..6e62d6e906 100644 --- a/app/Api/V1/Controllers/BillController.php +++ b/app/Api/V1/Controllers/BillController.php @@ -98,7 +98,7 @@ class BillController extends Controller $resource = new FractalCollection($bills, new BillTransformer($this->parameters), 'bills'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } @@ -120,7 +120,7 @@ class BillController extends Controller $resource = new Item($bill, new BillTransformer($this->parameters), 'bills'); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } /** @@ -137,7 +137,7 @@ class BillController extends Controller $resource = new Item($bill, new BillTransformer($this->parameters), 'bills'); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } @@ -158,7 +158,7 @@ class BillController extends Controller $resource = new Item($bill, new BillTransformer($this->parameters), 'bills'); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } } diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index 148934c4b5..91145d63b8 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -34,6 +34,7 @@ use Symfony\Component\HttpFoundation\ParameterBag; /** * Class Controller. + * @codeCoverageIgnore */ class Controller extends BaseController { diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php index e13131c606..5a35154206 100644 --- a/app/Api/V1/Controllers/TransactionController.php +++ b/app/Api/V1/Controllers/TransactionController.php @@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Controllers; use FireflyIII\Api\V1\Requests\TransactionRequest; use FireflyIII\Factory\TransactionJournalFactory; use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Helpers\Filter\NegativeAmountFilter; use FireflyIII\Helpers\Filter\PositiveAmountFilter; @@ -107,7 +108,7 @@ class TransactionController extends Controller $manager->setSerializer(new JsonApiSerializer($baseUrl)); // collect transactions using the journal collector - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser(auth()->user()); $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); $collector->setAllAssetAccounts(); @@ -130,7 +131,7 @@ class TransactionController extends Controller $resource = new FractalCollection($transactions, new TransactionTransformer($this->parameters), 'transactions'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } @@ -152,7 +153,7 @@ class TransactionController extends Controller // needs a lot of extra data to match the journal collector. Or just expand that one. // collect transactions using the journal collector - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser(auth()->user()); $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); // filter on specific journals. @@ -167,11 +168,10 @@ class TransactionController extends Controller $collector->addFilter(NegativeAmountFilter::class); } - $transaction = $collector->getJournals(); + $transactions = $collector->getJournals(); + $resource = new Item($transactions->first(), new TransactionTransformer($this->parameters), 'transactions'); - $resource = new FractalCollection($transaction, new TransactionTransformer($this->parameters), 'transactions'); - - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } /** @@ -199,7 +199,7 @@ class TransactionController extends Controller // needs a lot of extra data to match the journal collector. Or just expand that one. // collect transactions using the journal collector - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser(auth()->user()); $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); // filter on specific journals. @@ -214,10 +214,10 @@ class TransactionController extends Controller $collector->addFilter(NegativeAmountFilter::class); } - $transaction = $collector->getJournals(); - $resource = new FractalCollection($transaction, new TransactionTransformer($this->parameters), 'transactions'); + $transactions = $collector->getJournals(); + $resource = new Item($transactions->first(), new TransactionTransformer($this->parameters), 'transactions'); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } @@ -238,7 +238,7 @@ class TransactionController extends Controller $resource = new Item($bill, new BillTransformer($this->parameters), 'bills'); - return response()->json($manager->createData($resource)->toArray()); + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } @@ -308,7 +308,7 @@ class TransactionController extends Controller return $types[$type]; } - return $types['default']; + return $types['default']; // @codeCoverageIgnore } } \ No newline at end of file diff --git a/app/Api/V1/Requests/BillRequest.php b/app/Api/V1/Requests/BillRequest.php index cbef934efe..d39c08edda 100644 --- a/app/Api/V1/Requests/BillRequest.php +++ b/app/Api/V1/Requests/BillRequest.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use Illuminate\Validation\Validator; + /** * Class BillRequest */ @@ -93,4 +95,25 @@ class BillRequest extends Request return $rules; } + + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + $data = $validator->getData(); + $min = floatval($data['amount_min']); + $max = floatval($data['amount_max']); + if ($min > $max) { + $validator->errors()->add('amount_min', trans('validation.amount_min_over_max')); + } + } + ); + } } \ No newline at end of file diff --git a/app/Api/V1/Requests/TransactionRequest.php b/app/Api/V1/Requests/TransactionRequest.php index a1e1230a5d..588aacacd0 100644 --- a/app/Api/V1/Requests/TransactionRequest.php +++ b/app/Api/V1/Requests/TransactionRequest.php @@ -77,27 +77,26 @@ class TransactionRequest extends Request ]; foreach ($this->get('transactions') as $transaction) { $array = [ - 'description' => $transaction['description'] ?? null, - 'amount' => $transaction['amount'], - 'currency_id' => isset($transaction['currency_id']) ? intval($transaction['currency_id']) : null, - 'currency_code' => isset($transaction['currency_code']) ? $transaction['currency_code'] : null, - 'foreign_amount' => $transaction['foreign_amount'] ?? null, - 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? intval($transaction['foreign_currency_id']) : null, - 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, - 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : null, - 'budget_name' => $transaction['budget_name'] ?? null, - 'category_id' => isset($transaction['category_id']) ? intval($transaction['category_id']) : null, - 'category_name' => $transaction['category_name'] ?? null, - 'source_account_id' => isset($transaction['source_account_id']) ? intval($transaction['source_account_id']) : null, - 'source_account_name' => $transaction['source_account_name'] ?? null, - 'destination_account_id' => isset($transaction['destination_account_id']) ? intval($transaction['destination_account_id']) : null, - 'destination_account_name' => $transaction['destination_account_name'] ?? null, - 'reconciled' => intval($transaction['reconciled'] ?? 0) === 1 ? true : false, - 'identifier' => isset($transaction['identifier']) ? intval($transaction['identifier']) : 0, + 'description' => $transaction['description'] ?? null, + 'amount' => $transaction['amount'], + 'currency_id' => isset($transaction['currency_id']) ? intval($transaction['currency_id']) : null, + 'currency_code' => isset($transaction['currency_code']) ? $transaction['currency_code'] : null, + 'foreign_amount' => $transaction['foreign_amount'] ?? null, + 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? intval($transaction['foreign_currency_id']) : null, + 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, + 'budget_id' => isset($transaction['budget_id']) ? intval($transaction['budget_id']) : null, + 'budget_name' => $transaction['budget_name'] ?? null, + 'category_id' => isset($transaction['category_id']) ? intval($transaction['category_id']) : null, + 'category_name' => $transaction['category_name'] ?? null, + 'source_id' => isset($transaction['source_id']) ? intval($transaction['source_id']) : null, + 'source_name' => $transaction['source_name'] ?? null, + 'destination_id' => isset($transaction['destination_id']) ? intval($transaction['destination_id']) : null, + 'destination_name' => $transaction['destination_name'] ?? null, + 'reconciled' => intval($transaction['reconciled'] ?? 0) === 1 ? true : false, + 'identifier' => isset($transaction['identifier']) ? intval($transaction['identifier']) : 0, ]; $data['transactions'][] = $array; } - return $data; } @@ -108,44 +107,44 @@ class TransactionRequest extends Request { return [ // basic fields for journal: - 'type' => 'required|in:withdrawal,deposit,transfer', - 'date' => 'required|date', - 'description' => 'between:1,255', - 'piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser], - 'piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser], - 'bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser], - 'bill_name' => ['between:1,255', 'nullable', new BelongsUser], - 'tags' => 'between:1,255', + 'type' => 'required|in:withdrawal,deposit,transfer', + 'date' => 'required|date', + 'description' => 'between:1,255', + 'piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser], + 'piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser], + 'bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser], + 'bill_name' => ['between:1,255', 'nullable', new BelongsUser], + 'tags' => 'between:1,255', // then, custom fields for journal - 'interest_date' => 'date|nullable', - 'book_date' => 'date|nullable', - 'process_date' => 'date|nullable', - 'due_date' => 'date|nullable', - 'payment_date' => 'date|nullable', - 'invoice_date' => 'date|nullable', - 'internal_reference' => 'min:1,max:255|nullable', - 'notes' => 'min:1,max:50000|nullable', + 'interest_date' => 'date|nullable', + 'book_date' => 'date|nullable', + 'process_date' => 'date|nullable', + 'due_date' => 'date|nullable', + 'payment_date' => 'date|nullable', + 'invoice_date' => 'date|nullable', + 'internal_reference' => 'min:1,max:255|nullable', + 'notes' => 'min:1,max:50000|nullable', // transaction rules (in array for splits): - 'transactions.*.description' => 'nullable|between:1,255', - 'transactions.*.amount' => 'required|numeric|more:0', - 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|required_without:transactions.*.currency_code', - 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:transactions.*.currency_id', - 'transactions.*.foreign_amount' => 'numeric|more:0', - 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', - 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', - 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], - 'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser], - 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser], - 'transactions.*.category_name' => 'between:1,255|nullable', - 'transactions.*.reconciled' => 'boolean|nullable', - 'transactions.*.identifier' => 'numeric|nullable', + 'transactions.*.description' => 'nullable|between:1,255', + 'transactions.*.amount' => 'required|numeric|more:0', + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|required_without:transactions.*.currency_code', + 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|required_without:transactions.*.currency_id', + 'transactions.*.foreign_amount' => 'numeric|more:0', + 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], + 'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser], + 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser], + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.reconciled' => 'boolean|nullable', + 'transactions.*.identifier' => 'numeric|nullable', // basic rules will be expanded later. - 'transactions.*.source_account_id' => ['numeric', 'nullable', new BelongsUser], - 'transactions.*.source_account_name' => 'between:1,255|nullable', - 'transactions.*.destination_account_id' => ['numeric', 'nullable', new BelongsUser], - 'transactions.*.destination_account_name' => 'between:1,255|nullable', + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.source_name' => 'between:1,255|nullable', + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.destination_name' => 'between:1,255|nullable', // todo tags @@ -377,37 +376,37 @@ class TransactionRequest extends Request $transactions = $data['transactions'] ?? []; foreach ($transactions as $index => $transaction) { - $sourceId = isset($transaction['source_account_id']) ? intval($transaction['source_account_id']) : null; - $sourceName = $transaction['source_account_name'] ?? null; - $destinationId = isset($transaction['destination_account_id']) ? intval($transaction['destination_account_id']) : null; - $destinationName = $transaction['destination_account_name'] ?? null; + $sourceId = isset($transaction['source_id']) ? intval($transaction['source_id']) : null; + $sourceName = $transaction['source_name'] ?? null; + $destinationId = isset($transaction['destination_id']) ? intval($transaction['destination_id']) : null; + $destinationName = $transaction['destination_name'] ?? null; switch ($data['type']) { case 'withdrawal': - $idField = 'transactions.' . $index . '.source_account_id'; - $nameField = 'transactions.' . $index . '.source_account_name'; + $idField = 'transactions.' . $index . '.source_id'; + $nameField = 'transactions.' . $index . '.source_name'; $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); - $idField = 'transactions.' . $index . '.destination_account_id'; - $nameField = 'transactions.' . $index . '.destination_account_name'; + $idField = 'transactions.' . $index . '.destination_id'; + $nameField = 'transactions.' . $index . '.destination_name'; $this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField, $nameField); break; case 'deposit': - $idField = 'transactions.' . $index . '.source_account_id'; - $nameField = 'transactions.' . $index . '.source_account_name'; + $idField = 'transactions.' . $index . '.source_id'; + $nameField = 'transactions.' . $index . '.source_name'; $this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField, $nameField); - $idField = 'transactions.' . $index . '.destination_account_id'; - $nameField = 'transactions.' . $index . '.destination_account_name'; + $idField = 'transactions.' . $index . '.destination_id'; + $nameField = 'transactions.' . $index . '.destination_name'; $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); break; case 'transfer': - $idField = 'transactions.' . $index . '.source_account_id'; - $nameField = 'transactions.' . $index . '.source_account_name'; + $idField = 'transactions.' . $index . '.source_id'; + $nameField = 'transactions.' . $index . '.source_name'; $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); - $idField = 'transactions.' . $index . '.destination_account_id'; - $nameField = 'transactions.' . $index . '.destination_account_name'; + $idField = 'transactions.' . $index . '.destination_id'; + $nameField = 'transactions.' . $index . '.destination_name'; $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); break; default: diff --git a/app/Factory/PiggyBankEventFactory.php b/app/Factory/PiggyBankEventFactory.php new file mode 100644 index 0000000000..9ce51d1b47 --- /dev/null +++ b/app/Factory/PiggyBankEventFactory.php @@ -0,0 +1,88 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Factory; + +use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use Log; + +/** + * Class PiggyBankEventFactory + */ +class PiggyBankEventFactory +{ + /** + * @param TransactionJournal $journal + * @param PiggyBank|null $piggyBank + * + * @return PiggyBankEvent|null + */ + public function create(TransactionJournal $journal, ?PiggyBank $piggyBank): ?PiggyBankEvent + { + if (is_null($piggyBank)) { + return null; + } + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + /** @var PiggyBankRepositoryInterface $piggyRepos */ + $piggyRepos = app(PiggyBankRepositoryInterface::class); + $repository->setUser($journal->user); + $piggyRepos->setUser($journal->user); + + + // is a transfer? + if (!$repository->isTransfer($journal)) { + Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id)); + + return null; + } + + // repetition exists? + $repetition = $piggyRepos->getRepetition($piggyBank, $journal->date); + if (null === $repetition->id) { + Log::error(sprintf('No piggy bank repetition on %s!', $journal->date->format('Y-m-d'))); + + return null; + } + + // get the amount + $amount = $piggyRepos->getExactAmount($piggyBank, $repetition, $journal); + if (0 === bccomp($amount, '0')) { + Log::debug('Amount is zero, will not create event.'); + + return null; + } + + // update amount + $piggyRepos->addAmountToRepetition($repetition, $amount); + $event = $piggyRepos->createEventWithJournal($piggyBank, $amount, $journal); + + Log::debug(sprintf('Created piggy bank event #%d', $event->id)); + + return $event; + } +} \ No newline at end of file diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index 1b38865d4e..9e4caf1711 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -46,9 +46,31 @@ use Illuminate\Support\Collection; */ class TransactionFactory { + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var BudgetRepositoryInterface */ + private $budgetRepository; + /** @var CategoryRepositoryInterface */ + private $categoryRepository; + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; + /** @var JournalRepositoryInterface */ + private $repository; /** @var User */ private $user; + /** + * TransactionFactory constructor. + */ + public function __construct() + { + $this->repository = app(JournalRepositoryInterface::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->budgetRepository = app(BudgetRepositoryInterface::class); + $this->categoryRepository = app(CategoryRepositoryInterface::class); + $this->currencyRepository = app(CurrencyRepositoryInterface::class); + } + /** * @param array $data * @@ -68,12 +90,10 @@ class TransactionFactory 'foreign_currency_id' => $foreignCurrencyId, 'identifier' => $data['identifier'], ]; - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $transaction = $repository->storeBasicTransaction($values); + $transaction = $this->repository->storeBasicTransaction($values); // todo: add budget, category, etc. - + // todo link budget, category return $transaction; } @@ -94,21 +114,23 @@ class TransactionFactory $foreignCurrency = $this->findCurrency($data['foreign_currency_id'], $data['foreign_currency_code']); $budget = $this->findBudget($data['budget_id'], $data['budget_name']); $category = $this->findCategory($data['category_id'], $data['category_name']); - + $description = $journal->description === $data['description'] ? null : $data['description']; // type of source account depends on journal type: $sourceType = $this->accountType($journal, 'source'); - $sourceAccount = $this->findAccount($sourceType, $data['source_account_id'], $data['source_account_name']); + $sourceAccount = $this->findAccount($sourceType, $data['source_id'], $data['source_name']); + $sourceFa = is_null($data['foreign_amount']) ? null : app('steam')->negative(strval($data['foreign_amount'])); // same for destination account: $destinationType = $this->accountType($journal, 'destination'); - $destinationAccount = $this->findAccount($destinationType, $data['destination_account_id'], $data['destination_account_name']); + $destinationAccount = $this->findAccount($destinationType, $data['destination_id'], $data['destination_name']); + $destinationFa = is_null($data['foreign_amount']) ? null : app('steam')->positive(strval($data['foreign_amount'])); // first make a "negative" (source) transaction based on the data in the array. $sourceTransactionData = [ - 'description' => $journal->description === $data['description'] ? null : $data['description'], + 'description' => $description, 'amount' => app('steam')->negative(strval($data['amount'])), - 'foreign_amount' => is_null($data['foreign_amount']) ? null : app('steam')->negative(strval($data['foreign_amount'])), + 'foreign_amount' => $sourceFa, 'currency' => $currency, 'foreign_currency' => $foreignCurrency, 'budget' => $budget, @@ -124,7 +146,7 @@ class TransactionFactory $destTransactionData = [ 'description' => $sourceTransactionData['description'], 'amount' => app('steam')->positive(strval($data['amount'])), - 'foreign_amount' => is_null($data['foreign_amount']) ? null : app('steam')->positive(strval($data['foreign_amount'])), + 'foreign_amount' => $destinationFa, 'currency' => $currency, 'foreign_currency' => $foreignCurrency, 'budget' => $budget, @@ -136,8 +158,6 @@ class TransactionFactory ]; $dest = $this->create($destTransactionData); - // todo link budget, category - return new Collection([$source, $dest]); } @@ -147,6 +167,11 @@ class TransactionFactory public function setUser(User $user) { $this->user = $user; + $this->repository->setUser($user); + $this->accountRepository->setUser($user); + $this->budgetRepository->setUser($user); + $this->categoryRepository->setUser($user); + $this->currencyRepository->setUser($user); } /** @@ -195,45 +220,42 @@ class TransactionFactory { $accountId = intval($accountId); $accountName = strval($accountName); - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); switch ($expectedType) { case AccountType::ASSET: if ($accountId > 0) { // must be able to find it based on ID. Validator should catch invalid ID's. - return $repository->findNull($accountId); + return $this->accountRepository->findNull($accountId); } // alternatively, return by name. Validator should catch invalid names. - return $repository->findByName($accountName, [AccountType::ASSET]); + return $this->accountRepository->findByName($accountName, [AccountType::ASSET]); break; case AccountType::EXPENSE: if ($accountId > 0) { // must be able to find it based on ID. Validator should catch invalid ID's. - return $repository->findNull($accountId); + return $this->accountRepository->findNull($accountId); } if (strlen($accountName) > 0) { // alternatively, return by name. Validator should catch invalid names. - return $repository->findByName($accountName, [AccountType::EXPENSE]); + return $this->accountRepository->findByName($accountName, [AccountType::EXPENSE]); } // return cash account: - return $repository->getCashAccount(); + return $this->accountRepository->getCashAccount(); break; case AccountType::REVENUE: if ($accountId > 0) { // must be able to find it based on ID. Validator should catch invalid ID's. - return $repository->findNull($accountId); + return $this->accountRepository->findNull($accountId); } if (strlen($accountName) > 0) { // alternatively, return by name. Validator should catch invalid names. - return $repository->findByName($accountName, [AccountType::REVENUE]); + return $this->accountRepository->findByName($accountName, [AccountType::REVENUE]); } // return cash account: - return $repository->getCashAccount(); + return $this->accountRepository->getCashAccount(); default: throw new FireflyException(sprintf('Cannot find account of type "%s".', $expectedType)); @@ -254,21 +276,17 @@ class TransactionFactory if (strlen($budgetName) === 0 && $budgetId === 0) { return null; } - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - $budget = null; - $repository->setUser($this->user); // first by ID: if ($budgetId > 0) { - $budget = $repository->findNull($budgetId); + $budget = $this->budgetRepository->findNull($budgetId); if (!is_null($budget)) { return $budget; } } if (strlen($budgetName) > 0) { - $budget = $repository->findByName($budgetName); + $budget = $this->budgetRepository->findByName($budgetName); if (!is_null($budget)) { return $budget; } @@ -291,25 +309,21 @@ class TransactionFactory if (strlen($categoryName) === 0 && $categoryId === 0) { return null; } - /** @var CategoryRepositoryInterface $repository */ - $repository = app(CategoryRepositoryInterface::class); - $category = null; - $repository->setUser($this->user); - // first by ID: if ($categoryId > 0) { - $category = $repository->findNull($categoryId); + $category = $this->categoryRepository->findNull($categoryId); if (!is_null($category)) { return $category; } } if (strlen($categoryName) > 0) { - $category = $repository->findByName($categoryName); + $category = $this->categoryRepository->findByName($categoryName); if (!is_null($category)) { return $category; } // create it? + die('create category'); } return null; @@ -329,21 +343,17 @@ class TransactionFactory if (strlen($currencyCode) === 0 && intval($currencyId) === 0) { return null; } - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $currency = null; - $repository->setUser($this->user); // first by ID: if ($currencyId > 0) { - $currency = $repository->findNull($currencyId); + $currency = $this->currencyRepository->findNull($currencyId); if (!is_null($currency)) { return $currency; } } // then by code: if (strlen($currencyCode) > 0) { - $currency = $repository->findByCodeNull($currencyCode); + $currency = $this->currencyRepository->findByCodeNull($currencyCode); if (!is_null($currency)) { return $currency; } diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index 8ff3f5ec11..e60ac8cb9d 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -23,12 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Factory; +use FireflyIII\Events\StoredTransactionJournal; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Bill; +use FireflyIII\Models\PiggyBank; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; use FireflyIII\User; @@ -37,6 +40,14 @@ use FireflyIII\User; */ class TransactionJournalFactory { + /** @var BillRepositoryInterface */ + private $billRepository; + /** @var PiggyBankRepositoryInterface */ + private $piggyRepository; + /** @var JournalRepositoryInterface */ + private $repository; + /** @var TransactionTypeRepositoryInterface */ + private $ttRepository; /** @var User */ private $user; @@ -45,6 +56,10 @@ class TransactionJournalFactory */ public function __construct() { + $this->repository = app(JournalRepositoryInterface::class); + $this->billRepository = app(BillRepositoryInterface::class); + $this->piggyRepository = app(PiggyBankRepositoryInterface::class); + $this->ttRepository = app(TransactionTypeRepositoryInterface::class); } @@ -60,11 +75,9 @@ class TransactionJournalFactory { $type = $this->findTransactionType($data['type']); $bill = $this->findBill($data['bill_id'], $data['bill_name']); - $defaultCurrency = app('amount')->getDefaultCurrencyByUser(auth()->user()); - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - - $values = [ + $piggyBank = $this->findPiggyBank($data['piggy_bank_id'], $data['piggy_bank_name']); + $defaultCurrency = app('amount')->getDefaultCurrencyByUser($this->user); + $values = [ 'user_id' => $data['user'], 'transaction_type_id' => $type->id, 'bill_id' => is_null($bill) ? null : $bill->id, @@ -76,20 +89,28 @@ class TransactionJournalFactory 'completed' => 0, ]; - $journal = $repository->storeBasic($values); + $journal = $this->repository->storeBasic($values); // todo link other stuff to journal (meta-data etc). tags - + // todo // start creating transactions: + + $factory = app(TransactionFactory::class); + $factory->setUser($this->user); + /** @var array $trData */ foreach ($data['transactions'] as $trData) { - $factory = new TransactionFactory(); - $factory->setUser($this->user); - $trData['reconciled'] = $data['reconciled'] ?? false; $factory->createPair($journal, $trData); } - $repository->markCompleted($journal); + $this->repository->markCompleted($journal); + + // link piggy bank: + if ($type->type === TransactionType::TRANSFER && !is_null($piggyBank)) { + /** @var PiggyBankEventFactory $factory */ + $factory = app(PiggyBankEventFactory::class); + $factory->create($journal, $piggyBank); + } return $journal; } @@ -102,6 +123,9 @@ class TransactionJournalFactory public function setUser(User $user): void { $this->user = $user; + $this->repository->setUser($user); + $this->billRepository->setUser($user); + $this->piggyRepository->setUser($user); } /** @@ -117,14 +141,10 @@ class TransactionJournalFactory if (strlen($billName) === 0 && $billId === 0) { return null; } - /** @var BillRepositoryInterface $repository */ - $repository = app(BillRepositoryInterface::class); - $repository->setUser($this->user); - // first find by ID: if ($billId > 0) { /** @var Bill $bill */ - $bill = $repository->find($billId); + $bill = $this->billRepository->find($billId); if (!is_null($bill)) { return $bill; } @@ -132,7 +152,7 @@ class TransactionJournalFactory // then find by name: if (strlen($billName) > 0) { - $bill = $repository->findByName($billName); + $bill = $this->billRepository->findByName($billName); if (!is_null($bill)) { return $bill; } @@ -141,6 +161,41 @@ class TransactionJournalFactory return null; } + /** + * Find the given bill based on the ID or the name. ID takes precedence over the name. + * + * @param int $piggyBankId + * @param string $piggyBankName + * + * @return PiggyBank|null + */ + protected function findPiggyBank(int $piggyBankId, string $piggyBankName): ?PiggyBank + { + if (strlen($piggyBankName) === 0 && $piggyBankId === 0) { + return null; + } + // first find by ID: + if ($piggyBankId > 0) { + /** @var PiggyBank $piggyBank */ + $piggyBank = $this->piggyRepository->find($piggyBankId); + if (!is_null($piggyBank)) { + return $piggyBank; + } + } + + + // then find by name: + if (strlen($piggyBankName) > 0) { + /** @var PiggyBank $piggyBank */ + $piggyBank = $this->piggyRepository->findByName($piggyBankName); + if (!is_null($piggyBank)) { + return $piggyBank; + } + } + + return null; + } + /** * Get the transaction type. Since this is mandatory, will throw an exception when nothing comes up. Will always * use TransactionType repository. @@ -150,11 +205,9 @@ class TransactionJournalFactory * @return TransactionType * @throws FireflyException */ - private function findTransactionType(string $type): TransactionType + protected function findTransactionType(string $type): TransactionType { - /** @var TransactionTypeRepositoryInterface $repository */ - $repository = app(TransactionTypeRepositoryInterface::class); - $transactionType = $repository->findByType($type); + $transactionType = $this->ttRepository->findByType($type); if (is_null($transactionType)) { throw new FireflyException(sprintf('Could not find transaction type for "%s"', $type)); } diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php index b05a4f09c5..2dd019b57d 100644 --- a/app/Handlers/Events/StoredJournalEventHandler.php +++ b/app/Handlers/Events/StoredJournalEventHandler.php @@ -85,7 +85,7 @@ class StoredJournalEventHandler } // piggy exists? - if (null === $piggyBank->id) { + if (null === $piggyBank) { Log::error(sprintf('There is no piggy bank with ID #%d', $piggyBankId)); return true; diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index a7f350132d..3f83446d6e 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -230,7 +230,7 @@ class SingleController extends Controller $type = $transactionJournal->transactionTypeStr(); Session::flash('success', strval(trans('firefly.deleted_' . strtolower($type), ['description' => $transactionJournal->description]))); - $this->repository->delete($transactionJournal); + $this->repository->destroy($transactionJournal); Preferences::mark(); diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index e28d966b43..7e275e2d1c 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -107,7 +107,7 @@ class JournalRepository implements JournalRepositoryInterface * * @throws \Exception */ - public function delete(TransactionJournal $journal): bool + public function destroy(TransactionJournal $journal): bool { $journal->delete(); diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index e2e89e3e05..1e242b5833 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -60,7 +60,7 @@ interface JournalRepositoryInterface * * @return bool */ - public function delete(TransactionJournal $journal): bool; + public function destroy(TransactionJournal $journal): bool; /** * Find a specific journal. diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index b837797901..41aaf7514a 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -163,6 +163,26 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return new PiggyBank(); } + /** + * Find by name or return NULL. + * + * @param string $name + * + * @return PiggyBank|null + */ + public function findByName(string $name): ?PiggyBank + { + $set = $this->user->piggyBanks()->get(['piggy_banks.*']); + /** @var PiggyBank $piggy */ + foreach ($set as $piggy) { + if ($piggy->name === $name) { + return $piggy; + } + } + + return null; + } + /** * Get current amount saved in piggy bank. * diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index d3a3211d25..57fe6ce14b 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -102,6 +102,15 @@ interface PiggyBankRepositoryInterface */ public function find(int $piggyBankid): PiggyBank; + /** + * Find by name or return NULL. + * + * @param string $name + * + * @return PiggyBank|null + */ + public function findByName(string $name): ?PiggyBank; + /** * Get current amount saved in piggy bank. * diff --git a/app/Rules/BelongsUser.php b/app/Rules/BelongsUser.php index 4892627cdb..cab5074b54 100644 --- a/app/Rules/BelongsUser.php +++ b/app/Rules/BelongsUser.php @@ -83,8 +83,8 @@ class BelongsUser implements Rule return $count === 1; break; - case 'source_account_id': - case 'destination_account_id': + case 'source_id': + case 'destination_id': $count = Account::where('id', '=', intval($value))->where('user_id', '=', auth()->user()->id)->count(); return $count === 1; diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index ecec9f9c09..b36a05f25c 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -26,6 +26,7 @@ namespace FireflyIII\Transformers; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Note; @@ -100,7 +101,7 @@ class AccountTransformer extends TransformerAbstract $pageSize = intval(app('preferences')->getForUser($account->user, 'listPageSize', 50)->data); // journals always use collector and limited using URL parameters. - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser($account->user); $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); if ($account->accountType->type === AccountType::ASSET) { diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/BillTransformer.php index 3975cdd60c..878e88b5b3 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/BillTransformer.php @@ -25,6 +25,7 @@ namespace FireflyIII\Transformers; use Carbon\Carbon; use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Bill; use FireflyIII\Models\Note; use FireflyIII\Repositories\Bill\BillRepositoryInterface; @@ -96,7 +97,7 @@ class BillTransformer extends TransformerAbstract $pageSize = intval(app('preferences')->getForUser($bill->user, 'listPageSize', 50)->data); // journals always use collector and limited using URL parameters. - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser($bill->user); $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); $collector->setAllAssetAccounts(); diff --git a/app/Transformers/BudgetTransformer.php b/app/Transformers/BudgetTransformer.php index baf60542e4..6423aa70c1 100644 --- a/app/Transformers/BudgetTransformer.php +++ b/app/Transformers/BudgetTransformer.php @@ -25,6 +25,7 @@ namespace FireflyIII\Transformers; use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Budget; use Illuminate\Support\Collection; use League\Fractal\Resource\Collection as FractalCollection; @@ -78,7 +79,7 @@ class BudgetTransformer extends TransformerAbstract $pageSize = intval(app('preferences')->getForUser($budget->user, 'listPageSize', 50)->data); // journals always use collector and limited using URL parameters. - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser($budget->user); $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); $collector->setAllAssetAccounts(); diff --git a/app/Transformers/CategoryTransformer.php b/app/Transformers/CategoryTransformer.php index ac31d4ade5..c25222d43a 100644 --- a/app/Transformers/CategoryTransformer.php +++ b/app/Transformers/CategoryTransformer.php @@ -25,6 +25,7 @@ namespace FireflyIII\Transformers; use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Category; use Illuminate\Support\Collection; use League\Fractal\Resource\Collection as FractalCollection; @@ -78,7 +79,7 @@ class CategoryTransformer extends TransformerAbstract $pageSize = intval(app('preferences')->getForUser($category->user, 'listPageSize', 50)->data); // journals always use collector and limited using URL parameters. - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser($category->user); $collector->withOpposingAccount()->withCategoryInformation()->withCategoryInformation(); $collector->setAllAssetAccounts(); diff --git a/app/Transformers/JournalMetaTransformer.php b/app/Transformers/JournalMetaTransformer.php index 3a23582d68..23d9359397 100644 --- a/app/Transformers/JournalMetaTransformer.php +++ b/app/Transformers/JournalMetaTransformer.php @@ -25,6 +25,7 @@ namespace FireflyIII\Transformers; use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\TransactionJournalMeta; use Illuminate\Support\Collection; use League\Fractal\Resource\Collection as FractalCollection; @@ -78,7 +79,7 @@ class JournalMetaTransformer extends TransformerAbstract $pageSize = intval(app('preferences')->getForUser($journal->user, 'listPageSize', 50)->data); // journals always use collector and limited using URL parameters. - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser($journal->user); $collector->withOpposingAccount()->withCategoryInformation()->withCategoryInformation(); $collector->setAllAssetAccounts(); diff --git a/app/Transformers/PiggyBankEventTransformer.php b/app/Transformers/PiggyBankEventTransformer.php index d83383d18e..5a3daf2719 100644 --- a/app/Transformers/PiggyBankEventTransformer.php +++ b/app/Transformers/PiggyBankEventTransformer.php @@ -25,6 +25,7 @@ namespace FireflyIII\Transformers; use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Support\Collection; @@ -94,7 +95,7 @@ class PiggyBankEventTransformer extends TransformerAbstract $pageSize = intval(app('preferences')->getForUser($journal->user, 'listPageSize', 50)->data); // journals always use collector and limited using URL parameters. - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser($journal->user); $collector->withOpposingAccount()->withCategoryInformation()->withCategoryInformation(); $collector->setAllAssetAccounts(); diff --git a/app/Transformers/TagTransformer.php b/app/Transformers/TagTransformer.php index 32f64efe06..20c9ee2023 100644 --- a/app/Transformers/TagTransformer.php +++ b/app/Transformers/TagTransformer.php @@ -25,6 +25,7 @@ namespace FireflyIII\Transformers; use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; use FireflyIII\Models\Tag; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; @@ -77,7 +78,7 @@ class TagTransformer extends TransformerAbstract $pageSize = intval(app('preferences')->getForUser($tag->user, 'listPageSize', 50)->data); // journals always use collector and limited using URL parameters. - $collector = new JournalCollector; + $collector = app(JournalCollectorInterface::class); $collector->setUser($tag->user); $collector->withOpposingAccount()->withCategoryInformation()->withCategoryInformation(); $collector->setAllAssetAccounts(); diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index d7299db879..bb4eeeaedd 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -119,7 +119,6 @@ $factory->define( FireflyIII\Models\Bill::class, function (Faker\Generator $faker) { return [ - 'id' => $faker->numberBetween(1, 10), 'created_at' => new Carbon, 'updated_at' => new Carbon, 'user_id' => 1, @@ -130,9 +129,7 @@ $factory->define( 'date' => '2017-01-01', 'repeat_freq' => 'monthly', 'skip' => 0, - 'automatch' => 1, - 'name_encrypted' => 0, - 'match_encrypted' => 0, + 'automatch' => 1 ]; } ); diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index bb882f7d5d..28638c2570 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -89,6 +89,7 @@ return [ 'required_without_all' => 'The :attribute field is required when none of :values are present.', 'same' => 'The :attribute and :other must match.', 'size.numeric' => 'The :attribute must be :size.', + 'amount_min_over_max' => 'The minimum amount cannot be larger than the maximum amount.', 'size.file' => 'The :attribute must be :size kilobytes.', 'size.string' => 'The :attribute must be :size characters.', 'size.array' => 'The :attribute must contain :size items.', diff --git a/tests/Api/V1/Controllers/AboutControllerTest.php b/tests/Api/V1/Controllers/AboutControllerTest.php index 5d73971fd1..82a9809a87 100644 --- a/tests/Api/V1/Controllers/AboutControllerTest.php +++ b/tests/Api/V1/Controllers/AboutControllerTest.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace Tests\Api\V1\Controllers; -use FireflyIII\Transformers\UserTransformer; use Laravel\Passport\Passport; use Tests\TestCase; @@ -63,6 +62,7 @@ class AboutControllerTest extends TestCase /** * Test user end point + * * @covers \FireflyIII\Api\V1\Controllers\AboutController::user */ public function testUser() diff --git a/tests/Api/V1/Controllers/AccountControllerTest.php b/tests/Api/V1/Controllers/AccountControllerTest.php index 1e194fab59..76cb8fdca0 100644 --- a/tests/Api/V1/Controllers/AccountControllerTest.php +++ b/tests/Api/V1/Controllers/AccountControllerTest.php @@ -23,7 +23,6 @@ declare(strict_types=1); namespace Tests\Api\V1\Controllers; -use FireflyIII\Api\V1\Requests\AccountRequest; use FireflyIII\Models\Account; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -50,7 +49,7 @@ class AccountControllerTest extends TestCase * * @covers \FireflyIII\Api\V1\Controllers\AccountController::delete */ - public function testDestroy() + public function testDelete() { // mock stuff: $repository = $this->mock(AccountRepositoryInterface::class); @@ -434,11 +433,11 @@ class AccountControllerTest extends TestCase $account = $this->user()->accounts()->first(); // data to submit $data = [ - 'name' => $account->name, - 'currency_code' => 'EUR', - 'type' => 'asset', - 'active' => 1, - 'account_role' => 'defaultAsset', + 'name' => $account->name, + 'currency_code' => 'EUR', + 'type' => 'asset', + 'active' => 1, + 'account_role' => 'defaultAsset', ]; // test API diff --git a/tests/Api/V1/Controllers/BillControllerTest.php b/tests/Api/V1/Controllers/BillControllerTest.php new file mode 100644 index 0000000000..6dc03ae9f4 --- /dev/null +++ b/tests/Api/V1/Controllers/BillControllerTest.php @@ -0,0 +1,243 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + + +use FireflyIII\Models\Bill; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use Illuminate\Pagination\LengthAwarePaginator; +use Laravel\Passport\Passport; +use Tests\TestCase; + +/** + * Class BillControllerTest + */ +class BillControllerTest extends TestCase +{ + /** + * + */ + public function setUp() + { + parent::setUp(); + Passport::actingAs($this->user()); + + } + + /** + * Send delete + * + * @covers \FireflyIII\Api\V1\Controllers\BillController::delete + * @covers \FireflyIII\Api\V1\Controllers\BillController::__construct + */ + public function testDelete() + { + // mock stuff: + $repository = $this->mock(BillRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('destroy')->once()->andReturn(true); + + // get account: + $bill = $this->user()->bills()->first(); + + // call API + $response = $this->delete('/api/v1/bills/' . $bill->id); + $response->assertStatus(204); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\BillController::__construct + * @covers \FireflyIII\Api\V1\Controllers\BillController::index + */ + public function testIndex() + { + // create stuff + $bills = factory(Bill::class, 10)->create(); + $paginator = new LengthAwarePaginator($bills, 10, 50, 1); + // mock stuff: + $repository = $this->mock(BillRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('getPaginator')->withAnyArgs()->andReturn($paginator)->once(); + + // test API + $response = $this->get('/api/v1/bills'); + $response->assertStatus(200); + $response->assertJson(['data' => [],]); + $response->assertJson(['meta' => ['pagination' => ['total' => 10, 'count' => 10, 'per_page' => 50, 'current_page' => 1, 'total_pages' => 1]],]); + $response->assertJson( + ['links' => ['self' => true, 'first' => true, 'last' => true,],] + ); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\BillController::show + */ + public function testShow() + { + // create stuff + $bill = $this->user()->bills()->first(); + $repository = $this->mock(BillRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + + // test API + $response = $this->get('/api/v1/bills/' . $bill->id); + $response->assertStatus(200); + $response->assertJson( + ['data' => [ + 'type' => 'bills', + 'id' => $bill->id, + ],] + ); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\BillController::store + * @covers \FireflyIII\Api\V1\Requests\BillRequest::rules + * @covers \FireflyIII\Api\V1\Requests\BillRequest::authorize + * @covers \FireflyIII\Api\V1\Requests\BillRequest::getAll + * @covers \FireflyIII\Api\V1\Requests\BillRequest::withValidator + */ + public function testStoreMinOverMax() + { + // create stuff + $bill = $this->user()->bills()->first(); + $repository = $this->mock(BillRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('store')->andReturn($bill); + + // data to submit: + $data = [ + 'name' => 'New bill #' . rand(1, 1000), + 'match' => 'some,words,' . rand(1, 1000), + 'amount_min' => '66.34', + 'amount_max' => '45.67', + 'date' => '2018-01-01', + 'repeat_freq' => 'monthly', + 'skip' => 0, + 'automatch' => 1, + 'active' => 1, + + ]; + + // test API + $response = $this->post('/api/v1/bills', $data, ['Accept' => 'application/json']); + $response->assertStatus(422); + $response->assertExactJson( + [ + 'message' => 'The given data was invalid.', + 'errors' => [ + 'amount_min' => ['The minimum amount cannot be larger than the maximum amount.'], + ], + ] + ); + $response->assertHeader('Content-Type', 'application/json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\BillController::store + * @covers \FireflyIII\Api\V1\Requests\BillRequest::rules + * @covers \FireflyIII\Api\V1\Requests\BillRequest::authorize + * @covers \FireflyIII\Api\V1\Requests\BillRequest::getAll + */ + public function testStoreValid() + { + // create stuff + $bill = $this->user()->bills()->first(); + $repository = $this->mock(BillRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('store')->andReturn($bill); + + // data to submit: + $data = [ + 'name' => 'New bill #' . rand(1, 1000), + 'match' => 'some,words,' . rand(1, 1000), + 'amount_min' => '12.34', + 'amount_max' => '45.67', + 'date' => '2018-01-01', + 'repeat_freq' => 'monthly', + 'skip' => 0, + 'automatch' => 1, + 'active' => 1, + + ]; + + // test API + $response = $this->post('/api/v1/bills', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson(['data' => ['type' => 'bills', 'links' => true],]); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + $response->assertSee($bill->name); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\BillController::update + * @covers \FireflyIII\Api\V1\Requests\BillRequest::rules + * @covers \FireflyIII\Api\V1\Requests\BillRequest::authorize + * @covers \FireflyIII\Api\V1\Requests\BillRequest::getAll + */ + public function testUpdateValid() + { + // create stuff + $bill = $this->user()->bills()->first(); + $repository = $this->mock(BillRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('update')->andReturn($bill); + + // data to submit: + $data = [ + 'name' => 'New bill #' . rand(1, 1000), + 'match' => 'some,words,' . rand(1, 1000), + 'amount_min' => '12.34', + 'amount_max' => '45.67', + 'date' => '2018-01-01', + 'repeat_freq' => 'monthly', + 'skip' => 0, + 'automatch' => 1, + 'active' => 1, + + ]; + + // test API + $response = $this->put('/api/v1/bills/' . $bill->id, $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson(['data' => ['type' => 'bills', 'links' => true],]); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + $response->assertSee($bill->name); + } + +} \ No newline at end of file diff --git a/tests/Api/V1/Controllers/TransactionControllerTest.php b/tests/Api/V1/Controllers/TransactionControllerTest.php new file mode 100644 index 0000000000..6d7bca988e --- /dev/null +++ b/tests/Api/V1/Controllers/TransactionControllerTest.php @@ -0,0 +1,549 @@ +. + */ + +declare(strict_types=1); + +namespace Tests\Api\V1\Controllers; + + +use FireflyIII\Helpers\Collector\JournalCollector; +use FireflyIII\Helpers\Collector\JournalCollectorInterface; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Support\Collection; +use Laravel\Passport\Passport; +use Tests\TestCase; + +/** + * Class TransactionControllerTest + */ +class TransactionControllerTest extends TestCase +{ + /** + * + */ + public function setUp() + { + parent::setUp(); + Passport::actingAs($this->user()); + } + + /** + * Destroy account over API. + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::__construct + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::delete + */ + public function testDelete() + { + // mock stuff: + $repository = $this->mock(JournalRepositoryInterface::class); + + // mock calls: + $repository->shouldReceive('setUser')->once(); + $repository->shouldReceive('delete')->once()->andReturn(true); + + // get account: + $transaction = $this->user()->transactions()->first(); + + // call API + $response = $this->delete('/api/v1/transactions/' . $transaction->id); + $response->assertStatus(204); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::__construct + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::index + * + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function testIndex() + { + // get some transactions using the collector: + $collector = new JournalCollector; + $collector->setUser(auth()->user()); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setAllAssetAccounts(); + $collector->setLimit(5)->setPage(1); + $paginator = $collector->getPaginatedJournals(); + + // mock stuff: + $repository = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); + $repository->shouldReceive('setUser'); + + $collector->shouldReceive('setUser')->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf(); + $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf(); + $collector->shouldReceive('removeFilter')->andReturnSelf(); + $collector->shouldReceive('setLimit')->andReturnSelf(); + $collector->shouldReceive('setPage')->andReturnSelf(); + $collector->shouldReceive('setTypes')->andReturnSelf(); + $collector->shouldReceive('getPaginatedJournals')->andReturn($paginator); + + + // mock some calls: + + // test API + $response = $this->get('/api/v1/transactions'); + $response->assertStatus(200); + $response->assertJson(['data' => [],]); + $response->assertJson(['meta' => ['pagination' => ['total' => true, 'count' => true, 'per_page' => 5, 'current_page' => 1, 'total_pages' => true]],]); + $response->assertJson(['links' => ['self' => true, 'first' => true, 'last' => true,],]); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::__construct + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::index + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function testIndexWithRange() + { + // get some transactions using the collector: + $collector = new JournalCollector; + $collector->setUser(auth()->user()); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setAllAssetAccounts(); + $collector->setLimit(5)->setPage(1); + $paginator = $collector->getPaginatedJournals(); + + // mock stuff: + $repository = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); + $repository->shouldReceive('setUser'); + + $collector->shouldReceive('setUser')->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf(); + $collector->shouldReceive('setAllAssetAccounts')->andReturnSelf(); + $collector->shouldReceive('removeFilter')->andReturnSelf(); + $collector->shouldReceive('setLimit')->andReturnSelf(); + $collector->shouldReceive('setPage')->andReturnSelf(); + $collector->shouldReceive('setTypes')->andReturnSelf(); + $collector->shouldReceive('setRange')->andReturnSelf(); + $collector->shouldReceive('getPaginatedJournals')->andReturn($paginator); + + + // mock some calls: + + // test API + $response = $this->get('/api/v1/transactions?start=2018-01-01&end=2018-01-31'); + $response->assertStatus(200); + $response->assertJson(['data' => [],]); + $response->assertJson( + ['meta' => + ['pagination' => + [ + 'total' => true, + 'count' => true, + 'per_page' => 5, + 'current_page' => 1, + 'total_pages' => true, + ], + ], + ] + ); + + + $response->assertJson(['links' => ['self' => true, 'first' => true, 'last' => true,],]); + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::show + */ + public function testShowDeposit() + { + /** @var TransactionJournal $journal */ + $journal = auth()->user()->transactionJournals()->where('transaction_type_id', 2)->first(); + $transaction = $journal->transactions()->first(); + + // get some transactions using the collector: + $collector = new JournalCollector; + $collector->setUser(auth()->user()); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setAllAssetAccounts(); + $collector->setJournals(new Collection([$journal])); + $collector->setLimit(5)->setPage(1); + $transactions = $collector->getJournals(); + + // mock stuff: + $repository = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); + $repository->shouldReceive('setUser'); + + $collector->shouldReceive('setUser')->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf()->once(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf()->once(); + $collector->shouldReceive('setJournals')->andReturnSelf()->once(); + $collector->shouldReceive('addFilter')->andReturnSelf()->once(); + $collector->shouldReceive('getJournals')->andReturn($transactions); + + // test API + $response = $this->get('/api/v1/transactions/' . $transaction->id); + $response->assertStatus(200); + $response->assertJson( + [ + 'data' => [ + 'attributes' => [ + 'description' => $journal->description, + ], + 'links' => [ + 0 => [], + 'self' => true, + ], + ], + + ] + ); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::show + */ + public function testShowWithdrawal() + { + /** @var TransactionJournal $journal */ + $journal = auth()->user()->transactionJournals()->where('transaction_type_id', 1)->first(); + $transaction = $journal->transactions()->first(); + + // get some transactions using the collector: + $collector = new JournalCollector; + $collector->setUser(auth()->user()); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setAllAssetAccounts(); + $collector->setJournals(new Collection([$journal])); + $collector->setLimit(5)->setPage(1); + $transactions = $collector->getJournals(); + + // mock stuff: + $repository = $this->mock(JournalRepositoryInterface::class); + $collector = $this->mock(JournalCollectorInterface::class); + $repository->shouldReceive('setUser'); + + $collector->shouldReceive('setUser')->andReturnSelf(); + $collector->shouldReceive('withOpposingAccount')->andReturnSelf(); + $collector->shouldReceive('withCategoryInformation')->andReturnSelf()->once(); + $collector->shouldReceive('withBudgetInformation')->andReturnSelf()->once(); + $collector->shouldReceive('setJournals')->andReturnSelf()->once(); + $collector->shouldReceive('addFilter')->andReturnSelf()->once(); + $collector->shouldReceive('getJournals')->andReturn($transactions); + + // test API + $response = $this->get('/api/v1/transactions/' . $transaction->id); + $response->assertStatus(200); + $response->assertJson( + [ + 'data' => [ + 'attributes' => [ + 'description' => $journal->description, + ], + 'links' => [ + 0 => [], + 'self' => true, + ], + ], + + ] + ); + + $response->assertHeader('Content-Type', 'application/vnd.api+json'); + + } + + /** + * Submit a transaction (withdrawal) with attached bill ID + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store + */ + public function testSuccessBillId() + { + $bill = auth()->user()->bills()->first(); + $account = auth()->user()->accounts()->where('account_type_id', 3)->first(); + $data = [ + 'description' => 'Some transaction #' . rand(1, 1000), + 'date' => '2018-01-01', + 'type' => 'withdrawal', + 'bill_id' => $bill->id, + 'transactions' => [ + [ + 'amount' => '10', + 'currency_id' => 1, + 'source_id' => $account->id, + ], + + + ], + ]; + + // test API + $response = $this->post('/api/v1/transactions', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson( + [ + 'data' => [ + 'type' => 'transactions', + 'attributes' => [ + 'description' => $data['description'], + 'date' => $data['date'], + 'source_id' => $account->id, + 'source_name' => $account->name, + 'type' => 'Withdrawal', + 'source_type' => 'Asset account', + 'destination_name' => 'Cash account', + 'destination_type' => 'Cash account', + 'bill_id' => $bill->id, + 'bill_name' => $bill->name, + 'amount' => -10, + ], + 'links' => true, + ], + ] + ); + } + + + /** + * Submit a transaction (withdrawal) with attached bill ID + * TODO also test deposit / transfer (should be ignored). + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store + */ + public function testSuccessBillName() + { + $bill = auth()->user()->bills()->first(); + $account = auth()->user()->accounts()->where('account_type_id', 3)->first(); + $data = [ + 'description' => 'Some transaction #' . rand(1, 1000), + 'date' => '2018-01-01', + 'type' => 'withdrawal', + 'bill_name' => $bill->name, + 'transactions' => [ + [ + 'amount' => '10', + 'currency_id' => 1, + 'source_id' => $account->id, + ], + + + ], + ]; + + // test API + $response = $this->post('/api/v1/transactions', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson( + [ + 'data' => [ + 'type' => 'transactions', + 'attributes' => [ + 'description' => $data['description'], + 'date' => $data['date'], + 'source_id' => $account->id, + 'source_name' => $account->name, + 'type' => 'Withdrawal', + 'source_type' => 'Asset account', + 'destination_name' => 'Cash account', + 'destination_type' => 'Cash account', + 'bill_id' => $bill->id, + 'bill_name' => $bill->name, + 'amount' => -10, + ], + 'links' => true, + ], + ] + ); + } + + /** + * Submit the minimum amount of data required to create a withdrawal. + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store + */ + public function testSuccessStoreBasic() + { + $account = auth()->user()->accounts()->where('account_type_id', 3)->first(); + $data = [ + 'description' => 'Some transaction #' . rand(1, 1000), + 'date' => '2018-01-01', + 'type' => 'withdrawal', + 'transactions' => [ + [ + 'amount' => '10', + 'currency_id' => 1, + 'source_id' => $account->id, + ], + + + ], + ]; + + // test API + $response = $this->post('/api/v1/transactions', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson( + [ + 'data' => [ + 'type' => 'transactions', + 'attributes' => [ + 'description' => $data['description'], + 'date' => $data['date'], + 'source_id' => $account->id, + 'source_name' => $account->name, + 'type' => 'Withdrawal', + 'source_type' => 'Asset account', + 'destination_name' => 'Cash account', + 'destination_type' => 'Cash account', + 'amount' => -10, + ], + 'links' => true, + ], + ] + ); + } + + /** + * Submit the minimum amount of data required to create a withdrawal. + * When sending a piggy bank by name, this must be reflected in the output. + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store + */ + public function testSuccessStorePiggyId() + { + $source = auth()->user()->accounts()->where('account_type_id', 3)->first(); + $dest = auth()->user()->accounts()->where('account_type_id', 3)->where('id', '!=', $source->id)->first(); + $piggy = auth()->user()->piggyBanks()->first(); + $data = [ + 'description' => 'Some transfer #' . rand(1, 1000), + 'date' => '2018-01-01', + 'type' => 'transfer', + 'piggy_bank_id' => $piggy->id, + 'transactions' => [ + [ + 'amount' => '10', + 'currency_id' => 1, + 'source_id' => $source->id, + 'destination_id' => $dest->id, + ], + ], + ]; + // test API + $response = $this->post('/api/v1/transactions?include=piggy_bank_events', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson( + [ + 'data' => [ + 'type' => 'transactions', + 'attributes' => [ + 'description' => $data['description'], + 'date' => $data['date'], + 'type' => 'Transfer', + 'source_id' => $source->id, + 'source_name' => $source->name, + 'source_type' => 'Asset account', + 'destination_id' => $dest->id, + 'destination_name' => $dest->name, + 'destination_type' => 'Asset account', + 'amount' => 10, + ], + 'links' => [], + ], + 'included' => [ + 0 => [ + 'type' => 'piggy_bank_events', + 'attributes' => [ + 'amount' => 10, + ], + ], + ], + ] + ); + } + + /** + * Submit the minimum amount of data required to create a withdrawal. + * When sending a piggy bank by name, this must be reflected in the output. + * TODO only when sending a transfer. Ignore it with withdrawals. + * + * @covers \FireflyIII\Api\V1\Controllers\TransactionController::store + */ + public function testSuccessStorePiggyName() + { + $source = auth()->user()->accounts()->where('account_type_id', 3)->first(); + $dest = auth()->user()->accounts()->where('account_type_id', 3)->where('id', '!=', $source->id)->first(); + $piggy = auth()->user()->piggyBanks()->first(); + $data = [ + 'description' => 'Some transfer #' . rand(1, 1000), + 'date' => '2018-01-01', + 'type' => 'transfer', + 'piggy_bank_name' => $piggy->name, + 'transactions' => [ + [ + 'amount' => '10', + 'currency_id' => 1, + 'source_id' => $source->id, + 'destination_id' => $dest->id, + ], + ], + ]; + // test API + $response = $this->post('/api/v1/transactions?include=piggy_bank_events', $data, ['Accept' => 'application/json']); + $response->assertStatus(200); + $response->assertJson( + [ + 'data' => [ + 'type' => 'transactions', + 'attributes' => [ + 'description' => $data['description'], + 'date' => $data['date'], + 'type' => 'Transfer', + 'source_id' => $source->id, + 'source_name' => $source->name, + 'source_type' => 'Asset account', + 'destination_id' => $dest->id, + 'destination_name' => $dest->name, + 'destination_type' => 'Asset account', + 'amount' => 10, + ], + 'links' => [], + ], + 'included' => [ + 0 => [ + 'type' => 'piggy_bank_events', + 'attributes' => [ + 'amount' => 10, + ], + ], + ], + ] + ); + } + +} \ No newline at end of file