diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php index 8cae59e5ea..167a81b762 100644 --- a/app/Api/V1/Controllers/RecurrenceController.php +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -133,7 +133,7 @@ class RecurrenceController extends Controller /** * List single resource. * - * @param Request $request + * @param Request $request * @param Recurrence $recurrence * * @return JsonResponse @@ -165,7 +165,8 @@ class RecurrenceController extends Controller */ public function store(RecurrenceStoreRequest $request): JsonResponse { - $recurrence = $this->repository->store($request->getAllRecurrenceData()); + $data = $request->getAll(); + $recurrence = $this->repository->store($data); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); @@ -182,7 +183,7 @@ class RecurrenceController extends Controller /** * Show transactions for this recurrence. * - * @param Request $request + * @param Request $request * @param Recurrence $recurrence * * @return JsonResponse @@ -267,7 +268,7 @@ class RecurrenceController extends Controller * Update single recurrence. * * @param RecurrenceUpdateRequest $request - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return JsonResponse */ diff --git a/app/Api/V1/Requests/RecurrenceStoreRequest.php b/app/Api/V1/Requests/RecurrenceStoreRequest.php index d6118052e2..708b0497a4 100644 --- a/app/Api/V1/Requests/RecurrenceStoreRequest.php +++ b/app/Api/V1/Requests/RecurrenceStoreRequest.php @@ -48,6 +48,39 @@ class RecurrenceStoreRequest extends Request return auth()->check(); } + /** + * Get all data from the request. + * + * @return array + */ + public function getAll(): array + { + $active = true; + $applyRules = true; + if (null !== $this->get('active')) { + $active = $this->boolean('active'); + } + if (null !== $this->get('apply_rules')) { + $applyRules = $this->boolean('apply_rules'); + } + $return = [ + 'recurrence' => [ + 'type' => $this->string('type'), + 'title' => $this->string('title'), + 'description' => $this->string('description'), + 'first_date' => $this->date('first_date'), + 'repeat_until' => $this->date('repeat_until'), + 'repetitions' => $this->integer('nr_of_repetitions'), + 'apply_rules' => $applyRules, + 'active' => $active, + ], + 'transactions' => $this->getRecurrenceTransactionData(), + 'repetitions' => $this->getRecurrenceRepetitionData(), + ]; + + return $return; + } + /** * The rules that the incoming request must be matched against. * @@ -84,11 +117,15 @@ class RecurrenceStoreRequest extends Request // new and updated fields: 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], - 'transactions.*.budget_name' => 'between:1,255|nullable', + '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.*.tags' => 'between:1,64000', + 'transactions.*.piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser], 'transactions.*.piggy_bank_id' => ['numeric', 'mustExist:piggy_banks,id', new BelongsUser], + + 'transactions.*.tags' => 'between:1,64000', + + ]; } diff --git a/app/Api/V1/Requests/Request.php b/app/Api/V1/Requests/Request.php index 076b0e16ad..407652f7e1 100644 --- a/app/Api/V1/Requests/Request.php +++ b/app/Api/V1/Requests/Request.php @@ -24,10 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; -use Carbon\Carbon; use FireflyIII\Http\Requests\Request as FireflyIIIRequest; -use FireflyIII\Rules\BelongsUser; -use FireflyIII\Rules\IsBoolean; /** * Class Request. @@ -112,7 +109,7 @@ class Request extends FireflyIIIRequest 'meta' => [ 'piggy_bank_id' => $this->integer('piggy_bank_id'), 'piggy_bank_name' => $this->string('piggy_bank_name'), - 'tags' => explode(',', $this->string('tags')), + 'tags' => $this->get('tags'), ], 'transactions' => $this->getRecurrenceTransactionData(), 'repetitions' => $this->getRecurrenceRepetitionData(), @@ -167,16 +164,21 @@ class Request extends FireflyIIIRequest 'foreign_amount' => $transaction['foreign_amount'] ?? null, 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null, 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, - 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, - 'budget_name' => $transaction['budget_name'] ?? null, - 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, - 'category_name' => $transaction['category_name'] ?? null, 'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, 'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, 'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, 'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, 'description' => $transaction['description'], 'type' => $this->string('type'), + + // new and updated fields: + 'piggy_bank_id' => isset($transaction['piggy_bank_id']) ? (int)$transaction['piggy_bank_id'] : null, + 'piggy_bank_name' => $transaction['piggy_bank_name'] ?? null, + 'tags' => $transaction['tags'], + 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, + 'budget_name' => $transaction['budget_name'] ?? null, + 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, + 'category_name' => $transaction['category_name'] ?? null, ]; } diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php index acfe1aa586..f7cfb2f9d5 100644 --- a/app/Factory/RecurrenceFactory.php +++ b/app/Factory/RecurrenceFactory.php @@ -96,7 +96,7 @@ class RecurrenceFactory ); $recurrence->save(); - $this->updateMetaData($recurrence, $data); + //$this->updateMetaData($recurrence, $data); $this->createRepetitions($recurrence, $data['repetitions'] ?? []); try { $this->createTransactions($recurrence, $data['transactions'] ?? []); diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php index a4225c4e51..3aac8aef65 100644 --- a/app/Services/Internal/Support/RecurringTransactionTrait.php +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -50,7 +50,7 @@ trait RecurringTransactionTrait { /** * @param Recurrence $recurrence - * @param array $repetitions + * @param array $repetitions */ protected function createRepetitions(Recurrence $recurrence, array $repetitions): void { @@ -69,63 +69,12 @@ trait RecurringTransactionTrait } } - /** - * @param array $expectedTypes - * @param Account|null $account - * @param int|null $accountId - * @param string|null $accountName - * - * @return Account - */ - protected function findAccount(array $expectedTypes, ?int $accountId, ?string $accountName): Account - { - $result = null; - $accountId = (int)$accountId; - $accountName = (string)$accountName; - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - // if user has submitted an account ID, search for it. - $result = $repository->findNull((int)$accountId); - if (null !== $result) { - return $result; - } - - // if user has submitted a name, search for it: - $result = $repository->findByName($accountName, $expectedTypes); - if (null !== $result) { - return $result; - } - - // maybe we can create it? Try to avoid LOAN and other asset types. - $cannotCreate = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; - /** @var AccountFactory $factory */ - $factory = app(AccountFactory::class); - $factory->setUser($this->user); - foreach ($expectedTypes as $expectedType) { - if (in_array($expectedType, $cannotCreate, true)) { - continue; - } - if (!in_array($expectedType, $cannotCreate, true)) { - try { - $result = $factory->findOrCreate($accountName, $expectedType); - // @codeCoverageIgnoreStart - } catch (FireflyException $e) { - Log::error($e->getMessage()); - } - // @codeCoverageIgnoreEnd - } - } - - return $result ?? $repository->getCashAccount(); - } - /** * Store transactions of a recurring transactions. It's complex but readable. * * @param Recurrence $recurrence - * @param array $transactions + * @param array $transactions + * * @throws FireflyException */ protected function createTransactions(Recurrence $recurrence, array $transactions): void @@ -181,6 +130,15 @@ trait RecurringTransactionTrait $categoryFactory->setUser($recurrence->user); $category = $categoryFactory->findOrCreate($array['category_id'], $array['category_name']); + // same for piggy bank + $piggyId = (int)($array['piggy_bank_id'] ?? 0.0); + $piggyName = $array['piggy_bank_name'] ?? ''; + $this->updatePiggyBank($transaction, $piggyId, $piggyName); + + // same for tags + $tags = $array['tags'] ?? []; + $this->updateTags($transaction, $tags); + // create recurrence transaction meta: if (null !== $budget) { RecurrenceTransactionMeta::create( @@ -232,69 +190,121 @@ trait RecurringTransactionTrait } /** - * Update meta data for recurring transaction. + * @param array $expectedTypes + * @param Account|null $account + * @param int|null $accountId + * @param string|null $accountName * - * @param Recurrence $recurrence - * @param array $data + * @return Account */ - protected function updateMetaData(Recurrence $recurrence, array $data): void + protected function findAccount(array $expectedTypes, ?int $accountId, ?string $accountName): Account { - // only two special meta fields right now. Let's just hard code them. - $piggyId = (int)($data['meta']['piggy_bank_id'] ?? 0.0); - $piggyName = $data['meta']['piggy_bank_name'] ?? ''; - $this->updatePiggyBank($recurrence, $piggyId, $piggyName); + $result = null; + $accountId = (int)$accountId; + $accountName = (string)$accountName; + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $repository->setUser($this->user); + // if user has submitted an account ID, search for it. + $result = $repository->findNull((int)$accountId); + if (null !== $result) { + return $result; + } - $tags = $data['meta']['tags'] ?? []; - $this->updateTags($recurrence, $tags); + // if user has submitted a name, search for it: + $result = $repository->findByName($accountName, $expectedTypes); + if (null !== $result) { + return $result; + } + // maybe we can create it? Try to avoid LOAN and other asset types. + $cannotCreate = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; + /** @var AccountFactory $factory */ + $factory = app(AccountFactory::class); + $factory->setUser($this->user); + foreach ($expectedTypes as $expectedType) { + if (in_array($expectedType, $cannotCreate, true)) { + continue; + } + if (!in_array($expectedType, $cannotCreate, true)) { + try { + $result = $factory->findOrCreate($accountName, $expectedType); + // @codeCoverageIgnoreStart + } catch (FireflyException $e) { + Log::error($e->getMessage()); + } + // @codeCoverageIgnoreEnd + } + } + + return $result ?? $repository->getCashAccount(); } + // /** + // * Update meta data for recurring transaction. + // * + // * @param Recurrence $recurrence + // * @param array $data + // */ + // protected function updateMetaData(Recurrence $recurrence, array $data): void + // { + // // only two special meta fields right now. Let's just hard code them. + // $piggyId = (int)($data['meta']['piggy_bank_id'] ?? 0.0); + // $piggyName = $data['meta']['piggy_bank_name'] ?? ''; + // $this->updatePiggyBank($recurrence, $piggyId, $piggyName); + // + // + // $tags = $data['meta']['tags'] ?? []; + // $this->updateTags($recurrence, $tags); + // + // } + /** - * @param Recurrence $recurrence - * @param int $piggyId - * @param string $piggyName + * @param RecurrenceTransaction $transaction + * @param int $piggyId + * @param string $piggyName */ - protected function updatePiggyBank(Recurrence $recurrence, int $piggyId, string $piggyName): void + protected function updatePiggyBank(RecurrenceTransaction $transaction, int $piggyId, string $piggyName): void { /** @var PiggyBankFactory $factory */ $factory = app(PiggyBankFactory::class); - $factory->setUser($recurrence->user); + $factory->setUser($transaction->recurrence->user); $piggyBank = $factory->find($piggyId, $piggyName); if (null !== $piggyBank) { /** @var RecurrenceMeta $entry */ - $entry = $recurrence->recurrenceMeta()->where('name', 'piggy_bank_id')->first(); + $entry = $transaction->recurrenceTransactionMeta()->where('name', 'piggy_bank_id')->first(); if (null === $entry) { - $entry = RecurrenceMeta::create(['recurrence_id' => $recurrence->id, 'name' => 'piggy_bank_id', 'value' => $piggyBank->id]); + $entry = RecurrenceTransactionMeta::create(['rt_id' => $transaction->id, 'name' => 'piggy_bank_id', 'value' => $piggyBank->id]); } $entry->value = $piggyBank->id; $entry->save(); } if (null === $piggyBank) { // delete if present - $recurrence->recurrenceMeta()->where('name', 'piggy_bank_id')->delete(); + $transaction->recurrenceTransactionMeta()->where('name', 'piggy_bank_id')->delete(); } } /** - * @param Recurrence $recurrence - * @param array $tags + * @param RecurrenceTransaction $transaction + * @param array $tags */ - protected function updateTags(Recurrence $recurrence, array $tags): void + protected function updateTags(RecurrenceTransaction $transaction, array $tags): void { if (count($tags) > 0) { /** @var RecurrenceMeta $entry */ - $entry = $recurrence->recurrenceMeta()->where('name', 'tags')->first(); + $entry = $transaction->recurrenceTransactionMeta()->where('name', 'tags')->first(); if (null === $entry) { - $entry = RecurrenceMeta::create(['recurrence_id' => $recurrence->id, 'name' => 'tags', 'value' => implode(',', $tags)]); + $entry = RecurrenceTransactionMeta::create(['rt_id' => $transaction->id, 'name' => 'tags', 'value' => json_encode($tags)]); } - $entry->value = implode(',', $tags); + $entry->value = json_encode($tags); $entry->save(); } if (0 === count($tags)) { // delete if present - $recurrence->recurrenceMeta()->where('name', 'tags')->delete(); + $transaction->recurrenceTransactionMeta()->where('name', 'tags')->delete(); } } } diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index ea363ae513..94e14a0d49 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -222,14 +222,14 @@ class RecurrenceTransformer extends AbstractTransformer $array['budget_name'] = null; $array['piggy_bank_id'] = null; $array['piggy_bank_name'] = null; - + /** @var RecurrenceTransactionMeta $transactionMeta */ foreach ($transaction->recurrenceTransactionMeta as $transactionMeta) { switch ($transactionMeta->name) { default: throw new FireflyException(sprintf('Recurrence transformer cant handle field "%s"', $transactionMeta->name)); case 'tags': - $array['tags'] = explode(',', $transactionMeta->value); + $array['tags'] = json_decode($transactionMeta->value); break; case 'bill_id': $bill = $this->billRepos->find((int)$transactionMeta->value); diff --git a/app/Validation/TransactionValidation.php b/app/Validation/TransactionValidation.php index 62dd43259a..da1cf6101d 100644 --- a/app/Validation/TransactionValidation.php +++ b/app/Validation/TransactionValidation.php @@ -172,7 +172,7 @@ trait TransactionValidation $transactions = $data['transactions'] ?? []; // need at least one transaction if (0 === count($transactions)) { - $validator->errors()->add('description', (string)trans('validation.at_least_one_transaction')); + $validator->errors()->add('transactions', (string)trans('validation.at_least_one_transaction')); } }