mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-19 19:01:58 +00:00
New code for bills.
This commit is contained in:
240
app/Http/Controllers/BillController.php
Normal file
240
app/Http/Controllers/BillController.php
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
<?php namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Http\Requests;
|
||||||
|
use FireflyIII\Http\Requests\BillFormRequest;
|
||||||
|
use FireflyIII\Models\Bill;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
|
use FireflyIII\Models\TransactionJournal;
|
||||||
|
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||||
|
use Redirect;
|
||||||
|
use Session;
|
||||||
|
use URL;
|
||||||
|
use View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BillController
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Http\Controllers
|
||||||
|
*/
|
||||||
|
class BillController extends Controller
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
View::share('title', 'Bills');
|
||||||
|
View::share('mainTitleIcon', 'fa-calendar-o');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$periods = \Config::get('firefly.periods_to_text');
|
||||||
|
|
||||||
|
return view('bills.create')->with('periods', $periods)->with('subTitle', 'Create new');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function delete(Bill $bill)
|
||||||
|
{
|
||||||
|
return view('bills.delete')->with('bill', $bill)->with('subTitle', 'Delete "' . e($bill->name) . '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function destroy(Bill $bill)
|
||||||
|
{
|
||||||
|
$bill->delete();
|
||||||
|
Session::flash('success', 'The bill was deleted.');
|
||||||
|
|
||||||
|
return Redirect::route('bills.index');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function edit(Bill $bill)
|
||||||
|
{
|
||||||
|
$periods = \Config::get('firefly.periods_to_text');
|
||||||
|
|
||||||
|
return View::make('bills.edit')->with('periods', $periods)->with('bill', $bill)->with('subTitle', 'Edit "' . e($bill->name) . '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param BillRepositoryInterface $repository
|
||||||
|
*
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function index(BillRepositoryInterface $repository)
|
||||||
|
{
|
||||||
|
$bills = Auth::user()->bills()->get();
|
||||||
|
$bills->each(
|
||||||
|
function (Bill $bill) use ($repository) {
|
||||||
|
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
|
||||||
|
$last = $bill->transactionjournals()->orderBy('date', 'DESC')->first();
|
||||||
|
$bill->lastFoundMatch = null;
|
||||||
|
if ($last) {
|
||||||
|
$bill->lastFoundMatch = $last->date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return View::make('bills.index', compact('bills'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function rescan(Bill $bill, BillRepositoryInterface $repository)
|
||||||
|
{
|
||||||
|
if (intval($bill->active) == 0) {
|
||||||
|
Session::flash('warning', 'Inactive bills cannot be scanned.');
|
||||||
|
|
||||||
|
return Redirect::intended('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
$set = \DB::table('transactions')->where('amount', '>', 0)->where('amount', '>=', $bill->amount_min)->where('amount', '<=', $bill->amount_max)->get(['transaction_journal_id']);
|
||||||
|
$ids = [];
|
||||||
|
|
||||||
|
/** @var Transaction $entry */
|
||||||
|
foreach ($set as $entry) {
|
||||||
|
$ids[] = intval($entry->transaction_journal_id);
|
||||||
|
}
|
||||||
|
if (count($ids) > 0) {
|
||||||
|
$journals = Auth::user()->transactionjournals()->whereIn('id',$ids)->get();
|
||||||
|
/** @var TransactionJournal $journal */
|
||||||
|
foreach ($journals as $journal) {
|
||||||
|
$repository->scan($bill, $journal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Session::flash('success', 'Rescanned everything.');
|
||||||
|
|
||||||
|
return Redirect::to(URL::previous());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function show(Bill $bill, BillRepositoryInterface $repository)
|
||||||
|
{
|
||||||
|
$journals = $bill->transactionjournals()->withRelevantData()->orderBy('date', 'DESC')->get();
|
||||||
|
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
|
||||||
|
$hideBill = true;
|
||||||
|
|
||||||
|
|
||||||
|
return View::make('bills.show', compact('journals', 'hideBill', 'bill'))->with('subTitle', e($bill->name));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function store(BillFormRequest $request, BillRepositoryInterface $repository)
|
||||||
|
{
|
||||||
|
|
||||||
|
var_dump($request->all());
|
||||||
|
|
||||||
|
$billData = [
|
||||||
|
'name' => $request->get('name'),
|
||||||
|
'match' => $request->get('match'),
|
||||||
|
'amount_min' => floatval($request->get('amount_min')),
|
||||||
|
'amount_currency_id' => floatval($request->get('amount_currency_id')),
|
||||||
|
'amount_max' => floatval($request->get('amount_max')),
|
||||||
|
'date' => new Carbon($request->get('date')),
|
||||||
|
'user' => Auth::user()->id,
|
||||||
|
'repeat_freq' => $request->get('repeat_freq'),
|
||||||
|
'skip' => intval($request->get('skip')),
|
||||||
|
'automatch' => intval($request->get('automatch')) === 1,
|
||||||
|
'active' => intval($request->get('active')) === 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
$bill = $repository->store($billData);
|
||||||
|
Session::flash('success', 'Bill "' . e($bill->name) . '" stored.');
|
||||||
|
|
||||||
|
return Redirect::route('bills.index');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function update(Bill $bill, BillFormRequest $request, BillRepositoryInterface $repository)
|
||||||
|
{
|
||||||
|
$billData = [
|
||||||
|
'name' => $request->get('name'),
|
||||||
|
'match' => $request->get('match'),
|
||||||
|
'amount_min' => floatval($request->get('amount_min')),
|
||||||
|
'amount_currency_id' => floatval($request->get('amount_currency_id')),
|
||||||
|
'amount_max' => floatval($request->get('amount_max')),
|
||||||
|
'date' => new Carbon($request->get('date')),
|
||||||
|
'user' => Auth::user()->id,
|
||||||
|
'repeat_freq' => $request->get('repeat_freq'),
|
||||||
|
'skip' => intval($request->get('skip')),
|
||||||
|
'automatch' => intval($request->get('automatch')) === 1,
|
||||||
|
'active' => intval($request->get('active')) === 1,
|
||||||
|
];
|
||||||
|
|
||||||
|
$bill = $repository->update($bill, $billData);
|
||||||
|
|
||||||
|
Session::flash('success', 'Bill "' . e($bill->name) . '" updated.');
|
||||||
|
|
||||||
|
return Redirect::route('bills.index');
|
||||||
|
|
||||||
|
|
||||||
|
// $data = Input::except('_token');
|
||||||
|
// $data['active'] = intval(Input::get('active'));
|
||||||
|
// $data['automatch'] = intval(Input::get('automatch'));
|
||||||
|
// $data['user_id'] = Auth::user()->id;
|
||||||
|
//
|
||||||
|
// // always validate:
|
||||||
|
// $messages = $this->_repository->validate($data);
|
||||||
|
//
|
||||||
|
// // flash messages:
|
||||||
|
// Session::flash('warnings', $messages['warnings']);
|
||||||
|
// Session::flash('successes', $messages['successes']);
|
||||||
|
// Session::flash('errors', $messages['errors']);
|
||||||
|
// if ($messages['errors']->count() > 0) {
|
||||||
|
// Session::flash('error', 'Could not update bill: ' . $messages['errors']->first());
|
||||||
|
//
|
||||||
|
// return Redirect::route('bills.edit', $bill->id)->withInput();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // return to update screen:
|
||||||
|
// if ($data['post_submit_action'] == 'validate_only') {
|
||||||
|
// return Redirect::route('bills.edit', $bill->id)->withInput();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // update
|
||||||
|
// $this->_repository->update($bill, $data);
|
||||||
|
// Session::flash('success', 'Bill "' . e($data['name']) . '" updated.');
|
||||||
|
//
|
||||||
|
// // go back to list
|
||||||
|
// if ($data['post_submit_action'] == 'update') {
|
||||||
|
// return Redirect::route('bills.index');
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // go back to update screen.
|
||||||
|
// return Redirect::route('bills.edit', $bill->id)->withInput(['post_submit_action' => 'return_to_edit']);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,6 +1,7 @@
|
|||||||
<?php namespace FireflyIII\Http\Controllers;
|
<?php namespace FireflyIII\Http\Controllers;
|
||||||
|
|
||||||
use Auth;
|
use Auth;
|
||||||
|
use Carbon\Carbon;
|
||||||
use FireflyIII\Http\Requests;
|
use FireflyIII\Http\Requests;
|
||||||
use FireflyIII\Http\Requests\CategoryFormRequest;
|
use FireflyIII\Http\Requests\CategoryFormRequest;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
@@ -55,6 +56,26 @@ class CategoryController extends Controller
|
|||||||
return view('categories.show', compact('category', 'journals', 'hideCategory'));
|
return view('categories.show', compact('category', 'journals', 'hideCategory'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function noCategory()
|
||||||
|
{
|
||||||
|
$start = Session::get('start', Carbon::now()->startOfMonth());
|
||||||
|
$end = Session::get('end', Carbon::now()->startOfMonth());
|
||||||
|
$list = Auth::user()
|
||||||
|
->transactionjournals()
|
||||||
|
->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
|
||||||
|
->whereNull('category_transaction_journal.id')
|
||||||
|
->before($end)
|
||||||
|
->after($start)
|
||||||
|
->orderBy('transaction_journals.date')
|
||||||
|
->get(['transaction_journals.*']);
|
||||||
|
$subTitle = 'Transactions without a category in ' . $start->format('F Y');
|
||||||
|
|
||||||
|
return View::make('categories.noCategory', compact('list', 'subTitle'));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Category $category
|
* @param Category $category
|
||||||
*
|
*
|
||||||
|
@@ -12,6 +12,7 @@ use FireflyIII\Models\Bill;
|
|||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\LimitRepetition;
|
||||||
use FireflyIII\Models\TransactionJournal;
|
use FireflyIII\Models\TransactionJournal;
|
||||||
|
use FireflyIII\Models\Transaction;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||||
use Grumpydictator\Gchart\GChart;
|
use Grumpydictator\Gchart\GChart;
|
||||||
@@ -21,6 +22,7 @@ use Preferences;
|
|||||||
use Response;
|
use Response;
|
||||||
use Session;
|
use Session;
|
||||||
use Steam;
|
use Steam;
|
||||||
|
use Navigation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class GoogleChartController
|
* Class GoogleChartController
|
||||||
@@ -73,6 +75,50 @@ class GoogleChartController extends Controller
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function billOverview(Bill $bill, GChart $chart)
|
||||||
|
{
|
||||||
|
|
||||||
|
$chart->addColumn('Date', 'date');
|
||||||
|
$chart->addColumn('Max amount', 'number');
|
||||||
|
$chart->addColumn('Min amount', 'number');
|
||||||
|
$chart->addColumn('Current entry', 'number');
|
||||||
|
|
||||||
|
// get first transaction or today for start:
|
||||||
|
$first = $bill->transactionjournals()->orderBy('date', 'ASC')->first();
|
||||||
|
if ($first) {
|
||||||
|
$start = $first->date;
|
||||||
|
} else {
|
||||||
|
$start = new Carbon;
|
||||||
|
}
|
||||||
|
$end = new Carbon;
|
||||||
|
while ($start <= $end) {
|
||||||
|
$result = $bill->transactionjournals()->before($end)->after($start)->first();
|
||||||
|
if ($result) {
|
||||||
|
/** @var Transaction $tr */
|
||||||
|
foreach($result->transactions()->get() as $tr) {
|
||||||
|
if(floatval($tr->amount) > 0) {
|
||||||
|
$amount = floatval($tr->amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$amount = 0;
|
||||||
|
}
|
||||||
|
unset($result);
|
||||||
|
$chart->addRow(clone $start, $bill->amount_max, $bill->amount_min, $amount);
|
||||||
|
$start = Navigation::addPeriod($start, $bill->repeat_freq, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$chart->generate();
|
||||||
|
|
||||||
|
return Response::json($chart->getData());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Account $account
|
* @param Account $account
|
||||||
|
@@ -67,4 +67,6 @@ class PiggyBankController extends Controller {
|
|||||||
return view('piggy-banks.index', compact('piggyBanks', 'accounts'));
|
return view('piggy-banks.index', compact('piggyBanks', 'accounts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
55
app/Http/Requests/BillFormRequest.php
Normal file
55
app/Http/Requests/BillFormRequest.php
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by PhpStorm.
|
||||||
|
* User: sander
|
||||||
|
* Date: 25/02/15
|
||||||
|
* Time: 12:29
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Http\Requests;
|
||||||
|
|
||||||
|
use Auth;
|
||||||
|
use Input;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BillFormRequest
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Http\Requests
|
||||||
|
*/
|
||||||
|
class BillFormRequest extends Request
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
// Only allow logged in users
|
||||||
|
return Auth::check();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
$nameRule = 'required|between:1,255|uniqueForUser:bills,name';
|
||||||
|
if(intval(Input::get('id')) > 0) {
|
||||||
|
$nameRule .= ','.intval(Input::get('id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'name' => $nameRule,
|
||||||
|
'match' => 'required|between:1,255',
|
||||||
|
'amount_min' => 'required|numeric|min:0.01',
|
||||||
|
'amount_max' => 'required|numeric|min:0.01',
|
||||||
|
'amount_currency_id' => 'required|exists:transaction_currencies,id',
|
||||||
|
'date' => 'required|date',
|
||||||
|
'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly',
|
||||||
|
'skip' => 'required|between:0,31',
|
||||||
|
'automatch' => 'in:1',
|
||||||
|
'active' => 'in:1',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $rules;
|
||||||
|
}
|
||||||
|
}
|
@@ -4,6 +4,7 @@ use DaveJamesMiller\Breadcrumbs\Generator;
|
|||||||
use FireflyIII\Exceptions\FireflyException;
|
use FireflyIII\Exceptions\FireflyException;
|
||||||
use FireflyIII\Models\Account;
|
use FireflyIII\Models\Account;
|
||||||
use FireflyIII\Models\Budget;
|
use FireflyIII\Models\Budget;
|
||||||
|
use FireflyIII\Models\Bill;
|
||||||
use FireflyIII\Models\Category;
|
use FireflyIII\Models\Category;
|
||||||
use FireflyIII\Models\LimitRepetition;
|
use FireflyIII\Models\LimitRepetition;
|
||||||
use FireflyIII\Models\PiggyBank;
|
use FireflyIII\Models\PiggyBank;
|
||||||
|
@@ -95,12 +95,16 @@ Route::group(
|
|||||||
* Bills Controller
|
* Bills Controller
|
||||||
*/
|
*/
|
||||||
Route::get('/bills', ['uses' => 'BillController@index', 'as' => 'bills.index']);
|
Route::get('/bills', ['uses' => 'BillController@index', 'as' => 'bills.index']);
|
||||||
//Route::get('/bills/rescan/{bill}', ['uses' => 'BillController@rescan', 'as' => 'bills.rescan']); # rescan for matching.
|
Route::get('/bills/rescan/{bill}', ['uses' => 'BillController@rescan', 'as' => 'bills.rescan']); # rescan for matching.
|
||||||
Route::get('/bills/create', ['uses' => 'BillController@create', 'as' => 'bills.create']);
|
Route::get('/bills/create', ['uses' => 'BillController@create', 'as' => 'bills.create']);
|
||||||
//Route::get('/bills/edit/{bill}', ['uses' => 'BillController@edit', 'as' => 'bills.edit']);
|
Route::get('/bills/edit/{bill}', ['uses' => 'BillController@edit', 'as' => 'bills.edit']);
|
||||||
// Route::get('/bills/delete/{bill}', ['uses' => 'BillController@delete', 'as' => 'bills.delete']);
|
Route::get('/bills/delete/{bill}', ['uses' => 'BillController@delete', 'as' => 'bills.delete']);
|
||||||
Route::get('/bills/show/{bill}', ['uses' => 'BillController@show', 'as' => 'bills.show']);
|
Route::get('/bills/show/{bill}', ['uses' => 'BillController@show', 'as' => 'bills.show']);
|
||||||
|
|
||||||
|
Route::post('/bills/store', ['uses' => 'BillController@store', 'as' => 'bills.store']);
|
||||||
|
Route::post('/bills/update/{bill}', ['uses' => 'BillController@update', 'as' => 'bills.update']);
|
||||||
|
Route::post('/bills/destroy/{bill}', ['uses' => 'BillController@destroy', 'as' => 'bills.destroy']);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Budget Controller
|
* Budget Controller
|
||||||
*/
|
*/
|
||||||
@@ -155,7 +159,7 @@ Route::group(
|
|||||||
|
|
||||||
Route::get('/chart/reports/income-expenses/{year}', ['uses' => 'GoogleChartController@yearInExp']);
|
Route::get('/chart/reports/income-expenses/{year}', ['uses' => 'GoogleChartController@yearInExp']);
|
||||||
Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']);
|
Route::get('/chart/reports/income-expenses-sum/{year}', ['uses' => 'GoogleChartController@yearInExpSum']);
|
||||||
//Route::get('/chart/bills/{bill}', ['uses' => 'GoogleChartController@billOverview']);
|
Route::get('/chart/bills/{bill}', ['uses' => 'GoogleChartController@billOverview']);
|
||||||
|
|
||||||
// JSON controller
|
// JSON controller
|
||||||
Route::get('/json/expense-accounts', ['uses' => 'JsonController@expenseAccounts', 'as' => 'json.expense-accounts']);
|
Route::get('/json/expense-accounts', ['uses' => 'JsonController@expenseAccounts', 'as' => 'json.expense-accounts']);
|
||||||
|
@@ -10,6 +10,8 @@ use Illuminate\Database\Eloquent\Model;
|
|||||||
class Bill extends Model
|
class Bill extends Model
|
||||||
{
|
{
|
||||||
|
|
||||||
|
protected $fillable = ['name', 'match', 'amount_min','user_id', 'amount_max', 'date', 'repeat_freq', 'skip', 'automatch', 'active',];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
|
@@ -60,6 +60,7 @@ class FireflyServiceProvider extends ServiceProvider
|
|||||||
$this->app->bind('FireflyIII\Repositories\Budget\BudgetRepositoryInterface', 'FireflyIII\Repositories\Budget\BudgetRepository');
|
$this->app->bind('FireflyIII\Repositories\Budget\BudgetRepositoryInterface', 'FireflyIII\Repositories\Budget\BudgetRepository');
|
||||||
$this->app->bind('FireflyIII\Repositories\Category\CategoryRepositoryInterface', 'FireflyIII\Repositories\Category\CategoryRepository');
|
$this->app->bind('FireflyIII\Repositories\Category\CategoryRepositoryInterface', 'FireflyIII\Repositories\Category\CategoryRepository');
|
||||||
$this->app->bind('FireflyIII\Repositories\Journal\JournalRepositoryInterface', 'FireflyIII\Repositories\Journal\JournalRepository');
|
$this->app->bind('FireflyIII\Repositories\Journal\JournalRepositoryInterface', 'FireflyIII\Repositories\Journal\JournalRepository');
|
||||||
|
$this->app->bind('FireflyIII\Repositories\Bill\BillRepositoryInterface', 'FireflyIII\Repositories\Bill\BillRepository');
|
||||||
|
|
||||||
$this->app->bind('FireflyIII\Helpers\Report\ReportHelperInterface', 'FireflyIII\Helpers\Report\ReportHelper');
|
$this->app->bind('FireflyIII\Helpers\Report\ReportHelperInterface', 'FireflyIII\Helpers\Report\ReportHelper');
|
||||||
$this->app->bind('FireflyIII\Helpers\Report\ReportQueryInterface', 'FireflyIII\Helpers\Report\ReportQuery');
|
$this->app->bind('FireflyIII\Helpers\Report\ReportQueryInterface', 'FireflyIII\Helpers\Report\ReportQuery');
|
||||||
|
198
app/Repositories/Bill/BillRepository.php
Normal file
198
app/Repositories/Bill/BillRepository.php
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by PhpStorm.
|
||||||
|
* User: sander
|
||||||
|
* Date: 25/02/15
|
||||||
|
* Time: 07:40
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Repositories\Bill;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Models\Bill;
|
||||||
|
use FireflyIII\Models\TransactionJournal;
|
||||||
|
use Navigation;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class BillRepository
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Repositories\Bill
|
||||||
|
*/
|
||||||
|
class BillRepository implements BillRepositoryInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return Carbon
|
||||||
|
*/
|
||||||
|
public function nextExpectedMatch(Bill $bill)
|
||||||
|
{
|
||||||
|
$finalDate = null;
|
||||||
|
if ($bill->active == 0) {
|
||||||
|
return $finalDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* $today is the start of the next period, to make sure FF3 won't miss anything
|
||||||
|
* when the current period has a transaction journal.
|
||||||
|
*/
|
||||||
|
$today = Navigation::addPeriod(new Carbon, $bill->repeat_freq, 0);
|
||||||
|
|
||||||
|
$skip = $bill->skip + 1;
|
||||||
|
$start = Navigation::startOfPeriod(new Carbon, $bill->repeat_freq);
|
||||||
|
/*
|
||||||
|
* go back exactly one month/week/etc because FF3 does not care about 'next'
|
||||||
|
* bills if they're too far into the past.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$counter = 0;
|
||||||
|
while ($start <= $today) {
|
||||||
|
if (($counter % $skip) == 0) {
|
||||||
|
// do something.
|
||||||
|
$end = Navigation::endOfPeriod(clone $start, $bill->repeat_freq);
|
||||||
|
$journalCount = $bill->transactionjournals()->before($end)->after($start)->count();
|
||||||
|
if ($journalCount == 0) {
|
||||||
|
$finalDate = clone $start;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add period for next round!
|
||||||
|
$start = Navigation::addPeriod($start, $bill->repeat_freq, 0);
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $finalDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
* @param TransactionJournal $journal
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function scan(Bill $bill, TransactionJournal $journal)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Match words.
|
||||||
|
*/
|
||||||
|
$wordMatch = false;
|
||||||
|
$matches = explode(',', $bill->match);
|
||||||
|
$description = strtolower($journal->description);
|
||||||
|
Log::debug('Now scanning ' . $description);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attach expense account to description for more narrow matching.
|
||||||
|
*/
|
||||||
|
if (count($journal->transactions) < 2) {
|
||||||
|
$transactions = $journal->transactions()->get();
|
||||||
|
} else {
|
||||||
|
$transactions = $journal->transactions;
|
||||||
|
}
|
||||||
|
/** @var Transaction $transaction */
|
||||||
|
foreach ($transactions as $transaction) {
|
||||||
|
/** @var Account $account */
|
||||||
|
$account = $transaction->account()->first();
|
||||||
|
/** @var AccountType $type */
|
||||||
|
$type = $account->accountType()->first();
|
||||||
|
if ($type->type == 'Expense account' || $type->type == 'Beneficiary account') {
|
||||||
|
$description .= ' ' . strtolower($account->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log::debug('Final description: ' . $description);
|
||||||
|
Log::debug('Matches searched: ' . join(':', $matches));
|
||||||
|
|
||||||
|
$count = 0;
|
||||||
|
foreach ($matches as $word) {
|
||||||
|
if (!(strpos($description, strtolower($word)) === false)) {
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($count >= count($matches)) {
|
||||||
|
$wordMatch = true;
|
||||||
|
Log::debug('word match is true');
|
||||||
|
} else {
|
||||||
|
Log::debug('Count: ' . $count.', count(matches): ' . count($matches));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Match amount.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$amountMatch = false;
|
||||||
|
if (count($transactions) > 1) {
|
||||||
|
|
||||||
|
$amount = max(floatval($transactions[0]->amount), floatval($transactions[1]->amount));
|
||||||
|
$min = floatval($bill->amount_min);
|
||||||
|
$max = floatval($bill->amount_max);
|
||||||
|
if ($amount >= $min && $amount <= $max) {
|
||||||
|
$amountMatch = true;
|
||||||
|
Log::debug('Amount match is true!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If both, update!
|
||||||
|
*/
|
||||||
|
if ($wordMatch && $amountMatch) {
|
||||||
|
Log::debug('TOTAL match is true!');
|
||||||
|
$journal->bill()->associate($bill);
|
||||||
|
$journal->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return Bill
|
||||||
|
*/
|
||||||
|
public function store(array $data)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
$bill = Bill::create(
|
||||||
|
[
|
||||||
|
'name' => $data['name'],
|
||||||
|
'match' => $data['match'],
|
||||||
|
'amount_min' => $data['amount_min'],
|
||||||
|
'user_id' => $data['user'],
|
||||||
|
'amount_max' => $data['amount_max'],
|
||||||
|
'date' => $data['date'],
|
||||||
|
'repeat_freq' => $data['repeat_freq'],
|
||||||
|
'skip' => $data['skip'],
|
||||||
|
'automatch' => $data['automatch'],
|
||||||
|
'active' => $data['active'],
|
||||||
|
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $bill;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return Bill|static
|
||||||
|
*/
|
||||||
|
public function update(Bill $bill, array $data)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
$bill->name = $data['name'];
|
||||||
|
$bill->match = $data['match'];
|
||||||
|
$bill->amount_min = $data['amount_min'];
|
||||||
|
$bill->amount_max = $data['amount_max'];
|
||||||
|
$bill->date = $data['date'];
|
||||||
|
$bill->repeat_freq = $data['repeat_freq'];
|
||||||
|
$bill->skip = $data['skip'];
|
||||||
|
$bill->automatch = $data['automatch'];
|
||||||
|
$bill->active = $data['active'];
|
||||||
|
$bill->save();
|
||||||
|
|
||||||
|
return $bill;
|
||||||
|
}
|
||||||
|
}
|
51
app/Repositories/Bill/BillRepositoryInterface.php
Normal file
51
app/Repositories/Bill/BillRepositoryInterface.php
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Created by PhpStorm.
|
||||||
|
* User: sander
|
||||||
|
* Date: 25/02/15
|
||||||
|
* Time: 07:40
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace FireflyIII\Repositories\Bill;
|
||||||
|
|
||||||
|
use FireflyIII\Models\Bill;
|
||||||
|
use FireflyIII\Models\TransactionJournal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface BillRepositoryInterface
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Repositories\Bill
|
||||||
|
*/
|
||||||
|
interface BillRepositoryInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
*
|
||||||
|
* @return Carbon|null
|
||||||
|
*/
|
||||||
|
public function nextExpectedMatch(Bill $bill);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return Bill
|
||||||
|
*/
|
||||||
|
public function store(array $data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function update(Bill $bill, array $data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Bill $bill
|
||||||
|
* @param TransactionJournal $journal
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function scan(Bill $bill, TransactionJournal $journal);
|
||||||
|
|
||||||
|
}
|
@@ -16,6 +16,47 @@ use View;
|
|||||||
*/
|
*/
|
||||||
class ExpandedForm
|
class ExpandedForm
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $name
|
||||||
|
* @param null $value
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function integer($name, $value = null, array $options = [])
|
||||||
|
{
|
||||||
|
$label = $this->label($name, $options);
|
||||||
|
$options = $this->expandOptionArray($name, $label, $options);
|
||||||
|
$classes = $this->getHolderClasses($name);
|
||||||
|
$value = $this->fillFieldValue($name, $value);
|
||||||
|
$options['step'] = '1';
|
||||||
|
$html = \View::make('form.integer', compact('classes', 'name', 'label', 'value', 'options'))->render();
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $name
|
||||||
|
* @param null $value
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function tags($name, $value = null, array $options = [])
|
||||||
|
{
|
||||||
|
$label = $this->label($name, $options);
|
||||||
|
$options = $this->expandOptionArray($name, $label, $options);
|
||||||
|
$classes = $this->getHolderClasses($name);
|
||||||
|
$value = $this->fillFieldValue($name, $value);
|
||||||
|
$options['data-role'] = 'tagsinput';
|
||||||
|
$html = \View::make('form.tags', compact('classes', 'name', 'label', 'value', 'options'))->render();
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $name
|
* @param $name
|
||||||
* @param null $value
|
* @param null $value
|
||||||
|
@@ -15,31 +15,93 @@ class Navigation
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Carbon $date
|
* @param Carbon $theDate
|
||||||
* @param $repeatFrequency
|
* @param $repeatFreq
|
||||||
|
* @param $skip
|
||||||
*
|
*
|
||||||
* @return string
|
* @return \Carbon\Carbon
|
||||||
* @throws FireflyException
|
* @throws FireflyException
|
||||||
*/
|
*/
|
||||||
public function periodShow(Carbon $date, $repeatFrequency)
|
public function addPeriod(Carbon $theDate, $repeatFreq, $skip)
|
||||||
{
|
{
|
||||||
$formatMap = [
|
$date = clone $theDate;
|
||||||
'daily' => 'j F Y',
|
$add = ($skip + 1);
|
||||||
'week' => '\W\e\e\k W, Y',
|
|
||||||
'weekly' => '\W\e\e\k W, Y',
|
|
||||||
'quarter' => 'F Y',
|
|
||||||
'month' => 'F Y',
|
|
||||||
'monthly' => 'F Y',
|
|
||||||
'year' => 'Y',
|
|
||||||
'yearly' => 'Y',
|
|
||||||
|
|
||||||
|
$functionMap = [
|
||||||
|
'daily' => 'addDays',
|
||||||
|
'weekly' => 'addWeeks',
|
||||||
|
'week' => 'addWeeks',
|
||||||
|
'month' => 'addMonths',
|
||||||
|
'monthly' => 'addMonths',
|
||||||
|
'quarter' => 'addMonths',
|
||||||
|
'quarterly' => 'addMonths',
|
||||||
|
'half-year' => 'addMonths',
|
||||||
|
'year' => 'addYears',
|
||||||
|
'yearly' => 'addYears',
|
||||||
];
|
];
|
||||||
if (isset($formatMap[$repeatFrequency])) {
|
$modifierMap = [
|
||||||
return $date->format($formatMap[$repeatFrequency]);
|
'quarter' => 3,
|
||||||
|
'quarterly' => 3,
|
||||||
|
'half-year' => 6,
|
||||||
|
];
|
||||||
|
if (!isset($functionMap[$repeatFreq])) {
|
||||||
|
throw new FireflyException('Cannot do addPeriod for $repeat_freq "' . $repeatFreq . '"');
|
||||||
}
|
}
|
||||||
throw new FireflyException('No date formats for frequency "' . $repeatFrequency . '"!');
|
if (isset($modifierMap[$repeatFreq])) {
|
||||||
|
$add = $add * $modifierMap[$repeatFreq];
|
||||||
|
}
|
||||||
|
$function = $functionMap[$repeatFreq];
|
||||||
|
$date->$function($add);
|
||||||
|
|
||||||
|
return $date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $theCurrentEnd
|
||||||
|
* @param $repeatFreq
|
||||||
|
*
|
||||||
|
* @return Carbon
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function endOfPeriod(Carbon $theCurrentEnd, $repeatFreq)
|
||||||
|
{
|
||||||
|
$currentEnd = clone $theCurrentEnd;
|
||||||
|
|
||||||
|
$functionMap = [
|
||||||
|
'daily' => 'addDay',
|
||||||
|
'week' => 'addWeek',
|
||||||
|
'weekly' => 'addWeek',
|
||||||
|
'month' => 'addMonth',
|
||||||
|
'monthly' => 'addMonth',
|
||||||
|
'quarter' => 'addMonths',
|
||||||
|
'quarterly' => 'addMonths',
|
||||||
|
'half-year' => 'addMonths',
|
||||||
|
'year' => 'addYear',
|
||||||
|
'yearly' => 'addYear',
|
||||||
|
];
|
||||||
|
$modifierMap = [
|
||||||
|
'quarter' => 3,
|
||||||
|
'quarterly' => 3,
|
||||||
|
'half-year' => 6,
|
||||||
|
];
|
||||||
|
|
||||||
|
$subDay = ['week', 'weekly', 'month', 'monthly', 'quarter', 'quarterly', 'half-year', 'year', 'yearly'];
|
||||||
|
|
||||||
|
if (!isset($functionMap[$repeatFreq])) {
|
||||||
|
throw new FireflyException('Cannot do endOfPeriod for $repeat_freq ' . $repeatFreq);
|
||||||
|
}
|
||||||
|
$function = $functionMap[$repeatFreq];
|
||||||
|
if (isset($modifierMap[$repeatFreq])) {
|
||||||
|
$currentEnd->$function($modifierMap[$repeatFreq]);
|
||||||
|
} else {
|
||||||
|
$currentEnd->$function();
|
||||||
|
}
|
||||||
|
if (in_array($repeatFreq, $subDay)) {
|
||||||
|
$currentEnd->subDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $currentEnd;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $range
|
* @param $range
|
||||||
@@ -154,6 +216,72 @@ class Navigation
|
|||||||
throw new FireflyException('No _periodName() for range "' . $range . '"');
|
throw new FireflyException('No _periodName() for range "' . $range . '"');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $date
|
||||||
|
* @param $repeatFrequency
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function periodShow(Carbon $date, $repeatFrequency)
|
||||||
|
{
|
||||||
|
$formatMap = [
|
||||||
|
'daily' => 'j F Y',
|
||||||
|
'week' => '\W\e\e\k W, Y',
|
||||||
|
'weekly' => '\W\e\e\k W, Y',
|
||||||
|
'quarter' => 'F Y',
|
||||||
|
'month' => 'F Y',
|
||||||
|
'monthly' => 'F Y',
|
||||||
|
'year' => 'Y',
|
||||||
|
'yearly' => 'Y',
|
||||||
|
|
||||||
|
];
|
||||||
|
if (isset($formatMap[$repeatFrequency])) {
|
||||||
|
return $date->format($formatMap[$repeatFrequency]);
|
||||||
|
}
|
||||||
|
throw new FireflyException('No date formats for frequency "' . $repeatFrequency . '"!');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Carbon $theDate
|
||||||
|
* @param $repeatFreq
|
||||||
|
*
|
||||||
|
* @return Carbon
|
||||||
|
* @throws FireflyException
|
||||||
|
*/
|
||||||
|
public function startOfPeriod(Carbon $theDate, $repeatFreq)
|
||||||
|
{
|
||||||
|
$date = clone $theDate;
|
||||||
|
|
||||||
|
$functionMap = [
|
||||||
|
'daily' => 'startOfDay',
|
||||||
|
'week' => 'startOfWeek',
|
||||||
|
'weekly' => 'startOfWeek',
|
||||||
|
'month' => 'startOfMonth',
|
||||||
|
'monthly' => 'startOfMonth',
|
||||||
|
'quarter' => 'firstOfQuarter',
|
||||||
|
'quartly' => 'firstOfQuarter',
|
||||||
|
'year' => 'startOfYear',
|
||||||
|
'yearly' => 'startOfYear',
|
||||||
|
];
|
||||||
|
if (isset($functionMap[$repeatFreq])) {
|
||||||
|
$function = $functionMap[$repeatFreq];
|
||||||
|
$date->$function();
|
||||||
|
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
|
if ($repeatFreq == 'half-year') {
|
||||||
|
$month = intval($date->format('m'));
|
||||||
|
$date->startOfYear();
|
||||||
|
if ($month >= 7) {
|
||||||
|
$date->addMonths(6);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
|
throw new FireflyException('Cannot do startOfPeriod for $repeat_freq ' . $repeatFreq);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $range
|
* @param $range
|
||||||
* @param Carbon $start
|
* @param Carbon $start
|
||||||
@@ -224,47 +352,5 @@ class Navigation
|
|||||||
throw new FireflyException('updateStartDate cannot handle $range ' . $range);
|
throw new FireflyException('updateStartDate cannot handle $range ' . $range);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Carbon $theDate
|
|
||||||
* @param $repeatFreq
|
|
||||||
* @param $skip
|
|
||||||
*
|
|
||||||
* @return \Carbon\Carbon
|
|
||||||
* @throws FireflyException
|
|
||||||
*/
|
|
||||||
public function addPeriod(Carbon $theDate, $repeatFreq, $skip)
|
|
||||||
{
|
|
||||||
$date = clone $theDate;
|
|
||||||
$add = ($skip + 1);
|
|
||||||
|
|
||||||
$functionMap = [
|
|
||||||
'daily' => 'addDays',
|
|
||||||
'weekly' => 'addWeeks',
|
|
||||||
'week' => 'addWeeks',
|
|
||||||
'month' => 'addMonths',
|
|
||||||
'monthly' => 'addMonths',
|
|
||||||
'quarter' => 'addMonths',
|
|
||||||
'quarterly' => 'addMonths',
|
|
||||||
'half-year' => 'addMonths',
|
|
||||||
'year' => 'addYears',
|
|
||||||
'yearly' => 'addYears',
|
|
||||||
];
|
|
||||||
$modifierMap = [
|
|
||||||
'quarter' => 3,
|
|
||||||
'quarterly' => 3,
|
|
||||||
'half-year' => 6,
|
|
||||||
];
|
|
||||||
if (!isset($functionMap[$repeatFreq])) {
|
|
||||||
throw new FireflyException('Cannot do addPeriod for $repeat_freq "' . $repeatFreq . '"');
|
|
||||||
}
|
|
||||||
if (isset($modifierMap[$repeatFreq])) {
|
|
||||||
$add = $add * $modifierMap[$repeatFreq];
|
|
||||||
}
|
|
||||||
$function = $functionMap[$repeatFreq];
|
|
||||||
$date->$function($add);
|
|
||||||
|
|
||||||
return $date;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@@ -392,7 +392,7 @@ class TestDataSeeder extends Seeder
|
|||||||
$user = User::whereEmail('thegrumpydictator@gmail.com')->first();
|
$user = User::whereEmail('thegrumpydictator@gmail.com')->first();
|
||||||
// bill
|
// bill
|
||||||
Bill::create(
|
Bill::create(
|
||||||
['user_id' => $user->id, 'name' => 'Rent', 'match' => 'rent,landlord', 'amount_min' => 700, 'amount_max' => 900, 'date' => $this->som,
|
['user_id' => $user->id, 'name' => 'Rent', 'match' => 'rent,land,lord', 'amount_min' => 700, 'amount_max' => 900, 'date' => $this->som,
|
||||||
'active' => 1, 'automatch' => 1, 'repeat_freq' => 'monthly', 'skip' => 0,]
|
'active' => 1, 'automatch' => 1, 'repeat_freq' => 'monthly', 'skip' => 0,]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
47
public/css/bootstrap-tagsinput.css
vendored
Normal file
47
public/css/bootstrap-tagsinput.css
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.bootstrap-tagsinput {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||||
|
display: inline-block;
|
||||||
|
padding: 4px 6px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #555;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 4px;
|
||||||
|
max-width: 100%;
|
||||||
|
width:100%;
|
||||||
|
line-height: 22px;
|
||||||
|
cursor: text;
|
||||||
|
}
|
||||||
|
.bootstrap-tagsinput input {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: auto !important;
|
||||||
|
max-width: inherit;
|
||||||
|
}
|
||||||
|
.bootstrap-tagsinput input:focus {
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.bootstrap-tagsinput .tag {
|
||||||
|
margin-right: 2px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.bootstrap-tagsinput .tag [data-role="remove"] {
|
||||||
|
margin-left: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.bootstrap-tagsinput .tag [data-role="remove"]:after {
|
||||||
|
content: "x";
|
||||||
|
padding: 0px 2px;
|
||||||
|
}
|
||||||
|
.bootstrap-tagsinput .tag [data-role="remove"]:hover {
|
||||||
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.bootstrap-tagsinput .tag [data-role="remove"]:hover:active {
|
||||||
|
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
|
||||||
|
}
|
7
public/js/bills.js
Normal file
7
public/js/bills.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
$(document).ready(function () {
|
||||||
|
|
||||||
|
if (typeof(googleComboChart) === 'function' && typeof(billID) !== 'undefined') {
|
||||||
|
googleComboChart('chart/bills/' + billID, 'bill-overview');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
7
public/js/bootstrap-tagsinput.min.js
vendored
Executable file
7
public/js/bootstrap-tagsinput.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
63
resources/views/bills/create.blade.php
Normal file
63
resources/views/bills/create.blade.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
@extends('layouts.default')
|
||||||
|
@section('content')
|
||||||
|
{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName()) !!}
|
||||||
|
{!! Form::open(['class' => 'form-horizontal','id' => 'store','url' => route('bills.store')]) !!}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-md-12 col-sm-6">
|
||||||
|
<!-- 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">
|
||||||
|
{!! ExpandedForm::text('name') !!}
|
||||||
|
{!! ExpandedForm::tags('match') !!}
|
||||||
|
{!! ExpandedForm::amount('amount_min') !!}
|
||||||
|
{!! ExpandedForm::amount('amount_max') !!}
|
||||||
|
{!! ExpandedForm::date('date',Carbon\Carbon::now()->addDay()->format('Y-m-d')) !!}
|
||||||
|
{!! ExpandedForm::select('repeat_freq',$periods,'monthly') !!}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="btn btn-lg btn-success">
|
||||||
|
<i class="fa fa-plus-circle"></i> Store new bill
|
||||||
|
</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">
|
||||||
|
{!! ExpandedForm::integer('skip',0) !!}
|
||||||
|
{!! ExpandedForm::checkbox('automatch',1,true) !!}
|
||||||
|
{!! ExpandedForm::checkbox('active',1,true) !!}
|
||||||
|
</div>
|
||||||
|
</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">
|
||||||
|
{!! ExpandedForm::optionsList('create','bill') !!}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!! Form::close() !!}
|
||||||
|
|
||||||
|
|
||||||
|
@stop
|
||||||
|
@section('styles')
|
||||||
|
<link href="css/bootstrap-tagsinput.css" type="text/css" rel="stylesheet" media="all">
|
||||||
|
@stop
|
||||||
|
@section('scripts')
|
||||||
|
<script type="text/javascript" src="js/bootstrap-tagsinput.min.js"></script>
|
||||||
|
@stop
|
37
resources/views/bills/delete.blade.php
Normal file
37
resources/views/bills/delete.blade.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
@extends('layouts.default')
|
||||||
|
@section('content')
|
||||||
|
{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $bill) !!}
|
||||||
|
{!! Form::open(['class' => 'form-horizontal','id' => 'destroy','url' => route('bills.destroy',$bill->id)]) !!}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-md-12 col-sm-12">
|
||||||
|
<div class="panel panel-red">
|
||||||
|
<div class="panel-heading">
|
||||||
|
Delete bill "{{{$bill->name}}}"
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>
|
||||||
|
Are you sure?
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="btn btn-default btn-danger">Delete permanently</button>
|
||||||
|
<a href="{{URL::previous()}}" class="btn-default btn">Cancel</a >
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-8">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{!! Form::close() !!}
|
||||||
|
@stop
|
64
resources/views/bills/edit.blade.php
Normal file
64
resources/views/bills/edit.blade.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
@extends('layouts.default')
|
||||||
|
@section('content')
|
||||||
|
{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $bill) !!}
|
||||||
|
{!! Form::model($bill, ['class' => 'form-horizontal','id' => 'update','url' => route('bills.update', $bill->id)]) !!}
|
||||||
|
|
||||||
|
<input type="hidden" name="id" value="{{$bill->id}}" />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-md-12 col-sm-6">
|
||||||
|
<!-- 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">
|
||||||
|
{!! ExpandedForm::text('name') !!}
|
||||||
|
{!! ExpandedForm::tags('match') !!}
|
||||||
|
{!! ExpandedForm::amount('amount_min') !!}
|
||||||
|
{!! ExpandedForm::amount('amount_max') !!}
|
||||||
|
{!! ExpandedForm::date('date',$bill->date->format('Y-m-d')) !!}
|
||||||
|
{!! ExpandedForm::select('repeat_freq',$periods) !!}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="btn btn-lg btn-success">
|
||||||
|
<i class="fa fa-plus-circle"></i> Update bill
|
||||||
|
</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">
|
||||||
|
{!! ExpandedForm::integer('skip') !!}
|
||||||
|
{!! ExpandedForm::checkbox('automatch',1) !!}
|
||||||
|
{!! ExpandedForm::checkbox('active',1) !!}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<i class="fa fa-bolt"></i> Options
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
{!! ExpandedForm::optionsList('update','bill') !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!! Form::close() !!}
|
||||||
|
|
||||||
|
|
||||||
|
@stop
|
||||||
|
@section('styles')
|
||||||
|
<link href="css/bootstrap-tagsinput.css" type="text/css" rel="stylesheet" media="all">
|
||||||
|
@stop
|
||||||
|
@section('scripts')
|
||||||
|
<script type="text/javascript" src="js/bootstrap-tagsinput.min.js"></script>
|
||||||
|
@stop
|
32
resources/views/bills/index.blade.php
Normal file
32
resources/views/bills/index.blade.php
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
@extends('layouts.default')
|
||||||
|
@section('content')
|
||||||
|
{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName()) !!}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-sm-12 col-md-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<i class="fa {{$mainTitleIcon}}"></i> {{{$title}}}
|
||||||
|
|
||||||
|
<!-- ACTIONS MENU -->
|
||||||
|
<div class="pull-right">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
|
||||||
|
Actions
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu pull-right" role="menu">
|
||||||
|
<li><a href="{{route('bills.create')}}"><i class="fa fa-plus fa-fw"></i> New bill</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
@include('list.bills')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@stop
|
||||||
|
@section('scripts')
|
||||||
|
|
||||||
|
@stop
|
116
resources/views/bills/show.blade.php
Normal file
116
resources/views/bills/show.blade.php
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
@extends('layouts.default')
|
||||||
|
@section('content')
|
||||||
|
{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $bill) !!}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-6 col-sm-12 col-md-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<i class="fa fa-rotate-right"></i> {{{$bill->name}}}
|
||||||
|
|
||||||
|
@if($bill->active)
|
||||||
|
<span class="glyphicon glyphicon-ok" title="Active"></span>
|
||||||
|
@else
|
||||||
|
<span class="glyphicon glyphicon-remove" title="Inactive"></span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($bill->automatch)
|
||||||
|
<span class="glyphicon glyphicon-ok" title="Automatically matched by Firefly"></span>
|
||||||
|
@else
|
||||||
|
<span class="glyphicon glyphicon-remove" title="Not automatically matched by Firefly"></span>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- ACTIONS MENU -->
|
||||||
|
<div class="pull-right">
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown">
|
||||||
|
Actions
|
||||||
|
<span class="caret"></span>
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu pull-right" role="menu">
|
||||||
|
<li><a href="{{route('bills.edit',$bill->id)}}"><span class="glyphicon glyphicon-pencil"></span> edit</a></li>
|
||||||
|
<li><a href="{{route('bills.delete',$bill->id)}}"><span class="glyphicon glyphicon-trash"></span> delete</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
Matching on
|
||||||
|
@foreach(explode(',',$bill->match) as $word)
|
||||||
|
<span class="label label-info">{{{$word}}}</span>
|
||||||
|
@endforeach
|
||||||
|
between {!! Amount::format($bill->amount_min) !!} and {!! Amount::format($bill->amount_max) !!}.
|
||||||
|
Repeats {!! $bill->repeat_freq !!}.</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Next expected match</td>
|
||||||
|
<td>
|
||||||
|
@if($bill->nextExpectedMatch)
|
||||||
|
{{$bill->nextExpectedMatch->format('j F Y')}}
|
||||||
|
@else
|
||||||
|
<em>Unknown</em>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 col-sm-12 col-md-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
More
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>
|
||||||
|
<a href="{{route('bills.rescan',$bill->id)}}" class="btn btn-default">Rescan old transactions</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-sm-12 col-md-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
Chart
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div id="bill-overview"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-12 col-sm-12 col-md-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
Connected transaction journals
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
@include('list.journals-full')
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@stop
|
||||||
|
|
||||||
|
@section('scripts')
|
||||||
|
<script type="text/javascript">
|
||||||
|
var billID = {{{$bill->id}}};
|
||||||
|
var currencyCode = '{{Amount::getCurrencyCode()}}';
|
||||||
|
</script>
|
||||||
|
<!-- load the libraries and scripts necessary for Google Charts: -->
|
||||||
|
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
|
||||||
|
<script type="text/javascript" src="js/gcharts.options.js"></script>
|
||||||
|
<script type="text/javascript" src="js/gcharts.js"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="js/bills.js"></script>
|
||||||
|
@stop
|
71
resources/views/list/bills.blade.php
Normal file
71
resources/views/list/bills.blade.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<table class="table table-bordered table-striped">
|
||||||
|
<tr>
|
||||||
|
<th> </th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Matches on</th>
|
||||||
|
<th>Matching amount</th>
|
||||||
|
<th>Last seen match</th>
|
||||||
|
<th>Next expected match</th>
|
||||||
|
<th>Is active</th>
|
||||||
|
<th>Will be automatched</th>
|
||||||
|
<th>Repeats every</th>
|
||||||
|
</tr>
|
||||||
|
@foreach($bills as $entry)
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group btn-group-xs">
|
||||||
|
<a href="{{route('bills.edit',$entry->id)}}" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-pencil"></span></a>
|
||||||
|
<a href="{{route('bills.delete',$entry->id)}}" class="btn btn-danger btn-xs"><span class="glyphicon glyphicon-trash"></span></a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{route('bills.show',$entry->id)}}" title="{{{$entry->name}}}">{{{$entry->name}}}</a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@foreach(explode(',',$entry->match) as $match)
|
||||||
|
<span class="label label-info">{{{$match}}}</span>
|
||||||
|
@endforeach
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{!! Amount::format($entry->amount_min) !!}
|
||||||
|
—
|
||||||
|
{!! Amount::format($entry->amount_max) !!}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($entry->lastFoundMatch)
|
||||||
|
{{$entry->lastFoundMatch->format('j F Y')}}
|
||||||
|
@else
|
||||||
|
<em>Unknown</em>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($entry->nextExpectedMatch)
|
||||||
|
{{$entry->nextExpectedMatch->format('j F Y')}}
|
||||||
|
@else
|
||||||
|
<em>Unknown</em>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($entry->active)
|
||||||
|
<i class="fa fa-fw fa-check"></i>
|
||||||
|
@else
|
||||||
|
<i class="fa fa-fw fa-ban"></i>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($entry->automatch)
|
||||||
|
<i class="fa fa-fw fa-check"></i>
|
||||||
|
@else
|
||||||
|
<i class="fa fa-fw fa-ban"></i>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{{$entry->repeat_freq}}}
|
||||||
|
@if($entry->skip > 0)
|
||||||
|
skips over {{$entry->skip}}
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
@endforeach
|
||||||
|
</table>
|
Reference in New Issue
Block a user