Varioux fixes and cleaning up.

This commit is contained in:
James Cole 2014-09-28 08:47:51 +02:00
parent 9015d6ca16
commit aa9eb8ca64
15 changed files with 949 additions and 628 deletions

View File

@ -1,9 +1,6 @@
<?php <?php
use Firefly\Storage\Account\AccountRepositoryInterface as ARI; use Firefly\Helper\Controllers\JsonInterface as JI;
use Firefly\Storage\Budget\BudgetRepositoryInterface as Bud;
use Firefly\Storage\Category\CategoryRepositoryInterface as Cat;
use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as TJRI;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use LaravelBook\Ardent\Builder; use LaravelBook\Ardent\Builder;
@ -14,226 +11,60 @@ use LaravelBook\Ardent\Builder;
*/ */
class JsonController extends BaseController class JsonController extends BaseController
{ {
protected $_accounts; /** @var \Firefly\Helper\Controllers\JsonInterface $helper */
protected $_categories; protected $helper;
protected $_budgets;
/** @var TJRI $_journals */
protected $_journals;
/** public function __construct(JI $helper)
* @param ARI $accounts
* @param Cat $categories
* @param Bud $budgets
* @param TJRI $journals
*/
public function __construct(ARI $accounts, Cat $categories, Bud $budgets, TJRI $journals)
{ {
$this->_accounts = $accounts; $this->helper = $helper;
$this->_categories = $categories;
$this->_budgets = $budgets;
$this->_journals = $journals;
} }
/** /**
* Returns a list of categories.
* *
* @return \Illuminate\Http\JsonResponse
*/ */
public function revenue() public function categories()
{ {
$parameters = $this->_datatableParameters(); /** @var \Firefly\Storage\Category\EloquentCategoryRepository $categories */
$parameters['transactionTypes'] = ['Deposit']; $categories = App::make('Firefly\Storage\Category\CategoryRepositoryInterface');
$parameters['amount'] = 'positive'; $list = $categories->get();
$return = [];
$query = $this->_datatableQuery($parameters); foreach ($list as $entry) {
$resultSet = $this->_datatableResultset($parameters, $query); $return[] = $entry->name;
/*
* Build return data:
*/
if (Input::get('debug') == 'true') {
echo '<pre>';
print_r($parameters);
echo '<hr>';
print_r($resultSet);
return '';
} else {
return Response::json($resultSet);
} }
return Response::json($return);
} }
/** /**
* Returns a JSON list of all beneficiaries.
* *
* @return \Illuminate\Http\JsonResponse
*/ */
public function transfers() public function expenseAccounts()
{ {
$parameters = $this->_datatableParameters(); /** @var \Firefly\Storage\Account\EloquentAccountRepository $accounts */
$parameters['transactionTypes'] = ['Transfer']; $accounts = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$parameters['amount'] = 'positive'; $list = $accounts->getOfTypes(['Expense account', 'Beneficiary account']);
$return = [];
$query = $this->_datatableQuery($parameters); foreach ($list as $entry) {
$resultSet = $this->_datatableResultset($parameters, $query); $return[] = $entry->name;
/*
* Build return data:
*/
if (Input::get('debug') == 'true') {
echo '<pre>';
print_r($parameters);
echo '<hr>';
print_r($resultSet);
return '';
} else {
return Response::json($resultSet);
} }
return Response::json($return);
} }
/**
* @return array
*/
protected function _datatableParameters()
{
/*
* Process all parameters!
*/
$parameters = [
'start' => intval(Input::get('start')),
'length' => intval(Input::get('length')) < 0 ? 100000 : intval(Input::get('length')),
'draw' => intval(Input::get('draw')),
];
/*
* Columns:
*/
if (!is_null(Input::get('columns')) && is_array(Input::get('columns'))) {
foreach (Input::get('columns') as $column) {
$parameters['columns'][] = [
'data' => $column['data'],
'name' => $column['name'],
'searchable' => $column['searchable'] == 'true' ? true : false,
'orderable' => $column['orderable'] == 'true' ? true : false,
'search' => [
'value' => $column['search']['value'],
'regex' => $column['search']['regex'] == 'true' ? true : false,
]
];
}
}
/*
* Sorting.
*/
$parameters['orderOnAccount'] = false;
if (!is_null(Input::get('order')) && is_array(Input::get('order'))) {
foreach (Input::get('order') as $order) {
$columnIndex = intval($order['column']);
$columnName = $parameters['columns'][$columnIndex]['name'];
$parameters['order'][] = [
'name' => $columnName,
'dir' => strtoupper($order['dir'])
];
if ($columnName == 'to' || $columnName == 'from') {
$parameters['orderOnAccount'] = true;
}
}
}
/*
* Search parameters:
*/
if (!is_null(Input::get('search')) && is_array(Input::get('search'))) {
$search = Input::get('search');
$parameters['search'] = [
'value' => $search['value'],
'regex' => $search['regex'] == 'true' ? true : false
];
}
return $parameters;
}
/**
* @param array $parameters
*
* @return Builder
*/
protected function _datatableQuery(array $parameters)
{
/*
* We need the following vars to fine tune the query:
*/
if ($parameters['amount'] == 'negative') {
$operator = '<';
$operatorNegated = '>';
$function = 'lessThan';
} else {
$operator = '>';
$operatorNegated = '<';
$function = 'moreThan';
}
/*
* Build query:
*/
$query = \TransactionJournal::transactionTypes($parameters['transactionTypes'])->withRelevantData();
$query->where('completed',1);
/*
* This is complex. Join `transactions` twice, once for the "to" account and once for the
* "from" account. Then get the amount from one of these (depends on type).
*
* Only need to do this when there's a sort order for "from" or "to".
*
* Also need the table prefix for this to work.
*/
if ($parameters['orderOnAccount'] === true) {
$connection = \Config::get('database.default');
$prefix = \Config::get('database.connections.' . $connection . '.prefix');
// left join first table for "from" account:
$query->leftJoin(
'transactions AS ' . $prefix . 't1', function ($join) use ($operator) {
$join->on('t1.transaction_journal_id', '=', 'transaction_journals.id')
->on('t1.amount', $operator, \DB::Raw(0));
}
);
// left join second table for "to" account:
$query->leftJoin(
'transactions AS ' . $prefix . 't2', function ($join) use ($operatorNegated) {
$join->on('t2.transaction_journal_id', '=', 'transaction_journals.id')
->on('t2.amount', $operatorNegated, \DB::Raw(0));
}
);
// also join accounts twice to get the account's name, which we need for sorting.
$query->leftJoin('accounts as ' . $prefix . 'a1', 'a1.id', '=', 't1.account_id');
$query->leftJoin('accounts as ' . $prefix . 'a2', 'a2.id', '=', 't2.account_id');
} else {
// less complex
$query->$function(0);
}
/*
* Add sort parameters to query:
*/
if (isset($parameters['order']) && count($parameters['order']) > 0) {
foreach ($parameters['order'] as $order) {
$query->orderBy($order['name'], $order['dir']);
}
} else {
$query->defaultSorting();
}
return $query;
}
/** /**
* Returns a list of transactions, expenses only, using the given parameters. * Returns a list of transactions, expenses only, using the given parameters.
*
* @return \Illuminate\Http\JsonResponse
*/ */
public function expenses() public function expenses()
{ {
@ -241,7 +72,7 @@ class JsonController extends BaseController
/* /*
* Gets most parameters from the Input::all() array: * Gets most parameters from the Input::all() array:
*/ */
$parameters = $this->_datatableParameters(); $parameters = $this->helper->dataTableParameters();
/* /*
* Add some more parameters to fine tune the query: * Add some more parameters to fine tune the query:
@ -252,137 +83,58 @@ class JsonController extends BaseController
/* /*
* Get the query: * Get the query:
*/ */
$query = $this->_datatableQuery($parameters); $query = $this->helper->journalQuery($parameters);
/* /*
* Build result set: * Build result set:
*/ */
$resultSet = $this->_datatableResultset($parameters, $query); $resultSet = $this->helper->journalDataset($parameters, $query);
/* /*
* Build return data: * Build return data:
*/ */
return Response::json($resultSet);
if (Input::get('debug') == 'true') {
echo '<pre>';
print_r($parameters);
echo '<hr>';
print_r($resultSet);
return '';
} else {
return Response::json($resultSet);
}
} }
protected function _datatableResultset(array $parameters, Builder $query) public function recurring()
{ {
/* $parameters = $this->helper->dataTableParameters();
* Count query: $query = $this->helper->recurringTransactionsQuery($parameters);
*/ $resultSet = $this->helper->recurringTransactionsDataset($parameters, $query);
$count = $query->count(); return Response::json($resultSet);
/*
* Update the selection:
*/
$query->take($parameters['length']);
$query->skip($parameters['start']);
/*
* Input search parameters:
*/
$filtered = $count;
if(strlen($parameters['search']['value']) > 0) {
$query->where('transaction_journals.description','LIKE','%'.e($parameters['search']['value']).'%');
$filtered = $query->count();
}
/*
* Build return array:
*/
$data = [
'draw' => $parameters['draw'],
'recordsTotal' => $count,
'recordsFiltered' => $filtered,
'data' => [],
];
/*
* Get paginated result set:
*/
if ($parameters['orderOnAccount'] === true) {
/** @var Collection $set */
$set = $query->get(
[
'transaction_journals.*',
't1.amount',
't1.account_id AS from_id',
'a1.name AS from',
't2.account_id AS to_id',
'a2.name AS to',
]
);
} else {
/** @var Collection $set */
$set = $query->get(
[
'transaction_journals.*',
'transactions.amount',
]
);
}
/*
* Loop set and create entries to return.
*/
foreach ($set as $entry) {
$from = $entry->transactions[0]->account;
$to = $entry->transactions[1]->account;
$data['data'][] = [
'date' => $entry->date->format('j F Y'),
'description' => [
'description' => $entry->description,
'url' => route('transactions.show', $entry->id)
],
'amount' => floatval($entry->amount),
'from' => ['name' => $from->name, 'url' => route('accounts.show', $from->id)],
'to' => ['name' => $to->name, 'url' => route('accounts.show', $to->id)],
'id' => [
'edit' => route('transactions.edit', $entry->id),
'delete' => route('transactions.delete', $entry->id)
]
];
}
return $data;
} }
/** /**
* Returns a JSON list of all beneficiaries. * @return \Illuminate\Http\JsonResponse|string
*/ */
public function expenseAccounts() public function revenue()
{ {
$list = $this->_accounts->getOfTypes(['Expense account', 'Beneficiary account']); $parameters = $this->helper->dataTableParameters();
$return = []; $parameters['transactionTypes'] = ['Deposit'];
foreach ($list as $entry) { $parameters['amount'] = 'positive';
$return[] = $entry->name;
}
return Response::json($return); $query = $this->helper->journalQuery($parameters);
$resultSet = $this->helper->journalDataset($parameters, $query);
/*
* Build return data:
*/
return Response::json($resultSet);
} }
/** /**
* Returns a JSON list of all revenue accounts. * Returns a JSON list of all revenue accounts.
*
* @return \Illuminate\Http\JsonResponse
*/ */
public function revenueAccounts() public function revenueAccounts()
{ {
$list = $this->_accounts->getOfTypes(['Revenue account']); /** @var \Firefly\Storage\Account\EloquentAccountRepository $accounts */
$return = []; $accounts = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$list = $accounts->getOfTypes(['Revenue account']);
$return = [];
foreach ($list as $entry) { foreach ($list as $entry) {
$return[] = $entry->name; $return[] = $entry->name;
} }
@ -392,18 +144,23 @@ class JsonController extends BaseController
} }
/** /**
* Responds some JSON for typeahead fields. * Returns a list of all transfers.
*
* @return \Illuminate\Http\JsonResponse
*/ */
public function categories() public function transfers()
{ {
$list = $this->_categories->get(); $parameters = $this->helper->dataTableParameters();
$return = []; $parameters['transactionTypes'] = ['Transfer'];
foreach ($list as $entry) { $parameters['amount'] = 'positive';
$return[] = $entry->name;
}
return Response::json($return); $query = $this->helper->journalQuery($parameters);
$resultSet = $this->helper->journalDataset($parameters, $query);
/*
* Build return data:
*/
return Response::json($resultSet);
} }
} }

