Merge branch 'release/3.3.3'

This commit is contained in:
James Cole
2015-03-27 09:36:03 +01:00
84 changed files with 1149 additions and 420 deletions

View File

@@ -5,6 +5,11 @@ use FireflyIII\Events\Event;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Queue\SerializesModels;
/**
* Class JournalCreated
*
* @package FireflyIII\Events
*/
class JournalCreated extends Event {
use SerializesModels;
@@ -23,9 +28,6 @@ class JournalCreated extends Event {
$this->journal = $journal;
$this->piggyBankId = $piggyBankId;
}
}

View File

@@ -38,6 +38,9 @@ class ConnectJournalToPiggyBank
/** @var TransactionJournal $journal */
$journal = $event->journal;
$piggyBankId = $event->piggyBankId;
if(intval($piggyBankId) < 1) {
return;
}
Log::debug('JournalCreated event: ' . $journal->id . ', ' . $piggyBankId);
@@ -63,6 +66,7 @@ class ConnectJournalToPiggyBank
// update piggy bank rep for date of transaction journal.
$repetition = $piggyBank->piggyBankRepetitions()->relevantOnDate($journal->date)->first();
if (is_null($repetition)) {
Log::debug('Found no repetition for piggy bank for date '.$journal->date->format('Y M d'));
return;
}

View File

