diff --git a/app/Events/JournalDeleted.php b/app/Events/JournalDeleted.php index 375f2268dc..d19937a9cd 100644 --- a/app/Events/JournalDeleted.php +++ b/app/Events/JournalDeleted.php @@ -1,21 +1,20 @@ endOfMonth(); // all budgets - $set = \Auth::user()->budgets() - ->leftJoin( - 'budget_limits', function (JoinClause $join) use ($date) { - $join->on('budget_limits.budget_id', '=', 'budgets.id')->where('budget_limits.startdate', '=', $date->format('Y-m-d')); - } - ) - ->get(['budgets.*', 'budget_limits.amount as amount']); - + $set = \Auth::user()->budgets() + ->leftJoin( + 'budget_limits', function (JoinClause $join) use ($date) { + $join->on('budget_limits.budget_id', '=', 'budgets.id')->where('budget_limits.startdate', '=', $date->format('Y-m-d')); + } + ) + ->get(['budgets.*', 'budget_limits.amount as amount']); $budgets = $this->_helper->makeArray($set); diff --git a/app/Helpers/Report/ReportHelperInterface.php b/app/Helpers/Report/ReportHelperInterface.php index 2284523ff8..99fbad8742 100644 --- a/app/Helpers/Report/ReportHelperInterface.php +++ b/app/Helpers/Report/ReportHelperInterface.php @@ -33,11 +33,11 @@ interface ReportHelperInterface */ public function listOfYears(Carbon $date); - /** - * @param Carbon $date - * - * @return array - */ + /** + * @param Carbon $date + * + * @return array + */ public function yearBalanceReport(Carbon $date); /** diff --git a/app/Helpers/Report/ReportQuery.php b/app/Helpers/Report/ReportQuery.php index 45ee2b497f..f0625bed7c 100644 --- a/app/Helpers/Report/ReportQuery.php +++ b/app/Helpers/Report/ReportQuery.php @@ -11,10 +11,12 @@ namespace FireflyIII\Helpers\Report; use Auth; use Carbon\Carbon; use DB; +use FireflyIII\Models\Account; use FireflyIII\Models\TransactionJournal; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; +use Steam; /** * Class ReportQuery @@ -31,22 +33,141 @@ class ReportQuery implements ReportQueryInterface */ public function accountList() { - return \Auth::user()->accounts() - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->leftJoin( - 'account_meta', function (JoinClause $join) { - $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', "accountRole"); - } - ) - ->whereIn('account_types.type', ['Default account', 'Cash account', 'Asset account']) - ->where('active', 1) - ->where( - function (Builder $query) { - $query->where('account_meta.data', '!=', '"sharedExpense"'); - $query->orWhereNull('account_meta.data'); - } - ) - ->get(['accounts.*']); + return Auth::user()->accounts() + ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', "accountRole"); + } + ) + ->whereIn('account_types.type', ['Default account', 'Cash account', 'Asset account']) + ->where('active', 1) + ->where( + function (Builder $query) { + $query->where('account_meta.data', '!=', '"sharedExpense"'); + $query->orWhereNull('account_meta.data'); + } + ) + ->get(['accounts.*']); + } + + /** + * This method will get a list of all expenses in a certain time period that have no budget + * and are balanced by a transfer to make up for it. + * + * @param Account $account + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function balancedTransactionsList(Account $account, Carbon $start, Carbon $end) + { + + $set = TransactionJournal:: + leftJoin('transaction_group_transaction_journal', 'transaction_group_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin( + 'transaction_group_transaction_journal as otherFromGroup', function (JoinClause $join) { + $join->on('otherFromGroup.transaction_group_id', '=', 'transaction_group_transaction_journal.transaction_group_id') + ->on('otherFromGroup.transaction_journal_id', '!=', 'transaction_journals.id'); + } + ) + ->leftJoin('transaction_journals as otherJournals', 'otherJournals.id', '=', 'otherFromGroup.transaction_journal_id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'otherJournals.transaction_type_id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('amount', '>', 0); + } + ) + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'otherJournals.id') + ->before($end)->after($start) + ->where('transaction_types.type', 'Withdrawal') + ->where('transaction_journals.user_id', \Auth::user()->id) + ->whereNull('budget_transaction_journal.budget_id')->whereNull('transaction_journals.deleted_at') + ->whereNull('otherJournals.deleted_at') + ->where('transactions.account_id', $account->id) + ->whereNotNull('transaction_group_transaction_journal.transaction_group_id')->groupBy('transaction_journals.id') + ->first( + [ + DB::Raw('SUM(`transactions`.`amount`) as `amount`') + ] + ); + $sum = 0; + if (!is_null($set)) { + $sum = floatval($set->amount); + } + + return $sum; + } + + /** + * Get a users accounts combined with various meta-data related to the start and end date. + * + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getAllAccounts(Carbon $start, Carbon $end) + { + $set = Auth::user()->accounts() + ->accountTypeIn(['Default account', 'Asset account', 'Cash account']) + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + } + ) + ->where( + function (Builder $query) { + $query->where('account_meta.data', '!=', '"sharedExpense"'); + $query->orWhereNull('account_meta.data'); + } + ) + ->get(['accounts.*']); + $set->each( + function (Account $account) use ($start, $end) { + /** @noinspection PhpParamsInspection */ + $account->startBalance = Steam::balance($account, $start); + $account->endBalance = Steam::balance($account, $end); + } + ); + + return $set; + } + + /** + * Grabs a summary of all expenses grouped by budget, related to the account. + * + * @param Account $account + * @param Carbon $start + * @param Carbon $end + * + * @return mixed + */ + public function getBudgetSummary(Account $account, Carbon $start, Carbon $end) + { + $set = TransactionJournal:: + leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('budgets', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transactions.transaction_journal_id', '=', 'transaction_journals.id')->where('transactions.amount', '<', 0); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->before($end) + ->after($start) + ->where('accounts.id', $account->id) + ->where('transaction_journals.user_id', Auth::user()->id) + ->where('transaction_types.type', 'Withdrawal') + ->groupBy('budgets.id') + ->orderBy('budgets.id') + ->get(['budgets.id', 'budgets.name', DB::Raw('SUM(`transactions`.`amount`) as `amount`')]); + + return $set; + + } /** @@ -124,28 +245,28 @@ class ReportQuery implements ReportQueryInterface */ public function journalsByBudget(Carbon $start, Carbon $end) { - return \Auth::user()->transactionjournals() - ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id') - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); - } - ) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin( - 'account_meta', function (JoinClause $join) { - $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); - } - ) - ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->where('account_meta.data', '!=', '"sharedExpense"') - ->where('transaction_types.type', 'Withdrawal') - ->groupBy('budgets.id') - ->orderBy('budgets.name', 'ASC') - ->get(['budgets.id', 'budgets.name', \DB::Raw('SUM(`transactions`.`amount`) AS `spent`')]); + return Auth::user()->transactionjournals() + ->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + } + ) + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('account_meta.data', '!=', '"sharedExpense"') + ->where('transaction_types.type', 'Withdrawal') + ->groupBy('budgets.id') + ->orderBy('budgets.name', 'ASC') + ->get(['budgets.id', 'budgets.name', DB::Raw('SUM(`transactions`.`amount`) AS `spent`')]); } /** @@ -159,30 +280,30 @@ class ReportQuery implements ReportQueryInterface */ public function journalsByCategory(Carbon $start, Carbon $end) { - return \Auth::user()->transactionjournals() - ->leftJoin( - 'category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id' - ) - ->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id') - ->leftJoin( - 'transactions', function (JoinClause $join) { - $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); - } - ) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin( - 'account_meta', function (JoinClause $join) { - $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); - } - ) - ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->where('account_meta.data', '!=', '"sharedExpense"') - ->where('transaction_types.type', 'Withdrawal') - ->groupBy('categories.id') - ->orderBy('amount') - ->get(['categories.id', 'categories.name', \DB::Raw('SUM(`transactions`.`amount`) AS `amount`')]); + return Auth::user()->transactionjournals() + ->leftJoin( + 'category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id' + ) + ->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id') + ->leftJoin( + 'transactions', function (JoinClause $join) { + $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', '<', 0); + } + ) + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->leftJoin( + 'account_meta', function (JoinClause $join) { + $join->on('account_meta.account_id', '=', 'accounts.id')->where('account_meta.name', '=', 'accountRole'); + } + ) + ->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id') + ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) + ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) + ->where('account_meta.data', '!=', '"sharedExpense"') + ->where('transaction_types.type', 'Withdrawal') + ->groupBy('categories.id') + ->orderBy('amount') + ->get(['categories.id', 'categories.name', DB::Raw('SUM(`transactions`.`amount`) AS `amount`')]); } @@ -333,7 +454,7 @@ class ReportQuery implements ReportQueryInterface ->after($start) ->before($end) ->where('transaction_types.type', 'Transfer') - ->where('transaction_journals.user_id', \Auth::user()->id) + ->where('transaction_journals.user_id', Auth::user()->id) ->get( ['transaction_journals.id', 'transaction_journals.description', 'transactions.account_id', 'accounts.name', 'transactions.amount'] @@ -375,13 +496,13 @@ class ReportQuery implements ReportQueryInterface ->after($start) ->before($end) ->where('transaction_types.type', 'Transfer') - ->where('transaction_journals.user_id', \Auth::user()->id) + ->where('transaction_journals.user_id', Auth::user()->id) ->groupBy('categories.name') ->get( [ 'categories.id', 'categories.name as name', - \DB::Raw('SUM(`transactions`.`amount`) * -1 AS `amount`') + DB::Raw('SUM(`transactions`.`amount`) * -1 AS `amount`') ] ); } diff --git a/app/Helpers/Report/ReportQueryInterface.php b/app/Helpers/Report/ReportQueryInterface.php index 3d90390fe8..1bce968341 100644 --- a/app/Helpers/Report/ReportQueryInterface.php +++ b/app/Helpers/Report/ReportQueryInterface.php @@ -1,14 +1,9 @@ with('message', 'Invalid year.'); } - $budgets = Auth::user()->budgets()->get(); + $budgets = Auth::user()->budgets()->get(); $budgets->sortBy('name'); $chart->addColumn('Month', 'date'); foreach ($budgets as $budget) { diff --git a/app/Http/Controllers/ReportController.php b/app/Http/Controllers/ReportController.php index f0adba9b4c..39c1d205e1 100644 --- a/app/Http/Controllers/ReportController.php +++ b/app/Http/Controllers/ReportController.php @@ -29,6 +29,82 @@ class ReportController extends Controller } + /** + * @param string $year + * @param string $month + * + * @return \Illuminate\View\View + */ + public function budget($year = '2014', $month = '1', ReportQueryInterface $query) + { + try { + new Carbon($year . '-' . $month . '-01'); + } catch (Exception $e) { + return view('error')->with('message', 'Invalid date'); + } + $date = new Carbon($year . '-' . $month . '-01'); + $start = clone $date; + $start->startOfMonth(); + $end = clone $date; + $end->endOfMonth(); + $start->subDay(); + + $dayEarly = clone $date; + $subTitle = 'Budget report for ' . $date->format('F Y'); + $subTitleIcon = 'fa-calendar'; + $dayEarly = $dayEarly->subDay(); + $accounts = $query->getAllAccounts($start, $end); + + $accounts->each( + function (Account $account) use ($start, $end, $query) { + $budgets = $query->getBudgetSummary($account, $start, $end); + $balancedAmount = $query->balancedTransactionsList($account, $start, $end); + $array = []; + foreach ($budgets as $budget) { + $id = intval($budget->id); + $data = $budget->toArray(); + $array[$id] = $data; + } + $account->budgetInformation = $array; + $account->balancedAmount = $balancedAmount; + + } + ); + + $start = clone $date; + $start->startOfMonth(); + + /** + * Start getBudgetsForMonth DONE + */ + $set = Auth::user()->budgets() + ->leftJoin( + 'budget_limits', function (JoinClause $join) use ($date) { + $join->on('budget_limits.budget_id', '=', 'budgets.id')->where('budget_limits.startdate', '=', $date->format('Y-m-d')); + } + ) + ->get(['budgets.*', 'budget_limits.amount as amount']); + $budgets = Steam::makeArray($set); + $amountSet = $query->journalsByBudget($start, $end); + $amounts = Steam::makeArray($amountSet); + $budgets = Steam::mergeArrays($budgets, $amounts); + $budgets[0]['spent'] = isset($budgets[0]['spent']) ? $budgets[0]['spent'] : 0.0; + $budgets[0]['amount'] = isset($budgets[0]['amount']) ? $budgets[0]['amount'] : 0.0; + $budgets[0]['name'] = 'No budget'; + + // find transactions to shared expense accounts, which are without a budget by default: + $transfers = $query->sharedExpenses($start, $end); + foreach ($transfers as $transfer) { + $budgets[0]['spent'] += floatval($transfer->amount) * -1; + } + + /** + * End getBudgetsForMonth DONE + */ + + return View::make('reports.budget', compact('subTitle', 'subTitleIcon', 'date', 'accounts', 'budgets', 'dayEarly')); + + } /** * @param ReportHelperInterface $helper @@ -57,7 +133,7 @@ class ReportController extends Controller try { new Carbon($year . '-' . $month . '-01'); } catch (Exception $e) { - return View::make('error')->with('message', 'Invalid date.'); + return view('error')->with('message', 'Invalid date.'); } $date = new Carbon($year . '-' . $month . '-01'); $subTitle = 'Report for ' . $date->format('F Y'); diff --git a/app/Http/Middleware/Range.php b/app/Http/Middleware/Range.php index 609720eebf..49c3708b93 100644 --- a/app/Http/Middleware/Range.php +++ b/app/Http/Middleware/Range.php @@ -5,7 +5,6 @@ namespace FireflyIII\Http\Middleware; use Carbon\Carbon; use Closure; -use FireflyIII\Exception\FireflyException; use Illuminate\Contracts\Auth\Guard; use Navigation; use Preferences; diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index d126aae49c..3bd399b3c5 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -3,9 +3,10 @@ use Carbon\Carbon; use DaveJamesMiller\Breadcrumbs\Generator; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; -use FireflyIII\Models\Category; use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; use FireflyIII\Models\LimitRepetition; + /* * Back home. */ @@ -345,7 +346,7 @@ Breadcrumbs::register( Breadcrumbs::register( 'transactions.create', function (Generator $breadcrumbs, $what) { $breadcrumbs->parent('transactions.index', $what); - $breadcrumbs->push('Create new ' .e($what), route('transactions.create', $what)); + $breadcrumbs->push('Create new ' . e($what), route('transactions.create', $what)); } ); diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index 726327345f..1aba876ce2 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -12,16 +12,15 @@ class AccountMeta extends Model { use ValidatingTrait; + protected $fillable = ['account_id', 'name', 'data']; protected $rules - = [ + = [ 'account_id' => 'required|exists:accounts,id', 'name' => 'required|between:1,100', 'data' => 'required' ]; protected $table = 'account_meta'; - protected $fillable = ['account_id', 'name', 'data']; - /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index c1665ce53c..b98aa01128 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -1,9 +1,9 @@ startdate, $budgetLimit->repeat_freq, 0); - $end->subDay(); + $end = Navigation::addPeriod(clone $budgetLimit->startdate, $budgetLimit->repeat_freq, 0); + $end->subDay(); - $set = $budgetLimit->limitrepetitions()->where('startdate', $budgetLimit->startdate->format('Y-m-d'))->where('enddate', $end->format('Y-m-d'))->get(); - /* - * Create new LimitRepetition: - */ - if ($set->count() == 0) { + $set = $budgetLimit->limitrepetitions()->where('startdate', $budgetLimit->startdate->format('Y-m-d'))->where('enddate', $end->format('Y-m-d')) + ->get(); + /* + * Create new LimitRepetition: + */ + if ($set->count() == 0) { - $repetition = new LimitRepetition; - $repetition->startdate = $budgetLimit->startdate; - $repetition->enddate = $end; - $repetition->amount = $budgetLimit->amount; - $repetition->budgetLimit()->associate($budgetLimit); + $repetition = new LimitRepetition; + $repetition->startdate = $budgetLimit->startdate; + $repetition->enddate = $end; + $repetition->amount = $budgetLimit->amount; + $repetition->budgetLimit()->associate($budgetLimit); - try { - $repetition->save(); - } catch (QueryException $e) { - \Log::error('Trying to save new LimitRepetition failed!'); - \Log::error($e->getMessage()); - } - } else { - if ($set->count() == 1) { - /* - * Update existing one. - */ - $repetition = $set->first(); - $repetition->amount = $budgetLimit->amount; - $repetition->save(); + try { + $repetition->save(); + } catch (QueryException $e) { + \Log::error('Trying to save new LimitRepetition failed!'); + \Log::error($e->getMessage()); + } + } else { + if ($set->count() == 1) { + /* + * Update existing one. + */ + $repetition = $set->first(); + $repetition->amount = $budgetLimit->amount; + $repetition->save(); + } } } - }); - - - + ); } diff --git a/app/Providers/TestingServiceProvider.php b/app/Providers/TestingServiceProvider.php index 314f1f936d..ff26c311ca 100644 --- a/app/Providers/TestingServiceProvider.php +++ b/app/Providers/TestingServiceProvider.php @@ -10,7 +10,8 @@ use Illuminate\Support\ServiceProvider; * * @package FireflyIII\Providers */ -class TestingServiceProvider extends ServiceProvider { +class TestingServiceProvider extends ServiceProvider +{ /** * Register the service provider. @@ -19,8 +20,7 @@ class TestingServiceProvider extends ServiceProvider { */ public function register() { - if ($this->app->environment() == 'testing') - { + if ($this->app->environment() == 'testing') { $this->app['config']['session.driver'] = 'native'; } } diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 9488d28a2e..8723e9675e 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -58,6 +58,7 @@ class BudgetRepository implements BudgetRepositoryInterface foreach ($set as $entry) { $items[] = $entry; } + return new LengthAwarePaginator($items, $count, $take, $offset); } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index 771c01ccd8..eabda354f3 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -1,9 +1,11 @@ getName(), $date) }} +{!! Breadcrumbs::renderIfExists(Route::getCurrentRoute()->getName(), $date) !!}
{{{$account->name}}} | -{{Amount::format($account->startBalance)}} | -{{Amount::format($account->endBalance)}} | -{{Amount::format($account->startBalance - $account->endBalance,false)}} | +{!! Amount::format($account->startBalance) !!} | +{!! Amount::format($account->endBalance) !!} | +{!! Amount::format($account->startBalance - $account->endBalance,false) !!} |