diff --git a/app/Api/V1/Controllers/Models/Recurrence/ShowController.php b/app/Api/V1/Controllers/Models/Recurrence/ShowController.php index 74f29cface..ba5ae66248 100644 --- a/app/Api/V1/Controllers/Models/Recurrence/ShowController.php +++ b/app/Api/V1/Controllers/Models/Recurrence/ShowController.php @@ -70,10 +70,10 @@ class ShowController extends Controller */ public function index(): JsonResponse { - $manager = $this->getManager(); + $manager = $this->getManager(); // types to get, page size: - $pageSize = $this->parameters->get('limit'); + $pageSize = $this->parameters->get('limit'); // get list of budgets. Count it and split it. $collection = $this->repository->get(); @@ -82,20 +82,20 @@ class ShowController extends Controller // enrich /** @var User $admin */ - $admin = auth()->user(); - $enrichment = new RecurringEnrichment(); + $admin = auth()->user(); + $enrichment = new RecurringEnrichment(); $enrichment->setUser($admin); $recurrences = $enrichment->enrich($recurrences); // make paginator: - $paginator = new LengthAwarePaginator($recurrences, $count, $pageSize, $this->parameters->get('page')); - $paginator->setPath(route('api.v1.recurrences.index').$this->buildParams()); + $paginator = new LengthAwarePaginator($recurrences, $count, $pageSize, $this->parameters->get('page')); + $paginator->setPath(route('api.v1.recurrences.index') . $this->buildParams()); /** @var RecurrenceTransformer $transformer */ $transformer = app(RecurrenceTransformer::class); $transformer->setParameters($this->parameters); - $resource = new FractalCollection($recurrences, $transformer, 'recurrences'); + $resource = new FractalCollection($recurrences, $transformer, 'recurrences'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); @@ -109,21 +109,23 @@ class ShowController extends Controller */ public function show(Recurrence $recurrence): JsonResponse { - $manager = $this->getManager(); + $manager = $this->getManager(); // enrich /** @var User $admin */ - $admin = auth()->user(); - $enrichment = new RecurringEnrichment(); + $admin = auth()->user(); + $enrichment = new RecurringEnrichment(); $enrichment->setUser($admin); - $recurrence = $enrichment->enrichSingle($recurrence); + $recurrence = $enrichment->enrichSingle($recurrence); /** @var RecurrenceTransformer $transformer */ $transformer = app(RecurrenceTransformer::class); $transformer->setParameters($this->parameters); - $resource = new Item($recurrence, $transformer, 'recurrences'); + $resource = new Item($recurrence, $transformer, 'recurrences'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } + + } diff --git a/app/Api/V1/Controllers/Models/Recurrence/TriggerController.php b/app/Api/V1/Controllers/Models/Recurrence/TriggerController.php new file mode 100644 index 0000000000..31493b011a --- /dev/null +++ b/app/Api/V1/Controllers/Models/Recurrence/TriggerController.php @@ -0,0 +1,125 @@ +. + */ + +namespace FireflyIII\Api\V1\Controllers\Models\Recurrence; + +use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Generic\SingleDateRequest; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Jobs\CreateRecurringTransactions; +use FireflyIII\Models\Recurrence; +use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; +use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment; +use FireflyIII\Transformers\TransactionGroupTransformer; +use FireflyIII\User; +use Illuminate\Http\JsonResponse; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Resource\Collection as FractalCollection; + +class TriggerController extends Controller +{ + private RecurringRepositoryInterface $repository; + + /** + * RecurrenceController constructor. + */ + public function __construct() + { + parent::__construct(); + $this->middleware( + function ($request, $next) { + $this->repository = app(RecurringRepositoryInterface::class); + $this->repository->setUser(auth()->user()); + + return $next($request); + } + ); + } + + /** + */ + public function trigger(SingleDateRequest $request, Recurrence $recurrence): JsonResponse + { + // find recurrence occurrence for this date and trigger it. + // grab the date from the last time the recurrence fired: + $backupDate = $recurrence->latest_date; + $date = $request->getDate(); + + // fire the recurring cron job on the given date, then post-date the created transaction. + Log::info(sprintf('Trigger: will now fire recurring cron job task for date "%s".', $date->format('Y-m-d H:i:s'))); + + /** @var CreateRecurringTransactions $job */ + $job = app(CreateRecurringTransactions::class); + $job->setRecurrences(new Collection()->push($recurrence)); + $job->setDate($date); + $job->setForce(false); + $job->handle(); + Log::debug('Done with recurrence.'); + + $groups = $job->getGroups(); + $this->repository->markGroupsAsNow($groups); + $recurrence->latest_date = $backupDate; + $recurrence->latest_date_tz = $backupDate?->format('e'); + $recurrence->save(); + Preferences::mark(); + + // enrich groups and return them: + + if (0 === $groups->count()) { + $paginator = new LengthAwarePaginator(new Collection(), 0, 1,); + } + if ($groups->count() > 0) { + /** @var User $admin */ + $admin = auth()->user(); + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + ->setIds($groups->pluck('id')->toArray()) + ->withAPIInformation(); + $paginator = $collector->getPaginatedGroups(); + } + + $manager = $this->getManager(); + $paginator->setPath(route('api.v1.recurrences.trigger', [$recurrence->id]) . $this->buildParams()); + + // enrich + $admin = auth()->user(); + $enrichment = new TransactionGroupEnrichment(); + $enrichment->setUser($admin); + $transactions = $enrichment->enrich($paginator->getCollection()); + + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new FractalCollection($transactions, $transformer, 'transactions'); + $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); + } + +} diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 59b2748730..203a0283eb 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -861,8 +861,8 @@ class GroupCollector implements GroupCollectorInterface return new LengthAwarePaginator($set, $this->total, $total, 1); } - - return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); + $limit= $this->limit ?? 1; + return new LengthAwarePaginator($set, $this->total, $limit, $this->page); } /** diff --git a/app/Http/Controllers/Recurring/TriggerController.php b/app/Http/Controllers/Recurring/TriggerController.php index 818f353037..9ebfe64fac 100644 --- a/app/Http/Controllers/Recurring/TriggerController.php +++ b/app/Http/Controllers/Recurring/TriggerController.php @@ -28,8 +28,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\TriggerRecurrenceRequest; use FireflyIII\Jobs\CreateRecurringTransactions; use FireflyIII\Models\Recurrence; -use FireflyIII\Models\TransactionGroup; -use FireflyIII\Models\TransactionJournal; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\RedirectResponse; use Illuminate\Support\Collection; @@ -58,20 +57,11 @@ class TriggerController extends Controller app('log')->debug('Done with recurrence.'); $groups = $job->getGroups(); - - /** @var TransactionGroup $group */ - foreach ($groups as $group) { - /** @var TransactionJournal $journal */ - foreach ($group->transactionJournals as $journal) { - app('log')->debug(sprintf('Set date of journal #%d to today!', $journal->id)); - $journal->date = today(config('app.timezone')); - $journal->save(); - } - } + $this->repository->markGroupsAsNow($groups); $recurrence->latest_date = $backupDate; $recurrence->latest_date_tz = $backupDate?->format('e'); $recurrence->save(); - app('preferences')->mark(); + Preferences::mark(); if (0 === $groups->count()) { $request->session()->flash('info', (string) trans('firefly.no_new_transaction_in_recurrence')); diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index e12a543e38..c8ee7b89c1 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -35,6 +35,7 @@ use FireflyIII\Models\RecurrenceMeta; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransactionMeta; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Services\Internal\Destroy\RecurrenceDestroyService; @@ -582,4 +583,17 @@ class RecurringRepository implements RecurringRepositoryInterface, UserGroupInte return $service->update($recurrence, $data); } + + public function markGroupsAsNow(Collection $groups): void + { + /** @var TransactionGroup $group */ + foreach ($groups as $group) { + /** @var TransactionJournal $journal */ + foreach ($group->transactionJournals as $journal) { + Log::debug(sprintf('Set date of journal #%d to today!', $journal->id)); + $journal->date = now(config('app.timezone')); + $journal->save(); + } + } + } } diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index b59548b9de..9fb715e545 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -139,6 +139,8 @@ interface RecurringRepositoryInterface */ public function getXOccurrencesSince(RecurrenceRepetition $repetition, Carbon $date, Carbon $afterDate, int $count): array; + public function markGroupsAsNow(Collection $groups): void; + /** * Parse the repetition in a string that is user readable. */ diff --git a/changelog.md b/changelog.md index 1baeb14101..a5a464f4d8 100644 --- a/changelog.md +++ b/changelog.md @@ -36,6 +36,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - [Issue 10806](https://github.com/firefly-iii/firefly-iii/issues/10806) (API: `/v1/chart/balance/balance` has undocumented `period` parameter) reported by @dreautall - [Issue 10807](https://github.com/firefly-iii/firefly-iii/issues/10807) (API: `/v1/bills` field `object_group_id` returns int, should be string) reported by @dreautall - [Issue 10815](https://github.com/firefly-iii/firefly-iii/issues/10815) (API: `/v1/accounts` balance is off by a day) reported by @dreautall +- #10827 ## 6.3.2 - 2025-08-20 diff --git a/routes/api.php b/routes/api.php index 64775c9b77..31c902ab4b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -504,7 +504,8 @@ Route::group( static function (): void { Route::get('', ['uses' => 'ShowController@index', 'as' => 'index']); Route::post('', ['uses' => 'StoreController@store', 'as' => 'store']); - Route::get('{recurrence}', ['uses' => 'ShowController@show', 'as' => 'show']); + Route::get( '{recurrence}', ['uses' => 'ShowController@show', 'as' => 'show']); + Route::post('{recurrence}/trigger', ['uses' => 'TriggerController@trigger', 'as' => 'trigger']); Route::put('{recurrence}', ['uses' => 'UpdateController@update', 'as' => 'update']); Route::delete('{recurrence}', ['uses' => 'DestroyController@destroy', 'as' => 'delete']);