@@ -25,10 +25,7 @@ class ReminderHelper implements ReminderHelperInterface
*/
public function createReminder(PiggyBank $piggyBank, Carbon $start, Carbon $end)
{
$reminder = Auth::user()->reminders()
->where('remindersable_id', $piggyBank->id)
->onDates($start, $end)
->first();
$reminder = Auth::user()->reminders()->where('remindersable_id', $piggyBank->id)->onDates($start, $end)->first();
if (is_null($reminder)) {
if (!is_null($piggyBank->targetdate)) {
@@ -134,7 +131,8 @@ class ReminderHelper implements ReminderHelperInterface
{
/** @var PiggyBank $piggyBank */
$piggyBank = $reminder->remindersable;
if(is_null($piggyBank)) {
if (is_null($piggyBank)) {
return 'Piggy bank no longer exists.';
}

View File

@@ -87,6 +87,9 @@ class ReportQuery implements ReportQueryInterface
->whereNull('budget_transaction_journal.budget_id')->whereNull('transaction_journals.deleted_at')
->whereNull('otherJournals.deleted_at')
->where('transactions.account_id', $account->id)
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order','ASC')
->orderBy('transaction_journals.id','DESC')
->whereNotNull('transaction_group_transaction_journal.transaction_group_id')
->get(
[

View File

@@ -168,7 +168,7 @@ class AccountController extends Controller
$what = Config::get('firefly.shortNamesByFullName.' . $account->accountType->type);
$journals = $repository->getJournals($account, $page);
$subTitle = 'Details for ' . strtolower(e($account->accountType->type)) . ' "' . e($account->name) . '"';
$journals->setPath('accounts/show/'.$account->id);
return view('accounts.show', compact('account', 'what', 'subTitleIcon', 'journals', 'subTitle'));
}
@@ -196,7 +196,7 @@ class AccountController extends Controller
Session::flash('success', 'New account "' . $account->name . '" stored!');
if (intval(Input::get('create_another')) === 1) {
return Redirect::route('accounts.create', $request->input('what'));
return Redirect::route('accounts.create', $request->input('what'))->withInput();
}
return Redirect::route('accounts.index', $request->input('what'));

View File

@@ -63,7 +63,10 @@ class AuthController extends Controller
);
}
$this->auth->login($this->registrar->create($request->all()));
$data =$request->all();
$data['password'] = bcrypt($data['password']);
$this->auth->login($this->registrar->create($data));
// get the email address
$email = $this->auth->user()->email;

View File

@@ -138,7 +138,12 @@ class BillController extends Controller
*/
public function show(Bill $bill, BillRepositoryInterface $repository)
{
$journals = $bill->transactionjournals()->withRelevantData()->orderBy('date', 'DESC')->get();
$journals = $bill->transactionjournals()->withRelevantData()
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order','ASC')
->orderBy('transaction_journals.id','DESC')
->get();
$bill->nextExpectedMatch = $repository->nextExpectedMatch($bill);
$hideBill = true;
@@ -210,42 +215,6 @@ class BillController extends Controller
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']);
}
}

View File

@@ -133,7 +133,9 @@ class BudgetController extends Controller
->whereNull('budget_transaction_journal.id')
->before($end)
->after($start)
->orderBy('transaction_journals.date')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order','ASC')
->orderBy('transaction_journals.id','DESC')
->get(['transaction_journals.*']);
$subTitle = 'Transactions without a budget in ' . $start->format('F Y');
@@ -152,6 +154,12 @@ class BudgetController extends Controller
return Redirect::route('budgets.index');
}
/**
* @param BudgetFormRequest $request
* @param BudgetRepositoryInterface $repository
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store(BudgetFormRequest $request, BudgetRepositoryInterface $repository)
{
$budgetData = [
@@ -204,6 +212,10 @@ class BudgetController extends Controller
Session::flash('success', 'Budget "' . $budget->name . '" updated.');
if (intval(Input::get('return_to_edit')) === 1) {
return Redirect::route('budgets.edit', $budget->id);
}
return Redirect::route('budgets.index');
}

View File

@@ -88,7 +88,11 @@ class CategoryController extends Controller
$categories->each(
function (Category $category) {
$latest = $category->transactionjournals()->orderBy('date', 'DESC')->first();
$latest = $category->transactionjournals()
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order','ASC')
->orderBy('transaction_journals.id','DESC')
->first();
if ($latest) {
$category->lastActivity = $latest->date;
}
@@ -111,7 +115,9 @@ class CategoryController extends Controller
->whereNull('category_transaction_journal.id')
->before($end)
->after($start)
->orderBy('transaction_journals.date')
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order','ASC')
->orderBy('transaction_journals.id','DESC')
->get(['transaction_journals.*']);
$subTitle = 'Transactions without a category between ' . $start->format('jS F Y') . ' and ' . $end->format('jS F Y');
@@ -129,7 +135,13 @@ class CategoryController extends Controller
$hideCategory = true; // used in list.
$page = intval(Input::get('page'));
$offset = $page > 0 ? $page * 50 : 0;
$set = $category->transactionJournals()->withRelevantData()->take(50)->offset($offset)->orderBy('date', 'DESC')->get(
$set = $category->transactionJournals()->withRelevantData()->take(50)->offset($offset)
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order','ASC')
->orderBy('transaction_journals.id','DESC')
->get(
['transaction_journals.*']
);
$count = $category->transactionJournals()->count();
@@ -154,6 +166,10 @@ class CategoryController extends Controller
$category = $repository->store($categoryData);
Session::flash('success', 'New category "' . $category->name . '" stored!');
if (intval(Input::get('create_another')) === 1) {
return Redirect::route('categories.create')->withInput();
}
if (intval(Input::get('create_another')) === 1) {
return Redirect::route('categories.create');
@@ -181,6 +197,10 @@ class CategoryController extends Controller
Session::flash('success', 'Category "' . $category->name . '" updated.');
if (intval(Input::get('return_to_edit')) === 1) {
return Redirect::route('categories.edit', $category->id);
}
return Redirect::route('categories.index');
}

View File

@@ -148,7 +148,7 @@ class CurrencyController extends Controller
Session::flash('success', 'Currency "' . $currency->name . '" created');
if (intval(Input::get('create_another')) === 1) {
return Redirect::route('currency.create');
return Redirect::route('currency.create')->withInput();
}
return Redirect::route('currency.index');

View File

@@ -235,7 +235,9 @@ class GoogleChartController extends Controller
// query!
$start = Session::get('start', Carbon::now()->startOfMonth());
$end = Session::get('end', Carbon::now()->endOfMonth());
$set = TransactionJournal::leftJoin(
$set = TransactionJournal::
where('transaction_journals.user_id',Auth::user()->id)
->leftJoin(
'transactions',
function (JoinClause $join) {
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('amount', '>', 0);
@@ -247,6 +249,7 @@ class GoogleChartController extends Controller
->leftJoin('categories', 'categories.id', '=', 'category_transaction_journal.category_id')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->before($end)
->where('categories.user_id',Auth::user()->id)
->after($start)
->where('transaction_types.type', 'Withdrawal')
->groupBy('categories.id')
@@ -503,7 +506,7 @@ class GoogleChartController extends Controller
{
// oldest transaction in category:
/** @var TransactionJournal $first */
$start = Session::get('start');
$start = clone Session::get('start');
$chart->addColumn('Period', 'date');
$chart->addColumn('Spent', 'number');
@@ -532,10 +535,13 @@ class GoogleChartController extends Controller
$chart->addColumn('Date', 'date');
$chart->addColumn('Balance', 'number');
$set = \DB::table('piggy_bank_events')->where('piggy_bank_id', $piggyBank->id)->groupBy('date')->get(['date', DB::Raw('SUM(`amount`) AS `sum`')]);
/** @var Collection $set */
$set = DB::table('piggy_bank_events')->where('piggy_bank_id', $piggyBank->id)->groupBy('date')->get(['date', DB::Raw('SUM(`amount`) AS `sum`')]);
$sum = 0;
foreach ($set as $entry) {
$chart->addRow(new Carbon($entry->date), floatval($entry->sum));
$sum += floatval($entry->sum);
$chart->addRow(new Carbon($entry->date), $sum);
}
$chart->generate();

View File

@@ -1,6 +1,5 @@
<?php namespace FireflyIII\Http\Controllers;
use Auth;
use Cache;
use Carbon\Carbon;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -64,6 +63,7 @@ class HomeController extends Controller
$start = Session::get('start', Carbon::now()->startOfMonth());
$end = Session::get('end', Carbon::now()->endOfMonth());
$accounts = $repository->getFrontpageAccounts($frontPage);
$savings = $repository->getSavingsAccounts();
foreach ($accounts as $account) {
$set = $repository->getFrontpageTransactions($account, $start, $end);
@@ -74,7 +74,7 @@ class HomeController extends Controller
// var_dump($transactions);
return view('index', compact('count', 'title', 'subTitle', 'mainTitleIcon', 'transactions'));
return view('index', compact('count', 'title','savings', 'subTitle', 'mainTitleIcon', 'transactions'));
}

View File

@@ -136,14 +136,13 @@ class PiggyBankController extends Controller
return view('piggy-banks.edit', compact('subTitle', 'subTitleIcon', 'piggyBank', 'accounts', 'periods', 'preFilled'));
}
/**
* @return $this
*/
public function index(AccountRepositoryInterface $repository)
{
/** @var Collection $piggyBanks */
$piggyBanks = Auth::user()->piggyBanks()->where('repeats', 0)->get();
$piggyBanks = Auth::user()->piggyBanks()->where('repeats', 0)->orderBy('order', 'ASC')->get();
$accounts = [];
/** @var PiggyBank $piggyBank */
@@ -175,6 +174,22 @@ class PiggyBankController extends Controller
return view('piggy-banks.index', compact('piggyBanks', 'accounts'));
}
/**
* Allow user to order piggy banks.
*/
public function order(PiggyBankRepositoryInterface $repository)
{
$data = Input::get('order');
// set all users piggy banks to zero:
$repository->reset();
if (is_array($data)) {
foreach ($data as $order => $id) {
$repository->setOrder(intval($id), (intval($order) + 1));
}
}
}
/**
* POST add money to piggy bank
@@ -276,7 +291,10 @@ class PiggyBankController extends Controller
}
/**
* @param PiggyBankFormRequest $request
* @param PiggyBankRepositoryInterface $repository
*
* @return $this|\Illuminate\Http\RedirectResponse
*/
public function store(PiggyBankFormRequest $request, PiggyBankRepositoryInterface $repository)
{

View File

@@ -12,6 +12,7 @@ use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use Redirect;
use Session;
use View;
use Input;
/**
* Class RepeatedExpenseController
@@ -144,7 +145,10 @@ class RepeatedExpenseController extends Controller
}
/**
* @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind.
* @param PiggyBankFormRequest $request
* @param PiggyBankRepositoryInterface $repository
*
* @return \Illuminate\Http\RedirectResponse
*/
public function store(PiggyBankFormRequest $request, PiggyBankRepositoryInterface $repository)
{
@@ -159,6 +163,7 @@ class RepeatedExpenseController extends Controller
'reminder' => $request->get('reminder'),
'skip' => intval($request->get('skip')),
'rep_every' => intval($request->get('rep_every')),
'rep_length' => $request->get('rep_length'),
'rep_times' => intval($request->get('rep_times')),
];
@@ -166,6 +171,11 @@ class RepeatedExpenseController extends Controller
Session::flash('success', 'Stored repeated expense "' . e($piggyBank->name) . '".');
if (intval(Input::get('create_another')) === 1) {
return Redirect::route('repeated.create', $request->input('what'))->withInput();
}
return Redirect::route('repeated.index');
}
@@ -194,6 +204,10 @@ class RepeatedExpenseController extends Controller
$piggyBank = $repository->update($repeatedExpense, $piggyBankData);
if (intval(Input::get('return_to_edit')) === 1) {
return Redirect::route('repeated.edit', $piggyBank->id);
}
Session::flash('success', 'Updated repeated expense "' . e($piggyBank->name) . '".');
return Redirect::route('repeated.index');

View File

@@ -15,7 +15,7 @@ use Input;
use Redirect;
use Session;
use View;
use Response;
/**
* Class TransactionController
@@ -180,29 +180,33 @@ class TransactionController extends Controller
case 'withdrawal':
$subTitleIcon = 'fa-long-arrow-left';
$subTitle = 'Expenses';
//$journals = $this->_repository->getWithdrawalsPaginated(50);
$types = ['Withdrawal'];
$types = ['Withdrawal'];
break;
case 'revenue':
case 'deposit':
$subTitleIcon = 'fa-long-arrow-right';
$subTitle = 'Revenue, income and deposits';
// $journals = $this->_repository->getDepositsPaginated(50);
$types = ['Deposit'];
$types = ['Deposit'];
break;
case 'transfer':
case 'transfers':
$subTitleIcon = 'fa-arrows-h';
$subTitleIcon = 'fa-exchange';
$subTitle = 'Transfers';
//$journals = $this->_repository->getTransfersPaginated(50);
$types = ['Transfer'];
$types = ['Transfer'];
break;
}
$page = intval(\Input::get('page'));
$offset = $page > 0 ? ($page - 1) * 50 : 0;
$set = Auth::user()->transactionJournals()->transactionTypes($types)->withRelevantData()->take(50)->offset($offset)->orderBy('date', 'DESC')->get(
$set = Auth::user()->
transactionJournals()->
transactionTypes($types)->
withRelevantData()->take(50)->offset($offset)
->orderBy('date', 'DESC')
->orderBy('order','ASC')
->orderBy('id','DESC')
->get(
['transaction_journals.*']
);
$count = Auth::user()->transactionJournals()->transactionTypes($types)->count();
@@ -213,6 +217,27 @@ class TransactionController extends Controller
}
/**
* Reorder transactions (which all must have the same date)
*/
public function reorder()
{
$ids = Input::get('items');
if (count($ids) > 0) {
$order = 0;
foreach ($ids as $id) {
$journal = Auth::user()->transactionjournals()->where('id', $id)->where('date', Input::get('date'))->first();
if($journal) {
$journal->order = $order;
$order++;
$journal->save();
}
}
}
return Response::json(true);
}
/**
* @param TransactionJournal $journal
*
@@ -225,9 +250,11 @@ class TransactionController extends Controller
$t->before = floatval(
$t->account->transactions()->leftJoin(
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
)->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d'))->where(
'transaction_journals.created_at', '<=', $journal->created_at->format('Y-m-d H:i:s')
)->where('transaction_journals.id', '!=', $journal->id)->sum('transactions.amount')
)
->where('transaction_journals.date', '<=', $journal->date->format('Y-m-d'))
->where('transaction_journals.order','>=',$journal->order)
->where('transaction_journals.id', '!=', $journal->id)
->sum('transactions.amount')
);
$t->after = $t->before + $t->amount;
}
@@ -239,6 +266,12 @@ class TransactionController extends Controller
);
}
/**
* @param JournalFormRequest $request
* @param JournalRepositoryInterface $repository
*
* @return $this|\Illuminate\Http\RedirectResponse
*/
public function store(JournalFormRequest $request, JournalRepositoryInterface $repository)
{
$journalData = [
@@ -284,7 +317,6 @@ class TransactionController extends Controller
* @SuppressWarnings("CyclomaticComplexity") // It's exactly 5. So I don't mind.
*
* @return $this
* @throws FireflyException
*/
public function update(TransactionJournal $journal, JournalFormRequest $request, JournalRepositoryInterface $repository)
{
@@ -317,6 +349,7 @@ class TransactionController extends Controller
return Redirect::route('transactions.edit', $journal->id);
}
return Redirect::route('transactions.index', $journalData['what']);
}

View File

@@ -37,6 +37,7 @@ class Kernel extends HttpKernel
'guest' => 'FireflyIII\Http\Middleware\RedirectIfAuthenticated',
'range' => 'FireflyIII\Http\Middleware\Range',
'reminders' => 'FireflyIII\Http\Middleware\Reminders',
'piggybanks' => 'FireflyIII\Http\Middleware\PiggyBanks',
];
}

