mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Varioux fixes and cleaning up.
This commit is contained in:
parent
9015d6ca16
commit
aa9eb8ca64
@ -1,9 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
|
||||
use Firefly\Storage\Budget\BudgetRepositoryInterface as Bud;
|
||||
use Firefly\Storage\Category\CategoryRepositoryInterface as Cat;
|
||||
use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as TJRI;
|
||||
use Firefly\Helper\Controllers\JsonInterface as JI;
|
||||
use Illuminate\Support\Collection;
|
||||
use LaravelBook\Ardent\Builder;
|
||||
|
||||
@ -14,226 +11,60 @@ use LaravelBook\Ardent\Builder;
|
||||
*/
|
||||
class JsonController extends BaseController
|
||||
{
|
||||
protected $_accounts;
|
||||
protected $_categories;
|
||||
protected $_budgets;
|
||||
/** @var TJRI $_journals */
|
||||
protected $_journals;
|
||||
/** @var \Firefly\Helper\Controllers\JsonInterface $helper */
|
||||
protected $helper;
|
||||
|
||||
/**
|
||||
* @param ARI $accounts
|
||||
* @param Cat $categories
|
||||
* @param Bud $budgets
|
||||
* @param TJRI $journals
|
||||
*/
|
||||
public function __construct(ARI $accounts, Cat $categories, Bud $budgets, TJRI $journals)
|
||||
public function __construct(JI $helper)
|
||||
{
|
||||
$this->_accounts = $accounts;
|
||||
$this->_categories = $categories;
|
||||
$this->_budgets = $budgets;
|
||||
$this->_journals = $journals;
|
||||
$this->helper = $helper;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of categories.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function revenue()
|
||||
public function categories()
|
||||
{
|
||||
$parameters = $this->_datatableParameters();
|
||||
$parameters['transactionTypes'] = ['Deposit'];
|
||||
$parameters['amount'] = 'positive';
|
||||
|
||||
$query = $this->_datatableQuery($parameters);
|
||||
$resultSet = $this->_datatableResultset($parameters, $query);
|
||||
|
||||
|
||||
/*
|
||||
* Build return data:
|
||||
*/
|
||||
|
||||
if (Input::get('debug') == 'true') {
|
||||
echo '<pre>';
|
||||
print_r($parameters);
|
||||
echo '<hr>';
|
||||
print_r($resultSet);
|
||||
return '';
|
||||
|
||||
} else {
|
||||
return Response::json($resultSet);
|
||||
/** @var \Firefly\Storage\Category\EloquentCategoryRepository $categories */
|
||||
$categories = App::make('Firefly\Storage\Category\CategoryRepositoryInterface');
|
||||
$list = $categories->get();
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
}
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function transfers()
|
||||
public function expenseAccounts()
|
||||
{
|
||||
$parameters = $this->_datatableParameters();
|
||||
$parameters['transactionTypes'] = ['Transfer'];
|
||||
$parameters['amount'] = 'positive';
|
||||
|
||||
$query = $this->_datatableQuery($parameters);
|
||||
$resultSet = $this->_datatableResultset($parameters, $query);
|
||||
|
||||
|
||||
/*
|
||||
* Build return data:
|
||||
*/
|
||||
|
||||
if (Input::get('debug') == 'true') {
|
||||
echo '<pre>';
|
||||
print_r($parameters);
|
||||
echo '<hr>';
|
||||
print_r($resultSet);
|
||||
return '';
|
||||
|
||||
} else {
|
||||
return Response::json($resultSet);
|
||||
/** @var \Firefly\Storage\Account\EloquentAccountRepository $accounts */
|
||||
$accounts = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$list = $accounts->getOfTypes(['Expense account', 'Beneficiary account']);
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function expenses()
|
||||
{
|
||||
@ -241,7 +72,7 @@ class JsonController extends BaseController
|
||||
/*
|
||||
* Gets most parameters from the Input::all() array:
|
||||
*/
|
||||
$parameters = $this->_datatableParameters();
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
|
||||
/*
|
||||
* Add some more parameters to fine tune the query:
|
||||
@ -252,137 +83,58 @@ class JsonController extends BaseController
|
||||
/*
|
||||
* Get the query:
|
||||
*/
|
||||
$query = $this->_datatableQuery($parameters);
|
||||
$query = $this->helper->journalQuery($parameters);
|
||||
|
||||
/*
|
||||
* Build result set:
|
||||
*/
|
||||
$resultSet = $this->_datatableResultset($parameters, $query);
|
||||
$resultSet = $this->helper->journalDataset($parameters, $query);
|
||||
|
||||
|
||||
/*
|
||||
* 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($resultSet);
|
||||
}
|
||||
|
||||
protected function _datatableResultset(array $parameters, Builder $query)
|
||||
public function recurring()
|
||||
{
|
||||
/*
|
||||
* Count query:
|
||||
*/
|
||||
$count = $query->count();
|
||||
|
||||
/*
|
||||
* 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;
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
$query = $this->helper->recurringTransactionsQuery($parameters);
|
||||
$resultSet = $this->helper->recurringTransactionsDataset($parameters, $query);
|
||||
return Response::json($resultSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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']);
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
}
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
$parameters['transactionTypes'] = ['Deposit'];
|
||||
$parameters['amount'] = 'positive';
|
||||
|
||||
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.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function revenueAccounts()
|
||||
{
|
||||
$list = $this->_accounts->getOfTypes(['Revenue account']);
|
||||
$return = [];
|
||||
/** @var \Firefly\Storage\Account\EloquentAccountRepository $accounts */
|
||||
$accounts = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$list = $accounts->getOfTypes(['Revenue account']);
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$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();
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
}
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
$parameters['transactionTypes'] = ['Transfer'];
|
||||
$parameters['amount'] = 'positive';
|
||||
|
||||
return Response::json($return);
|
||||
$query = $this->helper->journalQuery($parameters);
|
||||
$resultSet = $this->helper->journalDataset($parameters, $query);
|
||||
|
||||
|
||||
/*
|
||||
* Build return data:
|
||||
*/
|
||||
return Response::json($resultSet);
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Firefly\Exception\FireflyException;
|
||||
use Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface as RTR;
|
||||
|
||||
/**
|
||||
@ -51,7 +52,7 @@ class RecurringController extends BaseController
|
||||
*/
|
||||
public function destroy(RecurringTransaction $recurringTransaction)
|
||||
{
|
||||
Event::fire('recurring.destroy', [$recurringTransaction]);
|
||||
//Event::fire('recurring.destroy', [$recurringTransaction]);
|
||||
$result = $this->_repository->destroy($recurringTransaction);
|
||||
if ($result === true) {
|
||||
Session::flash('success', 'The recurring transaction was deleted.');
|
||||
@ -84,11 +85,7 @@ class RecurringController extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$list = $this->_repository->get();
|
||||
|
||||
|
||||
|
||||
return View::make('recurring.index')->with('list', $list);
|
||||
return View::make('recurring.index');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -106,10 +103,18 @@ class RecurringController extends BaseController
|
||||
*/
|
||||
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());
|
||||
if ($recurringTransaction->validate()) {
|
||||
|
||||
if ($recurringTransaction->errors()->count() == 0) {
|
||||
Session::flash('success', 'Recurring transaction "' . $recurringTransaction->name . '" saved!');
|
||||
Event::fire('recurring.store', [$recurringTransaction]);
|
||||
//Event::fire('recurring.store', [$recurringTransaction]);
|
||||
if (Input::get('create') == '1') {
|
||||
return Redirect::route('recurring.create')->withInput();
|
||||
} else {
|
||||
@ -135,7 +140,7 @@ class RecurringController extends BaseController
|
||||
$recurringTransaction = $this->_repository->update($recurringTransaction, Input::all());
|
||||
if ($recurringTransaction->errors()->count() == 0) {
|
||||
Session::flash('success', 'The recurring transaction has been updated.');
|
||||
Event::fire('recurring.update', [$recurringTransaction]);
|
||||
//Event::fire('recurring.update', [$recurringTransaction]);
|
||||
|
||||
return Redirect::route('recurring.index');
|
||||
} else {
|
||||
|
369
app/lib/Firefly/Helper/Controllers/Json.php
Normal file
369
app/lib/Firefly/Helper/Controllers/Json.php
Normal 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;
|
||||
}
|
||||
}
|
64
app/lib/Firefly/Helper/Controllers/JsonInterface.php
Normal file
64
app/lib/Firefly/Helper/Controllers/JsonInterface.php
Normal 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);
|
||||
}
|
@ -247,7 +247,8 @@ class Transaction implements TransactionInterface
|
||||
/*
|
||||
* Add a custom error when they are the same.
|
||||
*/
|
||||
if ($to->id == $from->id) {
|
||||
if ($to->id ==
|
||||
$from->id) {
|
||||
$bag = new MessageBag;
|
||||
$bag->add('account_from_id', 'The account from cannot be the same as the account to.');
|
||||
return $bag;
|
||||
|
@ -27,6 +27,11 @@ class HelperServiceProvider extends ServiceProvider
|
||||
'Firefly\Helper\Controllers\Chart'
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
'Firefly\Helper\Controllers\JsonInterface',
|
||||
'Firefly\Helper\Controllers\Json'
|
||||
);
|
||||
|
||||
$this->app->bind(
|
||||
'Firefly\Helper\Controllers\SearchInterface',
|
||||
'Firefly\Helper\Controllers\Search'
|
||||
|
@ -24,6 +24,26 @@ class EloquentRecurringTransactionRepository implements RecurringTransactionRepo
|
||||
$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 array $payload
|
||||
@ -115,25 +135,43 @@ class EloquentRecurringTransactionRepository implements RecurringTransactionRepo
|
||||
*/
|
||||
public function store($data)
|
||||
{
|
||||
$recurringTransaction = new \RecurringTransaction;
|
||||
$recurringTransaction->user()->associate($this->_user);
|
||||
$recurringTransaction->name = $data['name'];
|
||||
$recurringTransaction->match = join(' ', explode(',', $data['match']));
|
||||
$recurringTransaction->amount_max = floatval($data['amount_max']);
|
||||
$recurringTransaction->amount_min = floatval($data['amount_min']);
|
||||
$recurringTransaction = new \RecurringTransaction(
|
||||
[
|
||||
'user_id' => $this->_user->id,
|
||||
'name' => $data['name'],
|
||||
'match' => join(' ', explode(',', $data['match'])),
|
||||
'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) {
|
||||
$recurringTransaction->errors()->add('amount_max', 'Amount max and min cannot both be zero.');
|
||||
|
||||
return $recurringTransaction;
|
||||
}
|
||||
|
||||
$recurringTransaction->date = new Carbon($data['date']);
|
||||
$recurringTransaction->active = isset($data['active']) ? intval($data['active']) : 0;
|
||||
$recurringTransaction->automatch = isset($data['automatch']) ? intval($data['automatch']) : 0;
|
||||
$recurringTransaction->skip = isset($data['skip']) ? intval($data['skip']) : 0;
|
||||
$recurringTransaction->repeat_freq = $data['repeat_freq'];
|
||||
if ($recurringTransaction->amount_max < $recurringTransaction->amount_min) {
|
||||
$recurringTransaction->errors()->add('amount_max', 'Amount max must be more than amount min.');
|
||||
return $recurringTransaction;
|
||||
}
|
||||
|
||||
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()) {
|
||||
$recurringTransaction->save();
|
||||
@ -142,26 +180,6 @@ class EloquentRecurringTransactionRepository implements RecurringTransactionRepo
|
||||
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 $data
|
||||
|
@ -51,6 +51,7 @@ class RecurringTransaction extends Ardent
|
||||
'skip' => 'required|between:0,31',
|
||||
];
|
||||
|
||||
protected $fillable = ['user_id','name','match','amount_min','amount_max','date','repeat_freq','skip','active','automatch'];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
|
@ -184,6 +184,7 @@ Route::group(['before' => 'auth'], function () {
|
||||
Route::get('/json/expenses', ['uses' => 'JsonController@expenses', 'as' => 'json.expenses']);
|
||||
Route::get('/json/revenue', ['uses' => 'JsonController@revenue', 'as' => 'json.revenue']);
|
||||
Route::get('/json/transfers', ['uses' => 'JsonController@transfers', 'as' => 'json.transfers']);
|
||||
Route::get('/json/recurring', ['uses' => 'JsonController@recurring', 'as' => 'json.recurring']);
|
||||
|
||||
// limit controller:
|
||||
Route::get('/budgets/limits/create/{budget?}',['uses' => 'LimitController@create','as' => 'budgets.limits.create']);
|
||||
|
@ -1,182 +1,186 @@
|
||||
@extends('layouts.default')
|
||||
@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')])}}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-12 col-sm-6">
|
||||
<h4>Mandatory fields</h4>
|
||||
|
||||
<!-- name -->
|
||||
<div class="form-group">
|
||||
<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
|
||||
<!-- panel for mandatory fields -->
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-exclamation-circle"></i> Mandatory fields
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="match" class="col-sm-4 control-label">Matches on</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="match" class="form-control" id="match" value="{{Input::old('match')}}" data-role="tagsinput">
|
||||
@if($errors->has('match'))
|
||||
<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. <em>Press enter after every match</em></span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{{ Form::label('amount_min', 'Minimum amount', ['class' => 'col-sm-4 control-label'])}}
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">€</span>
|
||||
{{Form::input('number','amount_min', Input::old('amount_min'), ['step' => 'any', 'class' => 'form-control'])}}
|
||||
<div class="panel-body">
|
||||
<!-- name -->
|
||||
<div class="form-group">
|
||||
<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>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="match" class="col-sm-4 control-label">Matches on</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" name="match" class="form-control" id="match" value="{{Input::old('match')}}" data-role="tagsinput">
|
||||
@if($errors->has('match'))
|
||||
<p class="text-danger">{{$errors->first('match')}}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($errors->has('amount_min'))
|
||||
<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 € 500,-, enter <code>450</code> to be safe.</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{{ Form::label('amount_min', 'Minimum amount', ['class' => 'col-sm-4 control-label'])}}
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">€</span>
|
||||
{{Form::input('number','amount_min', Input::old('amount_min'), ['step' => 'any', 'class' => 'form-control'])}}
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
{{ Form::label('amount_max', 'Maximum amount', ['class' => 'col-sm-4 control-label'])}}
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">€</span>
|
||||
{{Form::input('number','amount_max', Input::old('amount_max'), ['step' => 'any', 'class' => 'form-control'])}}
|
||||
@if($errors->has('amount_min'))
|
||||
<p class="text-danger">{{$errors->first('amount_min')}}</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($errors->has('amount_max'))
|
||||
<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 € 500,-, enter <code>550</code> to be safe.</span>
|
||||
@endif
|
||||
<div class="form-group">
|
||||
{{ Form::label('amount_max', 'Maximum amount', ['class' => 'col-sm-4 control-label'])}}
|
||||
<div class="col-sm-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon">€</span>
|
||||
{{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 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') ?: date('Y-m-d'), ['class'
|
||||
=> '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>
|
||||
|
||||
<p>
|
||||
<button type="submit" class="btn btn-lg btn-success">
|
||||
<i class="fa fa-plus-circle"></i> Store new recurring transaction
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<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">
|
||||
{{ 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'])}}
|
||||
|
||||
@if($errors->has('skip'))
|
||||
<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
|
||||
@if($errors->has('skip'))
|
||||
<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
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="automatch" class="col-sm-4 control-label">Auto-match</label>
|
||||
<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>
|
||||
|
||||
<!-- select budget -->
|
||||
|
||||
|
||||
|
||||
<!-- select category -->
|
||||
|
||||
<!-- select beneficiary -->
|
||||
|
||||
<div class="form-group">
|
||||
<label for="automatch" class="col-sm-4 control-label">Auto-match</label>
|
||||
<div class="col-sm-8">
|
||||
<div class="checkbox">
|
||||
<!-- panel for options -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-bolt"></i> Options
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label for="default" class="col-sm-4 control-label">
|
||||
Store
|
||||
</label>
|
||||
<div class="col-sm-8">
|
||||
<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>
|
||||
{{Form::checkbox('automatch',1,Input::old('automatch') == '1' || !Input::old('automatch'))}}
|
||||
Yes
|
||||
{{Form::radio('post_submit_action','create_another')}}
|
||||
After storing, return here to create another one.
|
||||
</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 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"> </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>
|
||||
|
||||
|
@ -1,20 +1,15 @@
|
||||
@extends('layouts.default')
|
||||
@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)])}}
|
||||
|
||||
<div class="row">
|
||||
<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 -->
|
||||
<div class="form-group">
|
||||
<label for="name" class="col-sm-4 control-label">Name</label>
|
||||
@ -23,8 +18,6 @@
|
||||
value="{{{Input::old('name') ?: $recurringTransaction->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>
|
||||
@ -36,9 +29,6 @@
|
||||
data-role="tagsinput">
|
||||
@if($errors->has('match'))
|
||||
<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
|
||||
</div>
|
||||
</div>
|
||||
@ -54,9 +44,6 @@
|
||||
|
||||
@if($errors->has('amount_min'))
|
||||
<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 € 500,-, enter <code>450</code> to be safe.</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@ -72,9 +59,6 @@
|
||||
|
||||
@if($errors->has('amount_max'))
|
||||
<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 € 500,-, enter <code>550</code> to be safe.</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@ -86,8 +70,6 @@
|
||||
['class' => '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>
|
||||
@ -99,16 +81,26 @@
|
||||
['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">
|
||||
<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">
|
||||
{{ Form::label('skip', 'Skip', ['class' => 'col-sm-4 control-label'])}}
|
||||
<div class="col-sm-8">
|
||||
@ -117,21 +109,10 @@
|
||||
|
||||
@if($errors->has('skip'))
|
||||
<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
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- select budget -->
|
||||
|
||||
|
||||
|
||||
<!-- select category -->
|
||||
|
||||
<!-- select beneficiary -->
|
||||
|
||||
<div class="form-group">
|
||||
<label for="automatch" class="col-sm-4 control-label">Auto-match</label>
|
||||
<div class="col-sm-8">
|
||||
@ -159,25 +140,57 @@
|
||||
<span class="help-block">This recurring transaction is actually active.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-12 col-sm-6">
|
||||
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-4 col-sm-8">
|
||||
<button type="submit" class="btn btn-default btn-success">Update the recurring transaction</button>
|
||||
</div>
|
||||
<!-- panel for options -->
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<i class="fa fa-bolt"></i> Options
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<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>
|
||||
|
||||
{{Form::close()}}
|
||||
|
||||
|
||||
|
@ -1,73 +1,41 @@
|
||||
@extends('layouts.default')
|
||||
@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="col-lg-12 col-sm-12 col-md-12">
|
||||
<table class="table table-striped">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Matches on</th>
|
||||
<th>Amount between</th>
|
||||
<th>Expected every</th>
|
||||
<th>Next expected match</th>
|
||||
<th>Auto-match</th>
|
||||
<th>Active</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
@foreach($list as $entry)
|
||||
<tr>
|
||||
<td><a href="{{route('recurring.show',$entry->id)}}">{{{$entry->name}}}</a></td>
|
||||
<td>
|
||||
@foreach(explode(' ',$entry->match) as $word)
|
||||
<span class="label label-info">{{{$word}}}</span>
|
||||
@endforeach
|
||||
</td>
|
||||
<td>
|
||||
{{mf($entry->amount_min)}} –
|
||||
{{mf($entry->amount_max)}}
|
||||
</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 class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<i class="fa {{$mainTitleIcon}}"></i> {{{$title}}}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-striped" id="recurringTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>name</th>
|
||||
<th>match</th>
|
||||
<th>amount_min</th>
|
||||
<th>amount_max</th>
|
||||
<th>date</th>
|
||||
<th>active</th>
|
||||
<th>automatch</th>
|
||||
<th>repeat_freq</th>
|
||||
<th>id</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
</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
|
@ -12,7 +12,7 @@
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Description</th>
|
||||
<th data-dynatable-column="amount">Amount (€)</th>
|
||||
<th>Amount (€)</th>
|
||||
<th>From</th>
|
||||
<th>To</th>
|
||||
<th>ID</th>
|
||||
|
112
public/assets/javascript/firefly/recurring.js
Normal file
112
public/assets/javascript/firefly/recurring.js
Normal 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: '→',
|
||||
render: function (data) {
|
||||
return '<span class="text-info">\u20AC ' + data.toFixed(2) + '</span>';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'amount_max',
|
||||
data: 'amount_max',
|
||||
searchable: false,
|
||||
title: '←',
|
||||
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>';
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
});
|
@ -51,6 +51,7 @@ $(document).ready(function () {
|
||||
{
|
||||
name: 'amount',
|
||||
data: 'amount',
|
||||
'title': 'Amount (\u20AC)',
|
||||
searchable: false,
|
||||
render: function (data, type, full, meta) {
|
||||
if (display == 'expenses') {
|
||||
@ -84,6 +85,8 @@ $(document).ready(function () {
|
||||
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 + '">' +
|
||||
|
Loading…
Reference in New Issue
Block a user