diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php index 38003cf0e1..4df3bfd22c 100644 --- a/app/Api/V1/Controllers/RecurrenceController.php +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -24,9 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; use FireflyIII\Api\V1\Requests\RecurrenceRequest; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Models\Recurrence; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Support\Cronjobs\RecurringCronjob; +use FireflyIII\Support\Http\Api\Transactions; use FireflyIII\Transformers\RecurrenceTransformer; +use FireflyIII\Transformers\TransactionTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -36,12 +44,14 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; +use Log; /** * Class RecurrenceController */ class RecurrenceController extends Controller { + use Transactions; /** @var RecurringRepositoryInterface The recurring transaction repository */ private $repository; @@ -156,6 +166,79 @@ class RecurrenceController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } + /** + * Show transactions for this recurrence. + * + * @param Request $request + * @param Recurrence $recurrence + * + * @return JsonResponse + */ + public function transactions(Request $request, Recurrence $recurrence): JsonResponse + { + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + $type = $request->get('type') ?? 'default'; + $this->parameters->set('type', $type); + + $types = $this->mapTypes($this->parameters->get('type')); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + // whatever is returned by the query, it must be part of these journals: + $journalIds = $this->repository->getJournalIds($recurrence); + + /** @var User $admin */ + $admin = auth()->user(); + /** @var TransactionCollectorInterface $collector */ + $collector = app(TransactionCollectorInterface::class); + $collector->setUser($admin); + $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); + $collector->setAllAssetAccounts(); + $collector->setJournalIds($journalIds); + + if (\in_array(TransactionType::TRANSFER, $types, true)) { + $collector->removeFilter(InternalTransferFilter::class); + } + + if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { + $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); + } + $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); + $collector->setTypes($types); + $paginator = $collector->getPaginatedTransactions(); + $paginator->setPath(route('api.v1.transactions.index') . $this->buildParams()); + $transactions = $paginator->getCollection(); + $repository = app(JournalRepositoryInterface::class); + + $resource = new FractalCollection($transactions, new TransactionTransformer($this->parameters, $repository), 'transactions'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @return JsonResponse + * @throws FireflyException + */ + public function trigger(): JsonResponse + { + $recurring = new RecurringCronjob; + try { + $result = $recurring->fire(); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + throw new FireflyException('Could not fire recurring cron job.'); + } + if (false === $result) { + return response()->json([], 204); + } + if (true === $result) { + return response()->json([], 200); + } + throw new FireflyException('Could not fire recurring cron job.'); + } + /** * Update single recurrence. * diff --git a/app/Api/V1/Requests/RecurrenceRequest.php b/app/Api/V1/Requests/RecurrenceRequest.php index 77adb8d242..ab5ce03d31 100644 --- a/app/Api/V1/Requests/RecurrenceRequest.php +++ b/app/Api/V1/Requests/RecurrenceRequest.php @@ -25,6 +25,7 @@ namespace FireflyIII\Api\V1\Requests; use Carbon\Carbon; use FireflyIII\Rules\BelongsUser; +use FireflyIII\Rules\IsBoolean; use FireflyIII\Validation\RecurrenceValidation; use FireflyIII\Validation\TransactionValidation; use Illuminate\Validation\Validator; @@ -54,6 +55,14 @@ class RecurrenceRequest extends Request */ 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'), @@ -62,8 +71,8 @@ class RecurrenceRequest extends Request 'first_date' => $this->date('first_date'), 'repeat_until' => $this->date('repeat_until'), 'repetitions' => $this->integer('nr_of_repetitions'), - 'apply_rules' => $this->boolean('apply_rules'), - 'active' => $this->boolean('active'), + 'apply_rules' => $applyRules, + 'active' => $active, ], 'meta' => [ 'piggy_bank_id' => $this->integer('piggy_bank_id'), @@ -93,8 +102,8 @@ class RecurrenceRequest extends Request 'first_date' => sprintf('required|date|after:%s', $today->format('Y-m-d')), 'repeat_until' => sprintf('date|after:%s', $today->format('Y-m-d')), 'nr_of_repetitions' => 'numeric|between:1,31', - 'apply_rules' => 'required|boolean', - 'active' => 'required|boolean', + 'apply_rules' => [new IsBoolean], + 'active' => [new IsBoolean], 'tags' => 'between:1,64000', 'piggy_bank_id' => 'numeric', 'repetitions.*.type' => 'required|in:daily,weekly,ndom,monthly,yearly', diff --git a/app/Helpers/Collector/TransactionCollector.php b/app/Helpers/Collector/TransactionCollector.php index 46c4b200a2..3862fd2d56 100644 --- a/app/Helpers/Collector/TransactionCollector.php +++ b/app/Helpers/Collector/TransactionCollector.php @@ -527,6 +527,22 @@ class TransactionCollector implements TransactionCollectorInterface return $this; } + /** + * @param array $journalIds + * + * @return TransactionCollectorInterface + */ + public function setJournalIds(array $journalIds): TransactionCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($journalIds) { + $q->whereIn('transaction_journals.id', $journalIds); + } + ); + + return $this; + } + /** * @param Collection $journals * diff --git a/app/Helpers/Collector/TransactionCollectorInterface.php b/app/Helpers/Collector/TransactionCollectorInterface.php index ef42ffbc9c..47e37a7a48 100644 --- a/app/Helpers/Collector/TransactionCollectorInterface.php +++ b/app/Helpers/Collector/TransactionCollectorInterface.php @@ -82,13 +82,6 @@ interface TransactionCollectorInterface */ public function count(): int; - /** - * Get all transactions. - * - * @return Collection - */ - public function getTransactions(): Collection; - /** * Get a paginated result. * @@ -103,6 +96,13 @@ interface TransactionCollectorInterface */ public function getQuery(): EloquentBuilder; + /** + * Get all transactions. + * + * @return Collection + */ + public function getTransactions(): Collection; + /** * Set to ignore the cache. * @@ -198,6 +198,15 @@ interface TransactionCollectorInterface */ public function setCategory(Category $category): TransactionCollectorInterface; + /** + * Set the journal IDs to filter on. + * + * @param array $journalIds + * + * @return TransactionCollectorInterface + */ + public function setJournalIds(array $journalIds): TransactionCollectorInterface; + /** * Set the journals to filter on. * diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index de9bad9bed..803a34cb31 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -175,6 +175,22 @@ class RecurringRepository implements RecurringRepositoryInterface return $query->get(['transaction_journals.*'])->count(); } + /** + * Get journal ID's for journals created by this recurring transaction. + * + * @param Recurrence $recurrence + * + * @return array + */ + public function getJournalIds(Recurrence $recurrence): array + { + return TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->where('transaction_journals.user_id', $this->user->id) + ->where('journal_meta.name', '=', 'recurrence_id') + ->where('journal_meta.data', '=', json_encode((string)$recurrence->id)) + ->get(['journal_meta.transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); + } + /** * Get the notes. * @@ -500,6 +516,8 @@ class RecurringRepository implements RecurringRepositoryInterface return $return; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Get the number of daily occurrences for a recurring transaction until date $end is reached. Will skip every $skipMod-1 occurrences. * @@ -528,6 +546,7 @@ class RecurringRepository implements RecurringRepositoryInterface } /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Get the number of daily occurrences for a recurring transaction until date $end is reached. Will skip every $skipMod-1 occurrences. * @@ -575,6 +594,7 @@ class RecurringRepository implements RecurringRepositoryInterface } /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Get the number of daily occurrences for a recurring transaction until date $end is reached. Will skip every $skipMod-1 occurrences. * @@ -607,7 +627,6 @@ class RecurringRepository implements RecurringRepositoryInterface return $return; } - /** @noinspection MoreThanThreeArgumentsInspection */ /** * Get the number of daily occurrences for a recurring transaction until date $end is reached. Will skip every $skipMod-1 occurrences. * @@ -652,6 +671,8 @@ class RecurringRepository implements RecurringRepositoryInterface return $return; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Calculates the number of daily occurrences for a recurring transaction, starting at the date, until $count is reached. It will skip * over $skipMod -1 recurrences. @@ -681,6 +702,7 @@ class RecurringRepository implements RecurringRepositoryInterface } /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Calculates the number of monthly occurrences for a recurring transaction, starting at the date, until $count is reached. It will skip * over $skipMod -1 recurrences. @@ -720,6 +742,7 @@ class RecurringRepository implements RecurringRepositoryInterface } /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Calculates the number of NDOM occurrences for a recurring transaction, starting at the date, until $count is reached. It will skip * over $skipMod -1 recurrences. @@ -759,6 +782,7 @@ class RecurringRepository implements RecurringRepositoryInterface } /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Calculates the number of weekly occurrences for a recurring transaction, starting at the date, until $count is reached. It will skip * over $skipMod -1 recurrences. @@ -802,6 +826,7 @@ class RecurringRepository implements RecurringRepositoryInterface } /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Calculates the number of yearly occurrences for a recurring transaction, starting at the date, until $count is reached. It will skip * over $skipMod -1 recurrences. @@ -838,7 +863,6 @@ class RecurringRepository implements RecurringRepositoryInterface } - /** @noinspection MoreThanThreeArgumentsInspection */ /** * Get the number of daily occurrences for a recurring transaction until date $end is reached. Will skip every $skipMod-1 occurrences. * diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index b0a6cee56d..fb28828be9 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -89,6 +89,15 @@ interface RecurringRepositoryInterface */ public function getJournalCount(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): int; + /** + * Get journal ID's for journals created by this recurring transaction. + * + * @param Recurrence $recurrence + * + * @return array + */ + public function getJournalIds(Recurrence $recurrence): array; + /** * Get the notes. * diff --git a/routes/api.php b/routes/api.php index a4eb5b6dae..bf9aaccdce 100644 --- a/routes/api.php +++ b/routes/api.php @@ -200,9 +200,11 @@ Route::group( // Recurrence API routes: Route::get('', ['uses' => 'RecurrenceController@index', 'as' => 'index']); Route::post('', ['uses' => 'RecurrenceController@store', 'as' => 'store']); + Route::post('trigger', ['uses' => 'RecurrenceController@trigger', 'as' => 'trigger']); Route::get('{recurrence}', ['uses' => 'RecurrenceController@show', 'as' => 'show']); Route::put('{recurrence}', ['uses' => 'RecurrenceController@update', 'as' => 'update']); Route::delete('{recurrence}', ['uses' => 'RecurrenceController@delete', 'as' => 'delete']); + Route::get('{recurrence}/transactions', ['uses' => 'RecurrenceController@transactions', 'as' => 'transactions']); } );