View File

@@ -0,0 +1,141 @@
<?php
namespace FireflyIII\Http\Middleware;
use Carbon\Carbon;
use Closure;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\Reminder;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;
use Navigation;
use Session;
/**
* Class PiggyBanks
*
* @package FireflyIII\Http\Middleware
*/
class PiggyBanks
{
/**
* The Guard implementation.
*
* @var Guard
*/
protected $auth;
/**
* Create a new filter instance.
*
* @param Guard $auth
*
*/
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($this->auth->check() && !$request->isXmlHttpRequest()) {
// get piggy banks without a repetition:
/** @var Collection $set */
$set = $this->auth->user()->piggybanks()
->leftJoin('piggy_bank_repetitions', 'piggy_banks.id', '=', 'piggy_bank_repetitions.piggy_bank_id')
->where('piggy_banks.repeats', 0)
->whereNull('piggy_bank_repetitions.id')
->get(['piggy_banks.id', 'piggy_banks.startdate', 'piggy_banks.targetdate']);
if ($set->count() > 0) {
/** @var PiggyBank $partialPiggy */
foreach ($set as $partialPiggy) {
$repetition = new PiggyBankRepetition;
$repetition->piggyBank()->associate($partialPiggy);
$repetition->startdate = is_null($partialPiggy->startdate) ? null : $partialPiggy->startdate;
$repetition->targetdate = is_null($partialPiggy->targetdate) ? null : $partialPiggy->targetdate;
$repetition->currentamount = 0;
$repetition->save();
}
}
unset($partialPiggy, $set, $repetition);
// get repeating piggy banks without a repetition for current time frame.
/** @var Collection $set */
$set = $this->auth->user()->piggybanks()->leftJoin(
'piggy_bank_repetitions', function (JoinClause $join) {
$join->on('piggy_bank_repetitions.piggy_bank_id', '=', 'piggy_banks.id')
->where('piggy_bank_repetitions.targetdate', '>=', Session::get('start')->format('Y-m-d'))
->where('piggy_bank_repetitions.startdate', '<=', Session::get('end')->format('Y-m-d'));
}
)
->where('repeats', 1)
->whereNull('piggy_bank_repetitions.id')
->get(['piggy_banks.*']);
// these piggy banks are missing a repetition. start looping and create them!
if ($set->count() > 0) {
/** @var PiggyBank $piggyBank */
foreach ($set as $piggyBank) {
$start = clone $piggyBank->startdate;
$end = clone $piggyBank->targetdate;
$max = clone $piggyBank->targetdate;
$index = 0;
// first loop: start date to target date.
// then, continue looping until end is > today
while ($start <= $max) {
// first loop fixes this date. or should fix it.
$max = new Carbon;
echo '[#'.$piggyBank->id.', from: '.$start->format('Y-m-d.').' to '.$end->format('Y-m-d.').']';
// create stuff. Or at least, try:
$repetition = $piggyBank->piggyBankRepetitions()->onDates($start, $end)->first();
if(!$repetition) {
$repetition = new PiggyBankRepetition;
$repetition->piggyBank()->associate($piggyBank);
$repetition->startdate = $start;
$repetition->targetdate = $end;
$repetition->currentamount = 0;
// it might exist, catch:
$repetition->save();
}
// start where end 'ended':
$start = clone $end;
// move end.
$end = Navigation::addPeriod($end, $piggyBank->rep_length, 0);
}
// first repetition: from original start to original target.
$repetition = new PiggyBankRepetition;
$repetition->piggyBank()->associate($piggyBank);
$repetition->startdate = is_null($piggyBank->startdate) ? null : $piggyBank->startdate;
$repetition->targetdate = is_null($piggyBank->targetdate) ? null : $piggyBank->targetdate;
$repetition->currentamount = 0;
// it might exist, catch:
//$repetition->save();
// then, loop from original target up to now.
}
}
}
return $next($request);
}
}

