mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2024-11-22 17:06:39 -06:00
Lots of new code for recurring transactions. #1469
This commit is contained in:
parent
968abd26e8
commit
1cf91c78f8
124
app/Factory/RecurrenceFactory.php
Normal file
124
app/Factory/RecurrenceFactory.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* RecurrenceFactory.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Factory;
|
||||
|
||||
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Models\RecurrenceTransaction;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Services\Internal\Support\TransactionServiceTrait;
|
||||
use FireflyIII\Services\Internal\Support\TransactionTypeTrait;
|
||||
use FireflyIII\User;
|
||||
|
||||
/**
|
||||
* Class RecurrenceFactory
|
||||
*/
|
||||
class RecurrenceFactory
|
||||
{
|
||||
use TransactionTypeTrait, TransactionServiceTrait;
|
||||
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return Recurrence
|
||||
*/
|
||||
public function create(array $data): Recurrence
|
||||
{
|
||||
echo '<pre>';
|
||||
print_r($data);
|
||||
echo '</pre>';
|
||||
$type = $this->findTransactionType(ucfirst($data['recurrence']['type']));
|
||||
$recurrence = new Recurrence(
|
||||
[
|
||||
'user_id' => $this->user->id,
|
||||
'transaction_type_id' => $type->id,
|
||||
'title' => $data['recurrence']['title'],
|
||||
'description' => $data['recurrence']['description'],
|
||||
'first_date' => $data['recurrence']['first_date']->format('Y-m-d'),
|
||||
'repeat_until' => $data['recurrence']['repeat_until'],
|
||||
'latest_date' => null,
|
||||
'repetitions' => $data['recurrence']['repetitions'],
|
||||
'apply_rules' => $data['recurrence']['apply_rules'],
|
||||
'active' => $data['recurrence']['active'],
|
||||
]
|
||||
);
|
||||
$recurrence->save();
|
||||
var_dump($recurrence->toArray());
|
||||
|
||||
// create transactions
|
||||
foreach ($data['transactions'] as $trArray) {
|
||||
$source = null;
|
||||
$destination = null;
|
||||
// search source account, depends on type
|
||||
switch ($type->type) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot create "%s".', $type->type));
|
||||
case TransactionType::WITHDRAWAL:
|
||||
$source = $this->findAccount(AccountType::ASSET, $trArray['source_account_id'], null);
|
||||
$destination = $this->findAccount(AccountType::EXPENSE, null, $trArray['destination_account_name']);
|
||||
break;
|
||||
}
|
||||
|
||||
// search destination account
|
||||
|
||||
$transaction = new RecurrenceTransaction(
|
||||
[
|
||||
'recurrence_id' => $recurrence->id,
|
||||
'transaction_currency_id' => $trArray['transaction_currency_id'],
|
||||
'foreign_currency_id' => '' === (string)$trArray['foreign_amount'] ? null : $trArray['foreign_currency_id'],
|
||||
'source_account_id' => $source->id,
|
||||
'destination_account_id' => $destination->id,
|
||||
'amount' => $trArray['amount'],
|
||||
'foreign_amount' => '' === (string)$trArray['foreign_amount'] ? null : (string)$trArray['foreign_amount'],
|
||||
'description' => $trArray['description'],
|
||||
]
|
||||
);
|
||||
$transaction->save();
|
||||
var_dump($transaction->toArray());
|
||||
}
|
||||
|
||||
// create meta data:
|
||||
if(\count($data['meta']['tags']) > 0) {
|
||||
// todo store tags
|
||||
}
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user): void
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
}
|
@ -28,6 +28,7 @@ use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
|
||||
use FireflyIII\Services\Internal\Support\TransactionTypeTrait;
|
||||
use FireflyIII\User;
|
||||
use Log;
|
||||
|
||||
@ -36,7 +37,7 @@ use Log;
|
||||
*/
|
||||
class TransactionJournalFactory
|
||||
{
|
||||
use JournalServiceTrait;
|
||||
use JournalServiceTrait, TransactionTypeTrait;
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
@ -137,25 +138,4 @@ class TransactionJournalFactory
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transaction type. Since this is mandatory, will throw an exception when nothing comes up. Will always
|
||||
* use TransactionType repository.
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return TransactionType
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function findTransactionType(string $type): TransactionType
|
||||
{
|
||||
$factory = app(TransactionTypeFactory::class);
|
||||
$transactionType = $factory->find($type);
|
||||
if (null === $transactionType) {
|
||||
Log::error(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore
|
||||
throw new FireflyException(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return $transactionType;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,9 @@ namespace FireflyIII\Http\Controllers\Recurring;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\RecurrenceFormRequest;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
@ -38,6 +40,8 @@ class CreateController extends Controller
|
||||
{
|
||||
/** @var BudgetRepositoryInterface */
|
||||
private $budgets;
|
||||
/** @var PiggyBankRepositoryInterface */
|
||||
private $piggyBanks;
|
||||
/** @var RecurringRepositoryInterface */
|
||||
private $recurring;
|
||||
|
||||
@ -55,8 +59,9 @@ class CreateController extends Controller
|
||||
app('view')->share('title', trans('firefly.recurrences'));
|
||||
app('view')->share('subTitle', trans('firefly.create_new_recurrence'));
|
||||
|
||||
$this->recurring = app(RecurringRepositoryInterface::class);
|
||||
$this->budgets = app(BudgetRepositoryInterface::class);
|
||||
$this->recurring = app(RecurringRepositoryInterface::class);
|
||||
$this->budgets = app(BudgetRepositoryInterface::class);
|
||||
$this->piggyBanks = app(PiggyBankRepositoryInterface::class);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@ -64,6 +69,8 @@ class CreateController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function create(Request $request)
|
||||
@ -71,6 +78,8 @@ class CreateController extends Controller
|
||||
// todo refactor to expandedform method.
|
||||
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||
$piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount();
|
||||
$piggies = app('expandedform')->makeSelectListWithEmpty($piggyBanks);
|
||||
$tomorrow = new Carbon;
|
||||
$tomorrow->addDay();
|
||||
|
||||
@ -90,7 +99,18 @@ class CreateController extends Controller
|
||||
];
|
||||
$request->session()->flash('preFilled', $preFilled);
|
||||
|
||||
return view('recurring.create', compact('tomorrow', 'preFilled','typesOfRepetitions', 'defaultCurrency', 'budgets'));
|
||||
return view('recurring.create', compact('tomorrow', 'preFilled', 'piggies', 'typesOfRepetitions', 'defaultCurrency', 'budgets'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RecurrenceFormRequest $request
|
||||
*/
|
||||
public function store(RecurrenceFormRequest $request)
|
||||
{
|
||||
$data = $request->getAll();
|
||||
$this->recurring->store($data);
|
||||
var_dump($data);
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
@ -76,9 +76,20 @@ class IndexController extends Controller
|
||||
$return = [];
|
||||
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
|
||||
$firstDate = Carbon::createFromFormat('Y-m-d', $request->get('first_date'));
|
||||
$endDate = '' !== (string)$request->get('end_date') ? Carbon::createFromFormat('Y-m-d', $request->get('end_date')) : null;
|
||||
$endsAt = (string)$request->get('ends');
|
||||
$repetitionType = explode(',', $request->get('type'))[0];
|
||||
$repetitions = (int)$request->get('reps');
|
||||
$repetitionMoment = '';
|
||||
$start->startOfDay();
|
||||
|
||||
// if $firstDate is beyond $end, simply return an empty array.
|
||||
if ($firstDate->gt($end)) {
|
||||
return Response::json([]);
|
||||
}
|
||||
// if $firstDate is beyond start, use that one:
|
||||
$actualStart = clone $firstDate;
|
||||
|
||||
switch ($repetitionType) {
|
||||
default:
|
||||
@ -90,32 +101,51 @@ class IndexController extends Controller
|
||||
$repetitionMoment = explode(',', $request->get('type'))[1] ?? '1';
|
||||
break;
|
||||
case 'ndom':
|
||||
$repetitionMoment = explode(',', $request->get('type'))[1] ?? '1,1';
|
||||
$repetitionMoment = str_ireplace('ndom,', '', $request->get('type'));
|
||||
break;
|
||||
case 'yearly':
|
||||
$repetitionMoment = explode(',', $request->get('type'))[1] ?? '2018-01-01';
|
||||
break;
|
||||
}
|
||||
|
||||
$repetition = new RecurrenceRepetition;
|
||||
$repetition->repetition_type = $repetitionType;
|
||||
$repetition->repetition_moment = $repetitionMoment;
|
||||
$repetition->repetition_skip = (int)$request->get('skip');
|
||||
|
||||
var_dump($repository->getXOccurrences($repetition, $start, 5));
|
||||
exit;
|
||||
|
||||
|
||||
// calculate events in range, depending on type:
|
||||
$actualEnd = clone $end;
|
||||
switch ($endsAt) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot generate events for "%s"', $endsAt));
|
||||
throw new FireflyException(sprintf('Cannot generate events for type that ends at "%s".', $endsAt));
|
||||
case 'forever':
|
||||
// simply generate up until $end. No change from default behavior.
|
||||
$occurrences = $repository->getOccurrencesInRange($repetition, $actualStart, $actualEnd);
|
||||
break;
|
||||
case 'until_date':
|
||||
$actualEnd = $endDate ?? clone $end;
|
||||
$occurrences = $repository->getOccurrencesInRange($repetition, $actualStart, $actualEnd);
|
||||
break;
|
||||
case 'times':
|
||||
$occurrences = $repository->getXOccurrences($repetition, $actualStart, $repetitions);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** @var Carbon $current */
|
||||
foreach ($occurrences as $current) {
|
||||
if ($current->gte($start)) {
|
||||
$event = [
|
||||
'id' => $repetitionType . $firstDate->format('Ymd'),
|
||||
'title' => 'X',
|
||||
'allDay' => true,
|
||||
'start' => $current->format('Y-m-d'),
|
||||
'end' => $current->format('Y-m-d'),
|
||||
'editable' => false,
|
||||
'rendering' => 'background',
|
||||
];
|
||||
$return[] = $event;
|
||||
}
|
||||
}
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Preferences;
|
||||
@ -218,7 +219,7 @@ class SingleController extends Controller
|
||||
*
|
||||
* @internal param JournalRepositoryInterface $repository
|
||||
*/
|
||||
public function destroy(TransactionJournal $transactionJournal)
|
||||
public function destroy(TransactionJournal $transactionJournal): RedirectResponse
|
||||
{
|
||||
// @codeCoverageIgnoreStart
|
||||
if ($this->isOpeningBalance($transactionJournal)) {
|
||||
@ -329,9 +330,10 @@ class SingleController extends Controller
|
||||
* @param JournalFormRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
* @return RedirectResponse
|
||||
* @throws \FireflyIII\Exceptions\FireflyException
|
||||
*/
|
||||
public function store(JournalFormRequest $request, JournalRepositoryInterface $repository)
|
||||
public function store(JournalFormRequest $request, JournalRepositoryInterface $repository): RedirectResponse
|
||||
{
|
||||
$doSplit = 1 === (int)$request->get('split_journal');
|
||||
$createAnother = 1 === (int)$request->get('create_another');
|
||||
|
189
app/Http/Requests/RecurrenceFormRequest.php
Normal file
189
app/Http/Requests/RecurrenceFormRequest.php
Normal file
@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* RecurrenceFormRequest.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Rules\ValidRecurrenceRepetitionType;
|
||||
|
||||
/**
|
||||
* Class RecurrenceFormRequest
|
||||
*/
|
||||
class RecurrenceFormRequest extends Request
|
||||
{
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Only allow logged in users
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getAll(): array
|
||||
{
|
||||
$data = $this->all();
|
||||
$return = [
|
||||
'recurrence' => [
|
||||
'type' => $this->string('transaction_type'),
|
||||
'title' => $this->string('title'),
|
||||
'description' => $this->string('recurring_description'),
|
||||
'first_date' => $this->date('first_date'),
|
||||
'repeat_until' => $this->date('repeat_until'),
|
||||
'repetitions' => $this->integer('repetitions'),
|
||||
'apply_rules' => $this->boolean('apply_rules'),
|
||||
'active' => $this->boolean('active'),
|
||||
],
|
||||
'transactions' => [
|
||||
[
|
||||
'transaction_currency_id' => $this->integer('transaction_currency_id'),
|
||||
'type' => $this->string('transaction_type'),
|
||||
'description' => $this->string('transaction_description'),
|
||||
'amount' => $this->string('amount'),
|
||||
'foreign_amount' => null,
|
||||
'foreign_currency_id' => null,
|
||||
'budget_id' => $this->integer('budget_id'),
|
||||
'category_name' => $this->string('category'),
|
||||
|
||||
],
|
||||
],
|
||||
'meta' => [
|
||||
// tags and piggy bank ID.
|
||||
'tags' => explode(',', $this->string('tags')),
|
||||
'piggy_bank_id' => $this->integer('piggy_bank_id'),
|
||||
],
|
||||
'repetitions' => [
|
||||
[
|
||||
'skip' => $this->integer('skip'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
// fill in foreign currency data
|
||||
if (null !== $this->float('foreign_amount')) {
|
||||
$return['transactions'][0]['foreign_amount'] = $this->string('foreign_amount');
|
||||
$return['transactions'][0]['foreign_currency_id'] = $this->integer('foreign_currency_id');
|
||||
}
|
||||
|
||||
// fill in source and destination account data
|
||||
switch ($this->string('transaction_type')) {
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->string('transaction_type')));
|
||||
case 'withdrawal':
|
||||
$return['transactions'][0]['source_account_id'] = $this->integer('source_account_id');
|
||||
$return['transactions'][0]['destination_account_name'] = $this->string('destination_account_name');
|
||||
break;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$today = new Carbon;
|
||||
$tomorrow = clone $today;
|
||||
$tomorrow->addDay();
|
||||
$rules = [
|
||||
// mandatory info for recurrence.
|
||||
//'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title',
|
||||
'title' => 'required|between:1,255',
|
||||
'first_date' => 'required|date|after:' . $today->format('Y-m-d'),
|
||||
'repetition_type' => ['required', new ValidRecurrenceRepetitionType, 'between:1,20'],
|
||||
'skip' => 'required|numeric|between:0,31',
|
||||
|
||||
// optional for recurrence:
|
||||
'recurring_description' => 'between:0,65000',
|
||||
'active' => 'numeric|between:0,1',
|
||||
'apply_rules' => 'numeric|between:0,1',
|
||||
|
||||
// mandatory for transaction:
|
||||
'transaction_description' => 'required|between:1,255',
|
||||
'transaction_type' => 'required|in:withdrawal,deposit,transfer',
|
||||
'transaction_currency_id' => 'required|exists:transaction_currencies,id',
|
||||
'amount' => 'numeric|required|more:0',
|
||||
// mandatory account info:
|
||||
'source_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'source_account_name' => 'between:1,255|nullable',
|
||||
'destination_account_id' => 'numeric|belongsToUser:accounts,id|nullable',
|
||||
'destination_account_name' => 'between:1,255|nullable',
|
||||
|
||||
// foreign amount data:
|
||||
'foreign_currency_id' => 'exists:transaction_currencies,id',
|
||||
'foreign_amount' => 'nullable|more:0',
|
||||
|
||||
// optional fields:
|
||||
'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable',
|
||||
'category' => 'between:1,255|nullable',
|
||||
'tags' => 'between:1,255|nullable',
|
||||
];
|
||||
|
||||
// if ends after X repetitions, set another rule
|
||||
if ($this->string('repetition_end') === 'times') {
|
||||
$rules['repetitions'] = 'required|numeric|between:0,254';
|
||||
}
|
||||
// if foreign amount, currency must be different.
|
||||
if ($this->float('foreign_amount') !== 0.0) {
|
||||
$rules['foreign_currency_id'] = 'exists:transaction_currencies,id|different:transaction_currency_id';
|
||||
}
|
||||
|
||||
// if ends at date X, set another rule.
|
||||
if ($this->string('repetition_end') === 'until_date') {
|
||||
$rules['repeat_until'] = 'required|date|after:' . $tomorrow->format('Y-m-d');
|
||||
}
|
||||
|
||||
// switchc on type to expand rules for source and destination accounts:
|
||||
switch ($this->string('transaction_type')) {
|
||||
case strtolower(TransactionType::WITHDRAWAL):
|
||||
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
|
||||
$rules['destination_account_name'] = 'between:1,255|nullable';
|
||||
break;
|
||||
case strtolower(TransactionType::DEPOSIT):
|
||||
$rules['source_account_name'] = 'between:1,255|nullable';
|
||||
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts';
|
||||
break;
|
||||
case strtolower(TransactionType::TRANSFER):
|
||||
// this may not work:
|
||||
$rules['source_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_account_id';
|
||||
$rules['destination_account_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_account_id';
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException(sprintf('Cannot handle transaction type of type "%s"', $this->string('transaction_type'))); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
@ -47,6 +47,16 @@ class Request extends FormRequest
|
||||
return 1 === (int)$this->input($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function float(string $field): float
|
||||
{
|
||||
return (float)$this->get($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
|
@ -64,8 +64,7 @@ class Recurrence extends Model
|
||||
* @var array
|
||||
*/
|
||||
protected $casts
|
||||
= [
|
||||
|
||||
= [
|
||||
'created_at' => 'datetime',
|
||||
'updated_at' => 'datetime',
|
||||
'first_date' => 'date',
|
||||
@ -73,6 +72,10 @@ class Recurrence extends Model
|
||||
'active' => 'bool',
|
||||
'apply_rules' => 'bool',
|
||||
];
|
||||
/** @var array */
|
||||
protected $fillable
|
||||
= ['user_id', 'transaction_type_id', 'title', 'description', 'first_date', 'repeat_until', 'latest_date', 'repetitions', 'apply_rules', 'active'];
|
||||
/** @var string */
|
||||
protected $table = 'recurrences';
|
||||
|
||||
/**
|
||||
|
@ -47,6 +47,11 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
*/
|
||||
class RecurrenceTransaction extends Model
|
||||
{
|
||||
/** @var array */
|
||||
protected $fillable
|
||||
= ['recurrence_id', 'transaction_currency_id', 'foreign_currency_id', 'source_account_id', 'destination_account_id', 'amount', 'foreign_amount',
|
||||
'description'];
|
||||
/** @var string */
|
||||
protected $table = 'recurrences_transactions';
|
||||
|
||||
/**
|
||||
@ -82,7 +87,7 @@ class RecurrenceTransaction extends Model
|
||||
*/
|
||||
public function recurrenceTransactionMeta(): HasMany
|
||||
{
|
||||
return $this->hasMany(RecurrenceTransactionMeta::class,'rt_id');
|
||||
return $this->hasMany(RecurrenceTransactionMeta::class, 'rt_id');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\Recurring;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Factory\RecurrenceFactory;
|
||||
use FireflyIII\Models\Note;
|
||||
use FireflyIII\Models\Preference;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
@ -73,28 +74,35 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next X iterations starting on the date given in $date.
|
||||
* Generate events in the date range.
|
||||
*
|
||||
* @param RecurrenceRepetition $repetition
|
||||
* @param Carbon $date
|
||||
* @param int $count
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array
|
||||
public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array
|
||||
{
|
||||
$return = [];
|
||||
$mutator = clone $date;
|
||||
$mutator = clone $start;
|
||||
$mutator->startOfDay();
|
||||
$skipMod = $repetition->repetition_skip + 1;
|
||||
$attempts = 0;
|
||||
switch ($repetition->repetition_type) {
|
||||
default:
|
||||
throw new FireflyException(
|
||||
sprintf('Cannot calculate occurrences for recurring transaction repetition type "%s"', $repetition->repetition_type)
|
||||
);
|
||||
case 'daily':
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
while ($mutator <= $end) {
|
||||
if ($attempts % $skipMod === 0) {
|
||||
$return[] = clone $mutator;
|
||||
}
|
||||
$mutator->addDay();
|
||||
$return[] = clone $mutator;
|
||||
$attempts++;
|
||||
}
|
||||
break;
|
||||
case 'weekly':
|
||||
@ -110,35 +118,38 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
// today is friday (5), expected is monday (1), subtract four days.
|
||||
$dayDifference = $dayOfWeek - $mutator->dayOfWeekIso;
|
||||
$mutator->addDays($dayDifference);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$return[] = clone $mutator;
|
||||
while ($mutator <= $end) {
|
||||
if ($attempts % $skipMod === 0) {
|
||||
$return[] = clone $mutator;
|
||||
}
|
||||
$attempts++;
|
||||
$mutator->addWeek();
|
||||
}
|
||||
break;
|
||||
case 'monthly':
|
||||
$mutator->addDay(); // always assume today has passed.
|
||||
$dayOfMonth = (int)$repetition->repetition_moment;
|
||||
if ($mutator->day > $dayOfMonth) {
|
||||
// day has passed already, add a month.
|
||||
$mutator->addMonth();
|
||||
}
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
while ($mutator < $end) {
|
||||
$domCorrected = min($dayOfMonth, $mutator->daysInMonth);
|
||||
$mutator->day = $domCorrected;
|
||||
$return[] = clone $mutator;
|
||||
if ($attempts % $skipMod === 0) {
|
||||
$return[] = clone $mutator;
|
||||
}
|
||||
$attempts++;
|
||||
$mutator->endOfMonth()->addDay();
|
||||
}
|
||||
break;
|
||||
case 'ndom':
|
||||
$mutator->addDay(); // always assume today has passed.
|
||||
$mutator->startOfMonth();
|
||||
// this feels a bit like a cop out but why reinvent the wheel?
|
||||
$string = '%s %s of %s %s';
|
||||
$counters = [1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth',];
|
||||
$daysOfWeek = [1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday',];
|
||||
$parts = explode(',', $repetition->repetition_moment);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
while ($mutator <= $end) {
|
||||
$string = sprintf('%s %s of %s %s', $counters[$parts[0]], $daysOfWeek[$parts[1]], $mutator->format('F'), $mutator->format('Y'));
|
||||
$newCarbon = new Carbon($string);
|
||||
$return[] = clone $newCarbon;
|
||||
@ -150,11 +161,131 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
$date->year = $mutator->year;
|
||||
if ($mutator > $date) {
|
||||
$date->addYear();
|
||||
|
||||
}
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$obj = clone $date;
|
||||
$obj->addYears($i);
|
||||
$return[] = $obj;
|
||||
|
||||
// is $date between $start and $end?
|
||||
$obj = clone $date;
|
||||
$count = 0;
|
||||
while ($obj <= $end && $obj >= $mutator && $count < 10) {
|
||||
|
||||
$return[] = clone $obj;
|
||||
$obj->addYears(1);
|
||||
$count++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next X iterations starting on the date given in $date.
|
||||
*
|
||||
* @param RecurrenceRepetition $repetition
|
||||
* @param Carbon $date
|
||||
* @param int $count
|
||||
*
|
||||
* @return array
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count): array
|
||||
{
|
||||
$return = [];
|
||||
$mutator = clone $date;
|
||||
$skipMod = $repetition->repetition_skip + 1;
|
||||
$total = 0;
|
||||
$attempts = 0;
|
||||
switch ($repetition->repetition_type) {
|
||||
default:
|
||||
throw new FireflyException(
|
||||
sprintf('Cannot calculate occurrences for recurring transaction repetition type "%s"', $repetition->repetition_type)
|
||||
);
|
||||
case 'daily':
|
||||
while ($total < $count) {
|
||||
$mutator->addDay();
|
||||
if ($attempts % $skipMod === 0) {
|
||||
$return[] = clone $mutator;
|
||||
$total++;
|
||||
}
|
||||
$attempts++;
|
||||
}
|
||||
break;
|
||||
case 'weekly':
|
||||
// monday = 1
|
||||
// sunday = 7
|
||||
$mutator->addDay(); // always assume today has passed.
|
||||
$dayOfWeek = (int)$repetition->repetition_moment;
|
||||
if ($mutator->dayOfWeekIso > $dayOfWeek) {
|
||||
// day has already passed this week, add one week:
|
||||
$mutator->addWeek();
|
||||
}
|
||||
// today is wednesday (3), expected is friday (5): add two days.
|
||||
// today is friday (5), expected is monday (1), subtract four days.
|
||||
$dayDifference = $dayOfWeek - $mutator->dayOfWeekIso;
|
||||
$mutator->addDays($dayDifference);
|
||||
|
||||
while ($total < $count) {
|
||||
if ($attempts % $skipMod === 0) {
|
||||
$return[] = clone $mutator;
|
||||
$total++;
|
||||
}
|
||||
$attempts++;
|
||||
$mutator->addWeek();
|
||||
}
|
||||
break;
|
||||
case 'monthly':
|
||||
$mutator->addDay(); // always assume today has passed.
|
||||
$dayOfMonth = (int)$repetition->repetition_moment;
|
||||
if ($mutator->day > $dayOfMonth) {
|
||||
// day has passed already, add a month.
|
||||
$mutator->addMonth();
|
||||
}
|
||||
|
||||
while ($total < $count) {
|
||||
$domCorrected = min($dayOfMonth, $mutator->daysInMonth);
|
||||
$mutator->day = $domCorrected;
|
||||
if ($attempts % $skipMod === 0) {
|
||||
$return[] = clone $mutator;
|
||||
$total++;
|
||||
}
|
||||
$attempts++;
|
||||
$mutator->endOfMonth()->addDay();
|
||||
}
|
||||
break;
|
||||
case 'ndom':
|
||||
$mutator->addDay(); // always assume today has passed.
|
||||
$mutator->startOfMonth();
|
||||
// this feels a bit like a cop out but why reinvent the wheel?
|
||||
$counters = [1 => 'first', 2 => 'second', 3 => 'third', 4 => 'fourth', 5 => 'fifth',];
|
||||
$daysOfWeek = [1 => 'Monday', 2 => 'Tuesday', 3 => 'Wednesday', 4 => 'Thursday', 5 => 'Friday', 6 => 'Saturday', 7 => 'Sunday',];
|
||||
$parts = explode(',', $repetition->repetition_moment);
|
||||
|
||||
while ($total < $count) {
|
||||
$string = sprintf('%s %s of %s %s', $counters[$parts[0]], $daysOfWeek[$parts[1]], $mutator->format('F'), $mutator->format('Y'));
|
||||
$newCarbon = new Carbon($string);
|
||||
if ($attempts % $skipMod === 0) {
|
||||
$return[] = clone $newCarbon;
|
||||
$total++;
|
||||
}
|
||||
$attempts++;
|
||||
$mutator->endOfMonth()->addDay();
|
||||
}
|
||||
break;
|
||||
case 'yearly':
|
||||
$date = new Carbon($repetition->repetition_moment);
|
||||
$date->year = $mutator->year;
|
||||
if ($mutator > $date) {
|
||||
$date->addYear();
|
||||
}
|
||||
$obj = clone $date;
|
||||
while ($total < $count) {
|
||||
if ($attempts % $skipMod === 0) {
|
||||
$return[] = clone $obj;
|
||||
$total++;
|
||||
}
|
||||
$obj->addYears(1);
|
||||
$attempts++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -223,4 +354,18 @@ class RecurringRepository implements RecurringRepositoryInterface
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return Recurrence
|
||||
*/
|
||||
public function store(array $data): Recurrence
|
||||
{
|
||||
$factory = new RecurrenceFactory;
|
||||
$factory->setUser($this->user);
|
||||
|
||||
return $factory->create($data);
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Repositories\Recurring;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Models\Recurrence;
|
||||
use FireflyIII\Models\RecurrenceRepetition;
|
||||
use FireflyIII\User;
|
||||
@ -53,6 +54,19 @@ interface RecurringRepositoryInterface
|
||||
*/
|
||||
public function getNoteText(Recurrence $recurrence): string;
|
||||
|
||||
/**
|
||||
* Generate events in the date range.
|
||||
*
|
||||
* @param RecurrenceRepetition $repetition
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @throws FireflyException
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array;
|
||||
|
||||
/**
|
||||
* Calculate the next X iterations starting on the date given in $date.
|
||||
* Returns an array of Carbon objects.
|
||||
@ -61,9 +75,10 @@ interface RecurringRepositoryInterface
|
||||
* @param Carbon $date
|
||||
* @param int $count
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return array
|
||||
*/
|
||||
public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array;
|
||||
public function getXOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count): array;
|
||||
|
||||
/**
|
||||
* Parse the repetition in a string that is user readable.
|
||||
@ -81,4 +96,12 @@ interface RecurringRepositoryInterface
|
||||
*/
|
||||
public function setUser(User $user): void;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @throws FireflyException
|
||||
* @return Recurrence
|
||||
*/
|
||||
public function store(array $data): Recurrence;
|
||||
|
||||
}
|
72
app/Rules/ValidRecurrenceRepetitionType.php
Normal file
72
app/Rules/ValidRecurrenceRepetitionType.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* ValidRecurrenceRepetitionType.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules;
|
||||
|
||||
use Illuminate\Contracts\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Class ValidRecurrenceRepetitionType
|
||||
*/
|
||||
class ValidRecurrenceRepetitionType implements Rule
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the validation error message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function message(): string
|
||||
{
|
||||
return trans('validation.valid_recurrence_rep_type');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
$value = (string)$value;
|
||||
if ($value === 'daily') {
|
||||
return true;
|
||||
}
|
||||
//monthly,17
|
||||
//ndom,3,7
|
||||
if (\in_array(substr($value, 0, 6), ['yearly', 'weekly'])) {
|
||||
return true;
|
||||
}
|
||||
if (0 === strpos($value, 'monthly')) {
|
||||
return true;
|
||||
}
|
||||
if (0 === strpos($value, 'ndom')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
57
app/Services/Internal/Support/TransactionTypeTrait.php
Normal file
57
app/Services/Internal/Support/TransactionTypeTrait.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* TransactionTypeTrait.php
|
||||
* Copyright (c) 2018 thegrumpydictator@gmail.com
|
||||
*
|
||||
* This file is part of Firefly III.
|
||||
*
|
||||
* Firefly III is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Firefly III is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Internal\Support;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Factory\TransactionTypeFactory;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Trait TransactionTypeTrait
|
||||
*
|
||||
* @package FireflyIII\Services\Internal\Support
|
||||
*/
|
||||
trait TransactionTypeTrait
|
||||
{
|
||||
/**
|
||||
* Get the transaction type. Since this is mandatory, will throw an exception when nothing comes up. Will always
|
||||
* use TransactionType repository.
|
||||
*
|
||||
* @param string $type
|
||||
*
|
||||
* @return TransactionType
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function findTransactionType(string $type): TransactionType
|
||||
{
|
||||
$factory = app(TransactionTypeFactory::class);
|
||||
$transactionType = $factory->find($type);
|
||||
if (null === $transactionType) {
|
||||
Log::error(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore
|
||||
throw new FireflyException(sprintf('Could not find transaction type for "%s"', $type)); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
return $transactionType;
|
||||
}
|
||||
}
|
35
public/js/ff/recurring/create.js
vendored
35
public/js/ff/recurring/create.js
vendored
@ -20,6 +20,8 @@
|
||||
|
||||
/** global: Modernizr, currencies */
|
||||
|
||||
var calendar;
|
||||
|
||||
$(document).ready(function () {
|
||||
"use strict";
|
||||
if (!Modernizr.inputtypes.date) {
|
||||
@ -37,6 +39,19 @@ $(document).ready(function () {
|
||||
$('#ffInput_repetition_end').on('change', respondToRepetitionEnd);
|
||||
$('#ffInput_first_date').on('change', respondToFirstDateChange);
|
||||
|
||||
// create calendar on load:
|
||||
calendar = $('#recurring_calendar').fullCalendar(
|
||||
{
|
||||
defaultDate: '2018-06-13',
|
||||
editable: false,
|
||||
height: 400,
|
||||
width: 200,
|
||||
contentHeight: 400,
|
||||
aspectRatio: 1.25,
|
||||
eventLimit: true,
|
||||
eventSources: [],
|
||||
});
|
||||
|
||||
$('#calendar-link').on('click', showRepCalendar);
|
||||
});
|
||||
|
||||
@ -49,22 +64,17 @@ function showRepCalendar() {
|
||||
var newEventsUri = eventsUri + '?type=' + $('#ffInput_repetition_type').val();
|
||||
newEventsUri += '&skip=' + $('#ffInput_skip').val();
|
||||
newEventsUri += '&ends=' + $('#ffInput_repetition_end').val();
|
||||
newEventsUri += '&endDate=' + $('#ffInput_repeat_until').val();
|
||||
newEventsUri += '&end_date=' + $('#ffInput_repeat_until').val();
|
||||
newEventsUri += '&reps=' + $('#ffInput_repetitions').val();
|
||||
newEventsUri += '&first_date=' + $('#ffInput_first_date').val();
|
||||
|
||||
// remove all event sources from calendar:
|
||||
calendar.fullCalendar('removeEventSources');
|
||||
|
||||
$('#recurring_calendar').fullCalendar(
|
||||
{
|
||||
defaultDate: '2018-06-13',
|
||||
editable: false,
|
||||
height: 400,
|
||||
width: 200,
|
||||
contentHeight: 300,
|
||||
aspectRatio: 1.25,
|
||||
eventLimit: true, // allow "more" link when too many events
|
||||
events: newEventsUri
|
||||
});
|
||||
// add a new one:
|
||||
calendar.fullCalendar('addEventSource', newEventsUri);
|
||||
$('#calendarModal').modal('show');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -169,6 +179,7 @@ function initializeButtons() {
|
||||
console.log('Value is ' + btn.data('value'));
|
||||
if (btn.data('value') === transactionType) {
|
||||
btn.addClass('btn-info disabled').removeClass('btn-default');
|
||||
$('input[name="transaction_type"]').val(transactionType);
|
||||
} else {
|
||||
btn.removeClass('btn-info disabled').addClass('btn-default');
|
||||
}
|
||||
|
@ -1239,6 +1239,7 @@ return [
|
||||
'repeat_forever' => 'Repeat forever',
|
||||
'repeat_until_date' => 'Repeat until date',
|
||||
'repeat_times' => 'Repeat a number of times',
|
||||
|
||||
|
||||
'recurring_skips_one' => 'Every other',
|
||||
'recurring_skips_more' => 'Skips :count occurrences',
|
||||
'store_new_recurrence' => 'Store recurring transaction',
|
||||
];
|
||||
|
@ -112,6 +112,7 @@ return [
|
||||
'amount_zero' => 'The total amount cannot be zero',
|
||||
'unique_piggy_bank_for_user' => 'The name of the piggy bank must be unique.',
|
||||
'secure_password' => 'This is not a secure password. Please try again. For more information, visit http://bit.ly/FF3-password-security',
|
||||
'valid_recurrence_rep_type' => 'Invalid repetition type for recurring transactions',
|
||||
'attributes' => [
|
||||
'email' => 'email address',
|
||||
'description' => 'description',
|
||||
|
@ -16,7 +16,7 @@
|
||||
<h3 class="box-title">{{ 'mandatory_for_recurring'|_ }}</h3>
|
||||
</div>
|
||||
<div class="box-body">
|
||||
{{ ExpandedForm.text('name') }}
|
||||
{{ ExpandedForm.text('title') }}
|
||||
{{ ExpandedForm.date('first_date',null, {helpText: trans('firefly.help_first_date')}) }}
|
||||
{{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }}
|
||||
{{ ExpandedForm.number('skip', 0) }}
|
||||
@ -78,6 +78,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" name="transaction_type" value="">
|
||||
{# end of three buttons#}
|
||||
|
||||
{{ ExpandedForm.text('transaction_description') }}
|
||||
@ -125,7 +126,7 @@
|
||||
{{ ExpandedForm.text('tags') }}
|
||||
|
||||
{# RELATE THIS TRANSFER TO A PIGGY BANK #}
|
||||
{{ ExpandedForm.select('piggy_bank_id', [], '0') }}
|
||||
{{ ExpandedForm.select('piggy_bank_id', piggies, 0) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -83,7 +83,16 @@
|
||||
<td>
|
||||
<ul>
|
||||
{% for rep in rt.repetitions %}
|
||||
<li>{{ rep.description }}</li>
|
||||
<li>{{ 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 %}
|
||||
|
||||
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</td>
|
||||
|
@ -42,7 +42,14 @@
|
||||
|
||||
<ul>
|
||||
{% for rep in array.repetitions %}
|
||||
<li>{{ rep.description }}
|
||||
<li>
|
||||
{{ 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 %}
|
||||
<ul>
|
||||
{% for occ in rep.occurrences %}
|
||||
<li>{{ occ.formatLocalized(trans('config.month_and_date_day')) }}</li>
|
||||
|
Loading…
Reference in New Issue
Block a user