View File

@ -1,5 +1,6 @@
<?php <?php
use Firefly\Exception\FireflyException;
use Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface as RTR; use Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface as RTR;
/** /**
@ -51,7 +52,7 @@ class RecurringController extends BaseController
*/ */
public function destroy(RecurringTransaction $recurringTransaction) public function destroy(RecurringTransaction $recurringTransaction)
{ {
Event::fire('recurring.destroy', [$recurringTransaction]); //Event::fire('recurring.destroy', [$recurringTransaction]);
$result = $this->_repository->destroy($recurringTransaction); $result = $this->_repository->destroy($recurringTransaction);
if ($result === true) { if ($result === true) {
Session::flash('success', 'The recurring transaction was deleted.'); Session::flash('success', 'The recurring transaction was deleted.');
@ -84,11 +85,7 @@ class RecurringController extends BaseController
*/ */
public function index() public function index()
{ {
$list = $this->_repository->get(); return View::make('recurring.index');
return View::make('recurring.index')->with('list', $list);
} }
/** /**
@ -106,10 +103,18 @@ class RecurringController extends BaseController
*/ */
public function store() public function store()
{ {
switch (Input::get('post_submit_action')) {
default:
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
break;
}
$recurringTransaction = $this->_repository->store(Input::all()); $recurringTransaction = $this->_repository->store(Input::all());
if ($recurringTransaction->validate()) {
if ($recurringTransaction->errors()->count() == 0) {
Session::flash('success', 'Recurring transaction "' . $recurringTransaction->name . '" saved!'); Session::flash('success', 'Recurring transaction "' . $recurringTransaction->name . '" saved!');
Event::fire('recurring.store', [$recurringTransaction]); //Event::fire('recurring.store', [$recurringTransaction]);
if (Input::get('create') == '1') { if (Input::get('create') == '1') {
return Redirect::route('recurring.create')->withInput(); return Redirect::route('recurring.create')->withInput();
} else { } else {
@ -135,7 +140,7 @@ class RecurringController extends BaseController
$recurringTransaction = $this->_repository->update($recurringTransaction, Input::all()); $recurringTransaction = $this->_repository->update($recurringTransaction, Input::all());
if ($recurringTransaction->errors()->count() == 0) { if ($recurringTransaction->errors()->count() == 0) {
Session::flash('success', 'The recurring transaction has been updated.'); Session::flash('success', 'The recurring transaction has been updated.');
Event::fire('recurring.update', [$recurringTransaction]); //Event::fire('recurring.update', [$recurringTransaction]);
return Redirect::route('recurring.index'); return Redirect::route('recurring.index');
} else { } else {

View File

@ -0,0 +1,369 @@
<?php
/**
* Created by PhpStorm.
* User: sander
* Date: 27/09/14
* Time: 07:39
*/
namespace Firefly\Helper\Controllers;
use LaravelBook\Ardent\Builder;
/**
* Class Json
*
* @package Firefly\Helper\Controllers
*/
class Json implements JsonInterface
{
/**
* Grabs all the parameters entered by the DataTables JQuery plugin and creates
* a nice array to be used by the other methods. It's also cleaning up and what-not.
*
* @return array
*/
public function dataTableParameters()
{
/*
* Process all parameters!
*/
if (intval(\Input::get('length')) < 0) {
$length = 10000; // we get them all if no length is defined.
} else {
$length = intval(\Input::get('length'));
}
$parameters = [
'start' => intval(\Input::get('start')),
'length' => $length,
'draw' => intval(\Input::get('draw')),
];
/*
* Columns:
*/
if (!is_null(\Input::get('columns')) && is_array(\Input::get('columns'))) {
foreach (\Input::get('columns') as $column) {
$parameters['columns'][] = [
'data' => $column['data'],
'name' => $column['name'],
'searchable' => $column['searchable'] == 'true' ? true : false,
'orderable' => $column['orderable'] == 'true' ? true : false,
'search' => [
'value' => $column['search']['value'],
'regex' => $column['search']['regex'] == 'true' ? true : false,
]
];
}
}
/*
* Sorting.
*/
$parameters['orderOnAccount'] = false;
if (!is_null(\Input::get('order')) && is_array(\Input::get('order'))) {
foreach (\Input::get('order') as $order) {
$columnIndex = intval($order['column']);
$columnName = $parameters['columns'][$columnIndex]['name'];
$parameters['order'][] = [
'name' => $columnName,
'dir' => strtoupper($order['dir'])
];
if ($columnName == 'to' || $columnName == 'from') {
$parameters['orderOnAccount'] = true;
}
}
}
/*
* Search parameters:
*/
$parameters['search'] = [
'value' => '',
'regex' => false
];
if (!is_null(\Input::get('search')) && is_array(\Input::get('search'))) {
$search = \Input::get('search');
$parameters['search'] = [
'value' => $search['value'],
'regex' => $search['regex'] == 'true' ? true : false
];
}
return $parameters;
}
/**
* Do some sorting, counting and ordering on the query and return a nicely formatted array
* that can be used by the DataTables JQuery plugin.
*
* @param array $parameters
* @param Builder $query
*
* @return array
*/
public function journalDataset(array $parameters, Builder $query)
{
/*
* Count query:
*/
$count = $query->count();
/*
* Update the selection:
*/
$query->take($parameters['length']);
if ($parameters['start'] > 0) {
$query->skip($parameters['start']);
}
/*
* Input search parameters:
*/
$filtered = $count;
if (strlen($parameters['search']['value']) > 0) {
$query->where('transaction_journals.description', 'LIKE', '%' . e($parameters['search']['value']) . '%');
$filtered = $query->count();
}
/*
* Build return array:
*/
$data = [
'draw' => $parameters['draw'],
'recordsTotal' => $count,
'recordsFiltered' => $filtered,
'data' => [],
];
/*
* Get paginated result set:
*/
if ($parameters['orderOnAccount'] === true) {
/** @var Collection $set */
$set = $query->get(
[
'transaction_journals.*',
't1.amount',
't1.account_id AS from_id',
'a1.name AS from',
't2.account_id AS to_id',
'a2.name AS to',
]
);
} else {
/** @var Collection $set */
$set = $query->get(
[
'transaction_journals.*',
'transactions.amount',
]
);
}
/*
* Loop set and create entries to return.
*/
foreach ($set as $entry) {
$from = $entry->transactions[0]->account;
$to = $entry->transactions[1]->account;
$data['data'][] = [
'date' => $entry->date->format('j F Y'),
'description' => [
'description' => $entry->description,
'url' => route('transactions.show', $entry->id)
],
'amount' => floatval($entry->amount),
'from' => ['name' => $from->name, 'url' => route('accounts.show', $from->id)],
'to' => ['name' => $to->name, 'url' => route('accounts.show', $to->id)],
'id' => [
'edit' => route('transactions.edit', $entry->id),
'delete' => route('transactions.delete', $entry->id)
]
];
}
return $data;
}
/**
* Builds most of the query required to grab transaction journals from the database.
* This is useful because all three pages showing different kinds of transactions use
* the exact same query with only slight differences.
*
* @param array $parameters
*
* @return Builder
*/
public function journalQuery(array $parameters)
{
/*
* We need the following vars to fine tune the query:
*/
if ($parameters['amount'] == 'negative') {
$operator = '<';
$operatorNegated = '>';
$function = 'lessThan';
} else {
$operator = '>';
$operatorNegated = '<';
$function = 'moreThan';
}
/*
* Build query:
*/
$query = \TransactionJournal::transactionTypes($parameters['transactionTypes'])->withRelevantData();
$query->where('user_id', \Auth::user()->id);
$query->where('completed', 1);
/*
* This is complex. Join `transactions` twice, once for the "to" account and once for the
* "from" account. Then get the amount from one of these (depends on type).
*
* Only need to do this when there's a sort order for "from" or "to".
*
* Also need the table prefix for this to work.
*/
if ($parameters['orderOnAccount'] === true) {
$connection = \Config::get('database.default');
$prefix = \Config::get('database.connections.' . $connection . '.prefix');
// left join first table for "from" account:
$query->leftJoin(
'transactions AS ' . $prefix . 't1', function ($join) use ($operator) {
$join->on('t1.transaction_journal_id', '=', 'transaction_journals.id')
->on('t1.amount', $operator, \DB::Raw(0));
}
);
// left join second table for "to" account:
$query->leftJoin(
'transactions AS ' . $prefix . 't2', function ($join) use ($operatorNegated) {
$join->on('t2.transaction_journal_id', '=', 'transaction_journals.id')
->on('t2.amount', $operatorNegated, \DB::Raw(0));
}
);
// also join accounts twice to get the account's name, which we need for sorting.
$query->leftJoin('accounts as ' . $prefix . 'a1', 'a1.id', '=', 't1.account_id');
$query->leftJoin('accounts as ' . $prefix . 'a2', 'a2.id', '=', 't2.account_id');
} else {
// less complex
$query->$function(0);
}
/*
* Add sort parameters to query:
*/
if (isset($parameters['order']) && count($parameters['order']) > 0) {
foreach ($parameters['order'] as $order) {
$query->orderBy($order['name'], $order['dir']);
}
} else {
$query->defaultSorting();
}
return $query;
}
/**
* Do some sorting, counting and ordering on the query and return a nicely formatted array
* that can be used by the DataTables JQuery plugin.
*
* @param array $parameters
* @param Builder $query
*
* @return array
*/
public function recurringTransactionsDataset(array $parameters, Builder $query)
{
/*
* Count query:
*/
$count = $query->count();
/*
* Update the selection:
*/
$query->take($parameters['length']);
if ($parameters['start'] > 0) {
$query->skip($parameters['start']);
}
/*
* Input search parameters:
*/
$filtered = $count;
if (strlen($parameters['search']['value']) > 0) {
$query->where('recurring_transactions.description', 'LIKE', '%' . e($parameters['search']['value']) . '%');
$filtered = $query->count();
}
/*
* Build return array:
*/
$data = [
'draw' => $parameters['draw'],
'recordsTotal' => $count,
'recordsFiltered' => $filtered,
'data' => [],
];
/*
* Get paginated result set:
*/
/** @var Collection $set */
$set = $query->get(
[
'recurring_transactions.*',
]
);
/*
* Loop set and create entries to return.
*/
foreach ($set as $entry) {
$data['data'][] = [
'name' => ['name' => $entry->name,'url' => route('recurring.show',$entry->id)],
'match' => explode(' ',$entry->match),
'amount_max' => floatval($entry->amount_max),
'amount_min' => floatval($entry->amount_min),
'date' => $entry->date->format('j F Y'),
'active' => intval($entry->active),
'automatch' => intval($entry->automatch),
'repeat_freq' => $entry->repeat_freq,
'id' => [
'edit' => route('recurring.edit', $entry->id),
'delete' => route('recurring.delete', $entry->id)
]
];
}
return $data;
}
/**
* Create a query that will pick up all recurring transactions from the database.
*
* @param array $parameters
*
* @return Builder
*/
public function recurringTransactionsQuery(array $parameters)
{
$query = \RecurringTransaction::where('user_id', \Auth::user()->id);
if (isset($parameters['order']) && count($parameters['order']) > 0) {
foreach ($parameters['order'] as $order) {
$query->orderBy($order['name'], $order['dir']);
}
} else {
$query->orderBy('name', 'ASC');
}
return $query;
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace Firefly\Helper\Controllers;
use LaravelBook\Ardent\Builder;
/**
* Interface JsonInterface
*
* @package Firefly\Helper\Controllers
*/
interface JsonInterface
{
/**
* Grabs all the parameters entered by the DataTables JQuery plugin and creates
* a nice array to be used by the other methods. It's also cleaning up and what-not.
*
* @return array
*/
public function dataTableParameters();
/**
* Do some sorting, counting and ordering on the query and return a nicely formatted array
* that can be used by the DataTables JQuery plugin.
*
* @param array $parameters
* @param Builder $query
*
* @return array
*/
public function journalDataset(array $parameters, Builder $query);
/**
* Builds most of the query required to grab transaction journals from the database.
* This is useful because all three pages showing different kinds of transactions use
* the exact same query with only slight differences.
*
* @param array $parameters
*
* @return Builder
*/
public function journalQuery(array $parameters);
/**
* Do some sorting, counting and ordering on the query and return a nicely formatted array
* that can be used by the DataTables JQuery plugin.
*
* @param array $parameters
* @param Builder $query
*
* @return array
*/
public function recurringTransactionsDataset(array $parameters, Builder $query);
/**
* Create a query that will pick up all recurring transactions from the database.
*
* @param array $parameters
*
* @return Builder
*/
public function recurringTransactionsQuery(array $parameters);
}

View File

@ -247,7 +247,8 @@ class Transaction implements TransactionInterface
/* /*
* Add a custom error when they are the same. * Add a custom error when they are the same.
*/ */
if ($to->id == $from->id) { if ($to->id ==
$from->id) {
$bag = new MessageBag; $bag = new MessageBag;
$bag->add('account_from_id', 'The account from cannot be the same as the account to.'); $bag->add('account_from_id', 'The account from cannot be the same as the account to.');
return $bag; return $bag;

View File

@ -27,6 +27,11 @@ class HelperServiceProvider extends ServiceProvider
'Firefly\Helper\Controllers\Chart' 'Firefly\Helper\Controllers\Chart'
); );
$this->app->bind(
'Firefly\Helper\Controllers\JsonInterface',
'Firefly\Helper\Controllers\Json'
);
$this->app->bind( $this->app->bind(
'Firefly\Helper\Controllers\SearchInterface', 'Firefly\Helper\Controllers\SearchInterface',
'Firefly\Helper\Controllers\Search' 'Firefly\Helper\Controllers\Search'

View File

@ -24,6 +24,26 @@ class EloquentRecurringTransactionRepository implements RecurringTransactionRepo
$this->_user = \Auth::user(); $this->_user = \Auth::user();
} }
/**
* @param \RecurringTransaction $recurringTransaction
*
* @return bool|mixed
*/
public function destroy(\RecurringTransaction $recurringTransaction)
{
$recurringTransaction->delete();
return true;
}
/**
* @return mixed
*/
public function get()
{
return $this->_user->recurringtransactions()->get();
}
/** /**
* @param Job $job * @param Job $job
* @param array $payload * @param array $payload
@ -115,25 +135,43 @@ class EloquentRecurringTransactionRepository implements RecurringTransactionRepo
*/ */
public function store($data) public function store($data)
{ {
$recurringTransaction = new \RecurringTransaction; $recurringTransaction = new \RecurringTransaction(
$recurringTransaction->user()->associate($this->_user); [
$recurringTransaction->name = $data['name']; 'user_id' => $this->_user->id,
$recurringTransaction->match = join(' ', explode(',', $data['match'])); 'name' => $data['name'],
$recurringTransaction->amount_max = floatval($data['amount_max']); 'match' => join(' ', explode(',', $data['match'])),
$recurringTransaction->amount_min = floatval($data['amount_min']); 'amount_max' => floatval($data['amount_max']),
'amount_min' => floatval($data['amount_min']),
'date' => new Carbon($data['date']),
'active' => isset($data['active']) ? intval($data['active']) : 0,
'automatch' => isset($data['automatch']) ? intval($data['automatch']) : 0,
'skip' => isset($data['skip']) ? intval($data['skip']) : 0,
'repeat_freq' => $data['repeat_freq'],
]
);
// both amounts zero: // both amounts zero?:
if ($recurringTransaction->amount_max == 0 && $recurringTransaction->amount_min == 0) { if ($recurringTransaction->amount_max == 0 && $recurringTransaction->amount_min == 0) {
$recurringTransaction->errors()->add('amount_max', 'Amount max and min cannot both be zero.'); $recurringTransaction->errors()->add('amount_max', 'Amount max and min cannot both be zero.');
return $recurringTransaction; return $recurringTransaction;
} }
$recurringTransaction->date = new Carbon($data['date']); if ($recurringTransaction->amount_max < $recurringTransaction->amount_min) {
$recurringTransaction->active = isset($data['active']) ? intval($data['active']) : 0; $recurringTransaction->errors()->add('amount_max', 'Amount max must be more than amount min.');
$recurringTransaction->automatch = isset($data['automatch']) ? intval($data['automatch']) : 0; return $recurringTransaction;
$recurringTransaction->skip = isset($data['skip']) ? intval($data['skip']) : 0; }
$recurringTransaction->repeat_freq = $data['repeat_freq'];
if ($recurringTransaction->amount_min > $recurringTransaction->amount_max) {
$recurringTransaction->errors()->add('amount_max', 'Amount min must be less than amount max.');
return $recurringTransaction;
}
if($recurringTransaction->date < Carbon::now()) {
$recurringTransaction->errors()->add('date', 'Must be in the future.');
return $recurringTransaction;
}
if ($recurringTransaction->validate()) { if ($recurringTransaction->validate()) {
$recurringTransaction->save(); $recurringTransaction->save();
@ -142,26 +180,6 @@ class EloquentRecurringTransactionRepository implements RecurringTransactionRepo
return $recurringTransaction; return $recurringTransaction;
} }
/**
* @param \RecurringTransaction $recurringTransaction
*
* @return bool|mixed
*/
public function destroy(\RecurringTransaction $recurringTransaction)
{
$recurringTransaction->delete();
return true;
}
/**
* @return mixed
*/
public function get()
{
return $this->_user->recurringtransactions()->get();
}
/** /**
* @param \RecurringTransaction $recurringTransaction * @param \RecurringTransaction $recurringTransaction
* @param $data * @param $data

View File

@ -51,6 +51,7 @@ class RecurringTransaction extends Ardent
'skip' => 'required|between:0,31', 'skip' => 'required|between:0,31',
]; ];
protected $fillable = ['user_id','name','match','amount_min','amount_max','date','repeat_freq','skip','active','automatch'];
/** /**
* @return array * @return array

View File

@ -184,6 +184,7 @@ Route::group(['before' => 'auth'], function () {
Route::get('/json/expenses', ['uses' => 'JsonController@expenses', 'as' => 'json.expenses']); Route::get('/json/expenses', ['uses' => 'JsonController@expenses', 'as' => 'json.expenses']);
Route::get('/json/revenue', ['uses' => 'JsonController@revenue', 'as' => 'json.revenue']); Route::get('/json/revenue', ['uses' => 'JsonController@revenue', 'as' => 'json.revenue']);
Route::get('/json/transfers', ['uses' => 'JsonController@transfers', 'as' => 'json.transfers']); Route::get('/json/transfers', ['uses' => 'JsonController@transfers', 'as' => 'json.transfers']);
Route::get('/json/recurring', ['uses' => 'JsonController@recurring', 'as' => 'json.recurring']);
// limit controller: // limit controller:
Route::get('/budgets/limits/create/{budget?}',['uses' => 'LimitController@create','as' => 'budgets.limits.create']); Route::get('/budgets/limits/create/{budget?}',['uses' => 'LimitController@create','as' => 'budgets.limits.create']);

View File

@ -1,182 +1,186 @@
@extends('layouts.default') @extends('layouts.default')
@section('content') @section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p class="lead">Use recurring transactions to track repeated expenses</p>
<p class="text-info">
Bla bla.
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('recurring.store')])}} {{Form::open(['class' => 'form-horizontal','url' => route('recurring.store')])}}
<div class="row"> <div class="row">
<div class="col-lg-6 col-md-12 col-sm-6"> <div class="col-lg-6 col-md-12 col-sm-6">
<h4>Mandatory fields</h4> <!-- panel for mandatory fields -->
<div class="panel panel-primary">
<!-- name --> <div class="panel-heading">
<div class="form-group"> <i class="fa fa-exclamation-circle"></i> Mandatory fields
<label for="name" class="col-sm-4 control-label">Name</label>
<div class="col-sm-8">
<input type="text" name="name" class="form-control" id="name" value="{{Input::old('name')}}" placeholder="Name">
@if($errors->has('name'))
<p class="text-danger">{{$errors->first('name')}}</p>
@else
<span class="help-block">For example: rent, gas, insurance</span>
@endif
</div> </div>
</div> <div class="panel-body">
<div class="form-group"> <!-- name -->
<label for="match" class="col-sm-4 control-label">Matches on</label> <div class="form-group">
<div class="col-sm-8"> <label for="name" class="col-sm-4 control-label">Name</label>
<input type="text" name="match" class="form-control" id="match" value="{{Input::old('match')}}" data-role="tagsinput"> <div class="col-sm-8">
@if($errors->has('match')) <input type="text" name="name" class="form-control" id="name" value="{{Input::old('name')}}" placeholder="Name">
<p class="text-danger">{{$errors->first('match')}}</p> @if($errors->has('name'))
@else <p class="text-danger">{{$errors->first('name')}}</p>
<span class="help-block">For example: rent, [company name]. All matches need to @endif
be present for the recurring transaction to be recognized. This field is not case-sensitive. <em>Press enter after every match</em></span> </div>
@endif </div>
</div> <div class="form-group">
</div> <label for="match" class="col-sm-4 control-label">Matches on</label>
<div class="col-sm-8">
<div class="form-group"> <input type="text" name="match" class="form-control" id="match" value="{{Input::old('match')}}" data-role="tagsinput">
{{ Form::label('amount_min', 'Minimum amount', ['class' => 'col-sm-4 control-label'])}} @if($errors->has('match'))
<div class="col-sm-8"> <p class="text-danger">{{$errors->first('match')}}</p>
<div class="input-group"> @endif
<span class="input-group-addon">&euro;</span> </div>
{{Form::input('number','amount_min', Input::old('amount_min'), ['step' => 'any', 'class' => 'form-control'])}}
</div> </div>
@if($errors->has('amount_min')) <div class="form-group">
<p class="text-danger">{{$errors->first('amount_min')}}</p> {{ Form::label('amount_min', 'Minimum amount', ['class' => 'col-sm-4 control-label'])}}
@else <div class="col-sm-8">
<span class="help-block">Firefly will only include transactions with a higher amount than this. If your rent <div class="input-group">
is usually around &euro; 500,-, enter <code>450</code> to be safe.</span> <span class="input-group-addon">&euro;</span>
@endif {{Form::input('number','amount_min', Input::old('amount_min'), ['step' => 'any', 'class' => 'form-control'])}}
</div> </div>
</div>
<div class="form-group"> @if($errors->has('amount_min'))
{{ Form::label('amount_max', 'Maximum amount', ['class' => 'col-sm-4 control-label'])}} <p class="text-danger">{{$errors->first('amount_min')}}</p>
<div class="col-sm-8"> @endif
<div class="input-group"> </div>
<span class="input-group-addon">&euro;</span>
{{Form::input('number','amount_max', Input::old('amount_max'), ['step' => 'any', 'class' => 'form-control'])}}
</div> </div>
@if($errors->has('amount_max')) <div class="form-group">
<p class="text-danger">{{$errors->first('amount_max')}}</p> {{ Form::label('amount_max', 'Maximum amount', ['class' => 'col-sm-4 control-label'])}}
@else <div class="col-sm-8">
<span class="help-block">Firefly will only include transactions with a lower amount than this. If your rent <div class="input-group">
is usually around &euro; 500,-, enter <code>550</code> to be safe.</span> <span class="input-group-addon">&euro;</span>
@endif {{Form::input('number','amount_max', Input::old('amount_max'), ['step' => 'any', 'class' => 'form-control'])}}
</div>
@if($errors->has('amount_max'))
<p class="text-danger">{{$errors->first('amount_max')}}</p>
@endif
</div>
</div>
<div class="form-group">
{{ Form::label('date', 'Date', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
{{ Form::input('date','date', Input::old('date') ?: Carbon\Carbon::now()->addDay()->format('Y-m-d'), ['class'
=> 'form-control']) }}
@if($errors->has('date'))
<p class="text-danger">{{$errors->first('date')}}</p>
@endif
</div>
</div>
<div class="form-group">
<label for="period" class="col-sm-4 control-label">Recurrence</label>
<div class="col-sm-8">
{{Form::select('repeat_freq',$periods,Input::old('repeat_freq') ?: 'monthly',['class' => 'form-control'])}}
@if($errors->has('repeat_freq'))
<p class="text-danger">{{$errors->first('repeat_freq')}}</p>
@endif
</div>
</div>
</div> </div>
</div> </div>
<p>
<div class="form-group"> <button type="submit" class="btn btn-lg btn-success">
{{ Form::label('date', 'Date', ['class' => 'col-sm-4 control-label'])}} <i class="fa fa-plus-circle"></i> Store new recurring transaction
<div class="col-sm-8"> </button>
{{ Form::input('date','date', Input::old('date') ?: date('Y-m-d'), ['class' </p>
=> 'form-control']) }}
@if($errors->has('date'))
<p class="text-danger">{{$errors->first('date')}}</p>
@else
<span class="help-block">Select the next date you expect the transaction to occur.</span>
@endif
</div>
</div>
<div class="form-group">
<label for="period" class="col-sm-4 control-label">Recurrence</label>
<div class="col-sm-8">
{{Form::select('repeat_freq',$periods,Input::old('repeat_freq') ?: 'monthly',['class' => 'form-control'])}}
@if($errors->has('repeat_freq'))
<p class="text-danger">{{$errors->first('repeat_freq')}}</p>
@else
<span class="help-block">Select the period over which this transaction repeats</span>
@endif
</div>
</div>
</div> </div>
<div class="col-lg-6 col-md-12 col-sm-6"> <div class="col-lg-6 col-md-12 col-sm-6">
<h4>Optional fields</h4> <!-- panel for optional fields -->
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-smile-o"></i> Optional fields
</div>
<div class="panel-body">
<div class="form-group">
{{ Form::label('skip', 'Skip', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8">
{{Form::input('number','skip', Input::old('skip') ?: 0, ['class' => 'form-control'])}}
<div class="form-group"> @if($errors->has('skip'))
{{ Form::label('skip', 'Skip', ['class' => 'col-sm-4 control-label'])}} <p class="text-danger">{{$errors->first('skip')}}</p>
<div class="col-sm-8"> @else
{{Form::input('number','skip', Input::old('skip') ?: 0, ['class' => 'form-control'])}} <span class="help-block">Make Firefly skip every <em>n</em> times. Fill in <code>2</code>, and Firefly
will match, skip, skip and match a transaction.</span>
@if($errors->has('skip')) @endif
<p class="text-danger">{{$errors->first('skip')}}</p> </div>
@else </div>
<span class="help-block">Make Firefly skip every <em>n</em> times. Fill in <code>2</code>, and Firefly <div class="form-group">
will match, skip, skip and match a transaction.</span> <label for="automatch" class="col-sm-4 control-label">Auto-match</label>
@endif <div class="col-sm-8">
<div class="checkbox">
<label>
{{Form::checkbox('automatch',1,Input::old('automatch') == '1' || !Input::old('automatch'))}}
Yes
</label>
</div>
<span class="help-block">Firefly will automatically match transactions.</span>
</div>
</div>
<div class="form-group">
<label for="active" class="col-sm-4 control-label">Active</label>
<div class="col-sm-8">
<div class="checkbox">
<label>
{{Form::checkbox('active',1,Input::old('active') == '1' || !Input::old('active'))}}
Yes
</label>
</div>
<span class="help-block">This recurring transaction is actually active.</span>
</div>
</div>
</div> </div>
</div> </div>
<!-- select budget --> <!-- panel for options -->
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-bolt"></i> Options
<!-- select category --> </div>
<div class="panel-body">
<!-- select beneficiary --> <div class="form-group">
<label for="default" class="col-sm-4 control-label">
<div class="form-group"> Store
<label for="automatch" class="col-sm-4 control-label">Auto-match</label> </label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="checkbox"> <div class="radio">
<label>
{{Form::radio('post_submit_action','store',true)}}
Store the recurring transaction
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="validate_only" class="col-sm-4 control-label">
Validate only
</label>
<div class="col-sm-8">
<div class="radio">
<label>
{{Form::radio('post_submit_action','validate_only')}}
Only validate, do not save
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="return_to_form" class="col-sm-4 control-label">
Return here
</label>
<div class="col-sm-8">
<div class="radio">
<label> <label>
{{Form::checkbox('automatch',1,Input::old('automatch') == '1' || !Input::old('automatch'))}} {{Form::radio('post_submit_action','create_another')}}
Yes After storing, return here to create another one.
</label> </label>
</div> </div>
<span class="help-block">Firefly will automatically match transactions.</span>
</div> </div>
</div> </div>
<div class="form-group">
<label for="active" class="col-sm-4 control-label">Active</label>
<div class="col-sm-8">
<div class="checkbox">
<label>
{{Form::checkbox('active',1,Input::old('active') == '1' || !Input::old('active'))}}
Yes
</label>
</div>
<span class="help-block">This recurring transaction is actually active.</span>
</div>
</div> </div>
</div> </div>
</div>
<div class="row">
<div class="col-lg-6 col-md-12 col-sm-6">
<!-- add another after this one? -->
<div class="form-group">
<label for="create" class="col-sm-4 control-label">&nbsp;</label>
<div class="col-sm-8">
<div class="checkbox">
<label>
{{Form::checkbox('create',1,Input::old('create') == '1')}}
Create another (return to this form)
</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<button type="submit" class="btn btn-default btn-success">Create the recurring transaction</button>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -1,20 +1,15 @@
@extends('layouts.default') @extends('layouts.default')
@section('content') @section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p class="lead">Use recurring transactions to track repeated expenses</p>
<p class="text-info">
Bla bla.
</p>
</div>
</div>
{{Form::open(['class' => 'form-horizontal','url' => route('recurring.update', $recurringTransaction->id)])}} {{Form::open(['class' => 'form-horizontal','url' => route('recurring.update', $recurringTransaction->id)])}}
<div class="row"> <div class="row">
<div class="col-lg-6 col-md-12 col-sm-6"> <div class="col-lg-6 col-md-12 col-sm-6">
<h4>Mandatory fields</h4> <!-- panel for mandatory fields -->
<div class="panel panel-primary">
<div class="panel-heading">
<i class="fa fa-exclamation-circle"></i> Mandatory fields
</div>
<div class="panel-body">
<!-- name --> <!-- name -->
<div class="form-group"> <div class="form-group">
<label for="name" class="col-sm-4 control-label">Name</label> <label for="name" class="col-sm-4 control-label">Name</label>
@ -23,8 +18,6 @@
value="{{{Input::old('name') ?: $recurringTransaction->name}}}" placeholder="Name"> value="{{{Input::old('name') ?: $recurringTransaction->name}}}" placeholder="Name">
@if($errors->has('name')) @if($errors->has('name'))
<p class="text-danger">{{$errors->first('name')}}</p> <p class="text-danger">{{$errors->first('name')}}</p>
@else
<span class="help-block">For example: rent, gas, insurance</span>
@endif @endif
</div> </div>
</div> </div>
@ -36,9 +29,6 @@
data-role="tagsinput"> data-role="tagsinput">
@if($errors->has('match')) @if($errors->has('match'))
<p class="text-danger">{{$errors->first('match')}}</p> <p class="text-danger">{{$errors->first('match')}}</p>
@else
<span class="help-block">For example: rent, [company name]. All matches need to
be present for the recurring transaction to be recognized. This field is not case-sensitive.</span>
@endif @endif
</div> </div>
</div> </div>
@ -54,9 +44,6 @@
@if($errors->has('amount_min')) @if($errors->has('amount_min'))
<p class="text-danger">{{$errors->first('amount_min')}}</p> <p class="text-danger">{{$errors->first('amount_min')}}</p>
@else
<span class="help-block">Firefly will only include transactions with a higher amount than this. If your rent
is usually around &euro; 500,-, enter <code>450</code> to be safe.</span>
@endif @endif
</div> </div>
</div> </div>
@ -72,9 +59,6 @@
@if($errors->has('amount_max')) @if($errors->has('amount_max'))
<p class="text-danger">{{$errors->first('amount_max')}}</p> <p class="text-danger">{{$errors->first('amount_max')}}</p>
@else
<span class="help-block">Firefly will only include transactions with a lower amount than this.
If your rent is usually around &euro; 500,-, enter <code>550</code> to be safe.</span>
@endif @endif
</div> </div>
</div> </div>
@ -86,8 +70,6 @@
['class' => 'form-control']) }} ['class' => 'form-control']) }}
@if($errors->has('date')) @if($errors->has('date'))
<p class="text-danger">{{$errors->first('date')}}</p> <p class="text-danger">{{$errors->first('date')}}</p>
@else
<span class="help-block">Select the next date you expect the transaction to occur.</span>
@endif @endif
</div> </div>
</div> </div>
@ -99,16 +81,26 @@
['class' => 'form-control'])}} ['class' => 'form-control'])}}
@if($errors->has('repeat_freq')) @if($errors->has('repeat_freq'))
<p class="text-danger">{{$errors->first('repeat_freq')}}</p> <p class="text-danger">{{$errors->first('repeat_freq')}}</p>
@else
<span class="help-block">Select the period over which this transaction repeats</span>
@endif @endif
</div> </div>
</div> </div>
</div>
</div> </div>
<div class="col-lg-6 col-md-12 col-sm-6">
<h4>Optional fields</h4>
<p>
<button type="submit" class="btn btn-lg btn-success">
<i class="fa fa-plus-circle"></i> Update recurring transasction
</button>
</p>
</div>
<div class="col-lg-6 col-md-12 col-sm-6">
<!-- panel for optional fields -->
<div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-smile-o"></i> Optional fields
</div>
<div class="panel-body">
<div class="form-group"> <div class="form-group">
{{ Form::label('skip', 'Skip', ['class' => 'col-sm-4 control-label'])}} {{ Form::label('skip', 'Skip', ['class' => 'col-sm-4 control-label'])}}
<div class="col-sm-8"> <div class="col-sm-8">
@ -117,21 +109,10 @@
@if($errors->has('skip')) @if($errors->has('skip'))
<p class="text-danger">{{$errors->first('skip')}}</p> <p class="text-danger">{{$errors->first('skip')}}</p>
@else
<span class="help-block">Make Firefly skip every <em>n</em> times. Fill in <code>2</code>, and Firefly
will match, skip, skip and match a transaction.</span>
@endif @endif
</div> </div>
</div> </div>
<!-- select budget -->
<!-- select category -->
<!-- select beneficiary -->
<div class="form-group"> <div class="form-group">
<label for="automatch" class="col-sm-4 control-label">Auto-match</label> <label for="automatch" class="col-sm-4 control-label">Auto-match</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -159,25 +140,57 @@
<span class="help-block">This recurring transaction is actually active.</span> <span class="help-block">This recurring transaction is actually active.</span>
</div> </div>
</div> </div>
</div>
</div> </div>
</div>
<div class="row"> <!-- panel for options -->
<div class="col-lg-6 col-md-12 col-sm-6"> <div class="panel panel-default">
<div class="panel-heading">
<i class="fa fa-bolt"></i> Options
</div>
<div class="form-group"> <div class="panel-body">
<div class="col-sm-offset-4 col-sm-8"> <div class="form-group">
<button type="submit" class="btn btn-default btn-success">Update the recurring transaction</button> <label for="default" class="col-sm-4 control-label">
Update
</label>
<div class="col-sm-8">
<div class="radio">
<label>
{{Form::radio('post_submit_action','store',true)}}
Update the recurring transaction
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="validate_only" class="col-sm-4 control-label">
Validate only
</label>
<div class="col-sm-8">
<div class="radio">
<label>
{{Form::radio('post_submit_action','validate_only')}}
Only validate, do not save changes
</label>
</div>
</div>
</div>
<div class="form-group">
<label for="return_to_form" class="col-sm-4 control-label">
Return here
</label>
<div class="col-sm-8">
<div class="radio">
<label>
{{Form::radio('post_submit_action','return_to_edit')}}
After update, return here again.
</label>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{{Form::close()}} {{Form::close()}}

View File

@ -1,73 +1,41 @@
@extends('layouts.default') @extends('layouts.default')
@section('content') @section('content')
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<p class="lead">Use recurring transactions to track repeated withdrawals</p>
<p class="text-info">We all have bills to pay. Firefly can help you organize those bills into recurring transactions,
which are exactly what the name suggests. Firefly can match new (and existing) transactions to such a recurring transaction
and help you organize these expenses into manageable groups. The front page of Firefly will show you which recurring
transactions you have missed, which are yet to come and which have been paid.</p>
</div>
</div>
<div class="row"> <div class="row">
<div class="col-lg-12 col-sm-12 col-md-12"> <div class="col-lg-12 col-sm-12 col-md-12">
<table class="table table-striped"> <div class="panel panel-default">
<tr> <div class="panel-heading">
<th>Name</th> <i class="fa {{$mainTitleIcon}}"></i> {{{$title}}}
<th>Matches on</th> </div>
<th>Amount between</th> <div class="panel-body">
<th>Expected every</th> <table class="table table-striped" id="recurringTable">
<th>Next expected match</th> <thead>
<th>Auto-match</th> <tr>
<th>Active</th> <th>name</th>
<th></th> <th>match</th>
</tr> <th>amount_min</th>
@foreach($list as $entry) <th>amount_max</th>
<tr> <th>date</th>
<td><a href="{{route('recurring.show',$entry->id)}}">{{{$entry->name}}}</a></td> <th>active</th>
<td> <th>automatch</th>
@foreach(explode(' ',$entry->match) as $word) <th>repeat_freq</th>
<span class="label label-info">{{{$word}}}</span> <th>id</th>
@endforeach </tr>
</td> </thead>
<td> </table>
{{mf($entry->amount_min)}} &ndash; </div>
{{mf($entry->amount_max)}} </div>
</td>
<td>
{{$entry->repeat_freq}}
</td>
<td>
{{$entry->next()->format('d-m-Y')}}
</td>
<td>
@if($entry->automatch)
<span class="glyphicon glyphicon-ok"></span>
@else
<span class="glyphicon glyphicon-remove"></span>
@endif
</td>
<td>
@if($entry->active)
<span class="glyphicon glyphicon-ok"></span>
@else
<span class="glyphicon glyphicon-remove"></span>
@endif
</td>
<td>
<div class="btn-group btn-group-xs">
<a href="{{route('recurring.edit',$entry->id)}}" class="btn btn-default"><span class="glyphicon glyphicon-pencil"></span></a>
<a href="{{route('recurring.delete',$entry->id)}}" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
</div>
</td>
</tr>
@endforeach
</table>
<p>
<a href="{{route('recurring.create')}}" class="btn btn-success btn-large">Create new recurring transaction</a>
</p>
</div> </div>
</div> </div>
@stop
@section('scripts')
<script type="text/javascript">
var URL = '{{route('json.recurring')}}';
</script>
{{HTML::script('assets/javascript/typeahead/bootstrap3-typeahead.min.js')}}
{{HTML::script('assets/javascript/datatables/jquery.dataTables.min.js')}}
{{HTML::script('assets/javascript/datatables/dataTables.bootstrap.js')}}
{{HTML::script('assets/javascript/firefly/recurring.js')}}
@stop
@section('styles')
{{HTML::style('assets/stylesheets/datatables/dataTables.bootstrap.css')}}
@stop @stop

View File

@ -12,7 +12,7 @@
<tr> <tr>
<th>Date</th> <th>Date</th>
<th>Description</th> <th>Description</th>
<th data-dynatable-column="amount">Amount (&euro;)</th> <th>Amount (&euro;)</th>
<th>From</th> <th>From</th>
<th>To</th> <th>To</th>
<th>ID</th> <th>ID</th>

View File

@ -0,0 +1,112 @@
$(document).ready(function () {
$('#recurringTable').DataTable(
{
serverSide: true,
ajax: URL,
paging: true,
processing: true,
order: [],
"lengthMenu": [[50, 100, 250, -1], [50, 100, 250, "All"]],
columns: [
{
name: 'name',
data: 'name',
searchable: true,
title: 'Name',
render: function (data) {
return '<a href="' + data.url + '" title="' + data.name + '">' + data.name + '</a>';
}
},
{
name: 'match',
data: 'match',
searchable: true,
title: 'Matches on',
render: function (data) {
var str = '';
for (x in data) {
str += '<span class="label label-info">' + data[x] + '</span> ';
}
return str;//return '<a href="' + data.url + '" title="' + data.name + '">' + data.name + '</a>';
}
},
{
name: 'amount_min',
data: 'amount_min',
searchable: false,
title: '&rarr;',
render: function (data) {
return '<span class="text-info">\u20AC ' + data.toFixed(2) + '</span>';
}
},
{
name: 'amount_max',
data: 'amount_max',
searchable: false,
title: '&larr;',
render: function (data) {
return '<span class="text-info">\u20AC ' + data.toFixed(2) + '</span>';
}
},
{
name: 'date',
data: 'date',
title: 'Expected on',
searchable: false
},
{
name: 'active',
data: 'active',
searchable: false,
sortable: false,
render: function(data) {
if(data == 1) {
return '<i class="fa fa-check fa-faw"></i>';
} else {
return '<i class="fa fa-remove fa-faw"></i>';
}
},
title: 'Is active?'
},
{
name: 'automatch',
data: 'automatch',
sortable: false,
searchable: false,
render: function(data) {
if(data == 1) {
return '<i class="fa fa-check fa-faw"></i>';
} else {
return '<i class="fa fa-remove fa-faw"></i>';
}
},
title: 'Automatch?'
},
{
name: 'repeat_freq',
data: 'repeat_freq',
searchable: false,
sortable: false,
title: 'Repeat frequency'
},
{
name: 'id',
data: 'id',
searchable: false,
sortable: false,
title: '',
render: function (data, type, full, meta) {
return '<div class="btn-group btn-group-xs">' +
'<a class="btn btn-default btn-xs" href="' + data.edit + '">' +
'<span class="glyphicon glyphicon-pencil"</a>' +
'<a class="btn btn-danger btn-xs" href="' + data.delete + '">' +
'<span class="glyphicon glyphicon-trash"</a>' +
'</a></div>';
}
}
]
}
);
});

View File

@ -51,6 +51,7 @@ $(document).ready(function () {
{ {
name: 'amount', name: 'amount',
data: 'amount', data: 'amount',
'title': 'Amount (\u20AC)',
searchable: false, searchable: false,
render: function (data, type, full, meta) { render: function (data, type, full, meta) {
if (display == 'expenses') { if (display == 'expenses') {
@ -84,6 +85,8 @@ $(document).ready(function () {
name: 'id', name: 'id',
data: 'id', data: 'id',
searchable: false, searchable: false,
sortable: false,
title: '',
render: function (data, type, full, meta) { render: function (data, type, full, meta) {
return '<div class="btn-group btn-group-xs">' + return '<div class="btn-group btn-group-xs">' +
'<a class="btn btn-default btn-xs" href="' + data.edit + '">' + '<a class="btn btn-default btn-xs" href="' + data.edit + '">' +