View File

@@ -45,7 +45,7 @@ class Reminders
*/
public function handle($request, Closure $next)
{
if ($this->auth->check()) {
if ($this->auth->check() && !$request->isXmlHttpRequest()) {
// do reminders stuff.
$piggyBanks = $this->auth->user()->piggyBanks()->where('remind_me', 1)->get();
$today = new Carbon;
@@ -67,15 +67,14 @@ class Reminders
}
}
// delete invalid reminders
$reminders = $this->auth->user()->reminders()->get();
foreach($reminders as $reminder) {
if(is_null($reminder->remindersable)) {
$reminder->delete();
}
$set = $this->auth->user()->reminders()->leftJoin('piggy_banks', 'piggy_banks.id', '=', 'remindersable_id')->whereNull('piggy_banks.id')->get(
['reminders.id']
);
foreach ($set as $reminder) {
$reminder->delete();
}
// get and list active reminders:
$reminders = $this->auth->user()->reminders()->today()->get();
$reminders->each(

View File

@@ -31,9 +31,9 @@ class AccountFormRequest extends Request
$accountRoles = join(',', array_keys(Config::get('firefly.accountRoles')));
$types = join(',', array_keys(Config::get('firefly.subTitlesByIdentifier')));
$nameRule = 'required|between:1,100|uniqueForUser:accounts,name';
$nameRule = 'required|between:1,100|uniqueAccountForUser';
if (Account::find(Input::get('id'))) {
$nameRule = 'required|between:1,100';
$nameRule = 'required|between:1,100|belongsToUser:accounts|uniqueForUser:'.Input::get('id');
}
return [
@@ -46,4 +46,4 @@ class AccountFormRequest extends Request
'what' => 'in:' . $types
];
}
}
}

