mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2024-11-26 02:40:43 -06:00
First batch of code for recurring transactions #1469
This commit is contained in:
parent
35a5ec78c3
commit
6743d99d9b
89
app/Http/Controllers/Recurring/CreateController.php
Normal file
89
app/Http/Controllers/Recurring/CreateController.php
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* CreateController.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\Controllers\Recurring;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Class CreateController
|
||||||
|
*/
|
||||||
|
class CreateController extends Controller
|
||||||
|
{
|
||||||
|
/** @var BudgetRepositoryInterface */
|
||||||
|
private $budgets;
|
||||||
|
/** @var RecurringRepositoryInterface */
|
||||||
|
private $recurring;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// translations:
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
app('view')->share('mainTitleIcon', 'fa-paint-brush');
|
||||||
|
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);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function create(Request $request)
|
||||||
|
{
|
||||||
|
// todo refactor to expandedform method.
|
||||||
|
$budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||||
|
$defaultCurrency = app('amount')->getDefaultCurrency();
|
||||||
|
$tomorrow = new Carbon;
|
||||||
|
$tomorrow->addDay();
|
||||||
|
|
||||||
|
// flash some data:
|
||||||
|
$preFilled = [
|
||||||
|
'first_date' => $tomorrow->format('Y-m-d'),
|
||||||
|
'transaction_type' => 'withdrawal',
|
||||||
|
'active' => $request->old('active') ?? true,
|
||||||
|
'apply_rules' => $request->old('apply_rules') ?? true,
|
||||||
|
];
|
||||||
|
$request->session()->flash('preFilled', $preFilled);
|
||||||
|
|
||||||
|
return view('recurring.create', compact('tomorrow', 'preFilled', 'defaultCurrency','budgets'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
66
app/Http/Controllers/Recurring/EditController.php
Normal file
66
app/Http/Controllers/Recurring/EditController.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* EditController.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\Controllers\Recurring;
|
||||||
|
|
||||||
|
|
||||||
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\Recurrence;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Class EditController
|
||||||
|
*/
|
||||||
|
class EditController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// translations:
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
app('view')->share('mainTitleIcon', 'fa-paint-brush');
|
||||||
|
app('view')->share('title', trans('firefly.recurrences'));
|
||||||
|
app('view')->share('subTitle', trans('firefly.recurrences'));
|
||||||
|
|
||||||
|
$this->recurring = app(RecurringRepositoryInterface::class);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Recurrence $recurrence
|
||||||
|
*/
|
||||||
|
public function edit(Recurrence $recurrence) {
|
||||||
|
|
||||||
|
return view('recurring.edit', compact('recurrence'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
146
app/Http/Controllers/Recurring/IndexController.php
Normal file
146
app/Http/Controllers/Recurring/IndexController.php
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* IndexController.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\Controllers\Recurring;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\Recurrence;
|
||||||
|
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||||
|
use FireflyIII\Transformers\RecurrenceTransformer;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Response;
|
||||||
|
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Class IndexController
|
||||||
|
*/
|
||||||
|
class IndexController extends Controller
|
||||||
|
{
|
||||||
|
/** @var RecurringRepositoryInterface */
|
||||||
|
private $recurring;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// translations:
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
app('view')->share('mainTitleIcon', 'fa-paint-brush');
|
||||||
|
app('view')->share('title', trans('firefly.recurrences'));
|
||||||
|
|
||||||
|
$this->recurring = app(RecurringRepositoryInterface::class);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
* @throws \FireflyIII\Exceptions\FireflyException
|
||||||
|
*/
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
|
||||||
|
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
|
||||||
|
$collection = $this->recurring->getActive();
|
||||||
|
|
||||||
|
// TODO: split collection into pages
|
||||||
|
|
||||||
|
$transformer = new RecurrenceTransformer(new ParameterBag);
|
||||||
|
$recurring = [];
|
||||||
|
/** @var Recurrence $recurrence */
|
||||||
|
foreach ($collection as $recurrence) {
|
||||||
|
$array = $transformer->transform($recurrence);
|
||||||
|
$array['first_date'] = new Carbon($array['first_date']);
|
||||||
|
$array['latest_date'] = null === $array['latest_date'] ? null : new Carbon($array['latest_date']);
|
||||||
|
$recurring[] = $array;
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('recurring.index', compact('recurring', 'page', 'pageSize'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Recurrence $recurrence
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
* @throws \FireflyIII\Exceptions\FireflyException
|
||||||
|
*/
|
||||||
|
public function show(Recurrence $recurrence)
|
||||||
|
{
|
||||||
|
$transformer = new RecurrenceTransformer(new ParameterBag);
|
||||||
|
$array = $transformer->transform($recurrence);
|
||||||
|
|
||||||
|
// transform dates back to Carbon objects:
|
||||||
|
foreach ($array['repetitions'] as $index => $repetition) {
|
||||||
|
foreach ($repetition['occurrences'] as $item => $occurrence) {
|
||||||
|
$array['repetitions'][$index]['occurrences'][$item] = new Carbon($occurrence);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$subTitle = trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]);
|
||||||
|
|
||||||
|
return view('recurring.show', compact('recurrence', 'subTitle', 'array'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
*
|
||||||
|
* @return JsonResponse
|
||||||
|
*/
|
||||||
|
public function suggest(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$today = new Carbon;
|
||||||
|
$date = Carbon::createFromFormat('Y-m-d', $request->get('date'));
|
||||||
|
$result = [];
|
||||||
|
if ($date > $today) {
|
||||||
|
$weekly = sprintf('weekly,%s', $date->dayOfWeekIso);
|
||||||
|
$monthly = sprintf('monthly,%s', $date->day);
|
||||||
|
$dayOfWeek = trans(sprintf('config.dow_%s', $date->dayOfWeekIso));
|
||||||
|
$ndom = sprintf('ndom,%s,%s', $date->weekOfMonth, $date->dayOfWeekIso);
|
||||||
|
$yearly = sprintf('yearly,%s', $date->format('Y-m-d'));
|
||||||
|
$yearlyDate = $date->formatLocalized(trans('config.month_and_day_no_year'));
|
||||||
|
$result = [
|
||||||
|
'daily' => trans('firefly.recurring_daily'),
|
||||||
|
$weekly => trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek]),
|
||||||
|
$monthly => trans('firefly.recurring_monthly', ['dayOfMonth' => $date->day]),
|
||||||
|
$ndom => trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $date->weekOfMonth]),
|
||||||
|
$yearly => trans('firefly.recurring_yearly', ['date' => $yearlyDate]),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Response::json($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
159
app/Models/Recurrence.php
Normal file
159
app/Models/Recurrence.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Recurrence.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\Models;
|
||||||
|
|
||||||
|
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Recurrence
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
* @property int $user_id
|
||||||
|
* @property int $transaction_type_id
|
||||||
|
* @property int $transaction_currency_id
|
||||||
|
* @property string $title
|
||||||
|
* @property string $description
|
||||||
|
* @property \Carbon\Carbon $first_date
|
||||||
|
* @property \Carbon\Carbon $repeat_until
|
||||||
|
* @property \Carbon\Carbon $latest_date
|
||||||
|
* @property string $repetition_type
|
||||||
|
* @property string $repetition_moment
|
||||||
|
* @property int $repetition_skip
|
||||||
|
* @property bool $active
|
||||||
|
* @property bool $apply_rules
|
||||||
|
* @property \FireflyIII\User $user
|
||||||
|
* @property \Illuminate\Support\Collection $recurrenceRepetitions
|
||||||
|
* @property \Illuminate\Support\Collection $recurrenceMeta
|
||||||
|
* @property \Illuminate\Support\Collection $recurrenceTransactions
|
||||||
|
* @property \FireflyIII\Models\TransactionType $transactionType
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class Recurrence extends Model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The attributes that should be casted to native types.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $casts
|
||||||
|
= [
|
||||||
|
|
||||||
|
'created_at' => 'datetime',
|
||||||
|
'updated_at' => 'datetime',
|
||||||
|
'first_date' => 'date',
|
||||||
|
'latest_date' => 'date',
|
||||||
|
'active' => 'bool',
|
||||||
|
'apply_rules' => 'bool',
|
||||||
|
];
|
||||||
|
protected $table = 'recurrences';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return Recurrence
|
||||||
|
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||||
|
*/
|
||||||
|
public static function routeBinder(string $value): Recurrence
|
||||||
|
{
|
||||||
|
if (auth()->check()) {
|
||||||
|
$recurrenceId = (int)$value;
|
||||||
|
$recurrence = auth()->user()->recurrences()->find($recurrenceId);
|
||||||
|
if (null !== $recurrence) {
|
||||||
|
return $recurrence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new NotFoundHttpException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* Get all of the notes.
|
||||||
|
*/
|
||||||
|
public function notes()
|
||||||
|
{
|
||||||
|
return $this->morphMany(Note::class, 'noteable');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function recurrenceMeta(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(RecurrenceMeta::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function recurrenceRepetitions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(RecurrenceRepetition::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function recurrenceTransactions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(RecurrenceTransaction::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function transactionCurrency(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(TransactionCurrency::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function transactionType(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(TransactionType::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function user(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
49
app/Models/RecurrenceMeta.php
Normal file
49
app/Models/RecurrenceMeta.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RecurrenceMeta.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\Models;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RecurrenceMeta
|
||||||
|
*
|
||||||
|
* @property string $name
|
||||||
|
* @property string $value
|
||||||
|
*/
|
||||||
|
class RecurrenceMeta extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'recurrences_meta';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsTo
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function recurrence(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Recurrence::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
53
app/Models/RecurrenceRepetition.php
Normal file
53
app/Models/RecurrenceRepetition.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RecurrenceRepetition.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\Models;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RecurrenceRepetition
|
||||||
|
*
|
||||||
|
* @property string $repetition_type
|
||||||
|
* @property string $repetition_moment
|
||||||
|
* @property int $repetition_skip
|
||||||
|
* @property \Carbon\Carbon $created_at
|
||||||
|
* @property \Carbon\Carbon $deleted_at
|
||||||
|
* @property \Carbon\Carbon $updated_at
|
||||||
|
* @property int $id
|
||||||
|
*/
|
||||||
|
class RecurrenceRepetition extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'recurrences_repetitions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsTo
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function recurrence(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Recurrence::class);
|
||||||
|
}
|
||||||
|
}
|
105
app/Models/RecurrenceTransaction.php
Normal file
105
app/Models/RecurrenceTransaction.php
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RecurrenceTransaction.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\Models;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Class RecurrenceTransaction
|
||||||
|
*
|
||||||
|
* @property int $transaction_currency_id,
|
||||||
|
* @property int $foreign_currency_id
|
||||||
|
* @property int $source_account_id
|
||||||
|
* @property int $destination_account_id
|
||||||
|
* @property string $amount
|
||||||
|
* @property string $foreign_amount
|
||||||
|
* @property string $description
|
||||||
|
* @property \FireflyIII\Models\TransactionCurrency $transactionCurrency
|
||||||
|
* @property \FireflyIII\Models\TransactionCurrency $foreignCurrency
|
||||||
|
* @property \FireflyIII\Models\Account $sourceAccount
|
||||||
|
* @property \FireflyIII\Models\Account $destinationAccount
|
||||||
|
* @property \Illuminate\Support\Collection $recurrenceTransactionMeta
|
||||||
|
*/
|
||||||
|
class RecurrenceTransaction extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'recurrences_transactions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function destinationAccount(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Account::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function foreignCurrency(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(TransactionCurrency::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsTo
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function recurrence(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Recurrence::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function recurrenceTransactionMeta(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(recurrenceTransactionMeta::class,'rt_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function sourceAccount(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Account::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* @return BelongsTo
|
||||||
|
*/
|
||||||
|
public function transactionCurrency(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(TransactionCurrency::class);
|
||||||
|
}
|
||||||
|
}
|
49
app/Models/RecurrenceTransactionMeta.php
Normal file
49
app/Models/RecurrenceTransactionMeta.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RecurrenceMeta.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\Models;
|
||||||
|
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class RecurrenceTransactionMeta
|
||||||
|
*
|
||||||
|
* @property string $name
|
||||||
|
* @property string $value
|
||||||
|
*/
|
||||||
|
class RecurrenceTransactionMeta extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'rt_meta';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return BelongsTo
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
public function recurrenceTransaction(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(RecurrenceTransaction::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
63
app/Providers/RecurringServiceProvider.php
Normal file
63
app/Providers/RecurringServiceProvider.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RecurringServiceProvider.php
|
||||||
|
* Copyright (c) 2017 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\Providers;
|
||||||
|
|
||||||
|
use FireflyIII\Repositories\Recurring\RecurringRepository;
|
||||||
|
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||||
|
use Illuminate\Foundation\Application;
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* Class RecurringServiceProvider.
|
||||||
|
*/
|
||||||
|
class RecurringServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Bootstrap the application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register the application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->app->bind(
|
||||||
|
RecurringRepositoryInterface::class,
|
||||||
|
function (Application $app) {
|
||||||
|
/** @var RecurringRepositoryInterface $repository */
|
||||||
|
$repository = app(RecurringRepository::class);
|
||||||
|
|
||||||
|
if ($app->auth->check()) {
|
||||||
|
$repository->setUser(auth()->user());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $repository;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
226
app/Repositories/Recurring/RecurringRepository.php
Normal file
226
app/Repositories/Recurring/RecurringRepository.php
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RecurringRepository.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\Repositories\Recurring;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Models\Note;
|
||||||
|
use FireflyIII\Models\Preference;
|
||||||
|
use FireflyIII\Models\Recurrence;
|
||||||
|
use FireflyIII\Models\RecurrenceRepetition;
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Class RecurringRepository
|
||||||
|
*/
|
||||||
|
class RecurringRepository implements RecurringRepositoryInterface
|
||||||
|
{
|
||||||
|
/** @var User */
|
||||||
|
private $user;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all of the user's recurring transactions.
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getActive(): Collection
|
||||||
|
{
|
||||||
|
return $this->user->recurrences()->with(['TransactionCurrency', 'TransactionType', 'RecurrenceRepetitions', 'RecurrenceTransactions'])->where(
|
||||||
|
'active', 1
|
||||||
|
)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notes.
|
||||||
|
*
|
||||||
|
* @param Recurrence $recurrence
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getNoteText(Recurrence $recurrence): string
|
||||||
|
{
|
||||||
|
/** @var Note $note */
|
||||||
|
$note = $recurrence->notes()->first();
|
||||||
|
if (null !== $note) {
|
||||||
|
return (string)$note->text;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array
|
||||||
|
{
|
||||||
|
$return = [];
|
||||||
|
$mutator = clone $date;
|
||||||
|
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++) {
|
||||||
|
$mutator->addDay();
|
||||||
|
$return[] = clone $mutator;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
$return[] = clone $mutator;
|
||||||
|
$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++) {
|
||||||
|
$domCorrected = min($dayOfMonth, $mutator->daysInMonth);
|
||||||
|
$mutator->day = $domCorrected;
|
||||||
|
$return[] = clone $mutator;
|
||||||
|
$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++) {
|
||||||
|
$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;
|
||||||
|
$mutator->endOfMonth()->addDay();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'yearly':
|
||||||
|
$date = new Carbon($repetition->repetition_moment);
|
||||||
|
$date->year = $mutator->year;
|
||||||
|
if ($mutator > $date) {
|
||||||
|
$date->addYear();
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $count; $i++) {
|
||||||
|
$obj = clone $date;
|
||||||
|
$obj->addYears($i);
|
||||||
|
$return[] = $obj;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the repetition in a string that is user readable.
|
||||||
|
*
|
||||||
|
* @param RecurrenceRepetition $repetition
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function repetitionDescription(RecurrenceRepetition $repetition): string
|
||||||
|
{
|
||||||
|
/** @var Preference $pref */
|
||||||
|
$pref = app('preferences')->getForUser($this->user, 'language', config('firefly.default_language', 'en_US'));
|
||||||
|
$language = $pref->data;
|
||||||
|
switch ($repetition->repetition_type) {
|
||||||
|
default:
|
||||||
|
throw new FireflyException(sprintf('Cannot translate recurring transaction repetition type "%s"', $repetition->repetition_type));
|
||||||
|
break;
|
||||||
|
case 'daily':
|
||||||
|
return trans('firefly.recurring_daily', [], $language);
|
||||||
|
break;
|
||||||
|
case 'weekly':
|
||||||
|
$dayOfWeek = trans(sprintf('config.dow_%s', $repetition->repetition_moment), [], $language);
|
||||||
|
|
||||||
|
return trans('firefly.recurring_weekly', ['weekday' => $dayOfWeek], $language);
|
||||||
|
break;
|
||||||
|
case 'monthly':
|
||||||
|
// format a date:
|
||||||
|
return trans('firefly.recurring_monthly', ['dayOfMonth' => $repetition->repetition_moment], $language);
|
||||||
|
break;
|
||||||
|
case 'ndom':
|
||||||
|
$parts = explode(',', $repetition->repetition_moment);
|
||||||
|
// first part is number of week, second is weekday.
|
||||||
|
$dayOfWeek = trans(sprintf('config.dow_%s', $parts[1]), [], $language);
|
||||||
|
|
||||||
|
return trans('firefly.recurring_ndom', ['weekday' => $dayOfWeek, 'dayOfMonth' => $parts[0]], $language);
|
||||||
|
break;
|
||||||
|
case 'yearly':
|
||||||
|
//
|
||||||
|
$today = new Carbon;
|
||||||
|
$today->endOfYear();
|
||||||
|
$repDate = Carbon::createFromFormat('Y-m-d', $repetition->repetition_moment);
|
||||||
|
$diffInYears = $today->diffInYears($repDate);
|
||||||
|
$repDate->addYears($diffInYears); // technically not necessary.
|
||||||
|
$string = $repDate->formatLocalized(trans('config.month_and_day_no_year'));
|
||||||
|
|
||||||
|
return trans('firefly.recurring_yearly', ['date' => $string], $language);
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user for in repository.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
*/
|
||||||
|
public function setUser(User $user): void
|
||||||
|
{
|
||||||
|
$this->user = $user;
|
||||||
|
}
|
||||||
|
}
|
84
app/Repositories/Recurring/RecurringRepositoryInterface.php
Normal file
84
app/Repositories/Recurring/RecurringRepositoryInterface.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RecurringRepositoryInterface.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\Repositories\Recurring;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Models\Recurrence;
|
||||||
|
use FireflyIII\Models\RecurrenceRepetition;
|
||||||
|
use FireflyIII\User;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface RecurringRepositoryInterface
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Repositories\Recurring
|
||||||
|
*/
|
||||||
|
interface RecurringRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns all of the user's recurring transactions.
|
||||||
|
*
|
||||||
|
* @return Collection
|
||||||
|
*/
|
||||||
|
public function getActive(): Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the notes.
|
||||||
|
*
|
||||||
|
* @param Recurrence $recurrence
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getNoteText(Recurrence $recurrence): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getOccurrences(RecurrenceRepetition $repetition, Carbon $date, int $count = 5): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the repetition in a string that is user readable.
|
||||||
|
*
|
||||||
|
* @param RecurrenceRepetition $repetition
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function repetitionDescription(RecurrenceRepetition $repetition): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set user for in repository.
|
||||||
|
*
|
||||||
|
* @param User $user
|
||||||
|
*/
|
||||||
|
public function setUser(User $user): void;
|
||||||
|
|
||||||
|
}
|
256
app/Transformers/RecurrenceTransformer.php
Normal file
256
app/Transformers/RecurrenceTransformer.php
Normal file
@ -0,0 +1,256 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* RecurringTransactionTransformer.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\Transformers;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
|
use FireflyIII\Factory\CategoryFactory;
|
||||||
|
use FireflyIII\Models\Recurrence;
|
||||||
|
use FireflyIII\Models\RecurrenceMeta;
|
||||||
|
use FireflyIII\Models\RecurrenceRepetition;
|
||||||
|
use FireflyIII\Models\RecurrenceTransaction;
|
||||||
|
use FireflyIII\Models\RecurrenceTransactionMeta;
|
||||||
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
|
||||||
|
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
|
||||||
|
use League\Fractal\Resource\Item;
|
||||||
|
use League\Fractal\TransformerAbstract;
|
||||||
|
use Symfony\Component\HttpFoundation\ParameterBag;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Class RecurringTransactionTransformer
|
||||||
|
*/
|
||||||
|
class RecurrenceTransformer extends TransformerAbstract
|
||||||
|
{
|
||||||
|
/** @noinspection ClassOverridesFieldOfSuperClassInspection */
|
||||||
|
/**
|
||||||
|
* List of resources possible to include.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $availableIncludes = ['user'];
|
||||||
|
/**
|
||||||
|
* List of resources to automatically include
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $defaultIncludes = [];
|
||||||
|
/** @var ParameterBag */
|
||||||
|
protected $parameters;
|
||||||
|
|
||||||
|
/** @var RecurringRepositoryInterface */
|
||||||
|
protected $repository;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(ParameterBag $parameters)
|
||||||
|
{
|
||||||
|
$this->repository = app(RecurringRepositoryInterface::class);
|
||||||
|
$this->parameters = $parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include user data in end result.
|
||||||
|
*
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*
|
||||||
|
* @param Recurrence $recurrence
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return Item
|
||||||
|
*/
|
||||||
|
public function includeUser(Recurrence $recurrence): Item
|
||||||
|
{
|
||||||
|
return $this->item($recurrence->user, new UserTransformer($this->parameters), 'user');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform the piggy bank.
|
||||||
|
*
|
||||||
|
* @param Recurrence $recurrence
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function transform(Recurrence $recurrence): array
|
||||||
|
{
|
||||||
|
$this->repository->setUser($recurrence->user);
|
||||||
|
$return = [
|
||||||
|
'id' => (int)$recurrence->id,
|
||||||
|
'updated_at' => $recurrence->updated_at->toAtomString(),
|
||||||
|
'created_at' => $recurrence->created_at->toAtomString(),
|
||||||
|
'transaction_type_id' => $recurrence->transaction_type_id,
|
||||||
|
'transaction_type' => $recurrence->transactionType->type,
|
||||||
|
'title' => $recurrence->title,
|
||||||
|
'description' => $recurrence->description,
|
||||||
|
'first_date' => $recurrence->first_date->format('Y-m-d'),
|
||||||
|
'latest_date' => null === $recurrence->latest_date ? null : $recurrence->latest_date->format('Y-m-d'),
|
||||||
|
'repeat_until' => null === $recurrence->repeat_until ? null : $recurrence->repeat_until->format('Y-m-d'),
|
||||||
|
'apply_rules' => $recurrence->apply_rules,
|
||||||
|
'active' => $recurrence->active,
|
||||||
|
'notes' => $this->repository->getNoteText($recurrence),
|
||||||
|
'repetitions' => [],
|
||||||
|
'transactions' => [],
|
||||||
|
'meta' => [],
|
||||||
|
'links' => [
|
||||||
|
[
|
||||||
|
'rel' => 'self',
|
||||||
|
'uri' => '/recurring/' . $recurrence->id,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$fromDate = $recurrence->latest_date ?? $recurrence->first_date;
|
||||||
|
// date in the past? use today:
|
||||||
|
$today = new Carbon;
|
||||||
|
$fromDate = $fromDate->lte($today) ? $today : $fromDate;
|
||||||
|
|
||||||
|
/** @var RecurrenceRepetition $repetition */
|
||||||
|
foreach ($recurrence->recurrenceRepetitions as $repetition) {
|
||||||
|
$repetitionArray = [
|
||||||
|
'id' => $repetition->id,
|
||||||
|
'updated_at' => $repetition->updated_at->toAtomString(),
|
||||||
|
'created_at' => $repetition->created_at->toAtomString(),
|
||||||
|
'repetition_type' => $repetition->repetition_type,
|
||||||
|
'repetition_moment' => $repetition->repetition_moment,
|
||||||
|
'repetition_skip' => (int)$repetition->repetition_skip,
|
||||||
|
'description' => $this->repository->repetitionDescription($repetition),
|
||||||
|
'occurrences' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
// get the (future) occurrences for this specific type of repetition:
|
||||||
|
$occurrences = $this->repository->getOccurrences($repetition, $fromDate, 5);
|
||||||
|
/** @var Carbon $carbon */
|
||||||
|
foreach ($occurrences as $carbon) {
|
||||||
|
$repetitionArray['occurrences'][] = $carbon->format('Y-m-d');
|
||||||
|
}
|
||||||
|
|
||||||
|
$return['repetitions'][] = $repetitionArray;
|
||||||
|
}
|
||||||
|
unset($repetitionArray);
|
||||||
|
|
||||||
|
// get all transactions:
|
||||||
|
/** @var RecurrenceTransaction $transaction */
|
||||||
|
foreach ($recurrence->recurrenceTransactions as $transaction) {
|
||||||
|
$transactionArray = [
|
||||||
|
'currency_id' => $transaction->transaction_currency_id,
|
||||||
|
'currency_code' => $transaction->transactionCurrency->code,
|
||||||
|
'currency_symbol' => $transaction->transactionCurrency->symbol,
|
||||||
|
'currency_dp' => $transaction->transactionCurrency->decimal_places,
|
||||||
|
'foreign_currency_id' => $transaction->foreign_currency_id,
|
||||||
|
'source_account_id' => $transaction->source_account_id,
|
||||||
|
'source_account_name' => $transaction->sourceAccount->name,
|
||||||
|
'destination_account_id' => $transaction->destination_account_id,
|
||||||
|
'destination_account_name' => $transaction->destinationAccount->name,
|
||||||
|
'amount' => $transaction->amount,
|
||||||
|
'foreign_amount' => $transaction->foreign_amount,
|
||||||
|
'description' => $transaction->description,
|
||||||
|
'meta' => [],
|
||||||
|
];
|
||||||
|
if (null !== $transaction->foreign_currency_id) {
|
||||||
|
$transactionArray['foreign_currency_code'] = $transaction->foreignCurrency->code;
|
||||||
|
$transactionArray['foreign_currency_symbol'] = $transaction->foreignCurrency->symbol;
|
||||||
|
$transactionArray['foreign_currency_dp'] = $transaction->foreignCurrency->decimal_places;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get meta data for each transaction:
|
||||||
|
/** @var RecurrenceTransactionMeta $transactionMeta */
|
||||||
|
foreach ($transaction->recurrenceTransactionMeta as $transactionMeta) {
|
||||||
|
$transactionMetaArray = [
|
||||||
|
'name' => $transactionMeta->name,
|
||||||
|
'value' => $transactionMeta->value,
|
||||||
|
];
|
||||||
|
switch ($transactionMeta->name) {
|
||||||
|
default:
|
||||||
|
throw new FireflyException(sprintf('Recurrence transformer cannot handle transaction meta-field "%s"', $transactionMeta->name));
|
||||||
|
case 'category_name':
|
||||||
|
/** @var CategoryFactory $factory */
|
||||||
|
$factory = app(CategoryFactory::class);
|
||||||
|
$factory->setUser($recurrence->user);
|
||||||
|
$category = $factory->findOrCreate(null, $transactionMeta->value);
|
||||||
|
if (null !== $category) {
|
||||||
|
$transactionMetaArray['category_id'] = $category->id;
|
||||||
|
$transactionMetaArray['category_name'] = $category->name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'budget_id':
|
||||||
|
/** @var BudgetRepositoryInterface $repository */
|
||||||
|
$repository = app(BudgetRepositoryInterface::class);
|
||||||
|
$budget = $repository->findNull((int)$transactionMeta->value);
|
||||||
|
if (null !== $budget) {
|
||||||
|
$transactionMetaArray['budget_id'] = $budget->id;
|
||||||
|
$transactionMetaArray['budget_name'] = $budget->name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// store transaction meta data in transaction
|
||||||
|
$transactionArray['meta'][] = $transactionMetaArray;
|
||||||
|
}
|
||||||
|
// store transaction in recurrence array.
|
||||||
|
$return['transactions'][] = $transactionArray;
|
||||||
|
}
|
||||||
|
// get all meta data for recurrence itself
|
||||||
|
/** @var RecurrenceMeta $recurrenceMeta */
|
||||||
|
foreach ($recurrence->recurrenceMeta as $recurrenceMeta) {
|
||||||
|
$recurrenceMetaArray = [
|
||||||
|
'name' => $recurrenceMeta->name,
|
||||||
|
'value' => $recurrenceMeta->value,
|
||||||
|
];
|
||||||
|
switch ($recurrenceMeta->name) {
|
||||||
|
default:
|
||||||
|
throw new FireflyException(sprintf('Recurrence transformer cannot handle meta-field "%s"', $recurrenceMeta->name));
|
||||||
|
case 'tags':
|
||||||
|
$recurrenceMetaArray['tags'] = explode(',', $recurrenceMeta->value);
|
||||||
|
break;
|
||||||
|
case 'notes':
|
||||||
|
break;
|
||||||
|
case 'bill_id':
|
||||||
|
/** @var BillRepositoryInterface $repository */
|
||||||
|
$repository = app(BillRepositoryInterface::class);
|
||||||
|
$bill = $repository->find((int)$recurrenceMeta->value);
|
||||||
|
if (null !== $bill) {
|
||||||
|
$recurrenceMetaArray['bill_id'] = $bill->id;
|
||||||
|
$recurrenceMetaArray['bill_name'] = $bill->name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'piggy_bank_id':
|
||||||
|
/** @var PiggyBankRepositoryInterface $repository */
|
||||||
|
$repository = app(PiggyBankRepositoryInterface::class);
|
||||||
|
$piggy = $repository->findNull((int)$recurrenceMeta->value);
|
||||||
|
if (null !== $piggy) {
|
||||||
|
$recurrenceMetaArray['piggy_bank_id'] = $piggy->id;
|
||||||
|
$recurrenceMetaArray['piggy_bank_name'] = $piggy->name;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// store meta date in recurring array
|
||||||
|
$return['meta'][] = $recurrenceMetaArray;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
app/User.php
12
app/User.php
@ -36,6 +36,7 @@ use FireflyIII\Models\ExportJob;
|
|||||||
use FireflyIII\Models\ImportJob;
|
use FireflyIII\Models\ImportJob;
|
||||||
use FireflyIII\Models\PiggyBank;
|
use FireflyIII\Models\PiggyBank;
|
||||||
use FireflyIII\Models\Preference;
|
use FireflyIII\Models\Preference;
|
||||||
|
use FireflyIII\Models\Recurrence;
|
||||||
use FireflyIII\Models\Role;
|
use FireflyIII\Models\Role;
|
||||||
use FireflyIII\Models\Rule;
|
use FireflyIII\Models\Rule;
|
||||||
use FireflyIII\Models\RuleGroup;
|
use FireflyIII\Models\RuleGroup;
|
||||||
@ -291,6 +292,17 @@ class User extends Authenticatable
|
|||||||
return $this->hasMany(Preference::class);
|
return $this->hasMany(Preference::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
* Link to recurring transactions.
|
||||||
|
*
|
||||||
|
* @return HasMany
|
||||||
|
*/
|
||||||
|
public function recurrences(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Recurrence::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @codeCoverageIgnore
|
* @codeCoverageIgnore
|
||||||
* Link to roles.
|
* Link to roles.
|
||||||
|
@ -98,6 +98,7 @@ return [
|
|||||||
FireflyIII\Providers\SearchServiceProvider::class,
|
FireflyIII\Providers\SearchServiceProvider::class,
|
||||||
FireflyIII\Providers\TagServiceProvider::class,
|
FireflyIII\Providers\TagServiceProvider::class,
|
||||||
FireflyIII\Providers\AdminServiceProvider::class,
|
FireflyIII\Providers\AdminServiceProvider::class,
|
||||||
|
FireflyIII\Providers\RecurringServiceProvider::class,
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
|
@ -271,6 +271,7 @@ return [
|
|||||||
'piggyBank' => \FireflyIII\Models\PiggyBank::class,
|
'piggyBank' => \FireflyIII\Models\PiggyBank::class,
|
||||||
'tj' => \FireflyIII\Models\TransactionJournal::class,
|
'tj' => \FireflyIII\Models\TransactionJournal::class,
|
||||||
'tag' => \FireflyIII\Models\Tag::class,
|
'tag' => \FireflyIII\Models\Tag::class,
|
||||||
|
'recurrence' => \FireflyIII\Models\Recurrence::class,
|
||||||
'rule' => \FireflyIII\Models\Rule::class,
|
'rule' => \FireflyIII\Models\Rule::class,
|
||||||
'ruleGroup' => \FireflyIII\Models\RuleGroup::class,
|
'ruleGroup' => \FireflyIII\Models\RuleGroup::class,
|
||||||
'exportJob' => \FireflyIII\Models\ExportJob::class,
|
'exportJob' => \FireflyIII\Models\ExportJob::class,
|
||||||
|
@ -37,7 +37,6 @@ class ChangesForV474 extends Migration
|
|||||||
*/
|
*/
|
||||||
public function down()
|
public function down()
|
||||||
{
|
{
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
129
database/migrations/2018_06_08_200526_changes_for_v475.php
Normal file
129
database/migrations/2018_06_08_200526_changes_for_v475.php
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Class ChangesForV475
|
||||||
|
*/
|
||||||
|
class ChangesForV475 extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('recurrences_repetitions');
|
||||||
|
Schema::dropIfExists('recurrences_meta');
|
||||||
|
Schema::dropIfExists('rt_meta');
|
||||||
|
Schema::dropIfExists('recurrences_transactions');
|
||||||
|
Schema::dropIfExists('recurrences');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create(
|
||||||
|
'recurrences', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->integer('user_id', false, true);
|
||||||
|
$table->integer('transaction_type_id', false, true);
|
||||||
|
|
||||||
|
$table->string('title', 1024);
|
||||||
|
$table->text('description');
|
||||||
|
|
||||||
|
$table->date('first_date');
|
||||||
|
$table->date('repeat_until')->nullable();
|
||||||
|
$table->date('latest_date')->nullable();
|
||||||
|
|
||||||
|
$table->boolean('apply_rules')->default(true);
|
||||||
|
$table->boolean('active')->default(true);
|
||||||
|
|
||||||
|
// also separate:
|
||||||
|
// category, budget, tags, notes, bill, piggy bank
|
||||||
|
|
||||||
|
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
$table->foreign('transaction_type_id')->references('id')->on('transaction_types')->onDelete('cascade');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Schema::create(
|
||||||
|
'recurrences_transactions', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->integer('recurrence_id', false, true);
|
||||||
|
$table->integer('transaction_currency_id', false, true);
|
||||||
|
$table->integer('foreign_currency_id', false, true)->nullable();
|
||||||
|
$table->integer('source_account_id', false, true);
|
||||||
|
$table->integer('destination_account_id', false, true);
|
||||||
|
|
||||||
|
$table->decimal('amount', 22, 12);
|
||||||
|
$table->decimal('foreign_amount', 22, 12)->nullable();
|
||||||
|
$table->string('description', 1024);
|
||||||
|
|
||||||
|
|
||||||
|
$table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade');
|
||||||
|
$table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade');
|
||||||
|
$table->foreign('foreign_currency_id')->references('id')->on('transaction_currencies')->onDelete('set null');
|
||||||
|
$table->foreign('source_account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||||
|
$table->foreign('destination_account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
Schema::create(
|
||||||
|
'recurrences_repetitions', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->integer('recurrence_id', false, true);
|
||||||
|
$table->string('repetition_type', 50);
|
||||||
|
$table->string('repetition_moment', 50);
|
||||||
|
$table->smallInteger('repetition_skip', false, true);
|
||||||
|
|
||||||
|
$table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Schema::create(
|
||||||
|
'recurrences_meta', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->integer('recurrence_id', false, true);
|
||||||
|
|
||||||
|
$table->string('name', 50);
|
||||||
|
$table->text('value');
|
||||||
|
|
||||||
|
$table->foreign('recurrence_id')->references('id')->on('recurrences')->onDelete('cascade');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Schema::create(
|
||||||
|
'rt_meta', function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->timestamps();
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->integer('rt_id', false, true);
|
||||||
|
|
||||||
|
$table->string('name', 50);
|
||||||
|
$table->text('value');
|
||||||
|
|
||||||
|
$table->foreign('rt_id')->references('id')->on('recurrences_transactions')->onDelete('cascade');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
170
public/js/ff/recurring/create.js
vendored
Normal file
170
public/js/ff/recurring/create.js
vendored
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
* create.js
|
||||||
|
* Copyright (c) 2017 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** global: Modernizr, currencies */
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
"use strict";
|
||||||
|
if (!Modernizr.inputtypes.date) {
|
||||||
|
$('input[type="date"]').datepicker(
|
||||||
|
{
|
||||||
|
dateFormat: 'yy-mm-dd'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
initializeButtons();
|
||||||
|
initializeAutoComplete();
|
||||||
|
respondToFirstDateChange();
|
||||||
|
$('.switch-button').on('click', switchTransactionType);
|
||||||
|
$('#ffInput_first_date').on('change', respondToFirstDateChange);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function respondToFirstDateChange() {
|
||||||
|
var obj = $('#ffInput_first_date');
|
||||||
|
var select = $('#ffInput_repetition_type');
|
||||||
|
var date = obj.val();
|
||||||
|
select.prop('disabled', true);
|
||||||
|
$.getJSON(suggestUri, {date: date}).fail(function () {
|
||||||
|
console.error('Could not load repetition suggestions');
|
||||||
|
alert('Could not load repetition suggestions');
|
||||||
|
}).done(parseRepetitionSuggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseRepetitionSuggestions(data) {
|
||||||
|
|
||||||
|
var select = $('#ffInput_repetition_type');
|
||||||
|
select.empty();
|
||||||
|
for (var k in data) {
|
||||||
|
if (data.hasOwnProperty(k)) {
|
||||||
|
select.append($('<option>').val(k).attr('label', data[k]).text(data[k]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
select.removeAttr('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializeAutoComplete() {
|
||||||
|
// auto complete things:
|
||||||
|
$.getJSON('json/tags').done(function (data) {
|
||||||
|
var opt = {
|
||||||
|
typeahead: {
|
||||||
|
source: data,
|
||||||
|
afterSelect: function () {
|
||||||
|
this.$element.val("");
|
||||||
|
},
|
||||||
|
autoSelect: false,
|
||||||
|
},
|
||||||
|
autoSelect: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
$('input[name="tags"]').tagsinput(
|
||||||
|
opt
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($('input[name="destination_account_name"]').length > 0) {
|
||||||
|
$.getJSON('json/expense-accounts').done(function (data) {
|
||||||
|
$('input[name="destination_account_name"]').typeahead({source: data, autoSelect: false});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($('input[name="source_account_name"]').length > 0) {
|
||||||
|
$.getJSON('json/revenue-accounts').done(function (data) {
|
||||||
|
$('input[name="source_account_name"]').typeahead({source: data, autoSelect: false});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$.getJSON('json/categories').done(function (data) {
|
||||||
|
$('input[name="category"]').typeahead({source: data, autoSelect: false});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
function switchTransactionType(e) {
|
||||||
|
var target = $(e.target);
|
||||||
|
transactionType = target.data('value');
|
||||||
|
initializeButtons();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop the three buttons and do some magic.
|
||||||
|
*/
|
||||||
|
function initializeButtons() {
|
||||||
|
console.log('Now in initializeButtons()');
|
||||||
|
$.each($('.switch-button'), function (i, v) {
|
||||||
|
var btn = $(v);
|
||||||
|
console.log('Value is ' + btn.data('value'));
|
||||||
|
if (btn.data('value') === transactionType) {
|
||||||
|
btn.addClass('btn-info disabled').removeClass('btn-default');
|
||||||
|
} else {
|
||||||
|
btn.removeClass('btn-info disabled').addClass('btn-default');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateFormFields();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide and/or show stuff when switching:
|
||||||
|
*/
|
||||||
|
function updateFormFields() {
|
||||||
|
|
||||||
|
if (transactionType === 'withdrawal') {
|
||||||
|
// hide source account name:
|
||||||
|
$('#source_account_name_holder').hide();
|
||||||
|
|
||||||
|
// show source account ID:
|
||||||
|
$('#source_account_id_holder').show();
|
||||||
|
|
||||||
|
// show destination name:
|
||||||
|
$('#destination_account_name_holder').show();
|
||||||
|
|
||||||
|
// hide destination ID:
|
||||||
|
$('#destination_account_id_holder').hide();
|
||||||
|
|
||||||
|
// show budget
|
||||||
|
$('#budget_id_holder').show();
|
||||||
|
|
||||||
|
// hide piggy bank:
|
||||||
|
$('#piggy_bank_id_holder').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionType === 'deposit') {
|
||||||
|
$('#source_account_name_holder').show();
|
||||||
|
$('#source_account_id_holder').hide();
|
||||||
|
$('#destination_account_name_holder').hide();
|
||||||
|
$('#destination_account_id_holder').show();
|
||||||
|
$('#budget_id_holder').hide();
|
||||||
|
$('#piggy_bank_id_holder').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionType === 'transfer') {
|
||||||
|
$('#source_account_name_holder').hide();
|
||||||
|
$('#source_account_id_holder').show();
|
||||||
|
$('#destination_account_name_holder').hide();
|
||||||
|
$('#destination_account_id_holder').show();
|
||||||
|
$('#budget_id_holder').hide();
|
||||||
|
$('#piggy_bank_id_holder').show();
|
||||||
|
}
|
||||||
|
}
|
@ -23,20 +23,29 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'html_language' => 'en',
|
'html_language' => 'en',
|
||||||
'locale' => 'en, English, en_US, en_US.utf8, en_US.UTF-8',
|
'locale' => 'en, English, en_US, en_US.utf8, en_US.UTF-8',
|
||||||
'month' => '%B %Y',
|
'month' => '%B %Y',
|
||||||
'month_and_day' => '%B %e, %Y',
|
'month_and_day' => '%B %e, %Y',
|
||||||
'date_time' => '%B %e, %Y, @ %T',
|
'month_and_date_day' => '%A %B %e, %Y',
|
||||||
'specific_day' => '%e %B %Y',
|
'month_and_day_no_year' => '%B %e',
|
||||||
'week_in_year' => 'Week %W, %Y',
|
'date_time' => '%B %e, %Y, @ %T',
|
||||||
'year' => '%Y',
|
'specific_day' => '%e %B %Y',
|
||||||
'half_year' => '%B %Y',
|
'week_in_year' => 'Week %W, %Y',
|
||||||
'month_js' => 'MMMM YYYY',
|
'year' => '%Y',
|
||||||
'month_and_day_js' => 'MMMM Do, YYYY',
|
'half_year' => '%B %Y',
|
||||||
'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss',
|
'month_js' => 'MMMM YYYY',
|
||||||
'specific_day_js' => 'D MMMM YYYY',
|
'month_and_day_js' => 'MMMM Do, YYYY',
|
||||||
'week_in_year_js' => '[Week] w, YYYY',
|
'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss',
|
||||||
'year_js' => 'YYYY',
|
'specific_day_js' => 'D MMMM YYYY',
|
||||||
'half_year_js' => 'Q YYYY',
|
'week_in_year_js' => '[Week] w, YYYY',
|
||||||
|
'year_js' => 'YYYY',
|
||||||
|
'half_year_js' => 'Q YYYY',
|
||||||
|
'dow_1' => 'Monday',
|
||||||
|
'dow_2' => 'Tuesday',
|
||||||
|
'dow_3' => 'Wednesday',
|
||||||
|
'dow_4' => 'Thursday',
|
||||||
|
'dow_5' => 'Friday',
|
||||||
|
'dow_6' => 'Saturday',
|
||||||
|
'dow_7' => 'Sunday',
|
||||||
];
|
];
|
||||||
|
@ -820,7 +820,7 @@ return [
|
|||||||
'language' => 'Language',
|
'language' => 'Language',
|
||||||
'new_savings_account' => ':bank_name savings account',
|
'new_savings_account' => ':bank_name savings account',
|
||||||
'cash_wallet' => 'Cash wallet',
|
'cash_wallet' => 'Cash wallet',
|
||||||
'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.',
|
'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.',
|
||||||
|
|
||||||
// home page:
|
// home page:
|
||||||
'yourAccounts' => 'Your accounts',
|
'yourAccounts' => 'Your accounts',
|
||||||
@ -1206,4 +1206,35 @@ return [
|
|||||||
'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent or insurance.',
|
'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent or insurance.',
|
||||||
'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:',
|
'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:',
|
||||||
'no_bills_create_default' => 'Create a bill',
|
'no_bills_create_default' => 'Create a bill',
|
||||||
|
|
||||||
|
// recurring transactions
|
||||||
|
'recurrences' => 'Recurring transactions',
|
||||||
|
'no_recurring_title_default' => 'Let\'s create a recurring transaction!',
|
||||||
|
'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.',
|
||||||
|
'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?-icon in the top right corner) before you continue.',
|
||||||
|
'no_recurring_create_default' => 'Create a recurring transaction',
|
||||||
|
'make_new_recurring' => 'Create a recurring transaction',
|
||||||
|
'recurring_daily' => 'Every day',
|
||||||
|
'recurring_weekly' => 'Every week on :weekday',
|
||||||
|
'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day',
|
||||||
|
'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday',
|
||||||
|
'recurring_yearly' => 'Every year on :date',
|
||||||
|
'overview_for_recurrence' => 'Overview for recurring transaction ":title"',
|
||||||
|
'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.',
|
||||||
|
'created_transactions' => 'Related transactions',
|
||||||
|
'expected_transactions' => 'Expected transactions',
|
||||||
|
'recurring_meta_field_tags' => 'Tags',
|
||||||
|
'recurring_meta_field_notes' => 'Notes',
|
||||||
|
'recurring_meta_field_bill_id' => 'Bill',
|
||||||
|
'recurring_meta_field_piggy_bank_id' => 'Piggy bank',
|
||||||
|
'create_new_recurrence' => 'Create new recurring transaction',
|
||||||
|
'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.',
|
||||||
|
'mandatory_for_recurring' => 'Mandatory recurrence information',
|
||||||
|
'mandatory_for_transaction' => 'Mandatory transaction information',
|
||||||
|
'optional_for_recurring' => 'Optional recurrence information',
|
||||||
|
'optional_for_transaction' => 'Optional transaction information',
|
||||||
|
'change_date_other_options' => 'Change the "first date" to see more options.',
|
||||||
|
'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created',
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -216,11 +216,19 @@ return [
|
|||||||
'country_code' => 'Country code',
|
'country_code' => 'Country code',
|
||||||
'provider_code' => 'Bank or data-provider',
|
'provider_code' => 'Bank or data-provider',
|
||||||
|
|
||||||
'due_date' => 'Due date',
|
'due_date' => 'Due date',
|
||||||
'payment_date' => 'Payment date',
|
'payment_date' => 'Payment date',
|
||||||
'invoice_date' => 'Invoice date',
|
'invoice_date' => 'Invoice date',
|
||||||
'internal_reference' => 'Internal reference',
|
'internal_reference' => 'Internal reference',
|
||||||
'inward' => 'Inward description',
|
'inward' => 'Inward description',
|
||||||
'outward' => 'Outward description',
|
'outward' => 'Outward description',
|
||||||
'rule_group_id' => 'Rule group',
|
'rule_group_id' => 'Rule group',
|
||||||
|
'transaction_description' => 'Transaction description',
|
||||||
|
'first_date' => 'First date',
|
||||||
|
'transaction_type' => 'Transaction type',
|
||||||
|
'repeat_until' => 'Repeat until',
|
||||||
|
'recurring_description' => 'Recurring transaction description',
|
||||||
|
'repetition_type' => 'Type of repetition',
|
||||||
|
'foreign_currency_id' => 'Foreign currency',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -123,4 +123,9 @@ return [
|
|||||||
'spectre_last_use' => 'Last login',
|
'spectre_last_use' => 'Last login',
|
||||||
'spectre_status' => 'Status',
|
'spectre_status' => 'Status',
|
||||||
'bunq_payment_id' => 'bunq payment ID',
|
'bunq_payment_id' => 'bunq payment ID',
|
||||||
|
'repetitions' => 'Repetitions',
|
||||||
|
'title' => 'Title',
|
||||||
|
'transaction_s' => 'Transaction(s)',
|
||||||
|
'field' => 'Field',
|
||||||
|
'value' => 'Value',
|
||||||
];
|
];
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
{{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }}
|
{{ ExpandedForm.textarea('notes',null,{helpText: trans('firefly.field_supports_markdown')}) }}
|
||||||
{{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }}
|
{{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }}
|
||||||
{{ ExpandedForm.integer('skip',0) }}
|
{{ ExpandedForm.integer('skip',0) }}
|
||||||
{{ ExpandedForm.checkbox('active',1,true) }}
|
{{ ExpandedForm.checkbox('active',1, true) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -98,6 +98,10 @@
|
|||||||
<a href="{{ route('rules.index') }}">
|
<a href="{{ route('rules.index') }}">
|
||||||
<i class="fa fa-random fa-fw"></i> {{ 'rules'|_ }}</a>
|
<i class="fa fa-random fa-fw"></i> {{ 'rules'|_ }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="{{ activeRoutePartial('recurring') }}">
|
||||||
|
<a href="{{ route('recurring.index') }}">
|
||||||
|
<i class="fa fa-paint-brush fa-fw"></i> {{ 'recurrences'|_ }}</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
150
resources/views/recurring/create.twig
Normal file
150
resources/views/recurring/create.twig
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
{% extends "./layout/default" %}
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ Breadcrumbs.render(Route.getCurrentRoute.getName) }}
|
||||||
|
{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form action="{{ route('recurring.store') }}" method="post" id="store" class="form-horizontal">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12">
|
||||||
|
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||||
|
<div class="box box-primary">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'mandatory_for_recurring'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
{{ ExpandedForm.text('name') }}
|
||||||
|
{{ ExpandedForm.date('first_date',null, {helpText: trans('firefly.help_first_date')}) }}
|
||||||
|
{{ ExpandedForm.date('repeat_until',null) }}
|
||||||
|
{{ ExpandedForm.select('repetition_type', [], null, {helpText: trans('firefly.change_date_other_options')}) }}
|
||||||
|
{{ ExpandedForm.number('skip', 0) }}
|
||||||
|
|
||||||
|
{# three buttons to distinguish type of transaction#}
|
||||||
|
<div class="form-group" id="name_holder">
|
||||||
|
<label for="ffInput_type" class="col-sm-4 control-label">
|
||||||
|
{{ trans('form.transaction_type') }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<div class="btn-group btn-group-sm">
|
||||||
|
<a href="#" class="btn btn-default switch-button" data-value="withdrawal">{{ 'withdrawal'|_ }}</a>
|
||||||
|
<a href="#" class="btn btn-default switch-button" data-value="deposit">{{ 'deposit'|_ }}</a>
|
||||||
|
<a href="#" class="btn btn-default switch-button" data-value="transfer">{{ 'transfer'|_ }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box box-primary">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'mandatory_for_transaction'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
<p><em>{{ 'mandatory_fields_for_tranaction'|_ }}</em></p>
|
||||||
|
{{ ExpandedForm.text('transaction_description') }}
|
||||||
|
{# transaction information (mandatory) #}
|
||||||
|
{{ ExpandedForm.currencyList('transaction_currency_id', defaultCurrency.id) }}
|
||||||
|
{{ ExpandedForm.amountNoCurrency('amount', []) }}
|
||||||
|
|
||||||
|
{# source account if withdrawal, or if transfer: #}
|
||||||
|
{{ ExpandedForm.assetAccountList('source_account_id', null, {label: trans('form.asset_source_account')}) }}
|
||||||
|
|
||||||
|
{# source account name for deposits: #}
|
||||||
|
{{ ExpandedForm.text('source_account_name', null, {label: trans('form.revenue_account')}) }}
|
||||||
|
|
||||||
|
{# destination if deposit or transfer: #}
|
||||||
|
{{ ExpandedForm.assetAccountList('destination_account_id', null, {label: trans('form.asset_destination_account')} ) }}
|
||||||
|
|
||||||
|
{# destination account name for withdrawals #}
|
||||||
|
{{ ExpandedForm.text('destination_account_name', null, {label: trans('form.expense_account')}) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="box box-primary">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'expected_repetitions'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
Here.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'optional_for_recurring'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
{{ ExpandedForm.textarea('recurring_description') }}
|
||||||
|
|
||||||
|
{{ ExpandedForm.checkbox('active',1) }}
|
||||||
|
|
||||||
|
{{ ExpandedForm.checkbox('apply_rules',1) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box box-primary">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'optional_for_transaction'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
{# transaction information (optional) #}
|
||||||
|
{{ ExpandedForm.currencyList('foreign_currency_id', defaultCurrency.id) }}
|
||||||
|
{{ ExpandedForm.amountNoCurrency('foreign_amount', []) }}
|
||||||
|
|
||||||
|
{# BUDGET ONLY WHEN CREATING A WITHDRAWAL #}
|
||||||
|
{% if budgets|length > 1 %}
|
||||||
|
{{ ExpandedForm.select('budget_id', budgets, null) }}
|
||||||
|
{% else %}
|
||||||
|
{{ ExpandedForm.select('budget_id', budgets, null, {helpText: trans('firefly.no_budget_pointer')}) }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# CATEGORY ALWAYS #}
|
||||||
|
{{ ExpandedForm.text('category') }}
|
||||||
|
|
||||||
|
{# TAGS #}
|
||||||
|
{{ ExpandedForm.text('tags') }}
|
||||||
|
|
||||||
|
{# RELATE THIS TRANSFER TO A PIGGY BANK #}
|
||||||
|
{{ ExpandedForm.select('piggy_bank_id', [], '0') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">{{ 'options'|_ }}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
{{ ExpandedForm.optionsList('create','recurrence') }}
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
<button type="submit" class="btn pull-right btn-success">
|
||||||
|
{{ ('store_new_recurrence')|_ }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
{% block scripts %}
|
||||||
|
<script type="text/javascript" src="js/lib/modernizr-custom.js?v={{ FF_VERSION }}"></script>
|
||||||
|
<script type="text/javascript" src="js/lib/bootstrap3-typeahead.min.js?v={{ FF_VERSION }}"></script>
|
||||||
|
<script type="text/javascript" src="js/lib/bootstrap-tagsinput.min.js?v={{ FF_VERSION }}"></script>
|
||||||
|
<script type="text/javascript" src="js/lib/jquery-ui.min.js?v={{ FF_VERSION }}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
var transactionType = "{{ preFilled.transaction_type }}";
|
||||||
|
var suggestUri = "{{ route('recurring.suggest') }}";
|
||||||
|
</script>
|
||||||
|
<script type="text/javascript" src="js/ff/recurring/create.js?v={{ FF_VERSION }}"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
<link href="css/bootstrap-tagsinput.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all">
|
||||||
|
<link href="css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all">
|
||||||
|
<link href="css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all">
|
||||||
|
{% endblock %}
|
120
resources/views/recurring/index.twig
Normal file
120
resources/views/recurring/index.twig
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
{% extends "./layout/default" %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ Breadcrumbs.render(Route.getCurrentRoute.getName) }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<!-- block with list of recurring transaction -->
|
||||||
|
{% if recurring|length > 0 %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">
|
||||||
|
{{ 'recurrences'|_ }}
|
||||||
|
</h3>
|
||||||
|
<div class="box-tools pull-right">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown"><i class="fa fa-ellipsis-v"></i></button>
|
||||||
|
<ul class="dropdown-menu" role="menu">
|
||||||
|
<li><a href="{{ route('recurring.create') }}"><i class="fa fa-plus fa-fw"></i> {{ ('make_new_recurring')|_ }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box-body table-responsive no-padding">
|
||||||
|
<div style="padding:8px;">
|
||||||
|
<a href="{{ route('recurring.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ ('make_new_recurring')|_ }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- list of recurring here -->
|
||||||
|
<div style="padding-left:8px;">
|
||||||
|
{{ recurring.render|raw }}
|
||||||
|
</div>
|
||||||
|
<table class="table table-hover sortable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="hidden-sm hidden-xs" data-defaultsort="disabled"> </th>
|
||||||
|
<th data-defaultsign="az">{{ trans('list.title') }}</th>
|
||||||
|
<th data-defaultsign="_19">{{ trans('list.transaction_s') }}</th>
|
||||||
|
<th data-defaultsort="disabled">{{ trans('list.repetitions') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for rt in recurring %}
|
||||||
|
<tr>
|
||||||
|
<td class="hidden-sm hidden-xs">
|
||||||
|
<div class="btn-group btn-group-xs edit_tr_buttons">
|
||||||
|
<a class="btn btn-default btn-xs" title="{{ 'edit'|_ }}" href="{{ route('recurring.edit',rt.id) }}"><i
|
||||||
|
class="fa fa-fw fa-pencil"></i></a>
|
||||||
|
<a class="btn btn-danger btn-xs" title="{{ 'delete'|_ }}" href="{{ route('recurring.delete',rt.id) }}"><i
|
||||||
|
class="fa fa-fw fa-trash-o"></i></a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td data-value="{{ rt.title }}">
|
||||||
|
{{ rt.transaction_type|_ }}:
|
||||||
|
|
||||||
|
<a href="{{ route('recurring.show',rt.id) }}">{{ rt.title }}</a>
|
||||||
|
{% if rt.description|length > 0 %}
|
||||||
|
<small><br>{{ rt.description }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td data-value="0">
|
||||||
|
<ol>
|
||||||
|
{% for rtt in rt.transactions %}
|
||||||
|
<li>
|
||||||
|
{# normal amount + comma#}
|
||||||
|
{{ formatAmountBySymbol(rtt['amount'],rtt['currency_symbol'],rtt['currency_dp']) }}{% if rtt['foreign_amount'] == null %},{% endif %}
|
||||||
|
|
||||||
|
{# foreign amount + comma #}
|
||||||
|
{% if null != rtt['foreign_amount'] %}
|
||||||
|
({{ formatAmountBySymbol(rtt['foreign_amount'],rtt['foreign_currency_symbol'],rtt['foreign_currency_dp']) }}),
|
||||||
|
{% endif %}
|
||||||
|
<a href="{{ route('accounts.show', rtt['source_account_id']) }}">{{ rtt['source_account_name'] }}</a>
|
||||||
|
→
|
||||||
|
<a href="{{ route('accounts.show', rtt['destination_account_id']) }}">{{ rtt['destination_account_name'] }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<ul>
|
||||||
|
{% for rep in rt.repetitions %}
|
||||||
|
<li>{{ rep.description }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div style="padding-left:8px;">
|
||||||
|
{{ recurring.render|raw }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
<a href="{{ route('recurring.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ ('make_new_recurring')|_ }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% if recurring|length == 0 and page == 1 %}
|
||||||
|
{% include 'partials.empty' with {what: 'default', type: 'recurring',route: route('recurring.create')} %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
<link rel="stylesheet" href="css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" media="all"/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script type="text/javascript" src="js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}"></script>
|
||||||
|
{% endblock %}
|
190
resources/views/recurring/show.twig
Normal file
190
resources/views/recurring/show.twig
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
{% extends "./layout/default" %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
{{ Breadcrumbs.render(Route.getCurrentRoute.getName, recurrence) }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<!-- basic info -->
|
||||||
|
<div class="col-lg-8 col-md-12 col-sm-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">
|
||||||
|
{{ array.title }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
<p><em>{{ array.description }}</em></p>
|
||||||
|
<ul>
|
||||||
|
{% for rep in array.repetitions %}
|
||||||
|
<li>{{ rep.description }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{{ route('recurring.edit', [array.id]) }}" class="btn btn-sm btn-default"><i class="fa fa-pencil"></i> {{ 'edit'|_ }}</a>
|
||||||
|
<a href="{{ route('recurring.delete', [array.id]) }}" class="btn btn-sm btn-danger">{{ 'delete'|_ }} <i class="fa fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- next and previous repetitions -->
|
||||||
|
<div class="col-lg-4 col-md-12 col-sm-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">
|
||||||
|
{{ 'expected_transactions'|_ }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{% for rep in array.repetitions %}
|
||||||
|
<li>{{ rep.description }}
|
||||||
|
<ul>
|
||||||
|
{% for occ in rep.occurrences %}
|
||||||
|
<li>{{ occ.formatLocalized(trans('config.month_and_date_day')) }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer">
|
||||||
|
<small>
|
||||||
|
<em>{{ 'warning_duplicates_repetitions'|_ }}</em>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<!-- transactions -->
|
||||||
|
<div class="col-lg-8 col-md-12 col-sm-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">
|
||||||
|
{{ 'transaction_data'|_ }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body no-padding">
|
||||||
|
<table class="table table-hover sortable">
|
||||||
|
<thead>
|
||||||
|
<th data-defaultsign="az">{{ trans('list.source') }}</th>
|
||||||
|
<th data-defaultsign="az">{{ trans('list.destination') }}</th>
|
||||||
|
<th data-defaultsign="_19">{{ trans('list.amount') }}</th>
|
||||||
|
<th data-defaultsign="az">{{ trans('list.category') }}</th>
|
||||||
|
<th data-defaultsign="az">{{ trans('list.budget') }}</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for transaction in array.transactions %}
|
||||||
|
<tr>
|
||||||
|
<td data-value="{{ transaction.source_account_name }}">
|
||||||
|
<a href="{{ route('accounts.show', [transaction.source_account_id]) }}">{{ transaction.source_account_name }}</a>
|
||||||
|
</td>
|
||||||
|
<td data-value="{{ transaction.destination_account_name }}">
|
||||||
|
<a href="{{ route('accounts.show', [transaction.destination_account_id]) }}">{{ transaction.destination_account_name }}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ formatAmountBySymbol(transaction.amount,transaction.currency_symbol,transaction.currency_dp) }}
|
||||||
|
{% if null != transaction.foreign_amount %}
|
||||||
|
({{ formatAmountBySymbol(transaction.foreign_amount,transaction.foreign_currency_symbol,transaction.foreign_currency_dp) }})
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td data-value="{% for meta in transaction.meta %}{% if meta.name == 'category_name' %}{{ meta.category_id }}{% endif %}{% endfor %}">
|
||||||
|
{% for meta in transaction.meta %}
|
||||||
|
{% if meta.name == 'category_name' %}
|
||||||
|
<a href="{{ route('categories.show', [meta.category_id]) }}">
|
||||||
|
{{ meta.category_name }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td data-value="{% for meta in transaction.meta %}{% if meta.name == 'budget_id' %}{{ meta.budget_id }}{% endif %}{% endfor %}">
|
||||||
|
{% for meta in transaction.meta %}
|
||||||
|
{% if meta.name == 'budget_id' %}
|
||||||
|
<a href="{{ route('budgets.show', [meta.budget_id]) }}">
|
||||||
|
{{ meta.budget_name }}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- meta data -->
|
||||||
|
{% if array.meta|length > 0 %}
|
||||||
|
<div class="col-lg-4 col-md-12 col-sm-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">
|
||||||
|
{{ 'meta_data'|_ }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body no-padding">
|
||||||
|
<table class="table table-hover sortable">
|
||||||
|
<thead>
|
||||||
|
<th style="width:30%;" data-defaultsign="az">{{ trans('list.field') }}</th>
|
||||||
|
<th data-defaultsign="az">{{ trans('list.value') }}</th>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for meta in array.meta %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ trans('firefly.recurring_meta_field_'~meta.name) }}</td>
|
||||||
|
<td>
|
||||||
|
{% if meta.name == 'tags' %}
|
||||||
|
{% for tag in meta.tags %}
|
||||||
|
<span class="label label-info">{{ tag }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if meta.name == 'notes' %}
|
||||||
|
{{ meta.value|markdown }}
|
||||||
|
{% endif %}
|
||||||
|
{% if meta.name == 'bill_id' %}
|
||||||
|
<a href="{{ route('bills.show', [meta.bill_id]) }}">{{ meta.bill_name }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if meta.name == 'piggy_bank_id' %}
|
||||||
|
<a href="{{ route('piggy-banks.show', [meta.piggy_bank_id]) }}">{{ meta.piggy_bank_name }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<!-- meta data -->
|
||||||
|
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||||
|
<div class="box">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">
|
||||||
|
{{ 'transactions'|_ }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="box-body">
|
||||||
|
Bla bla
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block styles %}
|
||||||
|
<link rel="stylesheet" href="css/bootstrap-sortable.css?v={{ FF_VERSION }}" type="text/css" media="all"/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
<script type="text/javascript" src="js/lib/bootstrap-sortable.js?v={{ FF_VERSION }}"></script>
|
||||||
|
{% endblock %}
|
@ -33,6 +33,7 @@ use FireflyIII\Models\Category;
|
|||||||
use FireflyIII\Models\ImportJob;
|
use FireflyIII\Models\ImportJob;
|
||||||
use FireflyIII\Models\LinkType;
|
use FireflyIII\Models\LinkType;
|
||||||
use FireflyIII\Models\PiggyBank;
|
use FireflyIII\Models\PiggyBank;
|
||||||
|
use FireflyIII\Models\Recurrence;
|
||||||
use FireflyIII\Models\Rule;
|
use FireflyIII\Models\Rule;
|
||||||
use FireflyIII\Models\RuleGroup;
|
use FireflyIII\Models\RuleGroup;
|
||||||
use FireflyIII\Models\Tag;
|
use FireflyIII\Models\Tag;
|
||||||
@ -761,6 +762,30 @@ try {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Recurring transactions controller:
|
||||||
|
Breadcrumbs::register(
|
||||||
|
'recurring.index',
|
||||||
|
function (BreadCrumbsGenerator $breadcrumbs) {
|
||||||
|
$breadcrumbs->parent('home');
|
||||||
|
$breadcrumbs->push(trans('firefly.recurrences'), route('recurring.index'));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
Breadcrumbs::register(
|
||||||
|
'recurring.show',
|
||||||
|
function (BreadCrumbsGenerator $breadcrumbs, Recurrence $recurrence) {
|
||||||
|
$breadcrumbs->parent('recurring.index');
|
||||||
|
$breadcrumbs->push($recurrence->title, route('recurring.show', [$recurrence->id]));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
Breadcrumbs::register(
|
||||||
|
'recurring.create',
|
||||||
|
function (BreadCrumbsGenerator $breadcrumbs) {
|
||||||
|
$breadcrumbs->parent('recurring.index');
|
||||||
|
$breadcrumbs->push(trans('firefly.create_new_recurrence'), route('recurring.create'));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Rules
|
// Rules
|
||||||
Breadcrumbs::register(
|
Breadcrumbs::register(
|
||||||
'rules.index',
|
'rules.index',
|
||||||
|
@ -466,28 +466,6 @@ Route::group(
|
|||||||
|
|
||||||
// download config:
|
// download config:
|
||||||
Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'job.download']);
|
Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'job.download']);
|
||||||
|
|
||||||
// import method prerequisites:
|
|
||||||
#
|
|
||||||
#
|
|
||||||
#Route::get('reset/{bank}', ['uses' => 'Import\IndexController@reset', 'as' => 'reset']);
|
|
||||||
|
|
||||||
// create the job:
|
|
||||||
#Route::get('create/{bank}', ['uses' => 'Import\IndexController@create', 'as' => 'create-job']);
|
|
||||||
|
|
||||||
// configure the job:
|
|
||||||
|
|
||||||
#Route::post('configure/{importJob}', ['uses' => 'Import\ConfigurationController@post', 'as' => 'configure.post']);
|
|
||||||
|
|
||||||
// get status of any job:
|
|
||||||
#Route::get('status/{importJob}', ['uses' => 'Import\StatusController@index', 'as' => 'status']);
|
|
||||||
#Route::get('json/{importJob}', ['uses' => 'Import\StatusController@json', 'as' => 'status.json']);
|
|
||||||
|
|
||||||
// start a job
|
|
||||||
#Route::any('start/{importJob}', ['uses' => 'Import\IndexController@start', 'as' => 'start']);
|
|
||||||
|
|
||||||
// download config
|
|
||||||
#Route::get('download/{importJob}', ['uses' => 'Import\IndexController@download', 'as' => 'download']);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -632,6 +610,23 @@ Route::group(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recurring Transactions Controller
|
||||||
|
*/
|
||||||
|
Route::group(
|
||||||
|
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Recurring', 'prefix' => 'recurring', 'as' => 'recurring.'], function () {
|
||||||
|
|
||||||
|
Route::get('', ['uses' => 'IndexController@index', 'as' => 'index']);
|
||||||
|
Route::get('suggest', ['uses' => 'IndexController@suggest', 'as' => 'suggest']);
|
||||||
|
Route::get('show/{recurrence}', ['uses' => 'IndexController@show', 'as' => 'show']);
|
||||||
|
Route::get('create', ['uses' => 'CreateController@create', 'as' => 'create']);
|
||||||
|
Route::get('edit/{recurrence}', ['uses' => 'EditController@edit', 'as' => 'edit']);
|
||||||
|
Route::get('delete/{recurrence}', ['uses' => 'DeleteController@delete', 'as' => 'delete']);
|
||||||
|
|
||||||
|
Route::post('store', ['uses' => 'CreateController@store', 'as' => 'store']);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Report Controller
|
* Report Controller
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user