diff --git a/app/Factory/TransactionJournalMetaFactory.php b/app/Factory/TransactionJournalMetaFactory.php index 0c19840f59..48f8ee1f83 100644 --- a/app/Factory/TransactionJournalMetaFactory.php +++ b/app/Factory/TransactionJournalMetaFactory.php @@ -57,7 +57,7 @@ class TransactionJournalMetaFactory } if ($data['data'] instanceof Carbon) { - //Log::debug('Is a carbon object.'); + Log::debug('Is a carbon object.'); $value = $data['data']->toW3cString(); } if ('' === (string) $value) { diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php index 906e888a09..af7ed34f17 100644 --- a/app/Http/Controllers/Account/IndexController.php +++ b/app/Http/Controllers/Account/IndexController.php @@ -166,7 +166,6 @@ class IndexController extends Controller $accounts->each( function (Account $account) use ($activities, $startBalances, $endBalances) { - $interest = (string)$this->repository->getMetaValue($account, 'interest'); $interest = '' === $interest ? '0' : $interest; diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php index c461b2ff08..9fa3aa4b79 100644 --- a/app/Http/Controllers/Account/ReconcileController.php +++ b/app/Http/Controllers/Account/ReconcileController.php @@ -125,7 +125,7 @@ class ReconcileController extends Controller $startDate = clone $start; $startDate->subDay(); $startBalance = app('steam')->bcround(app('steam')->balance($account, $startDate), $currency->decimal_places); - $endBalance = app('steam')->bcround( app('steam')->balance($account, $end), $currency->decimal_places); + $endBalance = app('steam')->bcround(app('steam')->balance($account, $end), $currency->decimal_places); $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); $subTitle = (string) trans('firefly.reconcile_account', ['account' => $account->name]); diff --git a/app/Http/Controllers/PiggyBank/AmountController.php b/app/Http/Controllers/PiggyBank/AmountController.php index 59421463f2..a5d00f662e 100644 --- a/app/Http/Controllers/PiggyBank/AmountController.php +++ b/app/Http/Controllers/PiggyBank/AmountController.php @@ -77,7 +77,7 @@ class AmountController extends Controller $leftOnAccount = $this->piggyRepos->leftOnAccount($piggyBank, today(config('app.timezone'))); $savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank); $maxAmount = $leftOnAccount; - if (0 !== bccomp($piggyBank->targetamount,'0')) { + if (0 !== bccomp($piggyBank->targetamount, '0')) { $leftToSave = bcsub($piggyBank->targetamount, $savedSoFar); $maxAmount = min($leftOnAccount, $leftToSave); } @@ -101,7 +101,7 @@ class AmountController extends Controller $savedSoFar = $this->piggyRepos->getCurrentAmount($piggyBank); $maxAmount = $leftOnAccount; - if (0 !== bccomp($piggyBank->targetamount,'0')) { + if (0 !== bccomp($piggyBank->targetamount, '0')) { $leftToSave = bcsub($piggyBank->targetamount, $savedSoFar); $maxAmount = min($leftOnAccount, $leftToSave); } diff --git a/app/Http/Controllers/Recurring/ShowController.php b/app/Http/Controllers/Recurring/ShowController.php index 8c9ff0b250..055518626a 100644 --- a/app/Http/Controllers/Recurring/ShowController.php +++ b/app/Http/Controllers/Recurring/ShowController.php @@ -59,7 +59,7 @@ class ShowController extends Controller $this->middleware( function ($request, $next) { app('view')->share('mainTitleIcon', 'fa-paint-brush'); - app('view')->share('title', (string) trans('firefly.recurrences')); + app('view')->share('title', (string)trans('firefly.recurrences')); $this->recurring = app(RecurringRepositoryInterface::class); @@ -71,7 +71,7 @@ class ShowController extends Controller /** * Show a single recurring transaction. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return Factory|View * @throws FireflyException @@ -87,14 +87,19 @@ class ShowController extends Controller $today = today(config('app.timezone')); $array['repeat_until'] = null !== $array['repeat_until'] ? new Carbon($array['repeat_until']) : null; - // transform dates back to Carbon objects: + // transform dates back to Carbon objects and expand information foreach ($array['repetitions'] as $index => $repetition) { foreach ($repetition['occurrences'] as $item => $occurrence) { - $array['repetitions'][$index]['occurrences'][$item] = new Carbon($occurrence); + $date = (new Carbon($occurrence))->startOfDay(); + $set = [ + 'date' => $date, + 'fired' => $this->recurring->createdPreviously($recurrence, $date) || $this->recurring->getJournalCount($recurrence, $date) > 0, + ]; + $array['repetitions'][$index]['occurrences'][$item] = $set; } } - $subTitle = (string) trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]); + $subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]); return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups', 'today')); } diff --git a/app/Http/Controllers/Recurring/TriggerController.php b/app/Http/Controllers/Recurring/TriggerController.php new file mode 100644 index 0000000000..1c06bb4ccb --- /dev/null +++ b/app/Http/Controllers/Recurring/TriggerController.php @@ -0,0 +1,89 @@ +. + */ + +namespace FireflyIII\Http\Controllers\Recurring; + +use Carbon\Carbon; +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 Illuminate\Http\RedirectResponse; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; + +/** + * Class TriggerController + */ +class TriggerController extends Controller +{ + /** + * @param Recurrence $recurrence + * @param TriggerRecurrenceRequest $request + * @return RedirectResponse + */ + public function trigger(Recurrence $recurrence, TriggerRecurrenceRequest $request): RedirectResponse + { + $all = $request->getAll(); + $date = $all['date']; + + // grab the date from the last time the recurrence fired: + $backupDate = $recurrence->latest_date; + + // 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([$recurrence])); + $job->setDate($date); + $job->setForce(false); + $job->handle(); + Log::debug('Done with recurrence.'); + + $groups = $job->getGroups(); + /** @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, $date)); + $journal->date = Carbon::today(); + $journal->save(); + } + } + $recurrence->latest_date = $backupDate; + $recurrence->save(); + app('preferences')->mark(); + + if (0 === $groups->count()) { + $request->session()->flash('info', (string)trans('firefly.no_new_transaction_in_recurrence')); + } + if (1 === $groups->count()) { + $first = $groups->first(); + $request->session()->flash('success', (string)trans('firefly.stored_journal_no_descr')); + $request->session()->flash('success_url', route('transactions.show', [$first->id])); + } + + + return redirect(route('recurring.show', [$recurrence->id])); + } +} diff --git a/app/Http/Requests/TriggerRecurrenceRequest.php b/app/Http/Requests/TriggerRecurrenceRequest.php new file mode 100644 index 0000000000..c625b671df --- /dev/null +++ b/app/Http/Requests/TriggerRecurrenceRequest.php @@ -0,0 +1,56 @@ +. + */ + +namespace FireflyIII\Http\Requests; + +use FireflyIII\Support\Request\ChecksLogin; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + +class TriggerRecurrenceRequest extends FormRequest +{ + use ConvertsDataTypes; + use ChecksLogin; + + /** + * Returns the data required by the controller. + * + * @return array + */ + public function getAll(): array + { + return [ + 'date' => $this->getCarbonDate('date'), + ]; + } + + /** + * Rules for this request. + * + * @return array + */ + public function rules(): array + { + return [ + 'date' => 'required|date', + ]; + } +} diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 27c81b3076..04563b115a 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -63,26 +63,27 @@ class CreateRecurringTransactions implements ShouldQueue private TransactionGroupRepositoryInterface $groupRepository; private JournalRepositoryInterface $journalRepository; private RecurringRepositoryInterface $repository; + private Collection $recurrences; + private Collection $groups; /** * Create a new job instance. * * @codeCoverageIgnore * - * @param Carbon|null $date + * @param Carbon|null $date */ public function __construct(?Carbon $date) { + $newDate = new Carbon(); + $newDate->startOfDay(); + $this->date = $newDate; + if (null !== $date) { $newDate = clone $date; $newDate->startOfDay(); $this->date = $newDate; } - if (null === $date) { - $newDate = new Carbon(); - $newDate->startOfDay(); - $this->date = $newDate; - } $this->repository = app(RecurringRepositoryInterface::class); $this->journalRepository = app(JournalRepositoryInterface::class); $this->groupRepository = app(TransactionGroupRepositoryInterface::class); @@ -90,6 +91,8 @@ class CreateRecurringTransactions implements ShouldQueue $this->submitted = 0; $this->executed = 0; $this->created = 0; + $this->recurrences = new Collection(); + $this->groups = new Collection(); Log::debug(sprintf('Created new CreateRecurringTransactions("%s")', $this->date->format('Y-m-d'))); } @@ -100,14 +103,23 @@ class CreateRecurringTransactions implements ShouldQueue public function handle(): void { Log::debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y'))); - $recurrences = $this->repository->getAll(); + + // only use recurrences from database if there is no collection submitted. + if (0 !== count($this->recurrences)) { + Log::debug('Using predetermined set of recurrences.'); + } + if (0 === count($this->recurrences)) { + Log::debug('Grab all recurrences from the database.'); + $this->recurrences = $this->repository->getAll(); + } + $result = []; - $count = $recurrences->count(); + $count = $this->recurrences->count(); $this->submitted = $count; Log::debug(sprintf('Count of collection is %d', $count)); // filter recurrences: - $filtered = $this->filterRecurrences($recurrences); + $filtered = $this->filterRecurrences($this->recurrences); Log::debug(sprintf('Left after filtering is %d', $filtered->count())); /** @var Recurrence $recurrence */ foreach ($filtered as $recurrence) { @@ -141,7 +153,7 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * @param Collection $recurrences + * @param Collection $recurrences * * @return Collection */ @@ -157,7 +169,7 @@ class CreateRecurringTransactions implements ShouldQueue /** * Is the info in the recurrence valid? * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return bool * @@ -179,6 +191,7 @@ class CreateRecurringTransactions implements ShouldQueue return false; } + // is no longer running if ($this->repeatUntilHasPassed($recurrence)) { Log::info( @@ -221,7 +234,7 @@ class CreateRecurringTransactions implements ShouldQueue /** * Return recurring transaction is active. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return bool */ @@ -233,7 +246,7 @@ class CreateRecurringTransactions implements ShouldQueue /** * Return true if the $repeat_until date is in the past. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return bool */ @@ -246,7 +259,7 @@ class CreateRecurringTransactions implements ShouldQueue /** * Has the recurrence started yet? * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return bool */ @@ -261,7 +274,7 @@ class CreateRecurringTransactions implements ShouldQueue /** * Get the start date of a recurrence. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return Carbon */ @@ -278,7 +291,7 @@ class CreateRecurringTransactions implements ShouldQueue /** * Has the recurrence fired today. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return bool */ @@ -291,7 +304,7 @@ class CreateRecurringTransactions implements ShouldQueue * Separate method that will loop all repetitions and do something with it. Will return * all created transaction journals. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return Collection * @throws DuplicateTransactionException @@ -312,7 +325,7 @@ class CreateRecurringTransactions implements ShouldQueue ); // start looping from $startDate to today perhaps we have a hit? - // add two days to $this->date so we always include the weekend. + // add two days to $this->date, so we always include the weekend. $includeWeekend = clone $this->date; $includeWeekend->addDays(2); $occurrences = $this->repository->getOccurrencesInRange($repetition, $recurrence->first_date, $includeWeekend); @@ -329,9 +342,9 @@ class CreateRecurringTransactions implements ShouldQueue /** * Check if the occurences should be executed. * - * @param Recurrence $recurrence - * @param RecurrenceRepetition $repetition - * @param array $occurrences + * @param Recurrence $recurrence + * @param RecurrenceRepetition $repetition + * @param array $occurrences * * @return Collection * @throws DuplicateTransactionException @@ -352,9 +365,9 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * @param Recurrence $recurrence - * @param RecurrenceRepetition $repetition - * @param Carbon $date + * @param Recurrence $recurrence + * @param RecurrenceRepetition $repetition + * @param Carbon $date * * @return TransactionGroup|null * @throws DuplicateTransactionException @@ -376,13 +389,20 @@ class CreateRecurringTransactions implements ShouldQueue return null; } + if ($this->repository->createdPreviously($recurrence, $date) && false === $this->force) { + Log::info('There is a transaction already made for this date, so will not be created now'); + + return null; + } + + if ($journalCount > 0 && true === $this->force) { app('log')->warning(sprintf('Already created %d groups for date %s but FORCED to continue.', $journalCount, $date->format('Y-m-d'))); } // create transaction array and send to factory. $groupTitle = null; - $count = $recurrence->recurrenceTransactions->count(); + $count = $recurrence->recurrenceTransactions->count(); if ($count > 1) { /** @var RecurrenceTransaction $first */ @@ -408,6 +428,7 @@ class CreateRecurringTransactions implements ShouldQueue // trigger event: event(new StoredTransactionGroup($group, $recurrence->apply_rules, true)); + $this->groups->push($group); // update recurring thing: $recurrence->latest_date = $date; @@ -419,9 +440,9 @@ class CreateRecurringTransactions implements ShouldQueue /** * Get transaction information from a recurring transaction. * - * @param Recurrence $recurrence - * @param RecurrenceRepetition $repetition - * @param Carbon $date + * @param Recurrence $recurrence + * @param RecurrenceRepetition $repetition + * @param Carbon $date * * @return array * @@ -439,7 +460,7 @@ class CreateRecurringTransactions implements ShouldQueue 'type' => strtolower($recurrence->transactionType->type), 'date' => $date, 'user' => $recurrence->user_id, - 'currency_id' => (int) $transaction->transaction_currency_id, + 'currency_id' => (int)$transaction->transaction_currency_id, 'currency_code' => null, 'description' => $transactions->first()->description, 'amount' => $transaction->amount, @@ -456,9 +477,9 @@ class CreateRecurringTransactions implements ShouldQueue 'foreign_amount' => $transaction->foreign_amount, 'reconciled' => false, 'identifier' => $index, - 'recurrence_id' => (int) $recurrence->id, + 'recurrence_id' => (int)$recurrence->id, 'order' => $index, - 'notes' => (string) trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]), + 'notes' => (string)trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]), 'tags' => $this->repository->getTags($transaction), 'piggy_bank_id' => $this->repository->getPiggyBank($transaction), 'piggy_bank_name' => null, @@ -466,6 +487,7 @@ class CreateRecurringTransactions implements ShouldQueue 'bill_name' => null, 'recurrence_total' => $total, 'recurrence_count' => $count, + 'recurrence_date' => $date, ]; $return[] = $single; } @@ -474,7 +496,7 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * @param Carbon $date + * @param Carbon $date */ public function setDate(Carbon $date): void { @@ -484,10 +506,26 @@ class CreateRecurringTransactions implements ShouldQueue } /** - * @param bool $force + * @param bool $force */ public function setForce(bool $force): void { $this->force = $force; } + + /** + * @param Collection $recurrences + */ + public function setRecurrences(Collection $recurrences): void + { + $this->recurrences = $recurrences; + } + + /** + * @return Collection + */ + public function getGroups(): Collection + { + return $this->groups; + } } diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index bd7c9d6116..cdc1eafcc3 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -43,6 +43,7 @@ use FireflyIII\Support\Repositories\Recurring\CalculateXOccurrences; use FireflyIII\Support\Repositories\Recurring\CalculateXOccurrencesSince; use FireflyIII\Support\Repositories\Recurring\FiltersWeekends; use FireflyIII\User; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection; use JsonException; @@ -63,7 +64,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Destroy a recurring transaction. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence */ public function destroy(Recurrence $recurrence): void { @@ -104,9 +105,9 @@ class RecurringRepository implements RecurringRepositoryInterface { // grab ALL recurring transactions: return Recurrence::with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions']) - ->orderBy('active', 'DESC') - ->orderBy('title', 'ASC') - ->get(); + ->orderBy('active', 'DESC') + ->orderBy('title', 'ASC') + ->get(); } /** @@ -118,7 +119,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var RecurrenceTransactionMeta $meta */ foreach ($recTransaction->recurrenceTransactionMeta as $meta) { if ('bill_id' === $meta->name) { - $return = (int) $meta->value; + $return = (int)$meta->value; } } @@ -128,7 +129,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Get the budget ID from a recurring transaction transaction. * - * @param RecurrenceTransaction $recTransaction + * @param RecurrenceTransaction $recTransaction * * @return null|int */ @@ -138,7 +139,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var RecurrenceTransactionMeta $meta */ foreach ($recTransaction->recurrenceTransactionMeta as $meta) { if ('budget_id' === $meta->name) { - $return = (int) $meta->value; + $return = (int)$meta->value; } } @@ -148,7 +149,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Get the category from a recurring transaction transaction. * - * @param RecurrenceTransaction $recTransaction + * @param RecurrenceTransaction $recTransaction * * @return null|int */ @@ -158,7 +159,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var RecurrenceTransactionMeta $meta */ foreach ($recTransaction->recurrenceTransactionMeta as $meta) { if ('category_id' === $meta->name) { - $return = (int) $meta->value; + $return = (int)$meta->value; } } @@ -168,7 +169,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Get the category from a recurring transaction transaction. * - * @param RecurrenceTransaction $recTransaction + * @param RecurrenceTransaction $recTransaction * * @return null|string */ @@ -178,7 +179,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var RecurrenceTransactionMeta $meta */ foreach ($recTransaction->recurrenceTransactionMeta as $meta) { if ('category_name' === $meta->name) { - $return = (string) $meta->value; + $return = (string)$meta->value; } } @@ -188,33 +189,32 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Returns the journals created for this recurrence, possibly limited by time. * - * @param Recurrence $recurrence - * @param Carbon|null $start - * @param Carbon|null $end + * @param Recurrence $recurrence + * @param Carbon|null $start + * @param Carbon|null $end * * @return int */ public function getJournalCount(Recurrence $recurrence, Carbon $start = null, Carbon $end = null): int { $query = TransactionJournal::leftJoin('journal_meta', 'journal_meta.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transaction_journals.user_id', $recurrence->user_id) - ->whereNull('transaction_journals.deleted_at') - ->where('journal_meta.name', 'recurrence_id') - ->where('journal_meta.data', '"' . $recurrence->id . '"'); + ->where('transaction_journals.user_id', $recurrence->user_id) + ->whereNull('transaction_journals.deleted_at') + ->where('journal_meta.name', 'recurrence_id') + ->where('journal_meta.data', '"'.$recurrence->id.'"'); if (null !== $start) { $query->where('transaction_journals.date', '>=', $start->format('Y-m-d 00:00:00')); } if (null !== $end) { $query->where('transaction_journals.date', '<=', $end->format('Y-m-d 00:00:00')); } - return $query->count(['transaction_journals.id']); } /** * Get journal ID's for journals created by this recurring transaction. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return array */ @@ -223,14 +223,14 @@ class RecurringRepository implements RecurringRepositoryInterface 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)) + ->where('journal_meta.data', '=', json_encode((string)$recurrence->id)) ->get(['journal_meta.transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); } /** * Get the notes. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return string */ @@ -239,14 +239,14 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var Note $note */ $note = $recurrence->notes()->first(); if (null !== $note) { - return (string) $note->text; + return (string)$note->text; } return ''; } /** - * @param RecurrenceTransaction $transaction + * @param RecurrenceTransaction $transaction * * @return int|null */ @@ -256,7 +256,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** @var RecurrenceTransactionMeta $metaEntry */ foreach ($meta as $metaEntry) { if ('piggy_bank_id' === $metaEntry->name) { - return (int) $metaEntry->value; + return (int)$metaEntry->value; } } @@ -266,7 +266,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Get the tags from the recurring transaction. * - * @param RecurrenceTransaction $transaction + * @param RecurrenceTransaction $transaction * * @return array * @throws JsonException @@ -285,23 +285,23 @@ class RecurringRepository implements RecurringRepositoryInterface } /** - * @param Recurrence $recurrence - * @param int $page - * @param int $pageSize + * @param Recurrence $recurrence + * @param int $page + * @param int $pageSize * * @return LengthAwarePaginator */ public function getTransactionPaginator(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator { $journalMeta = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') - ->whereNull('transaction_journals.deleted_at') - ->where('transaction_journals.user_id', $this->user->id) - ->where('name', 'recurrence_id') - ->where('data', json_encode((string) $recurrence->id)) - ->get()->pluck('transaction_journal_id')->toArray(); + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.user_id', $this->user->id) + ->where('name', 'recurrence_id') + ->where('data', json_encode((string)$recurrence->id)) + ->get()->pluck('transaction_journal_id')->toArray(); $search = []; foreach ($journalMeta as $journalId) { - $search[] = (int) $journalId; + $search[] = (int)$journalId; } /** @var GroupCollectorInterface $collector */ $collector = app(GroupCollectorInterface::class); @@ -315,22 +315,22 @@ class RecurringRepository implements RecurringRepositoryInterface } /** - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return Collection */ public function getTransactions(Recurrence $recurrence): Collection { $journalMeta = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') - ->whereNull('transaction_journals.deleted_at') - ->where('transaction_journals.user_id', $this->user->id) - ->where('name', 'recurrence_id') - ->where('data', json_encode((string) $recurrence->id)) - ->get()->pluck('transaction_journal_id')->toArray(); + ->whereNull('transaction_journals.deleted_at') + ->where('transaction_journals.user_id', $this->user->id) + ->where('name', 'recurrence_id') + ->where('data', json_encode((string)$recurrence->id)) + ->get()->pluck('transaction_journal_id')->toArray(); $search = []; foreach ($journalMeta as $journalId) { - $search[] = (int) $journalId; + $search[] = (int)$journalId; } if (0 === count($search)) { return new Collection(); @@ -350,9 +350,9 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Calculate the next X iterations starting on the date given in $date. * - * @param RecurrenceRepetition $repetition - * @param Carbon $date - * @param int $count + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param int $count * * @return array * @@ -387,10 +387,10 @@ class RecurringRepository implements RecurringRepositoryInterface * * Only returns them of they are after $afterDate * - * @param RecurrenceRepetition $repetition - * @param Carbon $date - * @param Carbon $afterDate - * @param int $count + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param Carbon $afterDate + * @param int $count * * @return array */ @@ -425,8 +425,8 @@ class RecurringRepository implements RecurringRepositoryInterface } /** - * @param Carbon|null $max - * @param array $occurrences + * @param Carbon|null $max + * @param array $occurrences * * @return array */ @@ -448,7 +448,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Parse the repetition in a string that is user readable. * - * @param RecurrenceRepetition $repetition + * @param RecurrenceRepetition $repetition * * @return string * @throws FireflyException @@ -460,26 +460,26 @@ class RecurringRepository implements RecurringRepositoryInterface $pref = app('preferences')->getForUser($this->user, 'language', config('firefly.default_language', 'en_US')); $language = $pref->data; if ('daily' === $repetition->repetition_type) { - return (string) trans('firefly.recurring_daily', [], $language); + return (string)trans('firefly.recurring_daily', [], $language); } if ('weekly' === $repetition->repetition_type) { $dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $language); if ($repetition->repetition_skip > 0) { - return (string) trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language); + return (string)trans('firefly.recurring_weekly_skip', ['weekday' => $dayOfWeek, 'skip' => $repetition->repetition_skip + 1], $language); } - return (string) trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language); + return (string)trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language); } if ('monthly' === $repetition->repetition_type) { if ($repetition->repetition_skip > 0) { - return (string) trans( + return (string)trans( 'firefly.recurring_monthly_skip', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip + 1], $language ); } - return (string) trans( + return (string)trans( 'firefly.recurring_monthly', ['dayOfMonth' => $repetition->repetition_moment, 'skip' => $repetition->repetition_skip - 1], $language @@ -490,7 +490,7 @@ class RecurringRepository implements RecurringRepositoryInterface // first part is number of week, second is weekday. $dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language); - return (string) trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language); + return (string)trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language); } if ('yearly' === $repetition->repetition_type) { // @@ -498,9 +498,9 @@ class RecurringRepository implements RecurringRepositoryInterface $repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment); $diffInYears = $today->diffInYears($repDate); $repDate->addYears($diffInYears); // technically not necessary. - $string = $repDate->isoFormat((string) trans('config.month_and_day_no_year_js')); + $string = $repDate->isoFormat((string)trans('config.month_and_day_no_year_js')); - return (string) trans('firefly.recurring_yearly', ['date' => $string], $language); + return (string)trans('firefly.recurring_yearly', ['date' => $string], $language); } return ''; @@ -524,7 +524,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Set user for in repository. * - * @param User $user + * @param User $user */ public function setUser(User $user): void { @@ -532,7 +532,7 @@ class RecurringRepository implements RecurringRepositoryInterface } /** - * @param array $data + * @param array $data * * @return Recurrence * @throws FireflyException @@ -556,16 +556,16 @@ class RecurringRepository implements RecurringRepositoryInterface public function totalTransactions(Recurrence $recurrence, RecurrenceRepetition $repetition): int { // if repeat = null just return 0. - if (null === $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) { + if (null === $recurrence->repeat_until && 0 === (int)$recurrence->repetitions) { return 0; } // expect X transactions then stop. Return that number - if (null === $recurrence->repeat_until && 0 !== (int) $recurrence->repetitions) { - return (int) $recurrence->repetitions; + if (null === $recurrence->repeat_until && 0 !== (int)$recurrence->repetitions) { + return (int)$recurrence->repetitions; } // need to calculate, this depends on the repetition: - if (null !== $recurrence->repeat_until && 0 === (int) $recurrence->repetitions) { + if (null !== $recurrence->repeat_until && 0 === (int)$recurrence->repetitions) { $occurrences = $this->getOccurrencesInRange($repetition, $recurrence->first_date ?? today(), $recurrence->repeat_until); return count($occurrences); @@ -577,9 +577,9 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Generate events in the date range. * - * @param RecurrenceRepetition $repetition - * @param Carbon $start - * @param Carbon $end + * @param RecurrenceRepetition $repetition + * @param Carbon $start + * @param Carbon $end * * @return array * @@ -616,8 +616,8 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Update a recurring transaction. * - * @param Recurrence $recurrence - * @param array $data + * @param Recurrence $recurrence + * @param array $data * * @return Recurrence * @throws FireflyException @@ -629,4 +629,34 @@ class RecurringRepository implements RecurringRepositoryInterface return $service->update($recurrence, $data); } + + /** + * @inheritDoc + */ + public function createdPreviously(Recurrence $recurrence, Carbon $date): bool + { + // if not, loop set and try to read the recurrence_date. If it matches start or end, return it as well. + $set = + TransactionJournalMeta::where(function (Builder $q1) use ($recurrence) { + $q1->where('name', 'recurrence_id'); + $q1->where('data', json_encode((string)$recurrence->id)); + })->get(['journal_meta.transaction_journal_id']); + + // there are X journals made for this recurrence. Any of them meant for today? + foreach ($set as $journalMeta) { + $count = TransactionJournalMeta::where(function (Builder $q2) use ($date) { + $string = (string)$date; + Log::debug(sprintf('Search for date: %s', json_encode($string))); + $q2->where('name', 'recurrence_date'); + $q2->where('data', json_encode($string)); + }) + ->where('transaction_journal_id', $journalMeta->transaction_journal_id) + ->count(); + if ($count > 0) { + Log::debug(sprintf('Looks like journal #%d was already created', $journalMeta->transaction_journal_id)); + return true; + } + } + return false; + } } diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index cdcaadd76d..eddce93351 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -41,10 +41,17 @@ interface RecurringRepositoryInterface /** * Destroy a recurring transaction. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence */ public function destroy(Recurrence $recurrence): void; + /** + * @param Recurrence $recurrence + * @param Carbon $date + * @return bool + */ + public function createdPreviously(Recurrence $recurrence, Carbon $date): bool; + /** * Destroy all recurring transactions. */ @@ -67,7 +74,7 @@ interface RecurringRepositoryInterface /** * Get the category from a recurring transaction transaction. * - * @param RecurrenceTransaction $recTransaction + * @param RecurrenceTransaction $recTransaction * * @return null|int */ @@ -76,7 +83,7 @@ interface RecurringRepositoryInterface /** * Get the budget ID from a recurring transaction transaction. * - * @param RecurrenceTransaction $recTransaction + * @param RecurrenceTransaction $recTransaction * * @return null|int */ @@ -85,7 +92,7 @@ interface RecurringRepositoryInterface /** * Get the category from a recurring transaction transaction. * - * @param RecurrenceTransaction $recTransaction + * @param RecurrenceTransaction $recTransaction * * @return null|int */ @@ -94,7 +101,7 @@ interface RecurringRepositoryInterface /** * Get the category from a recurring transaction transaction. * - * @param RecurrenceTransaction $recTransaction + * @param RecurrenceTransaction $recTransaction * * @return null|string */ @@ -103,9 +110,9 @@ interface RecurringRepositoryInterface /** * Returns the count of journals created for this recurrence, possibly limited by time. * - * @param Recurrence $recurrence - * @param Carbon|null $start - * @param Carbon|null $end + * @param Recurrence $recurrence + * @param Carbon|null $start + * @param Carbon|null $end * * @return int */ @@ -114,7 +121,7 @@ interface RecurringRepositoryInterface /** * Get journal ID's for journals created by this recurring transaction. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return array */ @@ -123,7 +130,7 @@ interface RecurringRepositoryInterface /** * Get the notes. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return string */ @@ -132,16 +139,16 @@ interface RecurringRepositoryInterface /** * Generate events in the date range. * - * @param RecurrenceRepetition $repetition - * @param Carbon $start - * @param Carbon $end + * @param RecurrenceRepetition $repetition + * @param Carbon $start + * @param Carbon $end * * @return array */ public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array; /** - * @param RecurrenceTransaction $transaction + * @param RecurrenceTransaction $transaction * * @return int|null */ @@ -150,23 +157,23 @@ interface RecurringRepositoryInterface /** * Get the tags from the recurring transaction. * - * @param RecurrenceTransaction $transaction + * @param RecurrenceTransaction $transaction * * @return array */ public function getTags(RecurrenceTransaction $transaction): array; /** - * @param Recurrence $recurrence - * @param int $page - * @param int $pageSize + * @param Recurrence $recurrence + * @param int $page + * @param int $pageSize * * @return LengthAwarePaginator */ public function getTransactionPaginator(Recurrence $recurrence, int $page, int $pageSize): LengthAwarePaginator; /** - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return Collection */ @@ -176,9 +183,9 @@ interface RecurringRepositoryInterface * Calculate the next X iterations starting on the date given in $date. * Returns an array of Carbon objects. * - * @param RecurrenceRepetition $repetition - * @param Carbon $date - * @param int $count + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param int $count * * @return array * @throws FireflyException @@ -191,10 +198,10 @@ interface RecurringRepositoryInterface * * Only returns them of they are after $afterDate * - * @param RecurrenceRepetition $repetition - * @param Carbon $date - * @param Carbon $afterDate - * @param int $count + * @param RecurrenceRepetition $repetition + * @param Carbon $date + * @param Carbon $afterDate + * @param int $count * * @return array * @throws FireflyException @@ -204,15 +211,15 @@ interface RecurringRepositoryInterface /** * Parse the repetition in a string that is user readable. * - * @param RecurrenceRepetition $repetition + * @param RecurrenceRepetition $repetition * * @return string */ public function repetitionDescription(RecurrenceRepetition $repetition): string; /** - * @param string $query - * @param int $limit + * @param string $query + * @param int $limit * * @return Collection */ @@ -221,14 +228,14 @@ interface RecurringRepositoryInterface /** * Set user for in repository. * - * @param User $user + * @param User $user */ public function setUser(User $user): void; /** * Store a new recurring transaction. * - * @param array $data + * @param array $data * * @return Recurrence * @throws FireflyException @@ -238,8 +245,8 @@ interface RecurringRepositoryInterface /** * Calculate how many transactions are to be expected from this recurrence. * - * @param Recurrence $recurrence - * @param RecurrenceRepetition $repetition + * @param Recurrence $recurrence + * @param RecurrenceRepetition $repetition * * @return int */ @@ -248,8 +255,8 @@ interface RecurringRepositoryInterface /** * Update a recurring transaction. * - * @param Recurrence $recurrence - * @param array $data + * @param Recurrence $recurrence + * @param array $data * * @return Recurrence */ diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php index 5b075a9aaf..ebc524b083 100644 --- a/app/Services/Internal/Support/AccountServiceTrait.php +++ b/app/Services/Internal/Support/AccountServiceTrait.php @@ -485,7 +485,7 @@ trait AccountServiceTrait $sourceName = trans('firefly.liability_credit_description', ['account' => $account->name], $language); $destId = $account->id; $destName = null; - if(-1 === bccomp($openingBalance, '0')) { + if (-1 === bccomp($openingBalance, '0')) { // amount is negative, reverse it $sourceId = $account->id; $sourceName = null; diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 1958cb78d0..6adaf58006 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -104,16 +104,15 @@ class Steam */ public function bcround(?string $number, int $precision = 0): string { - - if(null === $number) { + if (null === $number) { return '0'; } - if('' === trim($number)) { + if ('' === trim($number)) { return '0'; } // if the number contains "E", it's in scientific notation, so we need to convert it to a normal number first. - if(false !== stripos($number,'e')) { - $number = sprintf('%.24f',$number); + if (false !== stripos($number, 'e')) { + $number = sprintf('%.24f', $number); } Log::debug(sprintf('Trying bcround("%s",%d)', $number, $precision)); diff --git a/config/firefly.php b/config/firefly.php index eb9dc83f29..da277143da 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -156,7 +156,7 @@ return [ 'languages' => [ // currently enabled languages 'bg_BG' => ['name_locale' => 'Български', 'name_english' => 'Bulgarian'], -// 'ca_ES' => ['name_locale' => 'Catalan', 'name_english' => 'Catalan'], + // 'ca_ES' => ['name_locale' => 'Catalan', 'name_english' => 'Catalan'], 'cs_CZ' => ['name_locale' => 'Czech', 'name_english' => 'Czech'], 'da_DK' => ['name_locale' => 'Danish', 'name_english' => 'Danish'], 'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German'], @@ -164,17 +164,17 @@ return [ 'en_GB' => ['name_locale' => 'English (GB)', 'name_english' => 'English (GB)'], 'en_US' => ['name_locale' => 'English (US)', 'name_english' => 'English (US)'], 'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish'], -// 'et_EE' => ['name_locale' => 'Estonian', 'name_english' => 'Estonian'], -// 'fa_IR' => ['name_locale' => 'فارسی', 'name_english' => 'Persian'], + // 'et_EE' => ['name_locale' => 'Estonian', 'name_english' => 'Estonian'], + // 'fa_IR' => ['name_locale' => 'فارسی', 'name_english' => 'Persian'], 'fi_FI' => ['name_locale' => 'Suomi', 'name_english' => 'Finnish'], 'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French'], -// 'he_IL' => ['name_locale' => 'Hebrew', 'name_english' => 'Hebrew'], + // 'he_IL' => ['name_locale' => 'Hebrew', 'name_english' => 'Hebrew'], 'hu_HU' => ['name_locale' => 'Hungarian', 'name_english' => 'Hungarian'], 'id_ID' => ['name_locale' => 'Bahasa Indonesia', 'name_english' => 'Indonesian'], -// 'is_IS' => ['name_locale' => 'Icelandic', 'name_english' => 'Icelandic'], + // 'is_IS' => ['name_locale' => 'Icelandic', 'name_english' => 'Icelandic'], 'it_IT' => ['name_locale' => 'Italiano', 'name_english' => 'Italian'], 'ja_JP' => ['name_locale' => 'Japanese', 'name_english' => 'Japanese'], -// 'lt_LT' => ['name_locale' => 'Lietuvių', 'name_english' => 'Lithuanian'], + // 'lt_LT' => ['name_locale' => 'Lietuvių', 'name_english' => 'Lithuanian'], 'nb_NO' => ['name_locale' => 'Norsk', 'name_english' => 'Norwegian'], 'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch'], 'pl_PL' => ['name_locale' => 'Polski', 'name_english' => 'Polish'], @@ -182,12 +182,12 @@ return [ 'pt_PT' => ['name_locale' => 'Português', 'name_english' => 'Portuguese'], 'ro_RO' => ['name_locale' => 'Română', 'name_english' => 'Romanian'], 'ru_RU' => ['name_locale' => 'Русский', 'name_english' => 'Russian'], -// 'si_LK' => ['name_locale' => 'සිංහල', 'name_english' => 'Sinhala (Sri Lanka)'], + // 'si_LK' => ['name_locale' => 'සිංහල', 'name_english' => 'Sinhala (Sri Lanka)'], 'sk_SK' => ['name_locale' => 'Slovenčina', 'name_english' => 'Slovak'], 'sl_SI' => ['name_locale' => 'Slovenian', 'name_english' => 'Slovenian'], -//// 'sr_CS' => ['name_locale' => 'Serbian (Latin)', 'name_english' => 'Serbian (Latin)'], + //// 'sr_CS' => ['name_locale' => 'Serbian (Latin)', 'name_english' => 'Serbian (Latin)'], 'sv_SE' => ['name_locale' => 'Svenska', 'name_english' => 'Swedish'], -// // 'tlh_AA' => ['name_locale' => 'tlhIngan Hol', 'name_english' => 'Klingon'], + // // 'tlh_AA' => ['name_locale' => 'tlhIngan Hol', 'name_english' => 'Klingon'], 'tr_TR' => ['name_locale' => 'Türkçe', 'name_english' => 'Turkish'], 'uk_UA' => ['name_locale' => 'Ukranian', 'name_english' => 'Ukranian'], 'vi_VN' => ['name_locale' => 'Tiếng Việt', 'name_english' => 'Vietnamese'], @@ -221,9 +221,14 @@ return [ // account types that may have or set a currency 'valid_currency_account_types' => [ - AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, - AccountType::CASH, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, - AccountType::RECONCILIATION + AccountType::ASSET, + AccountType::LOAN, + AccountType::DEBT, + AccountType::MORTGAGE, + AccountType::CASH, + AccountType::INITIAL_BALANCE, + AccountType::LIABILITY_CREDIT, + AccountType::RECONCILIATION, ], // "value must be in this list" values @@ -528,8 +533,13 @@ return [ TransactionTypeModel::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], TransactionTypeEnum::DEPOSIT->value => [AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], TransactionTypeModel::TRANSFER => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], - TransactionTypeModel::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, - AccountType::MORTGAGE,], + TransactionTypeModel::OPENING_BALANCE => [ + AccountType::INITIAL_BALANCE, + AccountType::ASSET, + AccountType::LOAN, + AccountType::DEBT, + AccountType::MORTGAGE, + ], TransactionTypeModel::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET], TransactionTypeModel::LIABILITY_CREDIT => [AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], // in case no transaction type is known yet, it could be anything. @@ -543,46 +553,111 @@ return [ ], ], 'destination' => [ - TransactionTypeModel::WITHDRAWAL => [AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, - AccountType::MORTGAGE,], + TransactionTypeModel::WITHDRAWAL => [ + AccountType::EXPENSE, + AccountType::CASH, + AccountType::LOAN, + AccountType::DEBT, + AccountType::MORTGAGE, + ], TransactionTypeEnum::DEPOSIT->value => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], TransactionTypeModel::TRANSFER => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], - TransactionTypeModel::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, - AccountType::MORTGAGE,], + TransactionTypeModel::OPENING_BALANCE => [ + AccountType::INITIAL_BALANCE, + AccountType::ASSET, + AccountType::LOAN, + AccountType::DEBT, + AccountType::MORTGAGE, + ], TransactionTypeModel::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET], TransactionTypeModel::LIABILITY_CREDIT => [AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], ], ], 'allowed_opposing_types' => [ 'source' => [ - AccountType::ASSET => [AccountType::ASSET, AccountType::CASH, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, - AccountType::LOAN, AccountType::RECONCILIATION, AccountType::MORTGAGE], + AccountType::ASSET => [ + AccountType::ASSET, + AccountType::CASH, + AccountType::DEBT, + AccountType::EXPENSE, + AccountType::INITIAL_BALANCE, + AccountType::LOAN, + AccountType::RECONCILIATION, + AccountType::MORTGAGE, + ], AccountType::CASH => [AccountType::ASSET], - AccountType::DEBT => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN, - AccountType::MORTGAGE, AccountType::LIABILITY_CREDIT], + AccountType::DEBT => [ + AccountType::ASSET, + AccountType::DEBT, + AccountType::EXPENSE, + AccountType::INITIAL_BALANCE, + AccountType::LOAN, + AccountType::MORTGAGE, + AccountType::LIABILITY_CREDIT, + ], AccountType::EXPENSE => [], // is not allowed as a source. AccountType::INITIAL_BALANCE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], - AccountType::LOAN => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN, - AccountType::MORTGAGE, AccountType::LIABILITY_CREDIT], - AccountType::MORTGAGE => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN, - AccountType::MORTGAGE, AccountType::LIABILITY_CREDIT], + AccountType::LOAN => [ + AccountType::ASSET, + AccountType::DEBT, + AccountType::EXPENSE, + AccountType::INITIAL_BALANCE, + AccountType::LOAN, + AccountType::MORTGAGE, + AccountType::LIABILITY_CREDIT, + ], + AccountType::MORTGAGE => [ + AccountType::ASSET, + AccountType::DEBT, + AccountType::EXPENSE, + AccountType::INITIAL_BALANCE, + AccountType::LOAN, + AccountType::MORTGAGE, + AccountType::LIABILITY_CREDIT, + ], AccountType::RECONCILIATION => [AccountType::ASSET], AccountType::REVENUE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], AccountType::LIABILITY_CREDIT => [AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], ], 'destination' => [ - AccountType::ASSET => [AccountType::ASSET, AccountType::CASH, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, - AccountType::MORTGAGE, AccountType::RECONCILIATION, AccountType::REVENUE,], + AccountType::ASSET => [ + AccountType::ASSET, + AccountType::CASH, + AccountType::DEBT, + AccountType::INITIAL_BALANCE, + AccountType::LOAN, + AccountType::MORTGAGE, + AccountType::RECONCILIATION, + AccountType::REVENUE, + ], AccountType::CASH => [AccountType::ASSET], - AccountType::DEBT => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE, - AccountType::REVENUE,], + AccountType::DEBT => [ + AccountType::ASSET, + AccountType::DEBT, + AccountType::INITIAL_BALANCE, + AccountType::LOAN, + AccountType::MORTGAGE, + AccountType::REVENUE, + ], AccountType::EXPENSE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], AccountType::INITIAL_BALANCE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], - AccountType::LOAN => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE, - AccountType::REVENUE,], - AccountType::MORTGAGE => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE, - AccountType::REVENUE,], + AccountType::LOAN => [ + AccountType::ASSET, + AccountType::DEBT, + AccountType::INITIAL_BALANCE, + AccountType::LOAN, + AccountType::MORTGAGE, + AccountType::REVENUE, + ], + AccountType::MORTGAGE => [ + AccountType::ASSET, + AccountType::DEBT, + AccountType::INITIAL_BALANCE, + AccountType::LOAN, + AccountType::MORTGAGE, + AccountType::REVENUE, + ], AccountType::RECONCILIATION => [AccountType::ASSET], AccountType::REVENUE => [], // is not allowed as a destination AccountType::LIABILITY_CREDIT => [],// is not allowed as a destination @@ -591,31 +666,66 @@ return [ // depending on the account type, return the allowed transaction types: 'allowed_transaction_types' => [ 'source' => [ - AccountType::ASSET => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::TRANSFER, TransactionTypeModel::OPENING_BALANCE, - TransactionTypeModel::RECONCILIATION,], + AccountType::ASSET => [ + TransactionTypeModel::WITHDRAWAL, + TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE, + TransactionTypeModel::RECONCILIATION, + ], AccountType::EXPENSE => [], // is not allowed as a source. AccountType::REVENUE => [TransactionTypeEnum::DEPOSIT->value], - AccountType::LOAN => [TransactionTypeModel::WITHDRAWAL, TransactionTypeEnum::DEPOSIT->value, TransactionTypeModel::TRANSFER, - TransactionTypeModel::OPENING_BALANCE, TransactionTypeModel::LIABILITY_CREDIT], - AccountType::DEBT => [TransactionTypeModel::WITHDRAWAL, TransactionTypeEnum::DEPOSIT->value, TransactionTypeModel::TRANSFER, - TransactionTypeModel::OPENING_BALANCE, TransactionTypeModel::LIABILITY_CREDIT], - AccountType::MORTGAGE => [TransactionTypeModel::WITHDRAWAL, TransactionTypeEnum::DEPOSIT->value, TransactionTypeModel::TRANSFER, - TransactionTypeModel::OPENING_BALANCE, TransactionTypeModel::LIABILITY_CREDIT], + AccountType::LOAN => [ + TransactionTypeModel::WITHDRAWAL, + TransactionTypeEnum::DEPOSIT->value, + TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE, + TransactionTypeModel::LIABILITY_CREDIT, + ], + AccountType::DEBT => [ + TransactionTypeModel::WITHDRAWAL, + TransactionTypeEnum::DEPOSIT->value, + TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE, + TransactionTypeModel::LIABILITY_CREDIT, + ], + AccountType::MORTGAGE => [ + TransactionTypeModel::WITHDRAWAL, + TransactionTypeEnum::DEPOSIT->value, + TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE, + TransactionTypeModel::LIABILITY_CREDIT, + ], AccountType::INITIAL_BALANCE => [TransactionTypeModel::OPENING_BALANCE], AccountType::RECONCILIATION => [TransactionTypeModel::RECONCILIATION], AccountType::LIABILITY_CREDIT => [TransactionTypeModel::LIABILITY_CREDIT], ], 'destination' => [ - AccountType::ASSET => [TransactionTypeEnum::DEPOSIT->value, TransactionTypeModel::TRANSFER, TransactionTypeModel::OPENING_BALANCE, - TransactionTypeModel::RECONCILIATION,], + AccountType::ASSET => [ + TransactionTypeEnum::DEPOSIT->value, + TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE, + TransactionTypeModel::RECONCILIATION, + ], AccountType::EXPENSE => [TransactionTypeModel::WITHDRAWAL], AccountType::REVENUE => [], // is not allowed as destination. - AccountType::LOAN => [TransactionTypeModel::WITHDRAWAL, TransactionTypeEnum::DEPOSIT->value, TransactionTypeModel::TRANSFER, - TransactionTypeModel::OPENING_BALANCE,], - AccountType::DEBT => [TransactionTypeModel::WITHDRAWAL, TransactionTypeEnum::DEPOSIT->value, TransactionTypeModel::TRANSFER, - TransactionTypeModel::OPENING_BALANCE,], - AccountType::MORTGAGE => [TransactionTypeModel::WITHDRAWAL, TransactionTypeEnum::DEPOSIT->value, TransactionTypeModel::TRANSFER, - TransactionTypeModel::OPENING_BALANCE,], + AccountType::LOAN => [ + TransactionTypeModel::WITHDRAWAL, + TransactionTypeEnum::DEPOSIT->value, + TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE, + ], + AccountType::DEBT => [ + TransactionTypeModel::WITHDRAWAL, + TransactionTypeEnum::DEPOSIT->value, + TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE, + ], + AccountType::MORTGAGE => [ + TransactionTypeModel::WITHDRAWAL, + TransactionTypeEnum::DEPOSIT->value, + TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE, + ], AccountType::INITIAL_BALANCE => [TransactionTypeModel::OPENING_BALANCE], AccountType::RECONCILIATION => [TransactionTypeModel::RECONCILIATION], AccountType::LIABILITY_CREDIT => [], // is not allowed as a destination @@ -727,23 +837,40 @@ return [ AccountType::LIABILITY_CREDIT => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], ], ], - // if you add fields to this array, dont forget to update the export routine (ExportDataGenerator). + // if you add fields to this array, don't forget to update the export routine (ExportDataGenerator). 'journal_meta_fields' => [ // sepa - 'sepa_cc', 'sepa_ct_op', 'sepa_ct_id', - 'sepa_db', 'sepa_country', 'sepa_ep', - 'sepa_ci', 'sepa_batch_id', 'external_url', + 'sepa_cc', + 'sepa_ct_op', + 'sepa_ct_id', + 'sepa_db', + 'sepa_country', + 'sepa_ep', + 'sepa_ci', + 'sepa_batch_id', + 'external_url', // dates - 'interest_date', 'book_date', 'process_date', - 'due_date', 'payment_date', 'invoice_date', + 'interest_date', + 'book_date', + 'process_date', + 'due_date', + 'payment_date', + 'invoice_date', // others - 'recurrence_id', 'internal_reference', 'bunq_payment_id', - 'import_hash', 'import_hash_v2', 'external_id', 'original_source', + 'recurrence_id', + 'internal_reference', + 'bunq_payment_id', + 'import_hash', + 'import_hash_v2', + 'external_id', + 'original_source', // recurring transactions - 'recurrence_total', 'recurrence_count', + 'recurrence_total', + 'recurrence_count', + 'recurrence_date', ], 'webhooks' => [ 'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3), diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 8ee502c6a8..a70cf9637d 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -2447,6 +2447,8 @@ return [ 'no_bills_create_default' => 'Create a bill', // recurring transactions + 'create_right_now' => 'Create right now', + 'no_new_transaction_in_recurrence' => 'No new transaction was created. Perhaps it was already fired for this date?', 'recurrences' => 'Recurring transactions', 'repeat_until_in_past' => 'This recurring transaction stopped repeating on :date.', 'recurring_calendar_view' => 'Calendar', diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index 09f1a4260f..2d33f380ef 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -37,8 +37,10 @@
@@ -57,24 +59,37 @@ {{ trans('firefly.repeat_until_in_past', {date: array.repeat_until.isoFormat(monthAndDayFormat) }) }} {% endif %} -+ {{ rep.description }} + {% if rep.repetition_skip == 1 %} + ({{ trans('firefly.recurring_skips_one')|lower }}) + {% endif %} + {% if rep.repetition_skip > 1 %} + ({{ trans('firefly.recurring_skips_more', {count: rep.repetition_skip})|lower }}) + {% endif %} + +
+{{ occ.date.isoFormat(trans('config.month_and_date_day_js')) }} | ++ {% if not occ.fired %} + + {% endif %} + | +