View File

@@ -3,8 +3,10 @@
namespace FireflyIII\Http\Requests;
use Auth;
use Carbon\Carbon;
use FireflyIII\Models\Account;
use Input;
use Navigation;
/**
* Class PiggyBankFormRequest
@@ -36,8 +38,14 @@ class PiggyBankFormRequest extends Request
if (intval(Input::get('repeats')) == 1) {
$targetDateRule = 'required|date|after:' . date('Y-m-d');
// switch on rep_every, make sure it's not too far away.
if (!is_null(Input::get('rep_length'))) {
$end = Navigation::addPeriod(new Carbon, Input::get('rep_length'), 0);
$targetDateRule .= '|before:' . $end->format('Y-m-d');
}
}
$rules = [
'repeats' => 'required|boolean',
'name' => $nameRule,

View File

@@ -156,7 +156,7 @@ Route::controllers(
* Home Controller
*/
Route::group(
['middleware' => ['auth', 'range', 'reminders']], function () {
['middleware' => ['auth', 'range', 'reminders','piggybanks']], function () {
Route::get('/', ['uses' => 'HomeController@index', 'as' => 'index']);
Route::get('/home', ['uses' => 'HomeController@index', 'as' => 'home']);
Route::post('/daterange', ['uses' => 'HomeController@dateRange', 'as' => 'daterange']);
@@ -279,6 +279,7 @@ Route::group(
Route::post('/piggy-banks/destroy/{piggyBank}', ['uses' => 'PiggyBankController@destroy', 'as' => 'piggy-banks.destroy']);
Route::post('/piggy-banks/add/{piggyBank}', ['uses' => 'PiggyBankController@postAdd', 'as' => 'piggy-banks.add']); # add money
Route::post('/piggy-banks/remove/{piggyBank}', ['uses' => 'PiggyBankController@postRemove', 'as' => 'piggy-banks.remove']); # remove money.
Route::post('/piggy-banks/sort', ['uses' => 'PiggyBankController@order', 'as' => 'piggy-banks.order']);
/**
* Preferences Controller
@@ -365,6 +366,7 @@ Route::group(
);
Route::post('/transaction/update/{tj}', ['uses' => 'TransactionController@update', 'as' => 'transactions.update']);
Route::post('/transaction/destroy/{tj}', ['uses' => 'TransactionController@destroy', 'as' => 'transactions.destroy']);
Route::post('/transaction/reorder', ['uses' => 'TransactionController@reorder', 'as' => 'transactions.reorder']);
/**
* Auth\Auth Controller

View File

@@ -18,7 +18,7 @@ class Account extends Model
= [
'user_id' => 'required|exists:users,id',
'account_type_id' => 'required|exists:account_types,id',
'name' => 'required|between:1,1024|uniqueForUser:accounts,name',
'name' => 'required|between:1,1024|uniqueAccountForUser',
'active' => 'required|boolean'
];

View File

@@ -12,6 +12,8 @@ class Component extends Model
{
use SoftDeletes;
protected $fillable = ['user_id', 'name','class'];
/**
* @return array
*/

View File

@@ -14,7 +14,9 @@ class PiggyBank extends Model
{
use SoftDeletes;
protected $fillable = ['repeats', 'name', 'account_id','rep_every', 'rep_times', 'reminder_skip', 'targetamount', 'startdate', 'targetdate', 'reminder','remind_me'];
protected $fillable
= ['repeats', 'name', 'account_id', 'rep_every', 'rep_times', 'reminder_skip', 'targetamount', 'startdate', 'targetdate', 'reminder', 'remind_me',
'rep_length'];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
@@ -24,15 +26,6 @@ class PiggyBank extends Model
return $this->belongsTo('FireflyIII\Models\Account');
}
/**
* @param $value
*
* @return int
*/
public function getRemindMeAttribute($value) {
return intval($value) == 1;
}
/**
* Grabs the PiggyBankRepetition that's currently relevant / active
*
@@ -40,10 +33,10 @@ class PiggyBank extends Model
*/
public function currentRelevantRep()
{
if ($this->currentRep) {
if (!is_null($this->currentRep)) {
return $this->currentRep;
}
if ($this->repeats == 0) {
if (intval($this->repeats) === 0) {
$rep = $this->piggyBankRepetitions()->first(['piggy_bank_repetitions.*']);
$this->currentRep = $rep;
@@ -104,6 +97,16 @@ class PiggyBank extends Model
return ['created_at', 'updated_at', 'deleted_at', 'startdate', 'targetdate'];
}
/**
* @param $value
*
* @return int
*/
public function getRemindMeAttribute($value)
{
return intval($value) == 1;
}
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/

View File

@@ -37,15 +37,27 @@ class PiggyBankRepetition extends Model
{
return $query->where(
function($q) use ($date) {
$q->where('startdate', '>=', $date->format('Y-m-d 00:00:00'));
$q->where('startdate', '<=', $date->format('Y-m-d 00:00:00'));
$q->orWhereNull('startdate');
})
->where(function($q) use ($date) {
$q->where('targetdate', '<=', $date->format('Y-m-d 00:00:00'));
$q->where('targetdate', '>=', $date->format('Y-m-d 00:00:00'));
$q->orWhereNull('targetdate');
});
}
/**
* @param EloquentBuilder $query
* @param Carbon $start
* @param Carbon $target
*
* @return $this
*/
public function scopeOnDates(EloquentBuilder $query, Carbon $start, Carbon $target)
{
return $query->where('startdate',$start->format('Y-m-d'))->where('targetdate',$target->format('Y-m-d'));
}
}

View File

@@ -74,6 +74,8 @@ class EventServiceProvider extends ServiceProvider
}
);
// move this routine to a filter
// in case of repeated piggy banks and/or other problems.
PiggyBank::created(
function (PiggyBank $piggyBank) {
$repetition = new PiggyBankRepetition;

View File

@@ -18,6 +18,7 @@ use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
use Session;
use Steam;
/**
* Class AccountRepository
@@ -64,6 +65,10 @@ class AccountRepository implements AccountRepositoryInterface
}
/**
* This method is used on the front page where (in turn) its viewed journals-tiny.php which (in turn)
* is almost the only place where formatJournal is used. Aka, we can use some custom querying to get some specific.
* fields using left joins.
*
* @param Account $account
* @param Carbon $start
* @param Carbon $end
@@ -74,15 +79,17 @@ class AccountRepository implements AccountRepositoryInterface
{
return Auth::user()
->transactionjournals()
->with(['transactions', 'transactioncurrency', 'transactiontype'])
->with(['transactions'])
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')->where('accounts.id', $account->id)
->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transaction_journals.transaction_currency_id')
->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
->before($end)
->after($start)
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.id', 'DESC')
->take(10)
->get(['transaction_journals.*']);
->get(['transaction_journals.*', 'transaction_currencies.symbol', 'transaction_types.type']);
}
/**
@@ -99,7 +106,9 @@ class AccountRepository implements AccountRepositoryInterface
->withRelevantData()
->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
->where('transactions.account_id', $account->id)
->orderBy('date', 'DESC');
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order','ASC')
->orderBy('transaction_journals.id','DESC');
$query->before(Session::get('end', Carbon::now()->endOfMonth()));
$query->after(Session::get('start', Carbon::now()->startOfMonth()));
@@ -114,6 +123,48 @@ class AccountRepository implements AccountRepositoryInterface
}
/**
* Get savings accounts and the balance difference in the period.
*
* @return Collection
*/
public function getSavingsAccounts()
{
$accounts = Auth::user()->accounts()->accountTypeIn(['Default account', 'Asset account'])->orderBy('accounts.name', 'ASC')
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
->where('account_meta.name', 'accountRole')
->where('account_meta.data', '"savingAsset"')
->get(['accounts.*']);
$start = clone Session::get('start');
$end = clone Session::get('end');
$accounts->each(
function (Account $account) use ($start, $end) {
$account->startBalance = Steam::balance($account, $start);
$account->endBalance = Steam::balance($account, $end);
// diff (negative when lost, positive when gained)
$diff = $account->endBalance - $account->startBalance;
if ($diff < 0 && $account->startBalance > 0) {
// percentage lost compared to start.
$pct = (($diff * -1) / $account->startBalance) * 100;
} else {
if ($diff >= 0 && $account->startBalance > 0) {
$pct = ($diff / $account->startBalance) * 100;
} else {
$pct = 100;
}
}
$pct = $pct > 100 ? 100 : $pct;
$account->difference = $diff;
$account->percentage = round($pct);
}
);
return $accounts;
}
/**
* @param Account $account
*
@@ -236,12 +287,14 @@ class AccountRepository implements AccountRepositoryInterface
'active' => $data['active'] === true ? true : false,
]
);
if (!$newAccount->isValid()) {
// does the account already exist?
$existingAccount = Account::where('user_id', $data['user'])->where('account_type_id', $accountType->id)->where('name', $data['name'])->first();
if (!$existingAccount) {
Log::error('Account create error: ' . $newAccount->getErrors()->toJson());
var_dump($newAccount->getErrors()->toArray());
App::abort(500);
}
$newAccount = $existingAccount;
}

View File

@@ -78,4 +78,11 @@ interface AccountRepositoryInterface
* @return float
*/
public function leftOnAccount(Account $account);
/**
* Get savings accounts and the balance difference in the period.
*
* @return Collection
*/
public function getSavingsAccounts();
}

View File

@@ -42,7 +42,10 @@ class BudgetRepository implements BudgetRepositoryInterface
$offset = intval(\Input::get('page')) > 0 ? intval(\Input::get('page')) * $take : 0;
$setQuery = $budget->transactionJournals()->withRelevantData()->take($take)->offset($offset)->orderBy('date', 'DESC');
$setQuery = $budget->transactionJournals()->withRelevantData()->take($take)->offset($offset)
->orderBy('transaction_journals.date', 'DESC')
->orderBy('transaction_journals.order','ASC')
->orderBy('transaction_journals.id','DESC');
$countQuery = $budget->transactionJournals();

View File

@@ -11,6 +11,7 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use Illuminate\Support\Collection;
use Log;
/**
* Class JournalRepository

View File

@@ -2,9 +2,8 @@
namespace FireflyIII\Repositories\PiggyBank;
use Amount;
use Auth;
use Carbon\Carbon;
use DB;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\Reminder;
@@ -88,6 +87,39 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
return $part;
}
/**
* Set all piggy banks to order 0.
*
* @return void
*/
public function reset()
{
DB::table('piggy_banks')
->leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.id')
->where('accounts.user_id', Auth::user()->id)
->update(['order' => 0, 'piggy_banks.updated_at' => DB::Raw('NOW()')]);
//Auth::user()->piggyBanks()->update(['order' => 0]);
}
/**
*
* set id of piggy bank.
*
* @param int $id
* @param int $order
*
* @return void
*/
public function setOrder($id, $order)
{
$piggyBank = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')->where('accounts.user_id', Auth::user()->id)
->where('piggy_banks.id',$id)->first(['piggy_banks.*']);
if ($piggyBank) {
$piggyBank->order = $order;
$piggyBank->save();
}
}
/**
* @param array $data
*
@@ -132,5 +164,4 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
return $piggyBank;
}
}

View File

@@ -37,6 +37,23 @@ interface PiggyBankRepositoryInterface
*/
public function createPiggyBankPart(array $data);
/**
* Set all piggy banks to order 0.
* @return void
*/
public function reset();
/**
*
* set id of piggy bank.
*
* @param int $id
* @param int $order
*
* @return void
*/
public function setOrder($id, $order);
/**

View File

@@ -5,6 +5,7 @@ namespace FireflyIII\Support;
use Cache;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use Preferences as Prefs;
/**
@@ -30,19 +31,27 @@ class Amount
}
/**
* @param Transaction|\Transaction $transaction
* @param bool $coloured
*
* @return string
*/
public function formatTransaction(Transaction $transaction, $coloured = true)
public function getCurrencySymbol()
{
$symbol = $transaction->transactionJournal->transactionCurrency->symbol;
$amount = floatval($transaction->amount);
if (defined('FFCURRENCYSYMBOL')) {
return FFCURRENCYSYMBOL;
}
if (\Cache::has('FFCURRENCYSYMBOL')) {
define('FFCURRENCYSYMBOL', \Cache::get('FFCURRENCYSYMBOL'));
return $this->formatWithSymbol($symbol, $amount, $coloured);
return FFCURRENCYSYMBOL;
}
$currencyPreference = Prefs::get('currencyPreference', 'EUR');
$currency = TransactionCurrency::whereCode($currencyPreference->data)->first();
\Cache::forever('FFCURRENCYSYMBOL', $currency->symbol);
define('FFCURRENCYSYMBOL', $currency->symbol);
return $currency->symbol;
}
/**
@@ -73,29 +82,56 @@ class Amount
return $symbol . ' ' . $string;
}
/**
* @return string
*
* @param TransactionJournal $journal
*/
public function getCurrencySymbol()
public function formatJournal(TransactionJournal $journal, $coloured = true)
{
if (defined('FFCURRENCYSYMBOL')) {
return FFCURRENCYSYMBOL;
$showPositive = true;
if (is_null($journal->symbol)) {
$symbol = $journal->transactionCurrency->symbol;
} else {
$symbol = $journal->symbol;
}
if (\Cache::has('FFCURRENCYSYMBOL')) {
define('FFCURRENCYSYMBOL', \Cache::get('FFCURRENCYSYMBOL'));
$amount = 0;
return FFCURRENCYSYMBOL;
if (is_null($journal->type)) {
$type = $journal->transactionType->type;
} else {
$type = $journal->type;
}
$currencyPreference = Prefs::get('currencyPreference', 'EUR');
$currency = TransactionCurrency::whereCode($currencyPreference->data)->first();
if ($type == 'Withdrawal') {
$showPositive = false;
}
\Cache::forever('FFCURRENCYSYMBOL', $currency->symbol);
foreach ($journal->transactions as $t) {
if (floatval($t->amount) > 0 && $showPositive === true) {
$amount = floatval($t->amount);
break;
}
if (floatval($t->amount) < 0 && $showPositive === false) {
$amount = floatval($t->amount);
}
}
define('FFCURRENCYSYMBOL', $currency->symbol);
return $this->formatWithSymbol($symbol, $amount, $coloured);
}
return $currency->symbol;
/**
* @param Transaction $transaction
* @param bool $coloured
*
* @return string
*/
public function formatTransaction(Transaction $transaction, $coloured = true)
{
$symbol = $transaction->transactionJournal->transactionCurrency->symbol;
$amount = floatval($transaction->amount);
return $this->formatWithSymbol($symbol, $amount, $coloured);
}
/**

View File

@@ -2,13 +2,13 @@
namespace FireflyIII\Support;
use Amount as Amt;
use FireflyIII\Models\TransactionCurrency;
use Illuminate\Support\Collection;
use Illuminate\Support\MessageBag;
use Input;
use Session;
use View;
use Amount as Amt;
/**
* Class ExpandedForm
@@ -96,24 +96,14 @@ class ExpandedForm
public function getHolderClasses($name)
{
/*
* Get errors, warnings and successes from session:
* Get errors from session:
*/
/** @var MessageBag $errors */
$errors = Session::get('errors');
$errors = Session::get('errors');
$classes = 'form-group';
/** @var MessageBag $successes */
$successes = Session::get('successes');
switch (true) {
case (!is_null($errors) && $errors->has($name)):
$classes = 'form-group has-error has-feedback';
break;
case (!is_null($successes) && $successes->has($name)):
$classes = 'form-group has-success has-feedback';
break;
default:
$classes = 'form-group';
break;
if (!is_null($errors) && $errors->has($name)) {
$classes = 'form-group has-error has-feedback';
}
return $classes;

View File

@@ -3,11 +3,12 @@
namespace FireflyIII\Support\Search;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Auth;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Support\Collection;
/**
* Class Search
@@ -23,7 +24,7 @@ class Search implements SearchInterface
*/
public function searchAccounts(array $words)
{
return \Auth::user()->accounts()->with('accounttype')->where(
return Auth::user()->accounts()->with('accounttype')->where(
function (EloquentBuilder $q) use ($words) {
foreach ($words as $word) {
$q->orWhere('name', 'LIKE', '%' . e($word) . '%');
@@ -40,7 +41,7 @@ class Search implements SearchInterface
public function searchBudgets(array $words)
{
/** @var Collection $set */
$set = \Auth::user()->budgets()->get();
$set = Auth::user()->budgets()->get();
$newSet = $set->filter(
function (Budget $b) use ($words) {
$found = 0;
@@ -65,7 +66,7 @@ class Search implements SearchInterface
public function searchCategories(array $words)
{
/** @var Collection $set */
$set = \Auth::user()->categories()->get();
$set = Auth::user()->categories()->get();
$newSet = $set->filter(
function (Category $c) use ($words) {
$found = 0;
@@ -101,12 +102,39 @@ class Search implements SearchInterface
*/
public function searchTransactions(array $words)
{
return \Auth::user()->transactionjournals()->withRelevantData()->where(
// decrypted transaction journals:
$decrypted = Auth::user()->transactionjournals()->withRelevantData()->where('encrypted', 0)->where(
function (EloquentBuilder $q) use ($words) {
foreach ($words as $word) {
$q->orWhere('description', 'LIKE', '%' . e($word) . '%');
}
}
)->get();
// encrypted
$all = Auth::user()->transactionjournals()->withRelevantData()->where('encrypted', 1)->get();
$set = $all->filter(
function (TransactionJournal $journal) use ($words) {
foreach ($words as $word) {
$haystack = strtolower($journal->description);
$word = strtolower($word);
if (!(strpos($haystack, $word) === false)) {
return $journal;
}
}
}
);
$filtered = $set->merge($decrypted);
$filtered->sortBy(
function (TransactionJournal $journal) {
return intval($journal->date->format('U'));
}
);
$filtered = $filtered->reverse();
return $filtered;
}
}

View File

@@ -4,8 +4,11 @@ namespace FireflyIII\Validation;
use Auth;
use Carbon\Carbon;
use Config;
use DB;
use FireflyIII\Models\AccountType;
use Illuminate\Validation\Validator;
use Input;
use Navigation;
/**
@@ -34,6 +37,13 @@ class FireflyValidator extends Validator
}
/**
* @param $attribute
* @param $value
* @param $parameters
*
* @return bool
*/
public function validatePiggyBankReminder($attribute, $value, $parameters)
{
$array = $this->data;
@@ -51,14 +61,65 @@ class FireflyValidator extends Validator
return true;
}
$nextReminder = Navigation::addPeriod($startDate, $array['reminder'],0);
$nextReminder = Navigation::addPeriod($startDate, $array['reminder'], 0);
// reminder is beyond target?
if($nextReminder > $targetDate) {
if ($nextReminder > $targetDate) {
return false;
}
return true;
}
/**
* @param $attribute
* @param $value
* @param $parameters
*
* @return bool
*/
public function validateUniqueAccountForUser($attribute, $value, $parameters)
{
// get account type from data, we must have this:
$validTypes = array_keys(Config::get('firefly.subTitlesByIdentifier'));
$type = isset($this->data['what']) && in_array($this->data['what'],$validTypes) ? $this->data['what'] : null;
// some fallback:
if(is_null($type)) {
$type = in_array(Input::get('what'),$validTypes) ? Input::get('what') : null;
}
// still null?
if(is_null($type)) {
// find by other field:
$type = isset($this->data['account_type_id']) ? $this->data['account_type_id'] : 0;
$dbType = AccountType::find($type);
} else {
$longType = Config::get('firefly.accountTypeByIdentifier.' . $type);
$dbType = AccountType::whereType($longType)->first();
}
if (is_null($dbType)) {
return false;
}
// user id?
$userId = Auth::check() ? Auth::user()->id : $this->data['user_id'];
$query = DB::table('accounts')->where('name', $value)->where('account_type_id', $dbType->id)->where('user_id', $userId);
if (isset($parameters[0])) {
$query->where('id', '!=', $parameters[0]);
}
$count = $query->count();
if ($count == 0) {
return true;
}
return false;
}
/**
* @param $attribute
* @param $value
@@ -69,6 +130,7 @@ class FireflyValidator extends Validator
public function validateUniqueForUser($attribute, $value, $parameters)
{
$query = DB::table($parameters[0])->where($parameters[1], $value);
$query->where('user_id', Auth::user()->id);
if (isset($paramers[2])) {
$query->where('id', '!=', $parameters[2]);
}