mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-10-13 16:00:13 +00:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e1a2b4b9af | ||
|
0eadfa1c83 | ||
|
c8dd935460 | ||
|
3290ce85a9 | ||
|
60ef80c1a5 | ||
|
74e852b8bd | ||
|
90ae21d257 | ||
|
fdf03cd8e2 | ||
|
f6586be5e7 | ||
|
f9dc627d84 | ||
|
309177ca9c | ||
|
456d2342b6 | ||
|
0717aa22d7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ _ide_helper.php
|
||||
index.html*
|
||||
app/storage/firefly-export*
|
||||
.vagrant
|
||||
firefly-iii-import-*.json
|
||||
|
52
README.md
52
README.md
@@ -13,42 +13,52 @@ Firefly Mark III is a new version of Firefly built upon best practices and lesso
|
||||
from building [Firefly](https://github.com/JC5/Firefly). It's Mark III since the original Firefly never made it outside of my
|
||||
laptop and [Firefly II](https://github.com/JC5/Firefly) is live.
|
||||
|
||||
## Current features
|
||||
|
||||
- [A double-entry bookkeeping system](http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system).
|
||||
- You can store, edit and remove withdrawals, deposits and transfers. This allows you full financial management;
|
||||
- It's possible to create, change and manage money using _budgets_;
|
||||
- Organize transactions using categories;
|
||||
- Save towards a goal using piggy banks;
|
||||
- Predict and anticipate large expenses using "repeated expenses" (ie. yearly taxes);
|
||||
- Predict and anticipate bills using "recurring transactions" (rent for example).
|
||||
|
||||
Everything is organised:
|
||||
|
||||
- Clear views that should show you how you're doing;
|
||||
- Easy navigation through your records;
|
||||
- Browse back and forth to see previous months or even years;
|
||||
- Lots of help text in case you don't get it;
|
||||
- Lots of charts because we all love them.
|
||||
|
||||
## Changes
|
||||
|
||||
Firefly III will feature:
|
||||
Firefly III will feature, but does not feature yet:
|
||||
|
||||
- Double-entry bookkeeping system;
|
||||
- Better budgeting tools;
|
||||
- Better financial reporting;
|
||||
- Financial reporting showing you how well you are doing;
|
||||
- More control over other resources outside of personal finance
|
||||
- Accounts shared with a partner (household accounts)
|
||||
- Debts
|
||||
- Credit cards
|
||||
- More robust code base (mainly for my own peace of mind);
|
||||
- More test-coverage (aka: actual test coverage);
|
||||
|
||||
## More features
|
||||
|
||||
- Firefly will be able to split transactions; a single purchase can be split in multiple entries, for more fine-grained control.
|
||||
- Firefly will be able to join transactions.
|
||||
- Transfers and transactions will be combined into one internal datatype which is more consistent with what you're actually doing: moving money from A to B. The fact that A or B or both are yours should not matter. And it will not, in the future.
|
||||
- The nesting of budgets, categories and beneficiaries will be removed.
|
||||
- Firefly will be able to automatically login a specified account. Although this is pretty unsafe, it removes the need for you to login to your own tool.
|
||||
- Transfers and transactions are combined into one internal datatype which is more consistent with what you're actually doing: moving money from A to B. The fact that A or B or both are yours should not matter.
|
||||
- Any other features I might not have thought of.
|
||||
|
||||
## Not changed
|
||||
Some stuff has been removed:
|
||||
|
||||
- The nesting of budgets, categories and beneficiaries is removed because it was pretty pointless.
|
||||
- Firefly will not encrypt the content of the (MySQL) tables. Old versions of Firefly had this capability but it sucks when searching, sorting and organizing entries.
|
||||
|
||||
## Current state
|
||||
I have the basics up and running and test coverage is doing very well.
|
||||
I have the basics up and running. Test coverage is currently non-existent.
|
||||
|
||||
Current issues are the consistent look-and-feel of forms and likewise, the consistent inner workings of most of Firefly.
|
||||
Example: every "create"-action tends to be slightly different from the rest. Also is the fact that not all lists
|
||||
and forms are equally well thought of; some are not looking very well or miss feedback.
|
||||
Although I have not checked extensively, some forms and views have CSRF vulnerabilities. This is because not all
|
||||
views escape all characters by default. Will be fixed.
|
||||
|
||||
Most forms will not allow you to enter invalid data because the database cracks, not because it's actually checked.
|
||||
I'm still thinking about a way to build consistent forms. Laravel doesn't really cut it.
|
||||
The current layout / look & feel is a pretty basic Bootstrap3 template. I am currently working on a more consistent,
|
||||
expanded layout which will feature shiny AJAX things and data tables and all the Web 3.0 goodies you've come to expect
|
||||
from social media sites.
|
||||
|
||||
A lot of views have CSRF vulnerabilities. The general advice is NOT to use this tool in production.
|
||||
|
||||
Questions, ideas or other things to contribute? [Let me know](https://github.com/JC5/firefly-iii/issues/new)!
|
||||
Questions, ideas or other things to contribute? [Let me know](https://github.com/JC5/firefly-iii/issues/new)!
|
@@ -1,48 +0,0 @@
|
||||
.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%;
|
||||
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);
|
||||
}
|
||||
|
||||
.bootstrap-tagsinput {width:100%;}
|
@@ -8,8 +8,6 @@ use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
|
||||
|
||||
class AccountController extends \BaseController
|
||||
{
|
||||
|
||||
@@ -18,7 +16,7 @@ class AccountController extends \BaseController
|
||||
|
||||
/**
|
||||
* @param ARI $repository
|
||||
* @param AI $accounts
|
||||
* @param AI $accounts
|
||||
*/
|
||||
public function __construct(ARI $repository, AI $accounts)
|
||||
{
|
||||
@@ -42,7 +40,7 @@ class AccountController extends \BaseController
|
||||
public function delete(Account $account)
|
||||
{
|
||||
return View::make('accounts.delete')->with('account', $account)
|
||||
->with('title', 'Delete account "' . $account->name . '"');
|
||||
->with('title', 'Delete account "' . $account->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +67,7 @@ class AccountController extends \BaseController
|
||||
{
|
||||
$openingBalance = $this->_accounts->openingBalanceTransaction($account);
|
||||
return View::make('accounts.edit')->with('account', $account)->with('openingBalance', $openingBalance)
|
||||
->with('title', 'Edit account "' . $account->name . '"');
|
||||
->with('title', 'Edit account "' . $account->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,8 +103,10 @@ class AccountController extends \BaseController
|
||||
{
|
||||
$data = $this->_accounts->show($account, 40);
|
||||
|
||||
return View::make('accounts.show')->with('account', $account)->with('show', $data)->with('title',
|
||||
'Details for account "' . $account->name . '"');
|
||||
return View::make('accounts.show')->with('account', $account)->with('show', $data)->with(
|
||||
'title',
|
||||
'Details for account "' . $account->name . '"'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
use \Illuminate\Routing\Controller;
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
/**
|
||||
* Class BaseController
|
||||
@@ -13,8 +13,6 @@ class BaseController extends Controller
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
\Event::fire('limits.check');
|
||||
\Event::fire('piggybanks.check');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -8,6 +8,7 @@ use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
|
||||
* Class BudgetController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*
|
||||
*/
|
||||
class BudgetController extends BaseController
|
||||
@@ -17,7 +18,7 @@ class BudgetController extends BaseController
|
||||
protected $_repository;
|
||||
|
||||
/**
|
||||
* @param BI $budgets
|
||||
* @param BI $budgets
|
||||
* @param BRI $repository
|
||||
*/
|
||||
public function __construct(BI $budgets, BRI $repository)
|
||||
@@ -44,7 +45,7 @@ class BudgetController extends BaseController
|
||||
public function delete(Budget $budget)
|
||||
{
|
||||
return View::make('budgets.delete')->with('budget', $budget)
|
||||
->with('title', 'Delete budget "' . $budget->name . '"');
|
||||
->with('title', 'Delete budget "' . $budget->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,7 +76,7 @@ class BudgetController extends BaseController
|
||||
public function edit(Budget $budget)
|
||||
{
|
||||
return View::make('budgets.edit')->with('budget', $budget)
|
||||
->with('title', 'Edit budget "' . $budget->name . '"');
|
||||
->with('title', 'Edit budget "' . $budget->name . '"');
|
||||
|
||||
}
|
||||
|
||||
@@ -87,7 +88,7 @@ class BudgetController extends BaseController
|
||||
$budgets = $this->_repository->get();
|
||||
|
||||
return View::make('budgets.indexByBudget')->with('budgets', $budgets)->with('today', new Carbon)
|
||||
->with('title', 'All your budgets grouped by budget');
|
||||
->with('title', 'All your budgets grouped by budget');
|
||||
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ class BudgetController extends BaseController
|
||||
$budgets = $this->_budgets->organizeByDate($set);
|
||||
|
||||
return View::make('budgets.indexByDate')->with('budgets', $budgets)
|
||||
->with('title', 'All your budgets grouped by date');
|
||||
->with('title', 'All your budgets grouped by date');
|
||||
|
||||
|
||||
}
|
||||
@@ -113,7 +114,7 @@ class BudgetController extends BaseController
|
||||
* - Show a specific repetition.
|
||||
* - Show everything shows NO repetition.
|
||||
*
|
||||
* @param Budget $budget
|
||||
* @param Budget $budget
|
||||
* @param LimitRepetition $repetition
|
||||
*
|
||||
* @return int
|
||||
@@ -128,8 +129,10 @@ class BudgetController extends BaseController
|
||||
case (!is_null($repetition)):
|
||||
$data = $this->_budgets->organizeRepetition($repetition);
|
||||
$view = 1;
|
||||
$title = $budget->name . ', ' . $repetition->periodShow() . ', ' . mf($repetition->limit->amount,
|
||||
false);
|
||||
$title = $budget->name . ', ' . $repetition->periodShow() . ', ' . mf(
|
||||
$repetition->limit->amount,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case (Input::get('noenvelope') == 'true'):
|
||||
$data = $this->_budgets->outsideRepetitions($budget);
|
||||
@@ -144,12 +147,12 @@ class BudgetController extends BaseController
|
||||
}
|
||||
|
||||
return View::make('budgets.show')
|
||||
->with('budget', $budget)
|
||||
->with('repetitions', $data)
|
||||
->with('view', $view)
|
||||
->with('highlight', Input::get('highlight'))
|
||||
->with('useSessionDates', $useSessionDates)
|
||||
->with('title', $title);
|
||||
->with('budget', $budget)
|
||||
->with('repetitions', $data)
|
||||
->with('view', $view)
|
||||
->with('highlight', Input::get('highlight'))
|
||||
->with('useSessionDates', $useSessionDates)
|
||||
->with('title', $title);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -5,6 +5,8 @@ use Firefly\Storage\Category\CategoryRepositoryInterface as CRI;
|
||||
|
||||
/**
|
||||
* Class CategoryController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class CategoryController extends BaseController
|
||||
{
|
||||
@@ -13,7 +15,7 @@ class CategoryController extends BaseController
|
||||
|
||||
/**
|
||||
* @param CRI $repository
|
||||
* @param CI $category
|
||||
* @param CI $category
|
||||
*/
|
||||
public function __construct(CRI $repository, CI $category)
|
||||
{
|
||||
@@ -37,7 +39,7 @@ class CategoryController extends BaseController
|
||||
public function delete(Category $category)
|
||||
{
|
||||
return View::make('categories.delete')->with('category', $category)
|
||||
->with('title', 'Delete category "' . $category->name . '"');
|
||||
->with('title', 'Delete category "' . $category->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -88,8 +90,8 @@ class CategoryController extends BaseController
|
||||
$journals = $this->_category->journalsInRange($category, $start, $end);
|
||||
|
||||
return View::make('categories.show')->with('category', $category)->with('journals', $journals)->with(
|
||||
'highlight', Input::get('highlight')
|
||||
)->with('title', 'Overview for category "'.$category->name.'"');;
|
||||
'highlight', Input::get('highlight')
|
||||
)->with('title', 'Overview for category "' . $category->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,6 +6,9 @@ use Firefly\Storage\Account\AccountRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Class ChartController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class ChartController extends BaseController
|
||||
{
|
||||
@@ -143,15 +146,15 @@ class ChartController extends BaseController
|
||||
public function budgetNoLimits(\Budget $budget)
|
||||
{
|
||||
/*
|
||||
* We can go about this two ways. Either we find all transactions which definitely are IN an envelope
|
||||
* and exclude them or we search for transactions outside of the range of any of the envelopes we have.
|
||||
* Firefly can go about this two ways. Either it finds all transactions which definitely are IN an envelope
|
||||
* and exclude them or it searches for transactions outside of the range of any of the envelopes there are.
|
||||
*
|
||||
* Since either is shitty we go with the first one because it's easier to build.
|
||||
* Since either is kinda shitty Firefly uses the first one because it's easier to build.
|
||||
*/
|
||||
$inRepetitions = $this->_chart->allJournalsInBudgetEnvelope($budget);
|
||||
|
||||
/*
|
||||
* With this set of id's, we can search for all journals NOT in that set.
|
||||
* With this set of id's, Firefly can search for all journals NOT in that set.
|
||||
* BUT they have to be in the budget (duh).
|
||||
*/
|
||||
$set = $this->_chart->journalsNotInSet($budget, $inRepetitions);
|
||||
@@ -200,7 +203,6 @@ class ChartController extends BaseController
|
||||
$start = clone Session::get('start');
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Expenses per day in the session's period. That's easy.
|
||||
*/
|
||||
@@ -227,14 +229,15 @@ class ChartController extends BaseController
|
||||
$reps = $this->_chart->limitsInRange($budget, $start, $end);
|
||||
|
||||
/*
|
||||
* For each limitrepetition we create a serie that contains the amount left in
|
||||
* For each limitrepetition Firefly creates a serie that contains the amount left in
|
||||
* the limitrepetition for its entire date-range. Entries are only actually included when they
|
||||
* fall into the charts date range.
|
||||
*
|
||||
* So example: we have a session date from Jan 15 to Jan 30. The limitrepetition starts at 1 Jan until 1 Feb.
|
||||
* So example: the user has a session date from Jan 15 to Jan 30. The limitrepetition
|
||||
* starts at 1 Jan until 1 Feb.
|
||||
*
|
||||
* We loop from 1 Jan to 1 Feb but only include Jan 15 / Jan 30. But we do keep count of the amount outside
|
||||
* of these dates because otherwise the line might be wrong.
|
||||
* Firefly loops from 1 Jan to 1 Feb but only includes Jan 15 / Jan 30.
|
||||
* But it does keep count of the amount outside of these dates because otherwise the line might be wrong.
|
||||
*/
|
||||
/** @var \LimitRepetition $repetition */
|
||||
foreach ($reps as $repetition) {
|
||||
@@ -245,7 +248,7 @@ class ChartController extends BaseController
|
||||
'type' => 'spline',
|
||||
'id' => 'rep-' . $repetition->id,
|
||||
'yAxis' => 1,
|
||||
'name' => 'Envelope #'.$repetition->id.' in ' . $repetition->periodShow(),
|
||||
'name' => 'Envelope #' . $repetition->id . ' in ' . $repetition->periodShow(),
|
||||
'data' => []
|
||||
];
|
||||
$current = clone $repetition->startdate;
|
||||
|
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
use Carbon\Carbon;
|
||||
use Firefly\Helper\Preferences\PreferencesHelperInterface as PHI;
|
||||
use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
|
||||
use Firefly\Storage\Reminder\ReminderRepositoryInterface as RRI;
|
||||
@@ -7,6 +6,8 @@ use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as
|
||||
|
||||
/**
|
||||
* Class HomeController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class HomeController extends BaseController
|
||||
{
|
||||
@@ -15,12 +16,18 @@ class HomeController extends BaseController
|
||||
protected $_journal;
|
||||
protected $_reminders;
|
||||
|
||||
/**
|
||||
* @param ARI $accounts
|
||||
* @param PHI $preferences
|
||||
* @param TJRI $journal
|
||||
* @param RRI $reminders
|
||||
*/
|
||||
public function __construct(ARI $accounts, PHI $preferences, TJRI $journal, RRI $reminders)
|
||||
{
|
||||
$this->_accounts = $accounts;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_preferences = $preferences;
|
||||
$this->_journal = $journal;
|
||||
$this->_reminders = $reminders;
|
||||
$this->_journal = $journal;
|
||||
$this->_reminders = $reminders;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,20 +45,14 @@ class HomeController extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// Queue::push(function($job)
|
||||
// {
|
||||
// Log::debug('This is a job!');
|
||||
// });
|
||||
Event::fire('limits.check');
|
||||
Event::fire('piggybanks.check');
|
||||
Event::fire('recurring.check');
|
||||
|
||||
\Event::fire('limits.check');
|
||||
\Event::fire('piggybanks.check');
|
||||
\Event::fire('recurring.check');
|
||||
|
||||
|
||||
// count, maybe we need some introducing text to show:
|
||||
// count, maybe Firefly needs some introducing text to show:
|
||||
$count = $this->_accounts->count();
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$end = Session::get('end');
|
||||
|
||||
|
||||
// get the preference for the home accounts to show:
|
||||
@@ -78,13 +79,7 @@ class HomeController extends BaseController
|
||||
$transactions = array_chunk($transactions, 3);
|
||||
}
|
||||
|
||||
// get the users reminders:
|
||||
|
||||
$reminders = $this->_reminders->getCurrentRecurringReminders();
|
||||
|
||||
// build the home screen:
|
||||
return View::make('index')->with('count', $count)->with('transactions', $transactions)->with(
|
||||
'reminders', $reminders
|
||||
);
|
||||
return View::make('index')->with('count', $count)->with('transactions', $transactions);
|
||||
}
|
||||
}
|
@@ -7,6 +7,8 @@ use Firefly\Storage\Component\ComponentRepositoryInterface as CRI;
|
||||
|
||||
/**
|
||||
* Class JsonController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class JsonController extends BaseController
|
||||
{
|
||||
@@ -24,9 +26,9 @@ class JsonController extends BaseController
|
||||
public function __construct(ARI $accounts, CRI $components, Cat $categories, Bud $budgets)
|
||||
{
|
||||
$this->_components = $components;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_categories = $categories;
|
||||
$this->_budgets = $budgets;
|
||||
$this->_budgets = $budgets;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,7 +36,7 @@ class JsonController extends BaseController
|
||||
*/
|
||||
public function beneficiaries()
|
||||
{
|
||||
$list = $this->_accounts->getBeneficiaries();
|
||||
$list = $this->_accounts->getBeneficiaries();
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
@@ -49,7 +51,7 @@ class JsonController extends BaseController
|
||||
*/
|
||||
public function categories()
|
||||
{
|
||||
$list = $this->_categories->get();
|
||||
$list = $this->_categories->get();
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
|
@@ -5,6 +5,8 @@ use Firefly\Storage\Limit\LimitRepositoryInterface as LRI;
|
||||
|
||||
/**
|
||||
* Class LimitController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class LimitController extends BaseController
|
||||
{
|
||||
@@ -19,7 +21,7 @@ class LimitController extends BaseController
|
||||
public function __construct(BRI $budgets, LRI $limits)
|
||||
{
|
||||
$this->_budgets = $budgets;
|
||||
$this->_limits = $limits;
|
||||
$this->_limits = $limits;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +31,7 @@ class LimitController extends BaseController
|
||||
*/
|
||||
public function create(\Budget $budget = null)
|
||||
{
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
$prefilled = [
|
||||
'startdate' => \Input::get('startdate') ? : date('Y-m-d'),
|
||||
'repeat_freq' => \Input::get('repeat_freq') ? : 'monthly',
|
||||
@@ -98,7 +100,7 @@ class LimitController extends BaseController
|
||||
public function store(Budget $budget = null)
|
||||
{
|
||||
|
||||
// find a limit with these properties, as we might already have one:
|
||||
// find a limit with these properties, as Firefly might already have one:
|
||||
$limit = $this->_limits->store(Input::all());
|
||||
if ($limit->validate()) {
|
||||
Session::flash('success', 'Envelope created!');
|
||||
@@ -110,7 +112,7 @@ class LimitController extends BaseController
|
||||
}
|
||||
} else {
|
||||
Session::flash('error', 'Could not save new envelope.');
|
||||
$budgetId = $budget ? $budget->id : null;
|
||||
$budgetId = $budget ? $budget->id : null;
|
||||
$parameters = [$budgetId, 'from' => Input::get('from')];
|
||||
|
||||
return Redirect::route('budgets.limits.create', $parameters)->withInput()
|
||||
|
@@ -27,10 +27,18 @@ class MigrateController extends BaseController
|
||||
$fullName = $path . DIRECTORY_SEPARATOR . $fileName;
|
||||
if (is_writable($path)) {
|
||||
Input::file('file')->move($path, $fileName);
|
||||
// so now we push something in a queue and do something with it! Yay!
|
||||
// so now Firefly pushes something in a queue and does something with it! Yay!
|
||||
\Log::debug('Pushed a job to start the import.');
|
||||
Queue::push('Firefly\Queue\Import@start', ['file' => $fullName, 'user' => \Auth::user()->id]);
|
||||
Session::flash('success', 'The import job has been queued. Please be patient. Data will appear');
|
||||
if (Config::get('queue.default') == 'sync') {
|
||||
Session::flash('success', 'Your data has been imported!');
|
||||
} else {
|
||||
Session::flash(
|
||||
'success',
|
||||
'The import job has been queued. Please be patient. Data will appear slowly. Please be patient.'
|
||||
);
|
||||
}
|
||||
|
||||
return Redirect::route('index');
|
||||
}
|
||||
Session::flash('error', 'Could not save file to storage.');
|
||||
|
@@ -7,6 +7,10 @@ use Firefly\Storage\Piggybank\PiggybankRepositoryInterface as PRI;
|
||||
/**
|
||||
* Class PiggybankController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
* @SuppressWarnings(PHPMD.TooManyMethods)
|
||||
*
|
||||
*/
|
||||
class PiggybankController extends BaseController
|
||||
{
|
||||
@@ -21,16 +25,18 @@ class PiggybankController extends BaseController
|
||||
public function __construct(PRI $repository, ARI $accounts)
|
||||
{
|
||||
$this->_repository = $repository;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_accounts = $accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Piggybank $piggyBank
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addMoney(Piggybank $piggyBank)
|
||||
{
|
||||
$what = 'add';
|
||||
$maxAdd = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$what = 'add';
|
||||
$maxAdd = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$maxRemove = null;
|
||||
|
||||
return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with(
|
||||
@@ -43,7 +49,7 @@ class PiggybankController extends BaseController
|
||||
*/
|
||||
public function createPiggybank()
|
||||
{
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
|
||||
|
||||
return View::make('piggybanks.create-piggybank')->with('accounts', $accounts)->with('periods', $periods);
|
||||
@@ -54,7 +60,7 @@ class PiggybankController extends BaseController
|
||||
*/
|
||||
public function createRepeated()
|
||||
{
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
|
||||
|
||||
return View::make('piggybanks.create-repeated')->with('accounts', $accounts)->with('periods', $periods);
|
||||
@@ -93,7 +99,7 @@ class PiggybankController extends BaseController
|
||||
public function edit(Piggybank $piggyBank)
|
||||
{
|
||||
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
if ($piggyBank->repeats == 1) {
|
||||
return View::make('piggybanks.edit-repeated')->with('piggybank', $piggyBank)->with('accounts', $accounts)
|
||||
->with('periods', $periods);
|
||||
@@ -110,19 +116,19 @@ class PiggybankController extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$countRepeating = $this->_repository->countRepeating();
|
||||
$countRepeating = $this->_repository->countRepeating();
|
||||
$countNonRepeating = $this->_repository->countNonrepeating();
|
||||
|
||||
$piggybanks = $this->_repository->get();
|
||||
|
||||
// get the accounts with each piggy bank and check their balance; we might need to
|
||||
// get the accounts with each piggy bank and check their balance; Fireflyy might needs to
|
||||
// show the user a correction.
|
||||
|
||||
$accounts = [];
|
||||
/** @var \Piggybank $piggybank */
|
||||
foreach ($piggybanks as $piggybank) {
|
||||
$account = $piggybank->account;
|
||||
$id = $account->id;
|
||||
$id = $account->id;
|
||||
if (!isset($accounts[$id])) {
|
||||
$accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)];
|
||||
}
|
||||
@@ -158,7 +164,7 @@ class PiggybankController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'remove':
|
||||
$rep = $piggyBank->currentRelevantRep();
|
||||
$rep = $piggyBank->currentRelevantRep();
|
||||
$maxRemove = $rep->currentamount;
|
||||
if (round($amount, 2) <= round($maxRemove, 2)) {
|
||||
Session::flash('success', 'Amount updated!');
|
||||
@@ -175,11 +181,13 @@ class PiggybankController extends BaseController
|
||||
|
||||
/**
|
||||
* @param Piggybank $piggyBank
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeMoney(Piggybank $piggyBank)
|
||||
{
|
||||
$what = 'remove';
|
||||
$maxAdd = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$what = 'remove';
|
||||
$maxAdd = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$maxRemove = $piggyBank->currentRelevantRep()->currentamount;
|
||||
|
||||
return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with(
|
||||
@@ -193,7 +201,7 @@ class PiggybankController extends BaseController
|
||||
public function show(Piggybank $piggyBank)
|
||||
{
|
||||
$leftOnAccount = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$balance = $piggyBank->account->balance();
|
||||
$balance = $piggyBank->account->balance();
|
||||
|
||||
return View::make('piggybanks.show')->with('piggyBank', $piggyBank)->with('leftOnAccount', $leftOnAccount)
|
||||
->with('balance', $balance);
|
||||
@@ -208,10 +216,10 @@ class PiggybankController extends BaseController
|
||||
unset($data['_token']);
|
||||
|
||||
// extend the data array with the settings needed to create a piggy bank:
|
||||
$data['repeats'] = 0;
|
||||
$data['repeats'] = 0;
|
||||
$data['rep_times'] = 1;
|
||||
$data['rep_every'] = 1;
|
||||
$data['order'] = 0;
|
||||
$data['order'] = 0;
|
||||
|
||||
$piggyBank = $this->_repository->store($data);
|
||||
if (!is_null($piggyBank->id)) {
|
||||
@@ -240,7 +248,7 @@ class PiggybankController extends BaseController
|
||||
|
||||
// extend the data array with the settings needed to create a repeated:
|
||||
$data['repeats'] = 1;
|
||||
$data['order'] = 0;
|
||||
$data['order'] = 0;
|
||||
|
||||
$piggyBank = $this->_repository->store($data);
|
||||
if ($piggyBank->id) {
|
||||
@@ -258,6 +266,8 @@ class PiggybankController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Piggybank $piggyBank
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(Piggybank $piggyBank)
|
||||
|
@@ -5,6 +5,8 @@ use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
|
||||
|
||||
/**
|
||||
* Class PreferencesController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class PreferencesController extends BaseController
|
||||
{
|
||||
@@ -18,7 +20,7 @@ class PreferencesController extends BaseController
|
||||
public function __construct(ARI $accounts, PHI $preferences)
|
||||
{
|
||||
|
||||
$this->_accounts = $accounts;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_preferences = $preferences;
|
||||
}
|
||||
|
||||
@@ -29,7 +31,7 @@ class PreferencesController extends BaseController
|
||||
{
|
||||
$accounts = $this->_accounts->getDefault();
|
||||
|
||||
$viewRange = $this->_preferences->get('viewRange', '1M');
|
||||
$viewRange = $this->_preferences->get('viewRange', '1M');
|
||||
$viewRangeValue = $viewRange->data;
|
||||
|
||||
// pref:
|
||||
|
@@ -4,6 +4,8 @@ use Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface
|
||||
|
||||
/**
|
||||
* Class RecurringController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class RecurringController extends BaseController
|
||||
{
|
||||
@@ -114,6 +116,8 @@ class RecurringController extends BaseController
|
||||
|
||||
/**
|
||||
* @param RecurringTransaction $recurringTransaction
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(RecurringTransaction $recurringTransaction)
|
||||
{
|
||||
|
@@ -5,12 +5,17 @@ use Firefly\Storage\Reminder\ReminderRepositoryInterface as RRI;
|
||||
|
||||
/**
|
||||
* Class ReminderController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class ReminderController extends BaseController
|
||||
{
|
||||
|
||||
protected $_repository;
|
||||
|
||||
/**
|
||||
* @param RRI $repository
|
||||
*/
|
||||
public function __construct(RRI $repository)
|
||||
{
|
||||
$this->_repository = $repository;
|
||||
@@ -33,8 +38,8 @@ class ReminderController extends BaseController
|
||||
*/
|
||||
public function modalDialog()
|
||||
{
|
||||
$today = new Carbon;
|
||||
$reminders = $this->_repository->get();
|
||||
$today = new Carbon;
|
||||
$reminders = $this->_repository->getPiggybankReminders();
|
||||
|
||||
/** @var \Reminder $reminder */
|
||||
foreach ($reminders as $index => $reminder) {
|
||||
@@ -66,6 +71,8 @@ class ReminderController extends BaseController
|
||||
|
||||
/**
|
||||
* @param Reminder $reminder
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function redirect(\Reminder $reminder)
|
||||
{
|
||||
@@ -83,6 +90,7 @@ class ReminderController extends BaseController
|
||||
route('transactions.create', ['what' => 'transfer']) . '?' . http_build_query($parameters)
|
||||
);
|
||||
}
|
||||
return View::make('error')->with('message', 'No such reminder.');
|
||||
|
||||
}
|
||||
|
||||
|
@@ -6,6 +6,8 @@ use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as
|
||||
|
||||
/**
|
||||
* Class TransactionController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class TransactionController extends BaseController
|
||||
{
|
||||
@@ -30,18 +32,18 @@ class TransactionController extends BaseController
|
||||
// get accounts with names and id's.
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$accounts = $accountRepository->getActiveDefaultAsSelectList();
|
||||
$accounts = $accountRepository->getActiveDefaultAsSelectList();
|
||||
|
||||
// get budgets as a select list.
|
||||
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgetRepository */
|
||||
$budgetRepository = App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
|
||||
$budgets = $budgetRepository->getAsSelectList();
|
||||
$budgets[0] = '(no budget)';
|
||||
$budgets = $budgetRepository->getAsSelectList();
|
||||
$budgets[0] = '(no budget)';
|
||||
|
||||
// get the piggy banks.
|
||||
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
|
||||
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
|
||||
$piggies = $piggyRepository->get();
|
||||
$piggies = $piggyRepository->get();
|
||||
|
||||
return View::make('transactions.create')->with('accounts', $accounts)->with('budgets', $budgets)->with(
|
||||
'what', $what
|
||||
@@ -88,18 +90,18 @@ class TransactionController extends BaseController
|
||||
// get accounts with names and id's.
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$accounts = $accountRepository->getActiveDefaultAsSelectList();
|
||||
$accounts = $accountRepository->getActiveDefaultAsSelectList();
|
||||
|
||||
// get budgets as a select list.
|
||||
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgetRepository */
|
||||
$budgetRepository = App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
|
||||
$budgets = $budgetRepository->getAsSelectList();
|
||||
$budgets[0] = '(no budget)';
|
||||
$budgets = $budgetRepository->getAsSelectList();
|
||||
$budgets[0] = '(no budget)';
|
||||
|
||||
// get the piggy banks.
|
||||
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
|
||||
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
|
||||
$piggies = $piggyRepository->get();
|
||||
$piggies = $piggyRepository->get();
|
||||
// piggy bank id?
|
||||
$piggyBankId = null;
|
||||
foreach ($journal->transactions as $t) {
|
||||
@@ -107,7 +109,7 @@ class TransactionController extends BaseController
|
||||
}
|
||||
|
||||
// data to properly display form:
|
||||
$data = [
|
||||
$data = [
|
||||
'date' => $journal->date->format('Y-m-d'),
|
||||
'category' => '',
|
||||
'budget_id' => 0,
|
||||
@@ -119,23 +121,23 @@ class TransactionController extends BaseController
|
||||
}
|
||||
switch ($journal->transactiontype->type) {
|
||||
case 'Withdrawal':
|
||||
$data['account_id'] = $journal->transactions[0]->account->id;
|
||||
$data['account_id'] = $journal->transactions[0]->account->id;
|
||||
$data['beneficiary'] = $journal->transactions[1]->account->name;
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
$budget = $journal->budgets()->first();
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
$budget = $journal->budgets()->first();
|
||||
if (!is_null($budget)) {
|
||||
$data['budget_id'] = $budget->id;
|
||||
}
|
||||
break;
|
||||
case 'Deposit':
|
||||
$data['account_id'] = $journal->transactions[1]->account->id;
|
||||
$data['account_id'] = $journal->transactions[1]->account->id;
|
||||
$data['beneficiary'] = $journal->transactions[0]->account->name;
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
break;
|
||||
case 'Transfer':
|
||||
$data['account_from_id'] = $journal->transactions[1]->account->id;
|
||||
$data['account_to_id'] = $journal->transactions[0]->account->id;
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
$data['account_to_id'] = $journal->transactions[0]->account->id;
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -150,15 +152,15 @@ class TransactionController extends BaseController
|
||||
public function index()
|
||||
{
|
||||
$start = is_null(Input::get('startdate')) ? null : new Carbon(Input::get('startdate'));
|
||||
$end = is_null(Input::get('enddate')) ? null : new Carbon(Input::get('enddate'));
|
||||
$end = is_null(Input::get('enddate')) ? null : new Carbon(Input::get('enddate'));
|
||||
if ($start <= $end && !is_null($start) && !is_null($end)) {
|
||||
$journals = $this->_repository->paginate(25, $start, $end);
|
||||
$filtered = true;
|
||||
$filters = ['start' => $start, 'end' => $end];
|
||||
$filters = ['start' => $start, 'end' => $end];
|
||||
} else {
|
||||
$journals = $this->_repository->paginate(25);
|
||||
$filtered = false;
|
||||
$filters = null;
|
||||
$filters = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -192,12 +194,12 @@ class TransactionController extends BaseController
|
||||
if (Input::get('reminder')) {
|
||||
/** @var \Firefly\Storage\Reminder\ReminderRepositoryInterface $reminders */
|
||||
$reminders = App::make('Firefly\Storage\Reminder\ReminderRepositoryInterface');
|
||||
$reminder = $reminders->find(Input::get('reminder'));
|
||||
$reminder = $reminders->find(Input::get('reminder'));
|
||||
$reminders->deactivate($reminder);
|
||||
}
|
||||
|
||||
// trigger the creation for recurring transactions.
|
||||
Event::fire('journals.store',[$journal]);
|
||||
Event::fire('journals.store', [$journal]);
|
||||
|
||||
if (Input::get('create') == '1') {
|
||||
return Redirect::route('transactions.create', [$what])->withInput();
|
||||
@@ -226,7 +228,7 @@ class TransactionController extends BaseController
|
||||
if ($journal->validate()) {
|
||||
// has been saved, return to index:
|
||||
Session::flash('success', 'Transaction updated!');
|
||||
Event::fire('journals.update',[$journal]);
|
||||
Event::fire('journals.update', [$journal]);
|
||||
|
||||
return Redirect::route('transactions.index');
|
||||
} else {
|
||||
|
@@ -17,7 +17,7 @@ class UserController extends BaseController
|
||||
*/
|
||||
public function __construct(URI $user, EHI $email)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->user = $user;
|
||||
$this->email = $email;
|
||||
|
||||
}
|
||||
@@ -41,11 +41,11 @@ class UserController extends BaseController
|
||||
public function postLogin()
|
||||
{
|
||||
$rememberMe = Input::get('remember_me') == '1';
|
||||
$data = [
|
||||
$data = [
|
||||
'email' => Input::get('email'),
|
||||
'password' => Input::get('password')
|
||||
];
|
||||
$result = Auth::attempt($data, $rememberMe);
|
||||
$result = Auth::attempt($data, $rememberMe);
|
||||
if ($result) {
|
||||
Session::flash('success', 'Logged in!');
|
||||
|
||||
|
@@ -32,7 +32,7 @@ class CreateRemindersTable extends Migration
|
||||
$table->integer('recurring_transaction_id')->unsigned()->nullable();
|
||||
$table->integer('user_id')->unsigned();
|
||||
$table->date('startdate');
|
||||
$table->date('enddate');
|
||||
$table->date('enddate')->nullable();
|
||||
$table->boolean('active');
|
||||
|
||||
|
||||
|
@@ -73,7 +73,7 @@ abstract class SingleTableInheritanceEntity extends Ardent
|
||||
// newEloquentBuilder() was added in 4.1
|
||||
$builder = $this->newEloquentBuilder($this->newBaseQueryBuilder());
|
||||
|
||||
// Once we have the query builders, we will set the model instances so the
|
||||
// Once Firefly has the query builders, it will set the model instances so the
|
||||
// builder can easily access any information it may need from the model
|
||||
// while it is constructing and executing various queries against it.
|
||||
$builder->setModel($this)->with($this->with);
|
||||
|
@@ -28,13 +28,16 @@ class Account implements AccountInterface
|
||||
* Since it is entirely possible the database is messed up somehow it might be that a transaction
|
||||
* journal has only one transaction. This is mainly caused by wrong deletions and other artefacts from the past.
|
||||
*
|
||||
* If it is the case, we remove $item and continue like nothing ever happened. This will however,
|
||||
* mess up some statisics but we can live with that. We might be needing some cleanup routine in the future.
|
||||
* If it is the case, Firefly removes $item and continues like nothing ever happened. This will however,
|
||||
* mess up some statisics but it's decided everybody should learn to live with that.
|
||||
*
|
||||
* For now, we simply warn the user of this.
|
||||
* Firefly might be needing some cleanup routine in the future.
|
||||
*
|
||||
* For now, Firefly simply warns the user of this.
|
||||
*
|
||||
* @param \Account $account
|
||||
* @param $perPage
|
||||
*
|
||||
* @return array|mixed
|
||||
* @throws \Firefly\Exception\FireflyException
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
|
@@ -25,13 +25,20 @@ class Chart implements ChartInterface
|
||||
{
|
||||
$current = clone $start;
|
||||
$today = new Carbon;
|
||||
$return = ['name' => $account->name, 'id' => $account->id, 'data' => []];
|
||||
$return = [
|
||||
'name' => $account->name,
|
||||
'id' => $account->id,
|
||||
'type' => 'spline',
|
||||
'pointStart' => $start->timestamp * 1000,
|
||||
'pointInterval' => 24 * 3600 * 1000, // one day
|
||||
'data' => []
|
||||
];
|
||||
|
||||
while ($current <= $end) {
|
||||
if ($current > $today) {
|
||||
$return['data'][] = [$current->timestamp * 1000, $account->predict(clone $current)];
|
||||
$return['data'][] = $account->predict(clone $current);
|
||||
} else {
|
||||
$return['data'][] = [$current->timestamp * 1000, $account->balance(clone $current)];
|
||||
$return['data'][] = $account->balance(clone $current);
|
||||
}
|
||||
|
||||
$current->addDay();
|
||||
@@ -550,9 +557,10 @@ class Chart implements ChartInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* We check how much money has been spend on the limitrepetition (aka: the current envelope) in the period denoted.
|
||||
* Aka, we have a certain amount of money in an envelope and we wish to know how much we've spent between the dates
|
||||
* entered. This can be a partial match with the date range of the envelope or no match at all.
|
||||
* Firefly checks how much money has been spend on the limitrepetition (aka: the current envelope) in
|
||||
* the period denoted. Aka, the user has a certain amount of money in an envelope and wishes to know how
|
||||
* much he has spent between the dates entered. This date range can be a partial match with the date range
|
||||
* of the envelope or no match at all.
|
||||
*
|
||||
* @param \LimitRepetition $repetition
|
||||
* @param Carbon $start
|
||||
@@ -560,19 +568,21 @@ class Chart implements ChartInterface
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function spentOnLimitRepetitionBetweenDates(\LimitRepetition $repetition, Carbon $start, Carbon $end) {
|
||||
public function spentOnLimitRepetitionBetweenDates(\LimitRepetition $repetition, Carbon $start, Carbon $end)
|
||||
{
|
||||
return floatval(
|
||||
\Transaction::
|
||||
leftJoin('transaction_journals', 'transaction_journals.id', '=','transactions.transaction_journal_id')
|
||||
->leftJoin('component_transaction_journal', 'component_transaction_journal.transaction_journal_id','=',
|
||||
'transaction_journals.id'
|
||||
)->where('component_transaction_journal.component_id', '=', $repetition->limit->budget->id)->where(
|
||||
'transaction_journals.date', '>=', $start->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->where(
|
||||
'amount', '>', 0
|
||||
)->sum('amount')) ;
|
||||
leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin(
|
||||
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
|
||||
'transaction_journals.id'
|
||||
)->where('component_transaction_journal.component_id', '=', $repetition->limit->budget->id)->where(
|
||||
'transaction_journals.date', '>=', $start->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->where(
|
||||
'amount', '>', 0
|
||||
)->sum('amount')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@@ -98,9 +98,10 @@ interface ChartInterface
|
||||
|
||||
|
||||
/**
|
||||
* We check how much money has been spend on the limitrepetition (aka: the current envelope) in the period denoted.
|
||||
* Aka, we have a certain amount of money in an envelope and we wish to know how much we've spent between the dates
|
||||
* entered. This can be a partial match with the date range of the envelope or no match at all.
|
||||
* Firefly checks how much money has been spend on the limitrepetition (aka: the current envelope) in
|
||||
* the period denoted. Aka, the user has a certain amount of money in an envelope and wishes to know how
|
||||
* much he has spent between the dates entered. This date range can be a partial match with the date range
|
||||
* of the envelope or no match at all.
|
||||
*
|
||||
* @param \LimitRepetition $repetition
|
||||
* @param Carbon $start
|
||||
|
@@ -38,7 +38,7 @@ class EmailHelper implements EmailHelperInterface
|
||||
{
|
||||
|
||||
$password = \Str::random(12);
|
||||
$user->password = \Hash::make($password);
|
||||
$user->password = $password;
|
||||
$user->reset = \Str::random(32); // new one.
|
||||
$user->forceSave();
|
||||
$email = $user->email;
|
||||
|
@@ -133,7 +133,7 @@ class Import
|
||||
return;
|
||||
}
|
||||
|
||||
// if we try to import a beneficiary, Firefly will "merge" already,
|
||||
// if Firefly tries to import a beneficiary, Firefly will "merge" already existing ones,
|
||||
// so we don't care:
|
||||
if (isset($payload['data']['account_type']) && $payload['data']['account_type'] == 'Beneficiary account') {
|
||||
// store beneficiary
|
||||
|
@@ -65,6 +65,16 @@ class EloquentReminderRepository implements ReminderRepositoryInterface
|
||||
return $this->_user->reminders()->validOn($today)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPiggybankReminders()
|
||||
{
|
||||
$today = new Carbon;
|
||||
|
||||
return $this->_user->reminders()->where('class','PiggybankReminder')->validOn($today)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@@ -14,7 +14,9 @@ use Illuminate\Events\Dispatcher;
|
||||
class EloquentPiggybankTrigger
|
||||
{
|
||||
/**
|
||||
*
|
||||
* This method checks every repeating piggy bank the user has (these are called repeated expenses) and makes
|
||||
* sure each repeated expense has a "repetition" for the current time period. For example, if the user has
|
||||
* a weekly repeated expense of E 40,- this method will fire every week and create a new repetition.
|
||||
*/
|
||||
public function checkRepeatingPiggies()
|
||||
{
|
||||
@@ -25,26 +27,29 @@ class EloquentPiggybankTrigger
|
||||
$piggies = [];
|
||||
}
|
||||
|
||||
\Log::debug('Now in checkRepeatingPiggies with ' . count($piggies) . ' piggies');
|
||||
|
||||
\Log::debug('Now in checkRepeatingPiggies with ' . count($piggies) . ' piggies found.');
|
||||
|
||||
/** @var \Piggybank $piggyBank */
|
||||
foreach ($piggies as $piggyBank) {
|
||||
\Log::debug('Now working on ' . $piggyBank->name);
|
||||
|
||||
// get the latest repetition, see if we need to "append" more:
|
||||
/*
|
||||
* Get the latest repetition, see if Firefly needs to create more.
|
||||
*/
|
||||
/** @var \PiggybankRepetition $primer */
|
||||
$primer = $piggyBank->piggybankrepetitions()->orderBy('targetdate', 'DESC')->first();
|
||||
\Log::debug('Last target date is: ' . $primer->targetdate);
|
||||
|
||||
// for repeating piggy banks, the target date is mandatory:
|
||||
|
||||
$today = new Carbon;
|
||||
$end = clone $primer->targetdate;
|
||||
|
||||
// the next repetition must be created starting at the day after the target date:
|
||||
// the next repetition must be created starting at the day after the target date of the previous one.
|
||||
/*
|
||||
* A repeated expense runs from day 1 to day X. Since it repeats, the next repetition starts at day X+1
|
||||
* until however often the repeated expense is set to repeat: a month, a week, a year.
|
||||
*/
|
||||
$start = clone $primer->targetdate;
|
||||
$start->addDay();
|
||||
|
||||
while ($start <= $today) {
|
||||
\Log::debug('Looping! Start is: ' . $start);
|
||||
|
||||
@@ -104,15 +109,26 @@ class EloquentPiggybankTrigger
|
||||
|
||||
/**
|
||||
* Whenever a repetition is made, the decision is there to make reminders for it. Or not.
|
||||
*
|
||||
* Some combinations are "invalid" or impossible and will never trigger reminders. Others do.
|
||||
*
|
||||
* @param \PiggybankRepetition $rep
|
||||
* The numbers below refer to a small list I made in a text-file (it no longer exists) which contained the eight
|
||||
* binary combinations that can be made of three properties each piggy bank has (among others):
|
||||
*
|
||||
* - Whether or not it has a start date.
|
||||
* - Whether or not it has an end date.
|
||||
* - Whether or not the piggy bank repeats itself.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
|
||||
* @SuppressWarnings(PHPMD.NPathComplexity)
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*
|
||||
* @param \PiggybankRepetition $repetition
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function createdRepetition(\PiggybankRepetition $repetition)
|
||||
{
|
||||
\Log::debug('TRIGGER on createdRepetition() for repetition #' . $repetition->id);
|
||||
|
||||
$piggyBank = $repetition->piggybank;
|
||||
|
||||
@@ -120,49 +136,70 @@ class EloquentPiggybankTrigger
|
||||
|
||||
// no reminders needed (duh)
|
||||
if (is_null(($piggyBank->reminder))) {
|
||||
\Log::debug('No reminders because no reminder needed.');
|
||||
return null;
|
||||
}
|
||||
|
||||
// no start, no target, no repeat (#1):
|
||||
if (is_null($piggyBank->startdate) && is_null($piggyBank->targetdate) && $piggyBank->repeats == 0) {
|
||||
\Log::debug('No reminders because no start, no target, no repeat (#1)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// no start, but repeats (#5):
|
||||
if (is_null($piggyBank->startdate) && $piggyBank->repeats == 1) {
|
||||
\Log::debug('No reminders because no start, but repeats (#5)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// no start, no end, but repeats (#6)
|
||||
if (is_null($piggyBank->startdate) && is_null($piggyBank->targetdate) && $piggyBank->repeats == 1) {
|
||||
\Log::debug('No reminders because no start, no end, but repeats (#6)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// no end, but repeats (#7)
|
||||
if (is_null($piggyBank->targetdate) && $piggyBank->repeats == 1) {
|
||||
\Log::debug('No reminders because no end, but repeats (#7)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// #2, #3, #4 and #8 are valid combo's.
|
||||
\Log::debug('Will continue...');
|
||||
/*
|
||||
* #2, #3, #4 and #8 are valid combo's.
|
||||
*
|
||||
* We add two years to the end when the repetition has no target date; we "pretend" there is a target date.
|
||||
*
|
||||
*/
|
||||
if (is_null($repetition->targetdate)) {
|
||||
$end = new Carbon;
|
||||
$end->addYears(2);
|
||||
} else {
|
||||
$end = $repetition->targetdate;
|
||||
}
|
||||
/*
|
||||
* If there is no start date, the start dat becomes right now.
|
||||
*/
|
||||
if (is_null($repetition->startdate)) {
|
||||
$start = new Carbon;
|
||||
} else {
|
||||
$start = $repetition->startdate;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Firefly checks every period X between $start and $end and if necessary creates a reminder. Firefly
|
||||
* only creates reminders if the $current date is after today. Piggy banks may have their start in the past.
|
||||
*
|
||||
* This loop will jump a month when the reminder is set monthly, a week when it's set weekly, etcetera.
|
||||
*/
|
||||
$current = $start;
|
||||
$today = new Carbon;
|
||||
$today = new Carbon;
|
||||
$today->startOfDay();
|
||||
while ($current <= $end) {
|
||||
|
||||
// when do we start reminding?
|
||||
// X days before $current:
|
||||
\Log::debug('Looping reminder dates; now at ' . $current);
|
||||
/*
|
||||
* Piggy bank reminders start X days before the actual date of the event.
|
||||
*/
|
||||
$reminderStart = clone $current;
|
||||
switch ($piggyBank->reminder) {
|
||||
case 'day':
|
||||
@@ -179,20 +216,32 @@ class EloquentPiggybankTrigger
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the date is past today we create a reminder, otherwise we don't. The end date is the date
|
||||
* the reminder is due; after that it is invalid.
|
||||
*/
|
||||
if ($current >= $today) {
|
||||
$reminder = new \PiggybankReminder;
|
||||
$reminder->piggybank()->associate($piggyBank);
|
||||
$reminder->user()->associate(\Auth::user());
|
||||
$reminder->startdate = $reminderStart;
|
||||
$reminder->enddate = $current;
|
||||
$reminder->enddate = $current;
|
||||
$reminder->active = 1;
|
||||
\Log::debug('Will create a reminder. Is it valid?');
|
||||
\Log::debug($reminder->validate());
|
||||
try {
|
||||
$reminder->save();
|
||||
|
||||
$reminder->save();
|
||||
} catch (QueryException $e) {
|
||||
\Log::error('Could not save reminder: ' . $e->getMessage());
|
||||
}
|
||||
} else {
|
||||
\Log::debug('Current is before today, will not make a reminder.');
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Here Firefly jumps ahead to the next reminder period.
|
||||
*/
|
||||
switch ($piggyBank->reminder) {
|
||||
case 'day':
|
||||
$current->addDays($piggyBank->reminder_skip);
|
||||
@@ -234,12 +283,12 @@ class EloquentPiggybankTrigger
|
||||
*/
|
||||
public function modifyAmountAdd(\Piggybank $piggyBank, $amount)
|
||||
{
|
||||
$rep = $piggyBank->currentRelevantRep();
|
||||
$rep = $piggyBank->currentRelevantRep();
|
||||
$today = new Carbon;
|
||||
|
||||
// create event:
|
||||
$event = new \PiggybankEvent;
|
||||
$event->date = new Carbon;
|
||||
$event = new \PiggybankEvent;
|
||||
$event->date = new Carbon;
|
||||
$event->amount = $amount;
|
||||
$event->piggybank()->associate($piggyBank);
|
||||
|
||||
@@ -259,8 +308,8 @@ class EloquentPiggybankTrigger
|
||||
public function modifyAmountRemove(\Piggybank $piggyBank, $amount)
|
||||
{
|
||||
// create event:
|
||||
$event = new \PiggybankEvent;
|
||||
$event->date = new Carbon;
|
||||
$event = new \PiggybankEvent;
|
||||
$event->date = new Carbon;
|
||||
$event->amount = $amount;
|
||||
$event->piggybank()->associate($piggyBank);
|
||||
$event->save();
|
||||
@@ -359,7 +408,7 @@ class EloquentPiggybankTrigger
|
||||
if (!is_null($rep->targetdate)) {
|
||||
$eventSumQuery->where('date', '<=', $rep->targetdate->format('Y-m-d'));
|
||||
}
|
||||
$eventSum = floatval($eventSumQuery->sum('amount'));
|
||||
$eventSum = floatval($eventSumQuery->sum('amount'));
|
||||
$rep->currentamount = floatval($sum) + $eventSum;
|
||||
$rep->save();
|
||||
|
||||
|
@@ -72,6 +72,20 @@ class Account extends Ardent
|
||||
);
|
||||
}
|
||||
|
||||
public function balanceBeforeJournal(TransactionJournal $journal)
|
||||
{
|
||||
return floatval(
|
||||
$this->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')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transactions.
|
||||
*
|
||||
|
@@ -35,8 +35,8 @@ class PiggybankReminder extends Reminder
|
||||
protected $isSubclass = true;
|
||||
|
||||
/**
|
||||
* This method will render a string telling you something about what to save or something.
|
||||
* @return string
|
||||
* @throws Firefly\Exception\FireflyException
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
@@ -85,7 +85,7 @@ class PiggybankReminder extends Reminder
|
||||
$toSave = 0;
|
||||
switch ($piggyBank->reminder) {
|
||||
case 'day':
|
||||
throw new \Firefly\Exception\FireflyException('No impl day reminder/ PiggyBankReminder Render');
|
||||
$toSave = $left;// / ($diff->days / $piggyBank->reminder_skip);
|
||||
break;
|
||||
case 'week':
|
||||
$weeks = ceil($diff->days / 7);
|
||||
|
@@ -33,5 +33,10 @@ class RecurringTransactionReminder extends Reminder
|
||||
{
|
||||
protected $isSubclass = true;
|
||||
|
||||
public function render()
|
||||
{
|
||||
return '123';
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -71,66 +71,6 @@
|
||||
@endforeach
|
||||
@endif
|
||||
|
||||
@if(count($reminders) > 0)
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<h4>Recurring transactions</h4>
|
||||
<p class="text-info">These transactions are set to be expected between
|
||||
{{Session::get('start')->format('j F Y')}} and {{Session::get('end')->format('j F Y')}}.</p>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Tags</th>
|
||||
<th colspan="2">Amount</th>
|
||||
<th>Repeats</th>
|
||||
</tr>
|
||||
<?php $max =0;$min = 0;?>
|
||||
@foreach($reminders as $reminder)
|
||||
<?php
|
||||
$max += $reminder->recurringtransaction->amount_max;
|
||||
$min += $reminder->recurringtransaction->amount_min;
|
||||
?>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{route('recurring.show',$reminder->recurringtransaction->id)}}">
|
||||
{{{$reminder->recurringtransaction->name}}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
@foreach(explode(' ',$reminder->recurringtransaction->match) as $word)
|
||||
<span class="label label-info">{{{$word}}}</span>
|
||||
@endforeach
|
||||
</td>
|
||||
|
||||
<td>
|
||||
{{mf($reminder->recurringtransaction->amount_min)}}
|
||||
</td>
|
||||
<td>
|
||||
{{mf($reminder->recurringtransaction->amount_max)}}
|
||||
</td>
|
||||
<td>
|
||||
{{$reminder->recurringtransaction->repeat_freq}}
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group btn-group-xs">
|
||||
<a href="#" class="btn btn-default">postpone</a>
|
||||
<a href="#" class="btn btn-default">dismiss</a>
|
||||
<a href="#" class="btn btn-default">done!</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
<tr>
|
||||
<td colspan="2">Sum</td>
|
||||
<td>{{mf($max)}}</td>
|
||||
<td colspan="3">{{mf($min)}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<div id="budgets"></div>
|
||||
|
@@ -3,50 +3,36 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12">
|
||||
<h1>Firefly
|
||||
<small>Piggy bank "{{{$piggyBank->name}}}"</small>
|
||||
<small>
|
||||
@if($piggyBank->repeats == 1)
|
||||
Repeated expense
|
||||
@else
|
||||
Piggy bank
|
||||
@endif
|
||||
"{{{$piggyBank->name}}}"</small>
|
||||
</h1>
|
||||
<p>
|
||||
<a href="{{route('accounts.show',$piggyBank->account_id)}}">{{{$piggyBank->account->name}}}</a> has
|
||||
a balance of {{mf($balance)}}.
|
||||
Of that {{mf($balance)}}, you have {{mf($leftOnAccount)}} not yet locked up in other piggy banks.
|
||||
You can add {{mf(min(max($balance,$leftOnAccount),$piggyBank->targetamount))}} to this piggy bank.
|
||||
</p>
|
||||
<p>
|
||||
<div class="btn-group">
|
||||
<a href="{{route('piggybanks.edit',$piggyBank->id)}}" class="btn btn-default"><span class="glyphicon glyphicon-pencil"></span> Edit</a>
|
||||
<a href="{{route('piggybanks.delete',$piggyBank->id)}}" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span> Delete</a>
|
||||
|
||||
@if(min(max($balance,$leftOnAccount),$piggyBank->targetamount) > 0)
|
||||
<a data-toggle="modal" href="{{route('piggybanks.amount.add',$piggyBank->id)}}" data-target="#modal" class="btn btn-default"><span class="glyphicon glyphicon-plus-sign"></span> Add money</a>
|
||||
@endif
|
||||
|
||||
@if($piggyBank->currentRelevantRep()->currentamount > 0)
|
||||
<a data-toggle="modal" href="{{route('piggybanks.amount.remove',$piggyBank->id)}}" data-target="#modal" class="btn btn-default"><span class="glyphicon glyphicon-minus-sign"></span> Remove money</a>
|
||||
@endif
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||
<h3>Piggy bank info</h3>
|
||||
<h3>General information</h3>
|
||||
<table class="table table-bordered table-striped">
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type (repeats)</td>
|
||||
<td>
|
||||
@if($piggyBank->repeats == 1)
|
||||
Repeated expense
|
||||
@else
|
||||
Piggy bank
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{{{$piggyBank->name}}} (#{{$piggyBank->id}})</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Account</td>
|
||||
<td><a href="{{route('accounts.show',$piggyBank->account_id)}}">{{{$piggyBank->account->name}}}</a></td>
|
||||
|
@@ -46,12 +46,16 @@
|
||||
<div class="col-lg-6 col-md-6 col-sm-12">
|
||||
<h3>Transactions</h3>
|
||||
@foreach($journal->transactions as $t)
|
||||
<h4>{{{$t->account->name}}}<br /><small>{{{$t->account->accounttype->description}}}</small></h4>
|
||||
<h4><a href="{{route('accounts.show',$t->account->id)}}">{{{$t->account->name}}}</a><br /><small>{{{$t->account->accounttype->description}}}</small></h4>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td>Amount</td>
|
||||
<td>{{mf($t->amount)}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>New balance</td>
|
||||
<td>{{mf($t->account->balanceBeforeJournal($journal))}} → {{mf($t->account->balanceBeforeJournal($journal) + $t->amount)}}</td>
|
||||
</tr>
|
||||
@if(!is_null($t->description))
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
|
Reference in New Issue
Block a user