Compare commits

...

147 Commits
3.0.2 ... 3.1.2

Author SHA1 Message Date
Sander Dorigo
287c2e7af8 Added the ability to change the range preference on the fly. 2014-10-12 07:33:45 +02:00
Sander Dorigo
0fe6acc8cf Merge branch 'feature/recurring-expand' into develop 2014-10-11 21:24:38 +02:00
Sander Dorigo
7d2dab7ca0 First set of code. 2014-10-11 21:23:31 +02:00
Sander Dorigo
f68c1aff26 Cleaned up the show view. 2014-10-11 18:52:24 +02:00
Sander Dorigo
d40645be68 Merge branch 'feature/expand-transaction-view' into develop 2014-10-11 09:54:33 +02:00
Sander Dorigo
a53550537f Expand JS 2014-10-11 09:54:26 +02:00
Sander Dorigo
223ad16616 Expand JSON. 2014-10-11 09:54:20 +02:00
Sander Dorigo
3f060979d7 Merge branch 'feature/recurring-at-new-transaction' into develop 2014-10-11 09:21:44 +02:00
Sander Dorigo
2eac9081ea Build trigger. 2014-10-11 09:21:28 +02:00
Sander Dorigo
b3eef4f40b Cleanup. 2014-10-11 09:21:14 +02:00
Sander Dorigo
dd70fbad3f Cleanup old code. 2014-10-11 09:20:59 +02:00
Sander Dorigo
8cb7a1aef8 Installed Twig template engine. 2014-10-11 08:31:17 +02:00
Sander Dorigo
a687140056 Cleaned up date-time navigation, added some stuff to accounts, expanded JSON response for transactions. 2014-10-09 07:24:47 +02:00
Sander Dorigo
3cba673a9c Updated the chart. #23 2014-10-08 21:54:46 +02:00
Sander Dorigo
01de230785 Expanded the summary (related to #23). 2014-10-08 21:53:38 +02:00
Sander Dorigo
e405d06f23 First attempt at a view for transactions without a budget (issue #23) 2014-10-08 21:50:03 +02:00
Sander Dorigo
d9b70f7ad8 Fixed a bug that will properly attach both budgets AND categories 2014-10-08 21:39:27 +02:00
Sander Dorigo
0ef5825d98 Introduced a tiny bug by introducing NULL. 2014-10-08 21:29:28 +02:00
Sander Dorigo
1e76a5fc3f New buttons. 2014-10-08 21:04:31 +02:00
Sander Dorigo
1fbdb3d0ae A temporary fix for the problem that storing a transaction journal doesn't return the actual journal when successful. 2014-10-07 12:26:02 +02:00
Sander Dorigo
d5bcf5497f Various updates to facilitate validating things. 2014-10-06 19:32:09 +02:00
Sander Dorigo
28aaea1aa3 Extended forms and started on recurring transactions. 2014-10-05 19:29:25 +02:00
Sander Dorigo
980d9ce885 Also validate edits. 2014-10-05 08:49:36 +02:00
Sander Dorigo
ec601efa6e close #11, close #10 2014-10-05 08:27:49 +02:00
Sander Dorigo
b3209d3b4d Fixed validation and made some new form elements. 2014-10-05 08:27:18 +02:00
Sander Dorigo
4ce978b9f3 New breadcrumbs. 2014-10-04 07:15:56 +02:00
Sander Dorigo
a84064663a Fixed a bug where editing a transaction would lead to unset variables. 2014-10-03 21:36:42 +02:00
James Cole
6798cea268 Some more bug fixes. 2014-09-28 09:20:25 +02:00
James Cole
8e86196352 Bugfix in limit controller. 2014-09-28 09:09:48 +02:00
James Cole
1b3d345fbd Quick & dirty fixes for the piggy bank controller. 2014-09-28 09:01:57 +02:00
James Cole
7d2627515f Fixed route when removing piggy banks. 2014-09-28 08:52:24 +02:00
James Cole
aa9eb8ca64 Varioux fixes and cleaning up. 2014-09-28 08:47:51 +02:00
James Cole
9015d6ca16 Reordered the account repository and fixed a small bug. 2014-09-27 12:10:58 +02:00
James Cole
217483639d Cleanup. 2014-09-27 07:06:30 +02:00
James Cole
78e80530d3 Place holder for reports. 2014-09-27 07:06:19 +02:00
James Cole
3bbecfe830 Various bug fixes. 2014-09-27 06:06:31 +02:00
James Cole
9ab3679d49 Fixed a chart. 2014-09-27 06:05:53 +02:00
James Cole
fc44a52ba5 Fixed a bug that would recreate accounts if they existed had an old account type. 2014-09-25 07:41:45 +02:00
James Cole
bb2b71bdc0 Removed unused JS files and references. 2014-09-24 07:03:55 +02:00
James Cole
b23d2a9d95 Removed deprecated function. 2014-09-23 22:00:11 +02:00
James Cole
eeb773fd7b Move fonts to match CSS 2014-09-23 21:56:08 +02:00
James Cole
53a582f374 Replaced index.css 2014-09-23 21:53:51 +02:00
James Cole
73110f6a51 Replaced accounts.css 2014-09-23 21:53:24 +02:00
James Cole
5667663fef Replaced recurring.css 2014-09-23 21:52:40 +02:00
James Cole
fb664ba17d Fixed transactions.css 2014-09-23 21:51:39 +02:00
James Cole
0c10190a8e Replaced accounts, budgets and index. 2014-09-23 21:49:52 +02:00
James Cole
183a323ef6 Replaced budget*.js 2014-09-23 21:48:19 +02:00
James Cole
90bada5497 Replaced categories.js 2014-09-23 21:46:35 +02:00
James Cole
7c043e1923 Replaced piggybanks-create.js 2014-09-23 21:44:00 +02:00
James Cole
2720ae3c46 Replaced piggybanks.js 2014-09-23 21:41:34 +02:00
James Cole
401508577e Replaced recurring.js 2014-09-23 21:40:24 +02:00
James Cole
b0a31cebc2 Replaced transactions.js 2014-09-23 21:37:59 +02:00
James Cole
95adb428fa See previous. 2014-09-23 21:35:09 +02:00
James Cole
f92a0310dd Successfully replaced application.css 2014-09-23 21:34:13 +02:00
James Cole
84f0cb3765 Successfully replaced application.js 2014-09-23 21:31:38 +02:00
James Cole
d49e6787d6 Completely removed code sleeve asset management. 2014-09-23 21:28:05 +02:00
James Cole
0884853a6f Updated CSS. Again. Next step: extended debugging. 2014-09-23 21:00:36 +02:00
James Cole
1967c63006 Update packages. 2014-09-23 20:51:41 +02:00
James Cole
9461e7b70a Some package updates. 2014-09-23 20:50:57 +02:00
James Cole
f1e5df566c Remove cache from asset pipeline in an attempt to fix the "not including some files"-bug, although right now I have a clue what's causing it. 2014-09-23 20:32:33 +02:00
James Cole
fb02a0d5ad Update CSS, again. 2014-09-23 15:23:38 +02:00
James Cole
e438a02fa3 Fixed CSS 2014-09-23 15:19:10 +02:00
James Cole
b112452aa1 Different composer instructions. 2014-09-23 07:57:50 +02:00
James Cole
1a2fc81af3 Some more routes and attempt at bug fix. 2014-09-23 07:53:07 +02:00
James Cole
38bbda982c Attempt to include SB stylesheet. 2014-09-23 07:50:53 +02:00
James Cole
41ad6e64d1 Some more cleanup. 2014-09-22 07:25:23 +02:00
James Cole
efcad0b935 First version of a search interface. 2014-09-22 07:25:14 +02:00
James Cole
e892b69a96 Code cleanup. 2014-09-21 16:22:18 +02:00
James Cole
5dfc04e777 Some cleanup. 2014-09-21 15:51:07 +02:00
James Cole
c119a42d70 Clean up edit routines 2014-09-21 15:40:41 +02:00
James Cole
802541b796 Some CSS and style updates. 2014-09-21 12:30:00 +02:00
James Cole
0770c79777 Some cleanup. 2014-09-21 10:58:02 +02:00
James Cole
5f4669341e Completed views for transactions. 2014-09-21 10:55:51 +02:00
James Cole
f15fc80233 Made sure all columns can be sorted on. 2014-09-21 09:37:22 +02:00
James Cole
a7d75ea94a Fixed a bug in the import routine that would mislabel the import account and botch any import process. 2014-09-21 08:54:23 +02:00
James Cole
ba4bddf756 Updated the migration routine, started on data tables. 2014-09-21 08:25:30 +02:00
James Cole
6a26408552 Expanded the 'save transaction' routine and cleaned it up. Still some work to do though. 2014-09-20 08:39:24 +02:00
James Cole
c39c59fff5 Some cleaning up. 2014-09-19 23:05:57 +02:00
James Cole
c1ba8dc6a7 Forgot some files. 2014-09-17 08:56:10 +02:00
James Cole
f2825da878 Updated some routes. 2014-09-17 08:56:02 +02:00
James Cole
c61f1307d8 Fixed all titles, subtitles and icons to properly display new layout. 2014-09-17 08:55:51 +02:00
James Cole
9e88d7a60d These are the limit fixes, previous was category. 2014-09-15 17:57:46 +02:00
James Cole
406ae25162 Fix the limit views. 2014-09-15 17:57:19 +02:00
James Cole
dbfb342021 Fixed for budget views. 2014-09-15 17:46:01 +02:00
James Cole
4632142e06 Fixes for account routes. 2014-09-15 17:03:53 +02:00
James Cole
9ae036f297 Beanstalk is now supported. 2014-09-14 21:59:41 +02:00
James Cole
497b8c48c8 Added a default, higher debug level. 2014-09-14 21:59:30 +02:00
James Cole
5d11949313 Extended log routine. 2014-09-14 21:10:33 +02:00
James Cole
a91c9f04c5 Small cleanup. 2014-09-14 21:10:18 +02:00
James Cole
4f3493f9ff Time-based navigation and some feedback for the user as to his import. 2014-09-14 21:09:52 +02:00
James Cole
49b8742082 Clean up validator because the import would fail. 2014-09-14 21:08:39 +02:00
James Cole
69cee59e23 Moved some import routines to the repositories. 2014-09-14 21:07:43 +02:00
James Cole
19402b9022 Improvements upon the import routine. 2014-09-14 21:06:48 +02:00
James Cole
62ba40b687 Moved some scripts around. 2014-09-14 21:05:56 +02:00
James Cole
f9af9a4fbe Some cleanup in migration controller. 2014-09-13 06:30:31 +02:00
James Cole
c2ab43d0ab Cleanup account controller. 2014-09-13 06:30:09 +02:00
James Cole
af28e6e7b9 Extended cleanup scripts. 2014-09-13 06:29:53 +02:00
James Cole
114ad7f292 Helper that also resets everything. 2014-09-12 21:49:20 +02:00
James Cole
44eb67f94e Some cleanup, some bug fixes. 2014-09-12 21:47:27 +02:00
James Cole
0203fee174 Some small updates to various classes to support new stuff. 2014-09-12 17:34:54 +02:00
James Cole
a1ba340ead Updated the transaction everything so views and forms work with the new transaction controller. 2014-09-12 17:31:12 +02:00
James Cole
0ae9ff4575 Some initial title cleanup in the category controller. 2014-09-12 17:30:12 +02:00
James Cole
5b501cb942 Some initial title cleanup in the budget controller. 2014-09-12 17:29:32 +02:00
James Cole
0255b7a4a0 Experimental new title icon. 2014-09-12 17:29:16 +02:00
James Cole
15ef0bab1d New JSON routes and code. 2014-09-12 17:28:58 +02:00
James Cole
decad6830b Routes to show the different kinds of transactions. 2014-09-12 17:28:44 +02:00
James Cole
b6e0b985c2 Small bug fixed in account scope. 2014-09-11 22:46:11 +02:00
James Cole
c140f71878 Small bug fix in the import routine. 2014-09-11 22:29:49 +02:00
James Cole
87044e6b8e Menu responds way better. 2014-09-11 21:59:39 +02:00
James Cole
affa9014d2 Repositories can now deal with new account types. 2014-09-11 21:59:27 +02:00
James Cole
4bbc3c3bd8 Fix some bugs in the import tasks. 2014-09-11 21:59:10 +02:00
James Cole
d296dbbc23 Cleanup account views, controllers and repositories. 2014-09-11 21:58:51 +02:00
James Cole
9bcd27b847 Cleanup and improve charts. 2014-09-11 21:58:08 +02:00
James Cole
2a54b36db0 New route for different account types (and the creation thereof. 2014-09-11 21:57:57 +02:00
James Cole
77fb02daa4 Merge branch 'new-layout' 2014-09-11 15:53:33 +02:00
James Cole
1963ac191f Merge branch 'master' of https://github.com/JC5/firefly-iii 2014-09-11 15:52:02 +02:00
James Cole
33da8aa987 Merge branch 'master' of https://github.com/JC5/firefly-iii into new-layout 2014-09-11 15:22:12 +02:00
James Cole
0192484044 Revert "Fixed a bug that would stop you from registering."
This reverts commit 7cc7235f2d.
2014-09-11 15:21:37 +02:00
James Cole
3c0863d8ea Fixed a bug that would stop you from registering. 2014-09-11 15:20:45 +02:00
James Cole
710d6dfb74 Lighter chart. 2014-09-11 15:20:45 +02:00
James Cole
2359542d72 Better feedback. 2014-09-11 15:20:45 +02:00
James Cole
e1a2b4b9af Fixed a bug that would stop you from registering. 2014-09-11 15:19:30 +02:00
James Cole
0eadfa1c83 Lighter chart. 2014-09-11 15:19:18 +02:00
James Cole
c8dd935460 Better feedback. 2014-09-11 15:19:07 +02:00
James Cole
e2227271b5 More info on home screen. 2014-09-11 15:18:43 +02:00
James Cole
7a639a1d6e New views coming soon! 2014-09-11 15:18:32 +02:00
James Cole
9edb9b91b2 New account types for new layout (yep). 2014-09-11 10:12:30 +02:00
James Cole
b2adeb20d9 New routes for new layouts. 2014-09-11 10:11:11 +02:00
James Cole
fa665de847 Various chart cleanups. 2014-09-11 10:09:09 +02:00
James Cole
ab9e5f716d Clean up filters, extend index and small fix for title. 2014-09-10 22:22:44 +02:00
James Cole
5788db9f07 Reversed from flot/plot back to high charts. Cleaned up the index. 2014-09-10 20:54:01 +02:00
James Cole
3068a8d58d Some changes to the login view. 2014-09-10 19:44:09 +02:00
James Cole
14aacf42b9 Cleanup for new chart library (foot). 2014-09-10 16:15:28 +02:00
James Cole
d1b97da309 Home view gets a better title. 2014-09-10 14:39:38 +02:00
James Cole
867074e7b2 Cleaned up the menu. Not all links are working. 2014-09-10 08:16:22 +02:00
James Cole
18748510b1 First change; menu is now from sb-admin. 2014-09-10 06:56:57 +02:00
James Cole
bcf71cdf85 Updated the CSS and JS files to include the new CSS and JS. 2014-09-10 06:39:42 +02:00
James Cole
3290ce85a9 Updated read me. 2014-09-09 20:57:23 +02:00
James Cole
60ef80c1a5 Show the balance after the occurrence of a transaction (experimental). 2014-09-09 20:37:11 +02:00
James Cole
74e852b8bd Some final touches. 2014-09-09 20:19:19 +02:00
James Cole
90ae21d257 Some cleanup in the models. 2014-09-09 20:01:31 +02:00
James Cole
fdf03cd8e2 Some comment cleanup in the libraries. 2014-09-09 20:01:13 +02:00
James Cole
f6586be5e7 Enddate can be NULL. 2014-09-09 20:00:15 +02:00
James Cole
f9dc627d84 Cleanup controllers and small bug fixes. 2014-09-09 20:00:04 +02:00
James Cole
309177ca9c Extended gitignore. 2014-09-09 19:59:34 +02:00
James Cole
456d2342b6 Do not need a CSS file here. 2014-09-09 19:59:19 +02:00
James Cole
0717aa22d7 Some minor cleanup. 2014-09-09 14:03:55 +02:00
252 changed files with 11543 additions and 8272 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ _ide_helper.php
index.html*
app/storage/firefly-export*
.vagrant
firefly-iii-import-*.json

View File

@@ -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)!

View File

@@ -1 +0,0 @@
If you place an image here called foobar.png then you can access that image by going to http://<hostname>/assets/foobar.png

View File

@@ -1,16 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require highslide/highslide-full.min
//= require highslide/highslide.config
//= require_tree highcharts
//= require firefly/accounts

View File

@@ -1,15 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require jquery
//= require bootstrap/bootstrap.min
//= require firefly/reminders

View File

@@ -1,14 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require_tree highcharts
//= require firefly/budgets/default

View File

@@ -1,14 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require_tree highcharts
//= require firefly/budgets/limit

View File

@@ -1,14 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require_tree highcharts
//= require firefly/budgets/nolimit

View File

@@ -1,14 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require_tree highcharts
//= require firefly/budgets/session

View File

@@ -1,14 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require_tree highcharts
//= require firefly/budgets

View File

@@ -1,14 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require_tree highcharts
//= require firefly/categories

View File

@@ -1,95 +0,0 @@
$(function () {
if($('#chart').length == 1) {
/**
* get data from controller for home charts:
*/
$.getJSON('chart/home/account/' + accountID).success(function (data) {
var options = {
chart: {
renderTo: 'chart',
type: 'spline'
},
series: data.series,
title: {
text: data.chart_title
},
yAxis: {
formatter: function () {
return '$' + Highcharts.numberFormat(this.y, 0);
}
},
subtitle: {
text: data.subtitle,
useHTML: true
},
xAxis: {
floor: 0,
type: 'datetime',
dateTimeLabelFormats: {
day: '%e %b',
year: '%b'
},
title: {
text: 'Date'
}
},
tooltip: {
shared: true,
crosshairs: false,
formatter: function () {
var str = '<span style="font-size:80%;">' + Highcharts.dateFormat("%A, %e %B", this.x) + '</span><br />';
for (x in this.points) {
var point = this.points[x];
var colour = point.point.pointAttr[''].fill;
str += '<span style="color:' + colour + '">' + point.series.name + '</span>: € ' + Highcharts.numberFormat(point.y, 2) + '<br />';
}
//console.log();
return str;
return '<span style="font-size:80%;">' + this.series.name + ' on ' + Highcharts.dateFormat("%e %B", this.x) + ':</span><br /> € ' + Highcharts.numberFormat(this.y, 2);
}
},
plotOptions: {
line: {
shadow: true
},
series: {
cursor: 'pointer',
negativeColor: '#FF0000',
threshold: 0,
lineWidth: 1,
marker: {
radius: 2
},
point: {
events: {
click: function (e) {
hs.htmlExpand(null, {
src: 'chart/home/info/' + this.series.name + '/' + Highcharts.dateFormat("%d/%m/%Y", this.x),
pageOrigin: {
x: e.pageX,
y: e.pageY
},
objectType: 'ajax',
headingText: '<a href="#">' + this.series.name + '</a>',
width: 250
}
)
;
}
}
}
}
},
credits: {
enabled: false
}
};
$('#chart').highcharts(options);
});
}
});

View File

@@ -1,4 +0,0 @@
$(function () {
});

View File

@@ -1,7 +0,0 @@
$.getJSON('json/beneficiaries').success(function (data) {
$('input[name="beneficiary"]').typeahead({ source: data });
});
$.getJSON('json/categories').success(function (data) {
$('input[name="category"]').typeahead({ source: data });
});

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +0,0 @@
/**
* Site-specific configuration settings for Highslide JS
*/
hs.graphicsDir = 'assets/highslide/';
hs.outlineType = 'rounded-white';
hs.wrapperClassName = 'draggable-header';
hs.captionEval = 'this.a.title';
hs.showCredits = false;
hs.marginTop = 20;
hs.marginRight = 20;
hs.marginBottom = 20;
hs.marginLeft = 20;

View File

@@ -1,16 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require highslide/highslide-full.min
//= require highslide/highslide.config
//= require_tree highcharts
//= require firefly/index

View File

@@ -1,13 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require firefly/piggybanks-create

View File

@@ -1,13 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require firefly/piggybanks

View File

@@ -1,14 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require tagsinput/bootstrap-tagsinput.min
//= require firefly/recurring

View File

@@ -1,14 +0,0 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
// gets included (e.g. say you have require_tree . then the code will appear after all the directories
// but before any files alphabetically greater than 'application.js'
//
// The available directives right now are require, require_directory, and require_tree
//
//= require typeahead/bootstrap3-typeahead.min
//= require firefly/transactions

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
/**
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any Css/Less files within this directory, lib/assets/javascripts, vendor/assets/javascripts,
* can be referenced here using a relative path.
*
* It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
* gets included (e.g. say you have require_tree . then the code will appear after all the directories
* but before any files alphabetically greater than 'application.css'
*
*= require highslide/highslide
*/

View File

@@ -1,13 +0,0 @@
/**
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any Css/Less files within this directory, lib/assets/javascripts, vendor/assets/javascripts,
* can be referenced here using a relative path.
*
* It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
* gets included (e.g. say you have require_tree . then the code will appear after all the directories
* but before any files alphabetically greater than 'application.css'
*
*= require bootstrap/bootstrap.min
*/

View File

@@ -1,13 +0,0 @@
/**
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any Css/Less files within this directory, lib/assets/javascripts, vendor/assets/javascripts,
* can be referenced here using a relative path.
*
* It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
* gets included (e.g. say you have require_tree . then the code will appear after all the directories
* but before any files alphabetically greater than 'application.css'
*
*= require highslide/highslide
*/

View File

@@ -1,13 +0,0 @@
/**
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any Css/Less files within this directory, lib/assets/javascripts, vendor/assets/javascripts,
* can be referenced here using a relative path.
*
* It's not advisable to add code directly here, but if you do, it'll appear in whatever order it
* gets included (e.g. say you have require_tree . then the code will appear after all the directories
* but before any files alphabetically greater than 'application.css'
*
*= require tagsinput/bootstrap-tagsinput
*/

7
app/breadcrumbs.php Normal file
View File

@@ -0,0 +1,7 @@
<?php
/*
* Back home.
*/
Breadcrumbs::register('home', function($breadcrumbs) {
$breadcrumbs->push('Home', route('index'));
});

View File

@@ -6,6 +6,7 @@ return [
'timezone' => 'UTC',
'locale' => 'en',
'fallback_locale' => 'en',
'log_level' => 'notice',
'key' => 'D93oqmVsIARg23FC3cbsHuBGk0uXQc3r',
'cipher' => MCRYPT_RIJNDAEL_128,
'providers' => [
@@ -36,12 +37,13 @@ return [
'Illuminate\Validation\ValidationServiceProvider',
'Illuminate\View\ViewServiceProvider',
'Illuminate\Workbench\WorkbenchServiceProvider',
# 'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
# 'Barryvdh\Debugbar\ServiceProvider',
'Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider',
'Barryvdh\Debugbar\ServiceProvider',
'Firefly\Storage\StorageServiceProvider',
'Firefly\Helper\HelperServiceProvider',
'Firefly\Validation\ValidationServiceProvider',
'Codesleeve\AssetPipeline\AssetPipelineServiceProvider',
'DaveJamesMiller\Breadcrumbs\ServiceProvider',
'TwigBridge\ServiceProvider'
],
'manifest' => storage_path() . '/meta',
'aliases' => [
@@ -84,6 +86,8 @@ return [
'URL' => 'Illuminate\Support\Facades\URL',
'Validator' => 'Illuminate\Support\Facades\Validator',
'View' => 'Illuminate\Support\Facades\View',
'Breadcrumbs' => 'DaveJamesMiller\Breadcrumbs\Facade',
'Twig' => 'TwigBridge\Facade\Twig',
],

View File

@@ -2,7 +2,7 @@
use Carbon\Carbon;
return [
'index_periods' => ['1D', '1W', '1M', '3M', '6M', 'custom'],
'index_periods' => ['1D', '1W', '1M', '3M', '6M','1Y', 'custom'],
'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
'piggybank_periods' => ['day', 'week', 'month', 'year'],
'periods_to_text' => [
@@ -21,6 +21,14 @@ return [
'6M' => 'half year',
'custom' => '(custom)'
],
'range_to_name' => [
'1D' => 'one day',
'1W' => 'one week',
'1M' => 'one month',
'3M' => 'three months',
'6M' => 'six months',
'1Y' => 'one year',
],
'range_to_repeat_freq' => [
'1D' => 'weekly',
'1W' => 'weekly',

View File

@@ -1,331 +0,0 @@
<?php
/*
|--------------------------------------------------------------------------
| EnvironmentFilter
|--------------------------------------------------------------------------
|
| This is used to run filters on specific environments. For example, if you
| only want to run a filter on production and staging environments
|
| new EnvironmentFilter(new FilterExample, App::environment(), ['production', 'staging')),
|
*/
use Codesleeve\AssetPipeline\Filters\EnvironmentFilter;
return [
/*
|--------------------------------------------------------------------------
| routing array
|--------------------------------------------------------------------------
|
| This is passed to the Route::group and allows us to group and filter the
| routes for our package
|
*/
'routing' => [
'prefix' => '/assets'
],
/*
|--------------------------------------------------------------------------
| paths
|--------------------------------------------------------------------------
|
| These are the directories we search for files in.
|
| NOTE that the '.' in require_tree . is relative to where the manifest file
| (i.e. app/assets/javascripts/application.js) is located
|
*/
'paths' => [
'app/assets/javascripts',
'app/assets/stylesheets',
'app/assets/images',
'lib/assets/javascripts',
'lib/assets/stylesheets',
'lib/assets/images',
'provider/assets/javascripts',
'provider/assets/stylesheets',
'provider/assets/images'
],
/*
|--------------------------------------------------------------------------
| mimes
|--------------------------------------------------------------------------
|
| In order to know which mime type to send back to the server
| we need to know if it is a javascript or stylesheet type. If
| the extension is not found below then we just return a regular
| download.
|
*/
'mimes' => [
'javascripts' => ['.js', '.js.coffee', '.coffee', '.html', '.min.js'],
'stylesheets' => ['.css', '.css.less', '.css.sass', '.css.scss', '.less', '.sass', '.scss', '.min.css'],
],
/*
|--------------------------------------------------------------------------
| filters
|--------------------------------------------------------------------------
|
| In order for a file to be included with sprockets, it needs to be listed
| here and we can also do any preprocessing on files with the extension if
| we choose to.
|
*/
'filters' => [
'.min.js' => [
],
'.min.css' => [
new Codesleeve\AssetPipeline\Filters\URLRewrite(App::make('url')->to('/')),
],
'.js' => [
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\JSMinPlusFilter, App::environment()),
],
'.js.coffee' => [
new Codesleeve\AssetPipeline\Filters\CoffeeScript,
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\JSMinPlusFilter, App::environment()),
],
'.coffee' => [
new Codesleeve\AssetPipeline\Filters\CoffeeScript,
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\JSMinPlusFilter, App::environment()),
],
'.css' => [
new Codesleeve\AssetPipeline\Filters\URLRewrite(App::make('url')->to('/')),
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\CssMinFilter, App::environment()),
],
'.css.less' => [
new Codesleeve\AssetPipeline\Filters\LessphpFilter,
new Codesleeve\AssetPipeline\Filters\URLRewrite(App::make('url')->to('/')),
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\CssMinFilter, App::environment()),
],
'.css.sass' => [
new Codesleeve\AssetPipeline\Filters\SassFilter,
new Codesleeve\AssetPipeline\Filters\URLRewrite(App::make('url')->to('/')),
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\CssMinFilter, App::environment()),
],
'.css.scss' => [
new Assetic\Filter\ScssphpFilter,
new Codesleeve\AssetPipeline\Filters\URLRewrite(App::make('url')->to('/')),
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\CssMinFilter, App::environment()),
],
'.less' => [
new Codesleeve\AssetPipeline\Filters\LessphpFilter,
new Codesleeve\AssetPipeline\Filters\URLRewrite(App::make('url')->to('/')),
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\CssMinFilter, App::environment()),
],
'.sass' => [
new Codesleeve\AssetPipeline\Filters\SassFilter,
new Codesleeve\AssetPipeline\Filters\URLRewrite(App::make('url')->to('/')),
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\CssMinFilter, App::environment()),
],
'.scss' => [
new Assetic\Filter\ScssphpFilter,
new Codesleeve\AssetPipeline\Filters\URLRewrite(App::make('url')->to('/')),
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\CssMinFilter, App::environment()),
],
'.html' => [
new Codesleeve\AssetPipeline\Filters\JST,
new EnvironmentFilter(new Codesleeve\AssetPipeline\Filters\JSMinPlusFilter, App::environment()),
]
],
/*
|--------------------------------------------------------------------------
| cache
|--------------------------------------------------------------------------
|
| By default we cache assets on production environment permanently. We also cache
| all files using the `cache_server` driver below but the cache is busted anytime
| those files are modified. On production we will cache and the only way to bust
| the cache is to delete files from app/storage/cache/asset-pipeline or run a
| command php artisan assets:clean -f somefilename.js -f application.css ...
|
*/
'cache' => ['production'],
/*
|--------------------------------------------------------------------------
| cache_server
|--------------------------------------------------------------------------
|
| You can create your own CacheInterface if the filesystem cache is not up to
| your standards. This is for caching asset files on the server-side.
|
| Please note that caching is used on **ALL** environments always. This is done
| to increase performance of the pipeline. Cached files will be busted when the
| file changes.
|
| However, manifest files are regenerated (not cached) when the environment is
| not found within the 'cache' array. This lets you develop on local and still
| utilize caching, so you don't have to regenerate all precompiled files while
| developing on your assets.
|
| See more in CacheInterface.php at
|
| https://github.com/kriswallsmith/assetic/blob/master/src/Assetic/Cache
|
|
*/
'cache_server' => new Assetic\Cache\FilesystemCache(App::make('path.storage') . '/cache/asset-pipeline'),
/*
|--------------------------------------------------------------------------
| cache_client
|--------------------------------------------------------------------------
|
| If you want to handle 304's and what not, to keep users from refetching
| your assets and saving your bandwidth you can use a cache_client driver
| that handles this. This doesn't handle assets on the server-side, use
| cache_server for that. This only works when the current environment is
| listed within `cache`
|
| Note that this needs to implement the interface
|
| Codesleeve\Sprockets\Interfaces\ClientCacheInterface
|
| or this won't work correctly. It is a wrapper class around your cache_server
| driver and also uses the AssetCache class to help access files.
|
*/
'cache_client' => new Codesleeve\AssetPipeline\Filters\ClientCacheFilter,
/*
|--------------------------------------------------------------------------
| concat
|--------------------------------------------------------------------------
|
| This allows us to turn on the asset concatenation for specific
| environments listed below. You can turn off local environment if
| you are trying to troubleshoot, but you will likely have better
| performance if you leave concat on (except if you are doing a lot
| of minification stuff on each page refresh)
|
*/
'concat' => ['production', 'local'],
/*
|--------------------------------------------------------------------------
| directives
|--------------------------------------------------------------------------
|
| This allows us to turn completely control which directives are used
| for the sprockets parser that asset pipeline uses to parse manifest files.
|
| It is probably safe just to leave this alone unless you are familar with
| what is actually going on here.
|
*/
'directives' => [
'require ' => new Codesleeve\Sprockets\Directives\RequireFile,
'require_directory ' => new Codesleeve\Sprockets\Directives\RequireDirectory,
'require_tree ' => new Codesleeve\Sprockets\Directives\RequireTree,
'require_tree_df ' => new Codesleeve\Sprockets\Directives\RequireTreeDf,
'require_self' => new Codesleeve\Sprockets\Directives\RequireSelf,
'include ' => new Codesleeve\Sprockets\Directives\IncludeFile,
'include_directory ' => new Codesleeve\Sprockets\Directives\IncludeDirectory,
'include_tree ' => new Codesleeve\Sprockets\Directives\IncludeTree,
'stub ' => new Codesleeve\Sprockets\Directives\Stub,
'depend_on ' => new Codesleeve\Sprockets\Directives\DependOn,
],
/*
|--------------------------------------------------------------------------
| javascript_include_tag
|--------------------------------------------------------------------------
|
| This allows us to completely control how the javascript_include_tag function
| works for asset pipeline.
|
| It is probably safe just to leave this alone unless you are familar with
| what is actually going on here.
|
*/
'javascript_include_tag' => new Codesleeve\AssetPipeline\Composers\JavascriptComposer,
/*
|--------------------------------------------------------------------------
| stylesheet_link_tag
|--------------------------------------------------------------------------
|
| This allows us to completely control how the stylesheet_link_tag function
| works for asset pipeline.
|
| It is probably safe just to leave this alone unless you are familar with
| what is actually going on here.
|
*/
'stylesheet_link_tag' => new Codesleeve\AssetPipeline\Composers\StylesheetComposer,
/*
|--------------------------------------------------------------------------
| image_tag
|--------------------------------------------------------------------------
|
| This allows us to completely control how the image_tag function
| works for asset pipeline.
|
| It is probably safe just to leave this alone unless you are familar with
| what is actually going on here.
|
*/
'image_tag' => new Codesleeve\AssetPipeline\Composers\ImageComposer,
/*
|--------------------------------------------------------------------------
| controller_action
|--------------------------------------------------------------------------
|
| Asset pipeline will route all requests through the controller action
| listed here. This allows us to completely control how the controller
| should behave for incoming requests for assets.
|
| It is probably safe just to leave this alone unless you are familar with
| what is actually going on here.
|
*/
'controller_action' => '\Codesleeve\AssetPipeline\AssetPipelineController@file',
/*
|--------------------------------------------------------------------------
| sprockets_filter
|--------------------------------------------------------------------------
|
| When concatenation is turned on, when an asset is fetched from the sprockets
| generator it is filtered through this filter class named below. This allows us
| to modify the sprockets filter if we need to behave differently.
|
| It is probably safe just to leave this alone unless you are familar with
| what is actually going on here.
|
*/
'sprockets_filter' => '\Codesleeve\Sprockets\SprocketsFilter',
/*
|--------------------------------------------------------------------------
| sprockets_filter
|--------------------------------------------------------------------------
|
| When concatenation is turned on, assets are filtered via SprocketsFilter
| and we can do global filters on the resulting dump file. This would be
| useful if you wanted to apply a filter to all javascript or stylesheet files
| like minification. Out of the box we don't have any filters here. Add at
| your own risk. I don't put minification filters here because the minify
| doesn't always work perfectly and can bjork your entire concatenated
| javascript or stylesheet file if it messes up.
|
| It is probably safe just to leave this alone unless you are familar with
| what is actually going on here.
|
*/
'sprockets_filters' => [
'javascripts' => [],
'stylesheets' => [],
],
];

View File

@@ -0,0 +1,5 @@
<?php
return array(
'view' => 'laravel-breadcrumbs::bootstrap3',
);

View File

@@ -0,0 +1,134 @@
<?php
/**
* This file is part of the TwigBridge package.
*
* @copyright Robert Crowe <hello@vivalacrowe.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Configuration options for the built-in extensions.
*/
return [
/*
|--------------------------------------------------------------------------
| Extensions
|--------------------------------------------------------------------------
|
| Enabled extensions.
|
| `Twig_Extension_Debug` is enabled automatically if twig.debug is TRUE.
|
*/
'enabled' => [
'TwigBridge\Extension\Loader\Facades',
'TwigBridge\Extension\Loader\Filters',
'TwigBridge\Extension\Loader\Functions',
'TwigBridge\Extension\Laravel\Auth',
'TwigBridge\Extension\Laravel\Config',
'TwigBridge\Extension\Laravel\Form',
'TwigBridge\Extension\Laravel\Html',
'TwigBridge\Extension\Laravel\Input',
'TwigBridge\Extension\Laravel\Session',
'TwigBridge\Extension\Laravel\String',
'TwigBridge\Extension\Laravel\Translator',
'TwigBridge\Extension\Laravel\Url',
// 'TwigBridge\Extension\Laravel\Legacy\Facades',
],
/*
|--------------------------------------------------------------------------
| Facades
|--------------------------------------------------------------------------
|
| Available facades. Access like `{{ Config.get('foo.bar') }}`.
|
| Each facade can take an optional array of options. To mark the whole facade
| as safe you can set the option `'is_safe' => true`. Setting the facade as
| safe means that any HTML returned will not be escaped.
|
| It is advisable to not set the whole facade as safe and instead mark the
| each appropriate method as safe for security reasons. You can do that with
| the following syntax:
|
| <code>
| 'Form' => [
| 'is_safe' => [
| 'open'
| ]
| ]
| </code>
|
| The values of the `is_safe` array must match the called method on the facade
| in order to be marked as safe.
|
*/
'facades' => [],
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| Available functions. Access like `{{ secure_url(...) }}`.
|
| Each function can take an optional array of options. These options are
| passed directly to `Twig_SimpleFunction`.
|
| So for example, to mark a function as safe you can do the following:
|
| <code>
| 'link_to' => [
| 'is_safe' => ['html']
| ]
| </code>
|
| The options array also takes a `callback` that allows you to name the
| function differently in your Twig templates than what it's actually called.
|
| <code>
| 'link' => [
| 'callback' => 'link_to'
| ]
| </code>
|
*/
'functions' => [],
/*
|--------------------------------------------------------------------------
| Filters
|--------------------------------------------------------------------------
|
| Available filters. Access like `{{ variable|filter }}`.
|
| Each filter can take an optional array of options. These options are
| passed directly to `Twig_SimpleFilter`.
|
| So for example, to mark a filter as safe you can do the following:
|
| <code>
| 'studly_case' => [
| 'is_safe' => ['html']
| ]
| </code>
|
| The options array also takes a `callback` that allows you to name the
| filter differently in your Twig templates than what is actually called.
|
| <code>
| 'snake' => [
| 'callback' => 'snake_case'
| ]
| </code>
|
*/
'filters' => [],
];

View File

@@ -0,0 +1,88 @@
<?php
/**
* This file is part of the TwigBridge package.
*
* @copyright Robert Crowe <hello@vivalacrowe.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Illuminate\Support\Facades\Config;
/**
* Configuration options for Twig.
*/
return [
/*
|--------------------------------------------------------------------------
| Extension
|--------------------------------------------------------------------------
|
| File extension for Twig view files.
|
*/
'extension' => 'twig',
/*
|--------------------------------------------------------------------------
| Accepts all Twig environment configuration options
|--------------------------------------------------------------------------
|
| http://twig.sensiolabs.org/doc/api.html#environment-options
|
*/
'environment' => [
// When set to true, the generated templates have a __toString() method
// that you can use to display the generated nodes.
// default: false
'debug' => Config::get('app.debug', false),
// The charset used by the templates.
// default: utf-8
'charset' => 'utf-8',
// The base template class to use for generated templates.
// default: TwigBridge\Twig\Template
'base_template_class' => 'TwigBridge\Twig\Template',
// An absolute path where to store the compiled templates, or false to disable caching. If null
// then the cache file path is used.
// default: cache file storage path
'cache' => null,
// When developing with Twig, it's useful to recompile the template
// whenever the source code changes. If you don't provide a value
// for the auto_reload option, it will be determined automatically based on the debug value.
'auto_reload' => true,
// If set to false, Twig will silently ignore invalid variables
// (variables and or attributes/methods that do not exist) and
// replace them with a null value. When set to true, Twig throws an exception instead.
// default: false
'strict_variables' => false,
// If set to true, auto-escaping will be enabled by default for all templates.
// default: true
'autoescape' => true,
// A flag that indicates which optimizations to apply
// (default to -1 -- all optimizations are enabled; set it to 0 to disable)
'optimizations' => -1,
],
/*
|--------------------------------------------------------------------------
| Global variables
|--------------------------------------------------------------------------
|
| These will always be passed in and can be accessed as Twig variables.
| NOTE: these will be overwritten if you pass data into the view with the same key.
|
*/
'globals' => [],
];

View File

@@ -8,8 +8,6 @@ use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
*/
class AccountController extends \BaseController
{
@@ -18,20 +16,79 @@ class AccountController extends \BaseController
/**
* @param ARI $repository
* @param AI $accounts
* @param AI $accounts
*/
public function __construct(ARI $repository, AI $accounts)
{
$this->_accounts = $accounts;
$this->_repository = $repository;
View::share('mainTitleIcon', 'fa-credit-card');
View::share('title', 'Accounts');
}
/**
* @return \Illuminate\View\View
*/
public function create()
public function create($what)
{
return View::make('accounts.create')->with('title', 'Create account');
switch ($what) {
case 'asset':
View::share('subTitleIcon', 'fa-money');
break;
case 'expense':
View::share('subTitleIcon', 'fa-shopping-cart');
break;
case 'revenue':
View::share('subTitleIcon', 'fa-download');
break;
}
return View::make('accounts.create')->with('subTitle', 'Create a new ' . $what . ' account')->with(
'what', $what
);
}
/**
* @return $this
*/
public function asset()
{
View::share('subTitleIcon', 'fa-money');
$accounts = $this->_repository->getOfTypes(['Asset account', 'Default account']);
return View::make('accounts.asset')->with('subTitle', 'Asset accounts')->with(
'accounts', $accounts
);
}
/**
* @return $this
*/
public function expense()
{
View::share('subTitleIcon', 'fa-shopping-cart');
$accounts = $this->_repository->getOfTypes(['Expense account', 'Beneficiary account']);
return View::make('accounts.expense')->with('subTitle', 'Expense accounts')->with(
'accounts', $accounts
);
}
/**
* @return $this
*/
public function revenue()
{
View::share('subTitleIcon', 'fa-download');
$accounts = $this->_repository->getOfTypes(['Revenue account']);
return View::make('accounts.revenue')->with('subTitle', 'Revenue accounts')->with(
'accounts', $accounts
);
}
/**
@@ -42,7 +99,9 @@ class AccountController extends \BaseController
public function delete(Account $account)
{
return View::make('accounts.delete')->with('account', $account)
->with('title', 'Delete account "' . $account->name . '"');
->with(
'subTitle', 'Delete ' . strtolower($account->accountType->type) . ' "' . $account->name . '"'
);
}
/**
@@ -52,11 +111,23 @@ class AccountController extends \BaseController
*/
public function destroy(Account $account)
{
$type = $account->accountType->type;
$this->_repository->destroy($account);
Session::flash('success', 'The account was deleted.');
switch ($type) {
case 'Asset account':
case 'Default account':
return Redirect::route('accounts.asset');
break;
case 'Expense account':
case 'Beneficiary account':
return Redirect::route('accounts.expense');
break;
case 'Revenue account':
return Redirect::route('accounts.revenue');
break;
}
return Redirect::route('accounts.index');
}
@@ -67,9 +138,25 @@ class AccountController extends \BaseController
*/
public function edit(Account $account)
{
switch ($account->accountType->type) {
case 'Asset account':
case 'Default account':
View::share('subTitleIcon', 'fa-money');
break;
case 'Expense account':
case 'Beneficiary account':
View::share('subTitleIcon', 'fa-shopping-cart');
break;
case 'Revenue account':
View::share('subTitleIcon', 'fa-download');
break;
}
$openingBalance = $this->_accounts->openingBalanceTransaction($account);
return View::make('accounts.edit')->with('account', $account)->with('openingBalance', $openingBalance)
->with('title', 'Edit account "' . $account->name . '"');
->with('subTitle', 'Edit ' . strtolower($account->accountType->type) . ' "' . $account->name . '"');
}
/**
@@ -77,23 +164,7 @@ class AccountController extends \BaseController
*/
public function index()
{
$accounts = $this->_repository->get();
$set = [
'personal' => [],
'beneficiaries' => []
];
foreach ($accounts as $account) {
switch ($account->accounttype->type) {
case 'Default account':
$set['personal'][] = $account;
break;
case 'Beneficiary account':
$set['beneficiaries'][] = $account;
break;
}
}
return View::make('accounts.index')->with('accounts', $set)->with('title', 'All your accounts');
return View::make('error')->with('message', 'This view has been disabled');
}
/**
@@ -103,10 +174,27 @@ class AccountController extends \BaseController
*/
public function show(Account $account)
{
switch ($account->accountType->type) {
case 'Asset account':
case 'Default account':
View::share('subTitleIcon', 'fa-money');
break;
case 'Expense account':
case 'Beneficiary account':
View::share('subTitleIcon', 'fa-shopping-cart');
break;
case 'Revenue account':
View::share('subTitleIcon', 'fa-download');
break;
}
$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(
'subTitle',
'Details for ' . strtolower($account->accountType->type) . ' "' . $account->name . '"'
);
}
/**
@@ -115,21 +203,39 @@ class AccountController extends \BaseController
public function store()
{
$account = $this->_repository->store(Input::all());
$data = Input::all();
$data['what'] = isset($data['what']) && $data['what'] != '' ? $data['what'] : 'asset';
switch ($data['what']) {
default:
case 'asset':
$data['account_type'] = 'Asset account';
break;
case 'expense':
$data['account_type'] = 'Expense account';
break;
case 'revenue':
$data['account_type'] = 'Revenue account';
break;
}
$account = $this->_repository->store($data);
if ($account->validate()) {
// saved! return to wherever.
Session::flash('success', 'Account "' . $account->name . '" created!');
if (intval(Input::get('create')) === 1) {
return Redirect::route('accounts.create')->withInput();
return Redirect::route('accounts.create', $data['what'])->withInput();
} else {
return Redirect::route('accounts.index');
return Redirect::route('accounts.' . e($data['what']));
}
} else {
// did not save, return with error:
Session::flash('error', 'Could not save the new account: ' . $account->errors()->first());
return Redirect::route('accounts.create')->withErrors($account->errors())->withInput();
return Redirect::route('accounts.create', $data['what'])->withErrors($account->errors())->withInput();
}
}
@@ -141,11 +247,24 @@ class AccountController extends \BaseController
*/
public function update(Account $account)
{
/** @var \Account $account */
$account = $this->_repository->update($account, Input::all());
if ($account->validate()) {
Session::flash('success', 'Account "' . $account->name . '" updated.');
switch ($account->accountType->type) {
case 'Asset account':
case 'Default account':
return Redirect::route('accounts.asset');
break;
case 'Expense account':
case 'Beneficiary account':
return Redirect::route('accounts.expense');
break;
case 'Revenue account':
return Redirect::route('accounts.revenue');
break;
}
return Redirect::route('accounts.index');
} else {
Session::flash('error', 'Could not update account: ' . $account->errors()->first());

View File

@@ -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');
}
/**

View File

@@ -1,6 +1,7 @@
<?php
use Carbon\Carbon;
use Firefly\Exception\FireflyException;
use Firefly\Helper\Controllers\BudgetInterface as BI;
use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
@@ -8,6 +9,7 @@ use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
* Class BudgetController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*
*/
class BudgetController extends BaseController
@@ -17,13 +19,47 @@ class BudgetController extends BaseController
protected $_repository;
/**
* @param BI $budgets
* @param BI $budgets
* @param BRI $repository
*/
public function __construct(BI $budgets, BRI $repository)
{
$this->_budgets = $budgets;
$this->_repository = $repository;
View::share('title', 'Budgets');
View::share('mainTitleIcon', 'fa-tasks');
}
public function nobudget($view = 'session') {
switch($view) {
default:
throw new FireflyException('Cannot show transactions without a budget for view "'.$view.'".');
break;
case 'session':
$start = Session::get('start');
$end = Session::get('end');
break;
}
// Add expenses that have no budget:
$set = \Auth::user()->transactionjournals()->whereNotIn(
'transaction_journals.id', function ($query) use ($start, $end) {
$query->select('transaction_journals.id')->from('transaction_journals')
->leftJoin(
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
'transaction_journals.id'
)
->leftJoin('components', 'components.id', '=', 'component_transaction_journal.component_id')
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('components.class', 'Budget');
}
)->before($end)->after($start)->get();
return View::make('budgets.nobudget')
->with('view', $view)
->with('transactions',$set)
->with('subTitle', 'Transactions without a budget');
}
/**
@@ -33,7 +69,7 @@ class BudgetController extends BaseController
{
$periods = \Config::get('firefly.periods_to_text');
return View::make('budgets.create')->with('periods', $periods)->with('title', 'Create a new budget');
return View::make('budgets.create')->with('periods', $periods)->with('subTitle', 'Create a new budget');
}
/**
@@ -44,7 +80,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('subTitle', 'Delete budget "' . $budget->name . '"');
}
/**
@@ -75,7 +111,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('subTitle', 'Edit budget "' . $budget->name . '"');
}
@@ -84,10 +120,12 @@ class BudgetController extends BaseController
*/
public function indexByBudget()
{
View::share('subTitleIcon', 'fa-folder-open');
$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('subTitle', 'Grouped by budget');
}
@@ -96,12 +134,14 @@ class BudgetController extends BaseController
*/
public function indexByDate()
{
View::share('subTitleIcon', 'fa-calendar');
// get a list of dates by getting all repetitions:
$set = $this->_repository->get();
$budgets = $this->_budgets->organizeByDate($set);
return View::make('budgets.indexByDate')->with('budgets', $budgets)
->with('title', 'All your budgets grouped by date');
->with('subTitle', 'Grouped by date');
}
@@ -113,7 +153,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 +168,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 +186,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('subTitle', 'Overview for ' . $title);
}
/**

View File

@@ -5,6 +5,8 @@ use Firefly\Storage\Category\CategoryRepositoryInterface as CRI;
/**
* Class CategoryController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
*/
class CategoryController extends BaseController
{
@@ -13,12 +15,14 @@ class CategoryController extends BaseController
/**
* @param CRI $repository
* @param CI $category
* @param CI $category
*/
public function __construct(CRI $repository, CI $category)
{
$this->_repository = $repository;
$this->_category = $category;
View::share('title','Categories');
View::share('mainTitleIcon', 'fa-bar-chart');
}
/**
@@ -26,7 +30,7 @@ class CategoryController extends BaseController
*/
public function create()
{
return View::make('categories.create')->with('title', 'Create a new category');
return View::make('categories.create')->with('subTitle', 'Create a new category');
}
/**
@@ -37,7 +41,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('subTitle', 'Delete category "' . $category->name . '"');
}
/**
@@ -60,7 +64,7 @@ class CategoryController extends BaseController
public function edit(Category $category)
{
return View::make('categories.edit')->with('category', $category)
->with('title', 'Edit category "' . $category->name . '"');
->with('subTitle', 'Edit category "' . $category->name . '"');
}
/**
@@ -71,7 +75,7 @@ class CategoryController extends BaseController
$categories = $this->_repository->get();
return View::make('categories.index')->with('categories', $categories)
->with('title', 'All your categories');
->with('subTitle', 'All your categories');
}
/**
@@ -88,8 +92,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('subTitle', 'Overview for category "' . $category->name . '"');
}
/**

View File

@@ -6,6 +6,9 @@ use Firefly\Storage\Account\AccountRepositoryInterface;
/**
* Class ChartController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ChartController extends BaseController
{
@@ -43,7 +46,7 @@ class ChartController extends BaseController
/** @var \LimitRepetition $rep */
foreach ($limit->limitrepetitions as $rep) {
// get the amount of money spent in this period on this budget.
$spentInRep = $rep->amount - $rep->left();
$spentInRep = $rep->amount - $rep->leftInRepetition();
$pct = round((floatval($spentInRep) / floatval($limit->amount)) * 100, 2);
$name = $rep->periodShow();
$envelope[] = [$name, floatval($limit->amount)];
@@ -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;
@@ -369,9 +372,102 @@ class ChartController extends BaseController
*/
public function homeBudgets()
{
$start = \Session::get('start');
$start = Session::get('start');
$end = Session::get('end');
$data = [
'labels' => [],
'series' => [
[
'name' => 'Limit',
'data' => []
],
[
'name' => 'Spent',
'data' => []
],
]
];
// Get all budgets.
$budgets = \Auth::user()->budgets()->orderBy('name', 'ASC')->get();
$budgetIds = [];
/** @var \Budget $budget */
foreach ($budgets as $budget) {
$budgetIds[] = $budget->id;
// Does the budget have a limit starting on $start?
$rep = \LimitRepetition::
leftJoin('limits', 'limit_repetitions.limit_id', '=', 'limits.id')->leftJoin(
'components', 'limits.component_id', '=', 'components.id'
)->where('limit_repetitions.startdate', $start->format('Y-m-d'))->where(
'components.id', $budget->id
)->first(['limit_repetitions.*']);
if (is_null($rep)) {
$limit = 0.0;
$id = null;
$parameter = 'useSession=true';
} else {
$limit = floatval($rep->amount);
$id = $rep->id;
$parameter = '';
}
// Date range to check for expenses made?
if (is_null($rep)) {
// use the session start and end for our search query
$expenseStart = Session::get('start');
$expenseEnd = Session::get('end');
} else {
// use the limit's start and end for our search query
$expenseStart = $rep->startdate;
$expenseEnd = $rep->enddate;
}
// How much have we spent on this budget?
$expenses = floatval($budget->transactionjournals()->before($expenseEnd)->after($expenseStart)->lessThan(0)->sum('amount')) * -1;
// Append to chart:
if ($limit > 0 || $expenses > 0) {
$data['labels'][] = $budget->name;
$data['series'][0]['data'][] = [
'y' => $limit,
'url' => route('budgets.show', [$budget->id, $id]) . '?' . $parameter
];
$data['series'][1]['data'][] = [
'y' => $expenses,
'url' => route('budgets.show', [$budget->id, $id]) . '?' . $parameter
];
}
}
// Add expenses that have no budget:
$set = \Auth::user()->transactionjournals()->whereNotIn(
'transaction_journals.id', function ($query) use ($start, $end) {
$query->select('transaction_journals.id')->from('transaction_journals')
->leftJoin(
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
'transaction_journals.id'
)
->leftJoin('components', 'components.id', '=', 'component_transaction_journal.component_id')
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
->where('components.class', 'Budget');
}
)->before($end)->after($start)->lessThan(0)->transactionTypes(['Withdrawal'])->sum('amount');
// This can be debugged by using get(['transaction_journals.*','transactions.amount']);
$data['labels'][] = 'No budget';
$data['series'][0]['data'][] = [
'y' => 0,
'url' => route('budgets.nobudget','session')
];
$data['series'][1]['data'][] = [
'y' => floatval($set) * -1,
'url' => route('budgets.nobudget','session')
];
return Response::json($data);
return Response::json($this->_chart->budgets($start));
}
/**

View File

@@ -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,63 @@ 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;
}
public function jobDev()
{
$fullName = storage_path() . DIRECTORY_SEPARATOR . 'firefly-export-2014-07-23.json';
\Log::notice('Pushed start job.');
Queue::push('Firefly\Queue\Import@start', ['file' => $fullName, 'user' => 1]);
}
/*
*
*/
public function sessionPrev()
{
/** @var \Firefly\Helper\Toolkit\ToolkitInterface $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface');
$toolkit->prev();
return Redirect::back();
//return Redirect::route('index');
}
/*
*
*/
public function sessionNext()
{
/** @var \Firefly\Helper\Toolkit\ToolkitInterface $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface');
$toolkit->next();
return Redirect::back();
//return Redirect::route('index');
}
public function rangeJump($range)
{
$viewRange = $this->_preferences->get('viewRange', '1M');
$valid = ['1D', '1W', '1M', '3M', '6M', '1Y',];
if(in_array($range,$valid)) {
$this->_preferences->set('viewRange', $range);
Session::forget('range');
}
return Redirect::back();
}
/**
@@ -38,20 +90,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:
@@ -70,21 +116,8 @@ class HomeController extends BaseController
}
}
if (count($transactions) % 2 == 0) {
$transactions = array_chunk($transactions, 2);
} elseif (count($transactions) == 1) {
$transactions = array_chunk($transactions, 3);
} else {
$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)->with('title', 'Firefly')
->with('subTitle', 'What\'s playing?')->with('mainTitleIcon', 'fa-fire');
}
}

View File

@@ -1,41 +1,57 @@
<?php
use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
use Firefly\Storage\Budget\BudgetRepositoryInterface as Bud;
use Firefly\Storage\Category\CategoryRepositoryInterface as Cat;
use Firefly\Storage\Component\ComponentRepositoryInterface as CRI;
use Firefly\Helper\Controllers\JsonInterface as JI;
use Illuminate\Support\Collection;
use LaravelBook\Ardent\Builder;
/**
* Class JsonController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
*/
class JsonController extends BaseController
{
protected $_accounts;
protected $_components;
protected $_categories;
protected $_budgets;
/** @var \Firefly\Helper\Controllers\JsonInterface $helper */
protected $helper;
public function __construct(JI $helper)
{
$this->helper = $helper;
}
/**
* @param ARI $accounts
* @param CRI $components
* @param Cat $categories
* @param Bud $budgets
* Returns a list of categories.
*
* @return \Illuminate\Http\JsonResponse
*/
public function __construct(ARI $accounts, CRI $components, Cat $categories, Bud $budgets)
public function categories()
{
$this->_components = $components;
$this->_accounts = $accounts;
$this->_categories = $categories;
$this->_budgets = $budgets;
/** @var \Firefly\Storage\Category\EloquentCategoryRepository $categories */
$categories = App::make('Firefly\Storage\Category\CategoryRepositoryInterface');
$list = $categories->get();
$return = [];
foreach ($list as $entry) {
$return[] = $entry->name;
}
return Response::json($return);
}
/**
* Returns a JSON list of all beneficiaries.
*
* @return \Illuminate\Http\JsonResponse
*/
public function beneficiaries()
public function expenseAccounts()
{
$list = $this->_accounts->getBeneficiaries();
$return = [];
/** @var \Firefly\Storage\Account\EloquentAccountRepository $accounts */
$accounts = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$list = $accounts->getOfTypes(['Expense account', 'Beneficiary account']);
$return = [];
foreach ($list as $entry) {
$return[] = $entry->name;
}
@@ -45,18 +61,126 @@ class JsonController extends BaseController
}
/**
* Responds some JSON for typeahead fields.
* Returns a list of transactions, expenses only, using the given parameters.
*
* @return \Illuminate\Http\JsonResponse
*/
public function categories()
public function expenses()
{
$list = $this->_categories->get();
$return = [];
/*
* Gets most parameters from the Input::all() array:
*/
$parameters = $this->helper->dataTableParameters();
/*
* Add some more parameters to fine tune the query:
*/
$parameters['transactionTypes'] = ['Withdrawal'];
$parameters['amount'] = 'negative';
/*
* Get the query:
*/
$query = $this->helper->journalQuery($parameters);
/*
* Build result set:
*/
$resultSet = $this->helper->journalDataset($parameters, $query);
/*
* Build return data:
*/
return Response::json($resultSet);
}
/**
*
*/
public function recurringjournals(RecurringTransaction $recurringTransaction)
{
$parameters = $this->helper->dataTableParameters();
$parameters['transactionTypes'] = ['Withdrawal'];
$parameters['amount'] = 'negative';
$query = $this->helper->journalQuery($parameters);
$query->where('recurring_transaction_id', $recurringTransaction->id);
$resultSet = $this->helper->journalDataset($parameters, $query);
/*
* Build return data:
*/
return Response::json($resultSet);
}
public function recurring()
{
$parameters = $this->helper->dataTableParameters();
$query = $this->helper->recurringTransactionsQuery($parameters);
$resultSet = $this->helper->recurringTransactionsDataset($parameters, $query);
return Response::json($resultSet);
}
/**
* @return \Illuminate\Http\JsonResponse|string
*/
public function revenue()
{
$parameters = $this->helper->dataTableParameters();
$parameters['transactionTypes'] = ['Deposit'];
$parameters['amount'] = 'positive';
$query = $this->helper->journalQuery($parameters);
$resultSet = $this->helper->journalDataset($parameters, $query);
/*
* Build return data:
*/
return Response::json($resultSet);
}
/**
* Returns a JSON list of all revenue accounts.
*
* @return \Illuminate\Http\JsonResponse
*/
public function revenueAccounts()
{
/** @var \Firefly\Storage\Account\EloquentAccountRepository $accounts */
$accounts = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$list = $accounts->getOfTypes(['Revenue account']);
$return = [];
foreach ($list as $entry) {
$return[] = $entry->name;
}
return Response::json($return);
}
/**
* Returns a list of all transfers.
*
* @return \Illuminate\Http\JsonResponse
*/
public function transfers()
{
$parameters = $this->helper->dataTableParameters();
$parameters['transactionTypes'] = ['Transfer'];
$parameters['amount'] = 'positive';
$query = $this->helper->journalQuery($parameters);
$resultSet = $this->helper->journalDataset($parameters, $query);
/*
* Build return data:
*/
return Response::json($resultSet);
}
}

View File

@@ -5,6 +5,8 @@ use Firefly\Storage\Limit\LimitRepositoryInterface as LRI;
/**
* Class LimitController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
*/
class LimitController extends BaseController
{
@@ -19,7 +21,10 @@ class LimitController extends BaseController
public function __construct(BRI $budgets, LRI $limits)
{
$this->_budgets = $budgets;
$this->_limits = $limits;
$this->_limits = $limits;
View::share('title','Envelopes');
View::share('mainTitleIcon', 'fa-tasks');
}
/**
@@ -29,18 +34,20 @@ 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',
'budget_id' => $budget ? $budget->id : null
];
$budgets = $this->_budgets->getAsSelectList();
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
$budgets = $toolkit->makeSelectList($this->_budgets->get());
return View::make('limits.create')->with('budgets', $budgets)->with(
'periods', $periods
)->with('prefilled', $prefilled);
)->with('prefilled', $prefilled)->with('subTitle','New envelope');
}
/**
@@ -50,7 +57,7 @@ class LimitController extends BaseController
*/
public function delete(\Limit $limit)
{
return View::make('limits.delete')->with('limit', $limit);
return View::make('limits.delete')->with('limit', $limit)->with('subTitle','Delete envelope');
}
/**
@@ -82,12 +89,15 @@ class LimitController extends BaseController
*/
public function edit(Limit $limit)
{
$budgets = $this->_budgets->getAsSelectList();
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
$budgets = $toolkit->makeSelectList($this->_budgets->get());
$periods = \Config::get('firefly.periods_to_text');
return View::make('limits.edit')->with('limit', $limit)->with('budgets', $budgets)->with(
'periods', $periods
);
)->with('subTitle','Edit envelope');
}
/**
@@ -98,7 +108,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 +120,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()

View File

@@ -11,7 +11,8 @@ class MigrateController extends BaseController
*/
public function index()
{
return View::make('migrate.index')->with('index', 'Migration');
return View::make('migrate.index')->with('index', 'Migration')->with('title','Migrate')->
with('subTitle','From Firefly II to Firefly III');
}
/**
@@ -20,17 +21,22 @@ class MigrateController extends BaseController
public function upload()
{
if (Input::hasFile('file') && Input::file('file')->isValid()) {
// move file to storage:
// ->move($destinationPath, $fileName);
$path = storage_path();
$fileName = 'firefly-iii-import-' . date('Y-m-d-H-i') . '.json';
$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!
if (Input::file('file')->move($path, $fileName)) {
// 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.');

View File

@@ -7,12 +7,16 @@ use Firefly\Storage\Piggybank\PiggybankRepositoryInterface as PRI;
/**
* Class PiggybankController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.TooManyMethods)
*
*/
class PiggybankController extends BaseController
{
protected $_repository;
protected $_accounts;
protected $_repository;
/**
* @param PRI $repository
@@ -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,10 +49,20 @@ class PiggybankController extends BaseController
*/
public function createPiggybank()
{
$periods = Config::get('firefly.piggybank_periods');
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
return View::make('piggybanks.create-piggybank')->with('accounts', $accounts)->with('periods', $periods);
$periods = Config::get('firefly.piggybank_periods');
$list = $this->_accounts->getActiveDefault();
$accounts = $toolkit->makeSelectList($list);
View::share('title', 'Piggy banks');
View::share('subTitle', 'Create new');
View::share('mainTitleIcon', 'fa-sort-amount-asc');
return View::make('piggybanks.create-piggybank')->with('accounts', $accounts)
->with('periods', $periods);
}
/**
@@ -54,8 +70,16 @@ class PiggybankController extends BaseController
*/
public function createRepeated()
{
$periods = Config::get('firefly.piggybank_periods');
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
$periods = Config::get('firefly.piggybank_periods');
$list = $this->_accounts->getActiveDefault();
$accounts = $toolkit->makeSelectList($list);
View::share('title', 'Repeated expenses');
View::share('subTitle', 'Create new');
View::share('mainTitleIcon', 'fa-rotate-right');
return View::make('piggybanks.create-repeated')->with('accounts', $accounts)->with('periods', $periods);
}
@@ -67,6 +91,15 @@ class PiggybankController extends BaseController
*/
public function delete(Piggybank $piggyBank)
{
View::share('subTitle', 'Delete "' . $piggyBank->name . '"');
if ($piggyBank->repeats == 1) {
View::share('title', 'Repeated expenses');
View::share('mainTitleIcon', 'fa-rotate-right');
} else {
View::share('title', 'Piggy banks');
View::share('mainTitleIcon', 'fa-sort-amount-asc');
}
return View::make('piggybanks.delete')->with('piggybank', $piggyBank);
}
@@ -78,11 +111,18 @@ class PiggybankController extends BaseController
public function destroy(Piggybank $piggyBank)
{
Event::fire('piggybanks.destroy', [$piggyBank]);
if ($piggyBank->repeats == 1) {
$route = 'piggybanks.index.repeated';
$message = 'Repeated expense';
} else {
$route = 'piggybanks.index.piggybanks';
$message = 'Piggybank';
}
$this->_repository->destroy($piggyBank);
Session::flash('success', 'Piggy bank deleted.');
Session::flash('success', $message . ' deleted.');
return Redirect::route('piggybanks.index');
return Redirect::route($route);
}
/**
@@ -92,12 +132,28 @@ class PiggybankController extends BaseController
*/
public function edit(Piggybank $piggyBank)
{
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
$periods = Config::get('firefly.piggybank_periods');
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
$list = $this->_accounts->getActiveDefault();
$accounts = $toolkit->makeSelectList($list);
$periods = Config::get('firefly.piggybank_periods');
View::share('subTitle', 'Edit "' . $piggyBank->name . '"');
if ($piggyBank->repeats == 1) {
View::share('title', 'Repeated expenses');
View::share('mainTitleIcon', 'fa-rotate-left');
return View::make('piggybanks.edit-repeated')->with('piggybank', $piggyBank)->with('accounts', $accounts)
->with('periods', $periods);
} else {
// piggy bank.
View::share('title', 'Piggy banks');
View::share('mainTitleIcon', 'fa-sort-amount-asc');
return View::make('piggybanks.edit-piggybank')->with('piggybank', $piggyBank)->with('accounts', $accounts)
->with('periods', $periods);
}
@@ -105,35 +161,6 @@ class PiggybankController extends BaseController
}
/**
* @return $this
*/
public function index()
{
$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
// show the user a correction.
$accounts = [];
/** @var \Piggybank $piggybank */
foreach ($piggybanks as $piggybank) {
$account = $piggybank->account;
$id = $account->id;
if (!isset($accounts[$id])) {
$accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)];
}
}
return View::make('piggybanks.index')->with('piggybanks', $piggybanks)
->with('countRepeating', $countRepeating)
->with('countNonRepeating', $countNonRepeating)
->with('accounts', $accounts);
}
/**
* @param Piggybank $piggyBank
*
@@ -158,7 +185,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!');
@@ -169,17 +196,58 @@ class PiggybankController extends BaseController
}
break;
}
if($piggyBank->repeats == 1) {
$route = 'piggybanks.index.repeated';
return Redirect::route('piggybanks.index');
} else {
$route = 'piggybanks.index.piggybanks';
}
return Redirect::route($route);
}
/**
* @return $this
*/
public function piggybanks()
{
$countRepeating = $this->_repository->countRepeating();
$countNonRepeating = $this->_repository->countNonrepeating();
$piggybanks = $this->_repository->get();
// 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;
if (!isset($accounts[$id])) {
$account->leftOnAccount = $this->_repository->leftOnAccount($account);
$accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)];
}
}
View::share('title', 'Piggy banks');
View::share('subTitle', 'Save for big expenses');
View::share('mainTitleIcon', 'fa-sort-amount-asc');
return View::make('piggybanks.index')->with('piggybanks', $piggybanks)
->with('countRepeating', $countRepeating)
->with('countNonRepeating', $countNonRepeating)
->with('accounts', $accounts);
}
/**
* @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(
@@ -187,13 +255,60 @@ class PiggybankController extends BaseController
)->with('piggybank', $piggyBank);
}
/**
* @return $this
*/
public function repeated()
{
$countRepeating = $this->_repository->countRepeating();
$countNonRepeating = $this->_repository->countNonrepeating();
$piggybanks = $this->_repository->get();
// 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;
if (!isset($accounts[$id])) {
$account->leftOnAccount = $this->_repository->leftOnAccount($account);
$accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)];
}
}
View::share('title', 'Repeated expenses');
View::share('subTitle', 'Save for returning bills');
View::share('mainTitleIcon', 'fa-rotate-left');
return View::make('piggybanks.index')->with('piggybanks', $piggybanks)
->with('countRepeating', $countRepeating)
->with('countNonRepeating', $countNonRepeating)
->with('accounts', $accounts);
}
/**
*
*/
public function show(Piggybank $piggyBank)
{
$leftOnAccount = $this->_repository->leftOnAccount($piggyBank->account);
$balance = $piggyBank->account->balance();
$balance = $piggyBank->account->balance();
View::share('subTitle', $piggyBank->name);
if ($piggyBank->repeats == 1) {
// repeated expense.
View::share('title', 'Repeated expenses');
View::share('mainTitleIcon', 'fa-rotate-left');
} else {
// piggy bank.
View::share('title', 'Piggy banks');
View::share('mainTitleIcon', 'fa-sort-amount-asc');
}
return View::make('piggybanks.show')->with('piggyBank', $piggyBank)->with('leftOnAccount', $leftOnAccount)
->with('balance', $balance);
@@ -208,17 +323,17 @@ 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)) {
Session::flash('success', 'New piggy bank "' . $piggyBank->name . '" created!');
Event::fire('piggybanks.store', [$piggyBank]);
return Redirect::route('piggybanks.index');
return Redirect::route('piggybanks.index.piggybanks');
} else {
@@ -240,14 +355,13 @@ 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) {
Session::flash('success', 'New piggy bank "' . $piggyBank->name . '" created!');
Event::fire('piggybanks.store', [$piggyBank]);
return Redirect::route('piggybanks.index');
return Redirect::route('piggybanks.index.repeated');
} else {
Session::flash('error', 'Could not save piggy bank: ' . $piggyBank->errors()->first());
@@ -258,16 +372,27 @@ class PiggybankController extends BaseController
}
/**
* @param Piggybank $piggyBank
*
* @return $this|\Illuminate\Http\RedirectResponse
*/
public function update(Piggybank $piggyBank)
{
$piggyBank = $this->_repository->update($piggyBank, Input::all());
if ($piggyBank->validate()) {
Session::flash('success', 'Piggy bank "' . $piggyBank->name . '" updated.');
if ($piggyBank->repeats == 1) {
$route = 'piggybanks.index.repeated';
$message = 'Repeated expense';
} else {
$route = 'piggybanks.index.piggybanks';
$message = 'Piggy bank';
}
Session::flash('success', $message . ' "' . $piggyBank->name . '" updated.');
Event::fire('piggybanks.update', [$piggyBank]);
return Redirect::route('piggybanks.index');
return Redirect::route($route);
} else {
Session::flash('error', 'Could not update piggy bank: ' . $piggyBank->errors()->first());

View File

@@ -5,6 +5,8 @@ use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
/**
* Class PreferencesController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
*/
class PreferencesController extends BaseController
{
@@ -18,8 +20,10 @@ class PreferencesController extends BaseController
public function __construct(ARI $accounts, PHI $preferences)
{
$this->_accounts = $accounts;
$this->_accounts = $accounts;
$this->_preferences = $preferences;
View::share('title','Preferences');
View::share('mainTitleIcon','fa-gear');
}
/**
@@ -29,7 +33,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:

View File

@@ -22,6 +22,9 @@ class ProfileController extends BaseController
*/
public function index()
{
View::share('title','Profile');
View::share('subTitle',Auth::user()->email);
View::share('mainTitleIcon','fa-user');
return View::make('profile.index');
}
@@ -30,6 +33,9 @@ class ProfileController extends BaseController
*/
public function changePassword()
{
View::share('title',Auth::user()->email);
View::share('subTitle','Change your password');
View::share('mainTitleIcon','fa-user');
return View::make('profile.change-password');
}

View File

@@ -1,20 +1,30 @@
<?php
use Firefly\Exception\FireflyException;
use Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface as RTR;
use Firefly\Helper\Controllers\RecurringInterface as RI;
/**
* Class RecurringController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
*/
class RecurringController extends BaseController
{
protected $_repository;
protected $_helper;
/**
* @param RTR $repository
* @param RI $helper
*/
public function __construct(RTR $repository)
public function __construct(RTR $repository, RI $helper)
{
$this->_repository = $repository;
$this->_helper = $helper;
View::share('title', 'Recurring transactions');
View::share('mainTitleIcon', 'fa-rotate-right');
}
/**
@@ -22,6 +32,7 @@ class RecurringController extends BaseController
*/
public function create()
{
View::share('subTitle', 'Create new');
$periods = \Config::get('firefly.periods_to_text');
return View::make('recurring.create')->with('periods', $periods);
@@ -34,6 +45,7 @@ class RecurringController extends BaseController
*/
public function delete(RecurringTransaction $recurringTransaction)
{
View::share('subTitle', 'Delete "' . $recurringTransaction->name . '"');
return View::make('recurring.delete')->with('recurringTransaction', $recurringTransaction);
}
@@ -44,7 +56,7 @@ class RecurringController extends BaseController
*/
public function destroy(RecurringTransaction $recurringTransaction)
{
Event::fire('recurring.destroy', [$recurringTransaction]);
//Event::fire('recurring.destroy', [$recurringTransaction]);
$result = $this->_repository->destroy($recurringTransaction);
if ($result === true) {
Session::flash('success', 'The recurring transaction was deleted.');
@@ -65,6 +77,8 @@ class RecurringController extends BaseController
{
$periods = \Config::get('firefly.periods_to_text');
View::share('subTitle', 'Edit "' . $recurringTransaction->name . '"');
return View::make('recurring.edit')->with('periods', $periods)->with(
'recurringTransaction', $recurringTransaction
);
@@ -75,9 +89,7 @@ class RecurringController extends BaseController
*/
public function index()
{
$list = $this->_repository->get();
return View::make('recurring.index')->with('list', $list);
return View::make('recurring.index');
}
/**
@@ -85,53 +97,117 @@ class RecurringController extends BaseController
*/
public function show(RecurringTransaction $recurringTransaction)
{
View::share('subTitle', $recurringTransaction->name);
return View::make('recurring.show')->with('recurring', $recurringTransaction);
}
/**
* @return $this|\Illuminate\Http\RedirectResponse
*/
public function store()
{
$recurringTransaction = $this->_repository->store(Input::all());
if ($recurringTransaction->validate()) {
Session::flash('success', 'Recurring transaction "' . $recurringTransaction->name . '" saved!');
Event::fire('recurring.store', [$recurringTransaction]);
if (Input::get('create') == '1') {
return Redirect::route('recurring.create')->withInput();
} else {
return Redirect::route('recurring.index');
}
} else {
Session::flash(
'error', 'Could not save the recurring transaction: ' . $recurringTransaction->errors()->first()
);
$data = Input::except(['_token', 'post_submit_action']);
switch (Input::get('post_submit_action')) {
default:
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
break;
case 'store':
case 'create_another':
/*
* Try to store:
*/
$messageBag = $this->_repository->store($data);
return Redirect::route('recurring.create')->withInput()->withErrors($recurringTransaction->errors());
/*
* Failure!
*/
if ($messageBag->count() > 0) {
Session::flash('error', 'Could not save recurring transaction: ' . $messageBag->first());
return Redirect::route('recurring.create')->withInput()->withErrors($messageBag);
}
/*
* Success!
*/
Session::flash('success', 'Recurring transaction "' . e(Input::get('name')) . '" saved!');
/*
* Redirect to original location or back to the form.
*/
if (Input::get('post_submit_action') == 'create_another') {
return Redirect::route('recurring.create')->withInput();
} else {
return Redirect::route('recurring.index');
}
break;
case 'validate_only':
$messageBags = $this->_helper->validate($data);
Session::flash('warnings', $messageBags['warnings']);
Session::flash('successes', $messageBags['successes']);
Session::flash('errors', $messageBags['errors']);
return Redirect::route('recurring.create')->withInput();
break;
}
}
/**
* @param RecurringTransaction $recurringTransaction
*/
public function update(RecurringTransaction $recurringTransaction)
{
/** @var \RecurringTransaction $recurringTransaction */
$recurringTransaction = $this->_repository->update($recurringTransaction, Input::all());
if ($recurringTransaction->errors()->count() == 0) {
Session::flash('success', 'The recurring transaction has been updated.');
Event::fire('recurring.update', [$recurringTransaction]);
$data = Input::except(['_token', 'post_submit_action']);
switch (Input::get('post_submit_action')) {
case 'update':
case 'return_to_edit':
$messageBag = $this->_repository->update($recurringTransaction, $data);
if ($messageBag->count() == 0) {
// has been saved, return to index:
Session::flash('success', 'Recurring transaction updated!');
return Redirect::route('recurring.index');
} else {
Session::flash(
'error', 'Could not update the recurring transaction: ' . $recurringTransaction->errors()->first()
);
if (Input::get('post_submit_action') == 'return_to_edit') {
return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput();
} else {
return Redirect::route('recurring.index');
}
} else {
Session::flash('error', 'Could not update recurring transaction: ' . $messageBag->first());
return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput()->withErrors(
$recurringTransaction->errors()
);
return Redirect::route('transactions.edit', $recurringTransaction->id)->withInput()
->withErrors($messageBag);
}
break;
case 'validate_only':
$data = Input::all();
$data['id'] = $recurringTransaction->id;
$messageBags = $this->_helper->validate($data);
Session::flash('warnings', $messageBags['warnings']);
Session::flash('successes', $messageBags['successes']);
Session::flash('errors', $messageBags['errors']);
return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput();
break;
// update
default:
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
break;
}
// /** @var \RecurringTransaction $recurringTransaction */
// $recurringTransaction = $this->_repository->update($recurringTransaction, Input::all());
// if ($recurringTransaction->errors()->count() == 0) {
// Session::flash('success', 'The recurring transaction has been updated.');
// //Event::fire('recurring.update', [$recurringTransaction]);
//
// return Redirect::route('recurring.index');
// } else {
// Session::flash(
// 'error', 'Could not update the recurring transaction: ' . $recurringTransaction->errors()->first()
// );
//
// return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput()->withErrors(
// $recurringTransaction->errors()
// );
// }
}
}

View File

@@ -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.');
}

View File

@@ -12,7 +12,7 @@ class ReportController extends BaseController
*/
public function index()
{
return View::make('reports.index')->with('title','Reports')->with('mainTitleIcon','fa-line-chart');
}
}

View File

@@ -1,16 +1,50 @@
<?php
use Firefly\Helper\Controllers\SearchInterface as SI;
/**
* Class SearchController
*/
class SearchController extends BaseController
{
protected $_helper;
public function __construct(SI $helper)
{
$this->_helper = $helper;
}
/**
*
* Results always come in the form of an array [results, count, fullCount]
*/
public function index()
{
$subTitle = null;
$rawQuery = null;
$result = [];
if (!is_null(Input::get('q'))) {
$rawQuery = trim(Input::get('q'));
$words = explode(' ', $rawQuery);
$subTitle = 'Results for "' . e($rawQuery) . '"';
$transactions = $this->_helper->searchTransactions($words);
$accounts = $this->_helper->searchAccounts($words);
$categories = $this->_helper->searchCategories($words);
$budgets = $this->_helper->searchBudgets($words);
$tags = $this->_helper->searchTags($words);
$result = [
'transactions' => $transactions,
'accounts' => $accounts,
'categories' => $categories,
'budgets' => $budgets,
'tags' => $tags
];
}
return View::make('search.index')->with('title', 'Search')->with('subTitle', $subTitle)->with(
'mainTitleIcon', 'fa-search'
)->with('query', $rawQuery)->with('result',$result);
}
}

View File

@@ -1,61 +1,103 @@
<?php
use Carbon\Carbon;
use Firefly\Exception\FireflyException;
use Firefly\Helper\Controllers\TransactionInterface as TI;
use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as TJRI;
use Illuminate\Support\MessageBag;
/**
* Class TransactionController
*
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
*
*/
class TransactionController extends BaseController
{
protected $_helper;
protected $_repository;
/**
* Construct a new transaction controller with two of the most often used helpers.
*
* @param TJRI $repository
* @param TI $helper
*/
public function __construct(TJRI $repository)
public function __construct(TJRI $repository, TI $helper)
{
$this->_repository = $repository;
$this->_helper = $helper;
View::share('title', 'Transactions');
View::share('mainTitleIcon', 'fa-repeat');
}
/**
* Shows the view helping the user to create a new transaction journal.
*
* @param string $what
*
* @return \Illuminate\View\View
*/
public function create($what = 'deposit')
{
// get accounts with names and id's.
/*
* The repositories we need:
*/
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
$accountRepository = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$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();
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
// get asset accounts with names and id's.
$assetAccounts = $toolkit->makeSelectList($accountRepository->getActiveDefault());
// get budgets as a select list.
$budgets = $toolkit->makeSelectList($budgetRepository->get());
$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 = $toolkit->makeSelectList($piggyRepository->get());
$piggies[0] = '(no piggy bank)';
return View::make('transactions.create')->with('accounts', $accounts)->with('budgets', $budgets)->with(
/*
* respond to a possible given values in the URL.
*/
$prefilled = Session::has('prefilled') ? Session::get('prefilled') : [];
$respondTo = ['account_id', 'account_from_id'];
foreach ($respondTo as $r) {
if (!is_null(Input::get($r))) {
$prefilled[$r] = Input::get($r);
}
}
Session::put('prefilled', $prefilled);
return View::make('transactions.create')->with('accounts', $assetAccounts)->with('budgets', $budgets)->with(
'what', $what
)->with('piggies', $piggies);
)->with('piggies', $piggies)->with('subTitle', 'Add a new ' . $what);
}
/**
* Shows the form that allows a user to delete a transaction journal.
*
* @param TransactionJournal $transactionJournal
*
* @return $this
*/
public function delete(TransactionJournal $transactionJournal)
{
return View::make('transactions.delete')->with('journal', $transactionJournal);
$type = strtolower($transactionJournal->transactionType->type);
return View::make('transactions.delete')->with('journal', $transactionJournal)->with(
'subTitle', 'Delete ' . $type . ' "' . $transactionJournal->description . '"'
);
}
@@ -68,103 +110,142 @@ class TransactionController extends BaseController
*/
public function destroy(TransactionJournal $transactionJournal)
{
$type = $transactionJournal->transactionType->type;
$transactionJournal->delete();
return Redirect::route('transactions.index');
switch ($type) {
case 'Withdrawal':
return Redirect::route('transactions.expenses');
break;
case 'Deposit':
return Redirect::route('transactions.revenue');
break;
case 'Transfer':
return Redirect::route('transactions.transfers');
break;
}
}
/**
* Shows the view to edit a transaction.
*
* @param TransactionJournal $journal
*
* @return $this
*/
public function edit(TransactionJournal $journal)
{
/*
* All the repositories we need:
*/
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
$accountRepository = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgetRepository */
$budgetRepository = App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
// type is useful for display:
$what = strtolower($journal->transactiontype->type);
// some lists prefilled:
// get accounts with names and id's.
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
$accountRepository = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$accounts = $accountRepository->getActiveDefaultAsSelectList();
// get asset accounts with names and id's.
$accounts = $toolkit->makeSelectList($accountRepository->getActiveDefault());
// get budgets as a select list.
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgetRepository */
$budgetRepository = App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
$budgets = $budgetRepository->getAsSelectList();
$budgets = $toolkit->makeSelectList($budgetRepository->get());
$budgets[0] = '(no budget)';
/*
* Get all piggy banks plus (if any) the relevant piggy bank. Since just one
* of the transactions in the journal has this field, it should all fill in nicely.
*/
// get the piggy banks.
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
$piggies = $piggyRepository->get();
// piggy bank id?
$piggyBankId = null;
$piggies = $toolkit->makeSelectList($piggyRepository->get());
$piggies[0] = '(no piggy bank)';
$piggyBankId = 0;
foreach ($journal->transactions as $t) {
$piggyBankId = $t->piggybank_id;
if (!is_null($t->piggybank_id)) {
$piggyBankId = $t->piggybank_id;
}
}
// data to properly display form:
$data = [
/*
* Data to properly display the edit form.
*/
$prefilled = [
'date' => $journal->date->format('Y-m-d'),
'category' => '',
'budget_id' => 0,
'piggybank_id' => $piggyBankId
];
/*
* Fill in the category.
*/
$category = $journal->categories()->first();
if (!is_null($category)) {
$data['category'] = $category->name;
$prefilled['category'] = $category->name;
}
switch ($journal->transactiontype->type) {
case 'Withdrawal':
$data['account_id'] = $journal->transactions[0]->account->id;
$data['beneficiary'] = $journal->transactions[1]->account->name;
$data['amount'] = floatval($journal->transactions[1]->amount);
/*
* Switch on the type of transaction edited by the user and fill in other
* relevant fields:
*/
switch ($what) {
case 'withdrawal':
$prefilled['account_id'] = $journal->transactions[0]->account->id;
$prefilled['expense_account'] = $journal->transactions[1]->account->name;
$prefilled['amount'] = floatval($journal->transactions[1]->amount);
$budget = $journal->budgets()->first();
if (!is_null($budget)) {
$data['budget_id'] = $budget->id;
$prefilled['budget_id'] = $budget->id;
}
break;
case 'Deposit':
$data['account_id'] = $journal->transactions[1]->account->id;
$data['beneficiary'] = $journal->transactions[0]->account->name;
$data['amount'] = floatval($journal->transactions[1]->amount);
case 'deposit':
$prefilled['account_id'] = $journal->transactions[1]->account->id;
$prefilled['revenue_account'] = $journal->transactions[0]->account->name;
$prefilled['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);
case 'transfer':
$prefilled['account_from_id'] = $journal->transactions[1]->account->id;
$prefilled['account_to_id'] = $journal->transactions[0]->account->id;
$prefilled['amount'] = floatval($journal->transactions[1]->amount);
break;
}
/*
* Show the view.
*/
return View::make('transactions.edit')->with('journal', $journal)->with('accounts', $accounts)->with(
'what', $what
)->with('budgets', $budgets)->with('data', $data)->with('piggies', $piggies);
)->with('budgets', $budgets)->with('data', $prefilled)->with('piggies', $piggies)->with(
'subTitle', 'Edit ' . $what . ' "' . $journal->description . '"'
);
}
/**
* @return $this|\Illuminate\View\View
* @return $this
*/
public function index()
public function expenses()
{
$start = is_null(Input::get('startdate')) ? null : new Carbon(Input::get('startdate'));
$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];
} else {
$journals = $this->_repository->paginate(25);
$filtered = false;
$filters = null;
}
return View::make('transactions.list')->with('subTitle', 'Expenses')->with(
'subTitleIcon', 'fa-long-arrow-left'
)->with('what', 'expenses');
}
return View::make('transactions.index')->with('journals', $journals)->with('filtered', $filtered)->with(
'filters', $filters
);
/**
* @return $this
*/
public function revenue()
{
return View::make('transactions.list')->with('subTitle', 'Revenue')->with(
'subTitleIcon', 'fa-long-arrow-right'
)->with('what', 'revenue');
}
/**
@@ -174,65 +255,122 @@ class TransactionController extends BaseController
*/
public function show(TransactionJournal $journal)
{
return View::make('transactions.show')->with('journal', $journal);
return View::make('transactions.show')->with('journal', $journal)->with(
'subTitle', $journal->transactionType->type . ' "' . $journal->description . '"'
);
}
/**
* @param $what
*
* @return \Illuminate\Http\RedirectResponse
* @return $this|\Illuminate\Http\RedirectResponse
* @throws FireflyException
*/
public function store($what)
{
$journal = $this->_repository->store($what, Input::all());
if ($journal->validate()) {
Session::flash('success', 'Transaction "' . $journal->description . '" saved!');
/*
* Collect data to process:
*/
$data = Input::except(['_token']);
$data['what'] = $what;
// if reminder present, deactivate it:
if (Input::get('reminder')) {
/** @var \Firefly\Storage\Reminder\ReminderRepositoryInterface $reminders */
$reminders = App::make('Firefly\Storage\Reminder\ReminderRepositoryInterface');
$reminder = $reminders->find(Input::get('reminder'));
$reminders->deactivate($reminder);
}
switch (Input::get('post_submit_action')) {
case 'store':
case 'create_another':
/*
* Try to store:
*/
$messageBag = $this->_helper->store($data);
// trigger the creation for recurring transactions.
Event::fire('journals.store',[$journal]);
/*
* Failure!
*/
if ($messageBag->count() > 0) {
Session::flash('error', 'Could not save transaction: ' . $messageBag->first());
return Redirect::route('transactions.create', [$what])->withInput()->withErrors($messageBag);
}
if (Input::get('create') == '1') {
/*
* Success!
*/
Session::flash('success', 'Transaction "' . e(Input::get('description')) . '" saved!');
/*
* Redirect to original location or back to the form.
*/
if (Input::get('post_submit_action') == 'create_another') {
return Redirect::route('transactions.create', $what)->withInput();
} else {
return Redirect::route('transactions.index.' . $what);
}
break;
case 'validate_only':
$messageBags = $this->_helper->validate($data);
Session::flash('warnings', $messageBags['warnings']);
Session::flash('successes', $messageBags['successes']);
Session::flash('errors', $messageBags['errors']);
return Redirect::route('transactions.create', [$what])->withInput();
} else {
return Redirect::route('transactions.index');
}
} else {
Session::flash('error', 'Could not save transaction: ' . $journal->errors()->first());
return Redirect::route('transactions.create', [$what])->withInput()->withErrors(
$journal->errors()
);
break;
default:
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
break;
}
}
public function transfers()
{
return View::make('transactions.list')->with('subTitle', 'Transfers')->with(
'subTitleIcon', 'fa-arrows-h'
)->with('what', 'transfers');
}
/**
* @param TransactionJournal $journal
*
* @return $this|\Illuminate\Http\RedirectResponse
* @throws FireflyException
*/
public function update(TransactionJournal $journal)
{
$journal = $this->_repository->update($journal, Input::all());
if ($journal->validate()) {
// has been saved, return to index:
Session::flash('success', 'Transaction updated!');
Event::fire('journals.update',[$journal]);
switch (Input::get('post_submit_action')) {
case 'update':
case 'return_to_edit':
$what = strtolower($journal->transactionType->type);
$messageBag = $this->_helper->update($journal, Input::all());
if ($messageBag->count() == 0) {
// has been saved, return to index:
Session::flash('success', 'Transaction updated!');
Event::fire('journals.update', [$journal]);
return Redirect::route('transactions.index');
} else {
Session::flash('error', 'Could not update transaction: ' . $journal->errors()->first());
if (Input::get('post_submit_action') == 'return_to_edit') {
return Redirect::route('transactions.edit', $journal->id)->withInput();
} else {
return Redirect::route('transactions.index.' . $what);
}
} else {
Session::flash('error', 'Could not update transaction: ' . $journal->errors()->first());
return Redirect::route('transactions.edit', $journal->id)->withInput()->withErrors($journal->errors());
return Redirect::route('transactions.edit', $journal->id)->withInput()->withErrors(
$journal->errors()
);
}
break;
case 'validate_only':
$data = Input::all();
$data['what'] = strtolower($journal->transactionType->type);
$messageBags = $this->_helper->validate($data);
Session::flash('warnings', $messageBags['warnings']);
Session::flash('successes', $messageBags['successes']);
Session::flash('errors', $messageBags['errors']);
return Redirect::route('transactions.edit', $journal->id)->withInput();
break;
default:
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
break;
}

View File

@@ -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,14 +41,12 @@ 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!');
return Redirect::route('index');
}

View File

@@ -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');

View File

@@ -18,6 +18,8 @@ class CreateImportmapsTable extends Migration {
$table->timestamps();
$table->integer('user_id')->unsigned();
$table->string('file',500);
$table->integer('totaljobs')->unsigned();
$table->integer('jobsdone')->unsigned();
// connect maps to users
$table->foreign('user_id')

View File

@@ -11,20 +11,29 @@ class AccountTypeSeeder extends Seeder
DB::table('account_types')->delete();
AccountType::create(
['type' => 'Default account', 'editable' => true]
['type' => 'Default account', 'editable' => true]
);
AccountType::create(
['type' => 'Cash account', 'editable' => false]
['type' => 'Cash account', 'editable' => false]
);
AccountType::create(
['type' => 'Initial balance account', 'editable' => false]
['type' => 'Asset account', 'editable' => true]
);
AccountType::create(
['type' => 'Beneficiary account', 'editable' => true]
['type' => 'Expense account', 'editable' => true]
);
AccountType::create(
['type' => 'Revenue account', 'editable' => true]
);
AccountType::create(
['type' => 'Initial balance account', 'editable' => false]
);
AccountType::create(
['type' => 'Beneficiary account', 'editable' => true]
);
AccountType::create(
['type' => 'Import account', 'editable' => false]
['type' => 'Import account', 'editable' => false]
);
}

View File

@@ -6,10 +6,10 @@ App::before(
function ($request) {
if (Auth::check()) {
/** @var \Firefly\Helper\Toolkit\ToolkitInterface $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface');
$toolkit->getDateRange($request);
$toolkit->getReminders();
$toolkit->getDateRange();
$toolkit->checkImportJobs();
}
}

View File

@@ -1,7 +1,4 @@
<?php
namespace Firefly\Database;
use LaravelBook\Ardent\Ardent;
@@ -73,7 +70,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);

View File

@@ -1,14 +0,0 @@
<?php
namespace Firefly\Helper;
/**
* Class MigrationException
*
* @package Firefly\Helper
*/
class MigrationException extends \Exception
{
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Firefly\Exception;
/**
* Class ValidationException
*
* @package Firefly\Exception
*/
class ValidationException extends \Exception {
}

View File

@@ -0,0 +1,327 @@
<?php
namespace Firefly\Form;
use Firefly\Exception\FireflyException;
use Illuminate\Support\MessageBag;
class Form
{
/**
* @param $name
* @param null $value
* @param array $options
* @return string
* @throws FireflyException
*/
public static function ffInteger($name, $value = null, array $options = [])
{
$options['step'] = '1';
return self::ffInput('number', $name, $value, $options);
}
public static function ffCheckbox($name, $value = 1, $checked = null, $options = [])
{
$options['checked'] = $checked ? true : null;
return self::ffInput('checkbox', $name, $value, $options);
}
public static function ffAmount($name, $value = null, array $options = [])
{
$options['step'] = 'any';
$options['min'] = '0.01';
return self::ffInput('amount', $name, $value, $options);
}
/**
* @param $name
* @param null $value
* @param array $options
* @return string
* @throws FireflyException
*/
public static function ffDate($name, $value = null, array $options = [])
{
return self::ffInput('date', $name, $value, $options);
}
/**
* @param $name
* @param null $value
* @param array $options
* @return string
* @throws FireflyException
*/
public static function ffTags($name, $value = null, array $options = [])
{
$options['data-role'] = 'tagsinput';
return self::ffInput('text', $name, $value, $options);
}
/**
* @param $name
* @param array $list
* @param null $selected
* @param array $options
* @return string
* @throws FireflyException
*/
public static function ffSelect($name, array $list = [], $selected = null, array $options = [])
{
return self::ffInput('select', $name, $selected, $options, $list);
}
/**
* @param $name
* @param null $value
* @param array $options
* @return string
* @throws FireflyException
*/
public static function ffText($name, $value = null, array $options = array())
{
return self::ffInput('text', $name, $value, $options);
}
public static function label($name)
{
$labels = [
'amount_min' => 'Amount (min)',
'amount_max' => 'Amount (max)',
'match' => 'Matches on',
'repeat_freq' => 'Repetition',
'account_from_id' => 'Account from',
'account_to_id' => 'Account to',
'account_id' => 'Asset account'
];
return isset($labels[$name]) ? $labels[$name] : str_replace('_', ' ', ucfirst($name));
}
/**
* Return buttons for update/validate/return.
*
* @param $type
* @param $name
*/
public static function ffOptionsList($type, $name)
{
$previousValue = \Input::old('post_submit_action');
$previousValue = is_null($previousValue) ? 'store' : $previousValue;
/*
* Store.
*/
$store = '';
switch ($type) {
case 'create':
$store = '<div class="form-group"><label for="default" class="col-sm-4 control-label">Store</label>';
$store .= '<div class="col-sm-8"><div class="radio"><label>';
$store .= \Form::radio('post_submit_action', 'store', $previousValue == 'store');
$store .= 'Store ' . $name . '</label></div></div></div>';
break;
case 'update':
$store = '<div class="form-group"><label for="default" class="col-sm-4 control-label">Store</label>';
$store .= '<div class="col-sm-8"><div class="radio"><label>';
$store .= \Form::radio('post_submit_action', 'update', $previousValue == 'store');
$store .= 'Update ' . $name . '</label></div></div></div>';
break;
default:
throw new FireflyException('Cannot create ffOptionsList for option (store) ' . $type);
break;
}
/*
* validate is always the same:
*/
$validate = '<div class="form-group"><label for="validate_only" class="col-sm-4 control-label">Validate only';
$validate .= '</label><div class="col-sm-8"><div class="radio"><label>';
$validate .= \Form::radio('post_submit_action', 'validate_only', $previousValue == 'validate_only');
$validate .= 'Only validate, do not save</label></div></div></div>';
/*
* Store & return:
*/
switch ($type) {
case 'create':
$return = '<div class="form-group"><label for="return_to_form" class="col-sm-4 control-label">';
$return .= 'Return here</label><div class="col-sm-8"><div class="radio"><label>';
$return .= \Form::radio('post_submit_action','create_another', $previousValue == 'create_another');
$return .= 'After storing, return here to create another one.</label></div></div></div>';
break;
case 'update':
$return = '<div class="form-group"><label for="return_to_edit" class="col-sm-4 control-label">';
$return .= 'Return here</label><div class="col-sm-8"><div class="radio"><label>';
$return .= \Form::radio('post_submit_action','return_to_edit', $previousValue == 'return_to_edit');
$return .= 'After updating, return here.</label></div></div></div>';
break;
default:
throw new FireflyException('Cannot create ffOptionsList for option (store+return) ' . $type);
break;
}
return $store.$validate.$return;
}
/**
* @param $type
* @param $name
* @param null $value
* @param array $options
* @param array $list
* @return string
* @throws FireflyException
*/
public static function ffInput($type, $name, $value = null, array $options = array(), $list = [])
{
/*
* add some defaults to this method:
*/
$options['class'] = 'form-control';
$options['id'] = 'ffInput_' . $name;
$options['autocomplete'] = 'off';
$label = self::label($name);
/*
* Make label and placeholder look nice.
*/
$options['placeholder'] = ucfirst($name);
/*
* Get prefilled value:
*/
if(\Session::has('prefilled')) {
$prefilled = \Session::get('prefilled');
$value = isset($prefilled[$name]) && is_null($value) ? $prefilled[$name] : $value;
}
/*
* Get the value.
*/
if (!is_null(\Input::old($name))) {
/*
* Old value overrules $value.
*/
$value = \Input::old($name);
}
/*
* Get errors, warnings and successes from session:
*/
/** @var MessageBag $errors */
$errors = \Session::get('errors');
/** @var MessageBag $warnings */
$warnings = \Session::get('warnings');
/** @var MessageBag $successes */
$successes = \Session::get('successes');
/*
* If errors, add some more classes.
*/
switch (true) {
case (!is_null($errors) && $errors->has($name)):
$classes = 'form-group has-error has-feedback';
break;
case (!is_null($warnings) && $warnings->has($name)):
$classes = 'form-group has-warning has-feedback';
break;
case (!is_null($successes) && $successes->has($name)):
$classes = 'form-group has-success has-feedback';
break;
default:
$classes = 'form-group';
break;
}
/*
* Add some HTML.
*/
$html = '<div class="' . $classes . '">';
$html .= '<label for="' . $options['id'] . '" class="col-sm-4 control-label">' . $label . '</label>';
$html .= '<div class="col-sm-8">';
/*
* Switch input type:
*/
unset($options['label']);
switch ($type) {
case 'text':
$html .= \Form::input('text', $name, $value, $options);
break;
case 'amount':
$html .= '<div class="input-group"><div class="input-group-addon">&euro;</div>';
$html .= \Form::input('number', $name, $value, $options);
$html .= '</div>';
break;
case 'number':
$html .= \Form::input('number', $name, $value, $options);
break;
case 'checkbox':
$checked = $options['checked'];
unset($options['checked'], $options['placeholder'], $options['autocomplete'], $options['class']);
$html .= '<div class="checkbox"><label>';
$html .= \Form::checkbox($name, $value, $checked, $options);
$html .= '</label></div>';
break;
case 'date':
$html .= \Form::input('date', $name, $value, $options);
break;
case 'select':
$html .= \Form::select($name, $list, $value, $options);
break;
default:
throw new FireflyException('Cannot handle type "' . $type . '" in FFFormBuilder.');
break;
}
/*
* If errors, respond to them:
*/
if (!is_null($errors)) {
if ($errors->has($name)) {
$html .= '<span class="glyphicon glyphicon-remove form-control-feedback"></span>';
$html .= '<p class="text-danger">' . e($errors->first($name)) . '</p>';
}
}
unset($errors);
/*
* If warnings, respond to them:
*/
if (!is_null($warnings)) {
if ($warnings->has($name)) {
$html .= '<span class="glyphicon glyphicon-warning-sign form-control-feedback"></span>';
$html .= '<p class="text-warning">' . e($warnings->first($name)) . '</p>';
}
}
unset($warnings);
/*
* If successes, respond to them:
*/
if (!is_null($successes)) {
if ($successes->has($name)) {
$html .= '<span class="glyphicon glyphicon-ok form-control-feedback"></span>';
$html .= '<p class="text-success">' . e($successes->first($name)) . '</p>';
}
}
unset($successes);
$html .= '</div>';
$html .= '</div>';
return $html;
}
}

View File

@@ -17,7 +17,7 @@ class Account implements AccountInterface
public function openingBalanceTransaction(\Account $account)
{
return \TransactionJournal::withRelevantData()
->account($account)
->accountIs($account)
->leftJoin('transaction_types', 'transaction_types.id', '=',
'transaction_journals.transaction_type_id')
->where('transaction_types.type', 'Opening balance')
@@ -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)
@@ -51,7 +54,7 @@ class Account implements AccountInterface
// build a query:
$query = \TransactionJournal::withRelevantData()
->defaultSorting()
->account($account)
->accountIs($account)
->after($start)
->before($end);
// filter some:
@@ -107,16 +110,16 @@ class Account implements AccountInterface
// statistics (transactions)
$trIn = floatval(\Transaction::before($end)->after($start)->account($account)->moreThan(0)
$trIn = floatval(\Transaction::before($end)->after($start)->accountIs($account)->moreThan(0)
->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount'));
$trOut = floatval(\Transaction::before($end)->after($start)->account($account)->lessThan(0)
$trOut = floatval(\Transaction::before($end)->after($start)->accountIs($account)->lessThan(0)
->transactionTypes(['Deposit', 'Withdrawal'])->sum('transactions.amount'));
$trDiff = $trIn + $trOut;
// statistics (transfers)
$trfIn = floatval(\Transaction::before($end)->after($start)->account($account)->moreThan(0)
$trfIn = floatval(\Transaction::before($end)->after($start)->accountIs($account)->moreThan(0)
->transactionTypes(['Transfer'])->sum('transactions.amount'));
$trfOut = floatval(\Transaction::before($end)->after($start)->account($account)->lessThan(0)
$trfOut = floatval(\Transaction::before($end)->after($start)->accountIs($account)->lessThan(0)
->transactionTypes(['Transfer'])->sum('transactions.amount'));
$trfDiff = $trfIn + $trfOut;

View File

@@ -34,7 +34,7 @@ class Budget implements BudgetInterface
/** @var \LimitRepetition $repetition */
foreach ($limit->limitrepetitions as $repetition) {
$repetition->left = $repetition->left();
$repetition->left = $repetition->leftInRepetition();
$periodOrder = $repetition->periodOrder();
$period = $repetition->periodShow();
if (!isset($return[$periodOrder])) {

View File

@@ -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();
@@ -95,119 +102,6 @@ class Chart implements ChartInterface
}
/**
* @param Carbon $start
*
* @return array
*/
public function budgets(Carbon $start)
{
// grab all budgets in the time period, like the index does:
// get the budgets for this period:
$data = [];
$budgets = \Auth::user()->budgets()->with(
['limits' => function ($q) {
$q->orderBy('limits.startdate', 'ASC');
}, 'limits.limitrepetitions' => function ($q) use ($start) {
$q->orderBy('limit_repetitions.startdate', 'ASC');
$q->where('startdate', $start->format('Y-m-d'));
}]
)->orderBy('name', 'ASC')->get();
$limitInPeriod = '';
$spentInPeriod = '';
/** @var \Budget $budget */
foreach ($budgets as $budget) {
$budget->count = 0;
foreach ($budget->limits as $limit) {
/** @var $rep \LimitRepetition */
foreach ($limit->limitrepetitions as $index => $rep) {
if ($index == 0) {
$limitInPeriod = 'Envelope for ' . $rep->periodShow();
$spentInPeriod = 'Spent in ' . $rep->periodShow();
}
$rep->left = $rep->left();
// overspent:
if ($rep->left < 0) {
$rep->spent = ($rep->left * -1) + $rep->amount;
$rep->overspent = $rep->left * -1;
$total = $rep->spent + $rep->overspent;
$rep->spent_pct = round(($rep->spent / $total) * 100);
$rep->overspent_pct = 100 - $rep->spent_pct;
} else {
$rep->spent = $rep->amount - $rep->left;
$rep->spent_pct = round(($rep->spent / $rep->amount) * 100);
$rep->left_pct = 100 - $rep->spent_pct;
}
}
$budget->count += count($limit->limitrepetitions);
}
if ($budget->count == 0) {
// get expenses in period until today, starting at $start.
$end = \Session::get('end');
$expenses = $budget->transactionjournals()->after($start)->before($end)
->transactionTypes(
['Withdrawal']
)->get();
$budget->spentInPeriod = 0;
/** @var \TransactionJournal $expense */
foreach ($expenses as $expense) {
$transaction = $expense->transactions[1];
if (!is_null($transaction)) {
$budget->spentInPeriod += floatval($transaction->amount);
}
}
}
}
$data['series'] = [
[
'name' => $limitInPeriod,
'data' => []
],
[
'name' => $spentInPeriod,
'data' => []
],
];
foreach ($budgets as $budget) {
if ($budget->count > 0) {
$data['labels'][] = $budget->name;
foreach ($budget->limits as $limit) {
foreach ($limit->limitrepetitions as $rep) {
//0: envelope for period:
$amount = floatval($rep->amount);
$spent = $rep->spent;
$color = $spent > $amount ? '#FF0000' : null;
$data['series'][0]['data'][] = ['y' => $amount, 'id' => 'amount-' . $rep->id];
$data['series'][1]['data'][] = ['y' => $rep->spent, 'color' => $color,
'id' => 'spent-' . $rep->id];
}
}
} else {
// add for "empty" budget:
if ($budget->spentInPeriod > 0) {
$data['labels'][] = $budget->name;
$data['series'][0]['data'][] = ['y' => null, 'id' => 'amount-norep-' . $budget->id];
$data['series'][1]['data'][] = ['y' => $budget->spentInPeriod,
'id' => 'spent-norep-' . $budget->id];
}
}
}
return $data;
}
/**
* @param Carbon $start
* @param Carbon $end
@@ -550,9 +444,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 +455,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')
);
}
}

View File

@@ -30,13 +30,6 @@ interface ChartInterface
*/
public function categories(Carbon $start, Carbon $end);
/**
* @param Carbon $start
*
* @return mixed
*/
public function budgets(Carbon $start);
/**
* @param \Account $account
* @param Carbon $date
@@ -98,9 +91,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

View File

@@ -0,0 +1,398 @@
<?php
/**
* Created by PhpStorm.
* User: sander
* Date: 27/09/14
* Time: 07:39
*/
namespace Firefly\Helper\Controllers;
use LaravelBook\Ardent\Builder;
/**
* Class Json
*
* @package Firefly\Helper\Controllers
*/
class Json implements JsonInterface
{
/**
* Grabs all the parameters entered by the DataTables JQuery plugin and creates
* a nice array to be used by the other methods. It's also cleaning up and what-not.
*
* @return array
*/
public function dataTableParameters()
{
/*
* Process all parameters!
*/
if (intval(\Input::get('length')) < 0) {
$length = 10000; // we get them all if no length is defined.
} else {
$length = intval(\Input::get('length'));
}
$parameters = [
'start' => intval(\Input::get('start')),
'length' => $length,
'draw' => intval(\Input::get('draw')),
];
/*
* Columns:
*/
if (!is_null(\Input::get('columns')) && is_array(\Input::get('columns'))) {
foreach (\Input::get('columns') as $column) {
$parameters['columns'][] = [
'data' => $column['data'],
'name' => $column['name'],
'searchable' => $column['searchable'] == 'true' ? true : false,
'orderable' => $column['orderable'] == 'true' ? true : false,
'search' => [
'value' => $column['search']['value'],
'regex' => $column['search']['regex'] == 'true' ? true : false,
]
];
}
}
/*
* Sorting.
*/
$parameters['orderOnAccount'] = false;
if (!is_null(\Input::get('order')) && is_array(\Input::get('order'))) {
foreach (\Input::get('order') as $order) {
$columnIndex = intval($order['column']);
$columnName = $parameters['columns'][$columnIndex]['name'];
$parameters['order'][] = [
'name' => $columnName,
'dir' => strtoupper($order['dir'])
];
if ($columnName == 'to' || $columnName == 'from') {
$parameters['orderOnAccount'] = true;
}
}
}
/*
* Search parameters:
*/
$parameters['search'] = [
'value' => '',
'regex' => false
];
if (!is_null(\Input::get('search')) && is_array(\Input::get('search'))) {
$search = \Input::get('search');
$parameters['search'] = [
'value' => $search['value'],
'regex' => $search['regex'] == 'true' ? true : false
];
}
return $parameters;
}
/**
* Do some sorting, counting and ordering on the query and return a nicely formatted array
* that can be used by the DataTables JQuery plugin.
*
* @param array $parameters
* @param Builder $query
*
* @return array
*/
public function journalDataset(array $parameters, Builder $query)
{
/*
* Count query:
*/
$count = $query->count();
/*
* Update the selection:
*/
$query->take($parameters['length']);
if ($parameters['start'] > 0) {
$query->skip($parameters['start']);
}
/*
* Input search parameters:
*/
$filtered = $count;
if (strlen($parameters['search']['value']) > 0) {
$query->where('transaction_journals.description', 'LIKE', '%' . e($parameters['search']['value']) . '%');
$filtered = $query->count();
}
/*
* Build return array:
*/
$data = [
'draw' => $parameters['draw'],
'recordsTotal' => $count,
'recordsFiltered' => $filtered,
'data' => [],
];
/*
* Get paginated result set:
*/
if ($parameters['orderOnAccount'] === true) {
/** @var Collection $set */
$set = $query->get(
[
'transaction_journals.*',
't1.amount',
't1.account_id AS from_id',
'a1.name AS from',
't2.account_id AS to_id',
'a2.name AS to',
]
);
} else {
/** @var Collection $set */
$set = $query->get(
[
'transaction_journals.*',
'transactions.amount',
]
);
}
/*
* Loop set and create entries to return.
*/
/** @var \TransactionJournal $entry */
foreach ($set as $entry) {
$from = $entry->transactions[0]->account;
$to = $entry->transactions[1]->account;
$budget = $entry->budgets()->first();
$category = $entry->categories()->first();
$recurring = $entry->recurringTransaction()->first();
$arr = [
'date' => $entry->date->format('j F Y'),
'description' => [
'description' => $entry->description,
'url' => route('transactions.show', $entry->id)
],
'amount' => floatval($entry->amount),
'from' => ['name' => $from->name, 'url' => route('accounts.show', $from->id)],
'to' => ['name' => $to->name, 'url' => route('accounts.show', $to->id)],
'components' => [
'budget_id' => 0,
'budget_url' => '',
'budget_name' => '',
'category_id' => 0,
'category_url' => '',
'category_name' => ''
],
'id' => [
'edit' => route('transactions.edit', $entry->id),
'delete' => route('transactions.delete', $entry->id)
]
];
if ($budget) {
$arr['components']['budget_id'] = $budget->id;
$arr['components']['budget_name'] = $budget->name;
$arr['components']['budget_url'] = route('budgets.show', $budget->id);
}
if ($category) {
$arr['components']['category_id'] = $category->id;
$arr['components']['category_name'] = $category->name;
$arr['components']['category_url'] = route('categories.show', $category->id);
}
if ($recurring) {
$arr['components']['recurring_id'] = $recurring->id;
$arr['components']['recurring_name'] = e($recurring->name);
$arr['components']['recurring_url'] = route('recurring.show', $recurring->id);
}
$data['data'][] = $arr;
}
return $data;
}
/**
* Builds most of the query required to grab transaction journals from the database.
* This is useful because all three pages showing different kinds of transactions use
* the exact same query with only slight differences.
*
* @param array $parameters
*
* @return Builder
*/
public function journalQuery(array $parameters)
{
/*
* We need the following vars to fine tune the query:
*/
if ($parameters['amount'] == 'negative') {
$operator = '<';
$operatorNegated = '>';
$function = 'lessThan';
} else {
$operator = '>';
$operatorNegated = '<';
$function = 'moreThan';
}
/*
* Build query:
*/
$query = \TransactionJournal::transactionTypes($parameters['transactionTypes'])->withRelevantData();
$query->where('user_id', \Auth::user()->id);
$query->where('completed', 1);
/*
* This is complex. Join `transactions` twice, once for the "to" account and once for the
* "from" account. Then get the amount from one of these (depends on type).
*
* Only need to do this when there's a sort order for "from" or "to".
*
* Also need the table prefix for this to work.
*/
if ($parameters['orderOnAccount'] === true) {
$connection = \Config::get('database.default');
$prefix = \Config::get('database.connections.' . $connection . '.prefix');
// left join first table for "from" account:
$query->leftJoin(
'transactions AS ' . $prefix . 't1', function ($join) use ($operator) {
$join->on('t1.transaction_journal_id', '=', 'transaction_journals.id')
->on('t1.amount', $operator, \DB::Raw(0));
}
);
// left join second table for "to" account:
$query->leftJoin(
'transactions AS ' . $prefix . 't2', function ($join) use ($operatorNegated) {
$join->on('t2.transaction_journal_id', '=', 'transaction_journals.id')
->on('t2.amount', $operatorNegated, \DB::Raw(0));
}
);
// also join accounts twice to get the account's name, which we need for sorting.
$query->leftJoin('accounts as ' . $prefix . 'a1', 'a1.id', '=', 't1.account_id');
$query->leftJoin('accounts as ' . $prefix . 'a2', 'a2.id', '=', 't2.account_id');
} else {
// less complex
$query->$function(0);
}
/*
* Add sort parameters to query:
*/
if (isset($parameters['order']) && count($parameters['order']) > 0) {
foreach ($parameters['order'] as $order) {
$query->orderBy($order['name'], $order['dir']);
}
} else {
$query->defaultSorting();
}
return $query;
}
/**
* Do some sorting, counting and ordering on the query and return a nicely formatted array
* that can be used by the DataTables JQuery plugin.
*
* @param array $parameters
* @param Builder $query
*
* @return array
*/
public function recurringTransactionsDataset(array $parameters, Builder $query)
{
/*
* Count query:
*/
$count = $query->count();
/*
* Update the selection:
*/
$query->take($parameters['length']);
if ($parameters['start'] > 0) {
$query->skip($parameters['start']);
}
/*
* Input search parameters:
*/
$filtered = $count;
if (strlen($parameters['search']['value']) > 0) {
$query->where('recurring_transactions.description', 'LIKE', '%' . e($parameters['search']['value']) . '%');
$filtered = $query->count();
}
/*
* Build return array:
*/
$data = [
'draw' => $parameters['draw'],
'recordsTotal' => $count,
'recordsFiltered' => $filtered,
'data' => [],
];
/*
* Get paginated result set:
*/
/** @var Collection $set */
$set = $query->get(
[
'recurring_transactions.*',
]
);
/*
* Loop set and create entries to return.
*/
foreach ($set as $entry) {
$data['data'][] = [
'name' => ['name' => $entry->name, 'url' => route('recurring.show', $entry->id)],
'match' => explode(' ', $entry->match),
'amount_max' => floatval($entry->amount_max),
'amount_min' => floatval($entry->amount_min),
'date' => $entry->date->format('j F Y'),
'active' => intval($entry->active),
'automatch' => intval($entry->automatch),
'repeat_freq' => $entry->repeat_freq,
'id' => [
'edit' => route('recurring.edit', $entry->id),
'delete' => route('recurring.delete', $entry->id)
]
];
}
return $data;
}
/**
* Create a query that will pick up all recurring transactions from the database.
*
* @param array $parameters
*
* @return Builder
*/
public function recurringTransactionsQuery(array $parameters)
{
$query = \RecurringTransaction::where('user_id', \Auth::user()->id);
if (isset($parameters['order']) && count($parameters['order']) > 0) {
foreach ($parameters['order'] as $order) {
$query->orderBy($order['name'], $order['dir']);
}
} else {
$query->orderBy('name', 'ASC');
}
return $query;
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Firefly\Helper\Controllers;
use LaravelBook\Ardent\Builder;
/**
* Interface JsonInterface
*
* @package Firefly\Helper\Controllers
*/
interface JsonInterface
{
/**
* Grabs all the parameters entered by the DataTables JQuery plugin and creates
* a nice array to be used by the other methods. It's also cleaning up and what-not.
*
* @return array
*/
public function dataTableParameters();
/**
* Do some sorting, counting and ordering on the query and return a nicely formatted array
* that can be used by the DataTables JQuery plugin.
*
* @param array $parameters
* @param Builder $query
*
* @return array
*/
public function journalDataset(array $parameters, Builder $query);
/**
* Builds most of the query required to grab transaction journals from the database.
* This is useful because all three pages showing different kinds of transactions use
* the exact same query with only slight differences.
*
* @param array $parameters
*
* @return Builder
*/
public function journalQuery(array $parameters);
/**
* Do some sorting, counting and ordering on the query and return a nicely formatted array
* that can be used by the DataTables JQuery plugin.
*
* @param array $parameters
* @param Builder $query
*
* @return array
*/
public function recurringTransactionsDataset(array $parameters, Builder $query);
/**
* Create a query that will pick up all recurring transactions from the database.
*
* @param array $parameters
*
* @return Builder
*/
public function recurringTransactionsQuery(array $parameters);
}

View File

@@ -0,0 +1,120 @@
<?php
namespace Firefly\Helper\Controllers;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\MessageBag;
class Recurring implements RecurringInterface
{
/**
* Returns messages about the validation.
*
* @param array $data
*
* @return array
*/
public function validate(array $data)
{
$errors = new MessageBag;
$warnings = new MessageBag;
$successes = new MessageBag;
/*
* Name:
*/
if (strlen($data['name']) == 0) {
$errors->add('name', 'The name should not be this short.');
}
if (strlen($data['name']) > 250) {
$errors->add('name', 'The name should not be this long.');
}
if (! isset($data['id'])) {
$count = \Auth::user()->recurringtransactions()->whereName($data['name'])->count();
} else {
$count = \Auth::user()->recurringtransactions()->whereName($data['name'])->where('id', '!=', $data['id'])->count();
}
if ($count > 0) {
$errors->add('name', 'A recurring transaction with this name already exists.');
}
if (count($errors->get('name')) == 0) {
$successes->add('name', 'OK!');
}
/*
* Match
*/
if (count(explode(',', $data['match'])) > 10) {
$warnings->add('match', 'This many matches is pretty pointless');
}
if (strlen($data['match']) == 0) {
$errors->add('match', 'Cannot match on nothing.');
}
if (count($errors->get('match')) == 0) {
$successes->add('match', 'OK!');
}
/*
* Amount
*/
if (floatval($data['amount_max']) == 0 && floatval($data['amount_min']) == 0) {
$errors->add('amount_min', 'Amount max and min cannot both be zero.');
$errors->add('amount_max', 'Amount max and min cannot both be zero.');
}
if (floatval($data['amount_max']) < floatval($data['amount_min'])) {
$errors->add('amount_max', 'Amount max must be more than amount min.');
}
if (floatval($data['amount_min']) > floatval($data['amount_max'])) {
$errors->add('amount_max', 'Amount min must be less than amount max.');
}
if (count($errors->get('amount_min')) == 0) {
$successes->add('amount_min', 'OK!');
}
if (count($errors->get('amount_max')) == 0) {
$successes->add('amount_max', 'OK!');
}
/*
* Date
*/
try {
$date = new Carbon($data['date']);
} catch (Exception $e) {
$errors->add('date', 'The date entered was invalid');
}
if (strlen($data['date']) == 0) {
$errors->add('date', 'The date entered was invalid');
}
if (!$errors->has('date')) {
$successes->add('date', 'OK!');
}
$successes->add('active', 'OK!');
$successes->add('automatch', 'OK!');
if (intval($data['skip']) < 0) {
$errors->add('skip', 'Cannot be below zero.');
} else if (intval($data['skip']) > 31) {
$errors->add('skip', 'Cannot be above 31.');
}
if (count($errors->get('skip')) == 0) {
$successes->add('skip', 'OK!');
}
$set = \Config::get('firefly.budget_periods');
if (!in_array($data['repeat_freq'], $set)) {
$errors->add('repeat_freq', 'Invalid value.');
}
if (count($errors->get('repeat_freq')) == 0) {
$successes->add('repeat_freq', 'OK!');
}
return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes];
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Firefly\Helper\Controllers;
interface RecurringInterface {
/**
* Returns messages about the validation.
*
* @param array $data
*
* @return array
*/
public function validate(array $data);
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Firefly\Helper\Controllers;
use Illuminate\Support\Collection;
/**
* Class Search
*
* @package Firefly\Helper\Controllers
*/
class Search implements SearchInterface
{
/**
* @param array $words
*
* @return Collection
*/
public function searchTransactions(array $words)
{
return \Auth::user()->transactionjournals()->withRelevantData()->where(
function ($q) use ($words) {
foreach ($words as $word) {
$q->orWhere('description', 'LIKE', '%' . e($word) . '%');
}
}
)->get();
}
/**
* @param array $words
*
* @return Collection
*/
public function searchAccounts(array $words)
{
return \Auth::user()->accounts()->with('accounttype')->where(
function ($q) use ($words) {
foreach ($words as $word) {
$q->orWhere('name', 'LIKE', '%' . e($word) . '%');
}
}
)->get();
}
/**
* @param array $words
*
* @return Collection
*/
public function searchCategories(array $words)
{
/** @var Collection $set */
$set = \Auth::user()->categories()->get();
$newSet = $set->filter(
function (\Category $c) use ($words) {
$found = 0;
foreach ($words as $word) {
if (!(strpos(strtolower($c->name), strtolower($word)) === false)) {
$found++;
}
}
return $found > 0;
}
);
return $newSet;
}
/**
* @param array $words
*
* @return Collection
*/
public function searchBudgets(array $words)
{
/** @var Collection $set */
$set = \Auth::user()->budgets()->get();
$newSet = $set->filter(
function (\Budget $b) use ($words) {
$found = 0;
foreach ($words as $word) {
if (!(strpos(strtolower($b->name), strtolower($word)) === false)) {
$found++;
}
}
return $found > 0;
}
);
return $newSet;
}
/**
* @param array $words
*
* @return Collection
*/
public function searchTags(array $words)
{
return new Collection;
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Firefly\Helper\Controllers;
/**
* Interface SearchInterface
*
* @package Firefly\Helper\Controllers
*/
interface SearchInterface
{
/**
* @param array $words
*/
public function searchTransactions(array $words);
/**
* @param array $words
*/
public function searchAccounts(array $words);
/**
* @param array $words
*/
public function searchCategories(array $words);
/**
* @param array $words
*/
public function searchBudgets(array $words);
/**
* @param array $words
*/
public function searchTags(array $words);
}

View File

@@ -0,0 +1,485 @@
<?php
namespace Firefly\Helper\Controllers;
use Carbon\Carbon;
use Exception;
use Firefly\Exception\FireflyException;
use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
use Firefly\Storage\Category\CategoryRepositoryInterface as CRI;
use Firefly\Storage\Piggybank\PiggybankRepositoryInterface as PRI;
use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as TJRI;
use Illuminate\Support\MessageBag;
/**
* Class Transaction
*
* @package Firefly\Helper\Controllers
*/
class Transaction implements TransactionInterface
{
protected $_user = null;
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $_journals */
protected $_journals;
/** @var \Firefly\Storage\Category\CategoryRepositoryInterface $_categories */
protected $_categories;
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $_budgets */
protected $_budgets;
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $_piggybanks */
protected $_piggybanks;
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $_accounts */
protected $_accounts;
/**
* @param TJRI $journals
* @param CRI $categories
* @param BRI $budgets
* @param PRI $piggybanks
* @param ARI $accounts
*/
public function __construct(TJRI $journals, CRI $categories, BRI $budgets, PRI $piggybanks, ARI $accounts)
{
$this->_journals = $journals;
$this->_categories = $categories;
$this->_budgets = $budgets;
$this->_piggybanks = $piggybanks;
$this->_accounts = $accounts;
$this->overruleUser(\Auth::user());
}
/**
* @param \User $user
*
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
$this->_journals->overruleUser($user);
$this->_categories->overruleUser($user);
$this->_budgets->overruleUser($user);
$this->_piggybanks->overruleUser($user);
$this->_accounts->overruleUser($user);
return true;
}
/**
* @param \TransactionJournal $journal
* @param array $data
*
* @return MessageBag|\TransactionJournal
*/
public function update(\TransactionJournal $journal, array $data)
{
/*
* Update the journal using the repository.
*/
$journal = $this->_journals->update($journal, $data);
/*
* If invalid, return the message bag:
*/
if (!$journal->validate()) {
return $journal->errors();
}
/*
* find budget using repository
*/
if (isset($data['budget_id'])) {
$budget = $this->_budgets->find($data['budget_id']);
}
/*
* find category using repository
*/
$category = $this->_categories->firstOrCreate($data['category']);
/*
* Find piggy bank using repository:
*/
$piggybank = null;
if (isset($data['piggybank_id'])) {
$piggybank = $this->_piggybanks->find($data['piggybank_id']);
}
/*
* save accounts using repositories
* this depends on the kind of transaction and i've yet to fix this.
*/
if (isset($data['account_id'])) {
$from = $this->_accounts->findAssetAccountById($data['account_id']);
}
if (isset($data['expense_account'])) {
$to = $this->_accounts->findExpenseAccountByName($data['expense_account']);
}
if (isset($data['revenue_account'])) {
$from = $this->_accounts->findRevenueAccountByName($data['revenue_account']);
$to = $this->_accounts->findAssetAccountById($data['account_id']);
}
if (isset($data['account_from_id'])) {
$from = $this->_accounts->findAssetAccountById($data['account_from_id']);
}
if (isset($data['account_to_id'])) {
$to = $this->_accounts->findAssetAccountById($data['account_to_id']);
}
/*
* Add a custom error when they are the same.
*/
if ($to->id == $from->id) {
$bag = new MessageBag;
$bag->add('account_from_id', 'The account from cannot be the same as the account to.');
return $bag;
}
/*
* Check if the transactions need new data:
*/
$transactions = $journal->transactions()->orderBy('amount', 'ASC')->get();
/** @var \Transaction $transaction */
foreach ($transactions as $index => $transaction) {
switch (true) {
case ($index == 0): // FROM account
$transaction->account()->associate($from);
$transaction->amount = floatval($data['amount']) * -1;
break;
case ($index == 1): // TO account.
$transaction->account()->associate($to);
$transaction->amount = floatval($data['amount']);
break;
}
$transaction->save();
// either way, try to attach the piggy bank:
if (!is_null($piggybank)) {
if ($piggybank->account_id == $transaction->account_id) {
$transaction->piggybank()->associate($piggybank);
}
}
}
/*
* Connect budget and category:
*/
$budgetids = !isset($budget) || (isset($budget) && is_null($budget)) ? [] : [$budget->id];
$catids = is_null($category) ? [] : [$category->id];
$components = array_merge($budgetids,$catids);
$journal->components()->sync($components);
$journal->save();
if (isset($data['return_journal']) && $data['return_journal'] == true) {
return $journal;
}
return $journal->errors();
}
/**
* Returns messages about the validation.
*
* @param array $data
* @return array
* @throws FireflyException
*/
public function validate(array $data)
{
$errors = new MessageBag;
$warnings = new MessageBag;
$successes = new MessageBag;
/*
* Description:
*/
if (strlen($data['description']) == 0) {
$errors->add('description', 'The description should not be this short.');
}
if (strlen($data['description']) > 250) {
$errors->add('description', 'The description should not be this long.');
}
/*
* Amount
*/
if (floatval($data['amount']) <= 0) {
$errors->add('amount', 'The amount cannot be zero or less than zero.');
}
if (floatval($data['amount']) > 10000) {
$warnings->add('amount', 'OK, but that\'s a lot of money dude.');
}
/*
* Date
*/
try {
$date = new Carbon($data['date']);
} catch (Exception $e) {
$errors->add('date', 'The date entered was invalid');
}
if (strlen($data['date']) == 0) {
$errors->add('date', 'The date entered was invalid');
}
if (!$errors->has('date')) {
$successes->add('date', 'OK!');
}
/*
* Category
*/
$category = $this->_categories->findByName($data['category']);
if (strlen($data['category']) == 0) {
$warnings->add('category', 'No category will be created.');
} else {
if (is_null($category)) {
$warnings->add('category', 'Will have to be created.');
} else {
$successes->add('category', 'OK!');
}
}
switch ($data['what']) {
default:
throw new FireflyException('Cannot validate a ' . $data['what']);
break;
case 'deposit':
/*
* Tests for deposit
*/
// asset account
$accountId = isset($data['account_id']) ? intval($data['account_id']) : 0;
$account = $this->_accounts->find($accountId);
if (is_null($account)) {
$errors->add('account_id', 'Cannot find this asset account.');
} else {
$successes->add('account_id', 'OK!');
}
// revenue account:
if (strlen($data['revenue_account']) == 0) {
$warnings->add('revenue_account', 'Revenue account will be "cash".');
} else {
$exp = $this->_accounts->findRevenueAccountByName($data['revenue_account'], false);
if (is_null($exp)) {
$warnings->add('revenue_account', 'Expense account will be created.');
} else {
$successes->add('revenue_account', 'OK!');
}
}
break;
case 'transfer':
// account from
$accountId = isset($data['account_from_id']) ? intval($data['account_from_id']) : 0;
$account = $this->_accounts->find($accountId);
if (is_null($account)) {
$errors->add('account_from_id', 'Cannot find this asset account.');
} else {
$successes->add('account_from_id', 'OK!');
}
unset($accountId);
// account to
$accountId = isset($data['account_to_id']) ? intval($data['account_to_id']) : 0;
$account = $this->_accounts->find($accountId);
if (is_null($account)) {
$errors->add('account_to_id', 'Cannot find this asset account.');
} else {
$successes->add('account_to_id', 'OK!');
}
unset($accountId);
// piggy bank
$piggybankId = isset($data['piggybank_id']) ? intval($data['piggybank_id']) : 0;
$piggybank = $this->_piggybanks->find($piggybankId);
if (is_null($piggybank)) {
$warnings->add('piggybank_id', 'No piggy bank will be modified.');
} else {
$successes->add('piggybank_id', 'OK!');
}
break;
case 'withdrawal':
/*
* Tests for withdrawal
*/
// asset account
$accountId = isset($data['account_id']) ? intval($data['account_id']) : 0;
$account = $this->_accounts->find($accountId);
if (is_null($account)) {
$errors->add('account_id', 'Cannot find this asset account.');
} else {
$successes->add('account_id', 'OK!');
}
// expense account
if (strlen($data['expense_account']) == 0) {
$warnings->add('expense_account', 'Expense account will be "cash".');
} else {
$exp = $this->_accounts->findExpenseAccountByName($data['expense_account'], false);
if (is_null($exp)) {
$warnings->add('expense_account', 'Expense account will be created.');
} else {
$successes->add('expense_account', 'OK!');
}
}
// budget
if (!isset($data['budget_id']) || (isset($data['budget_id']) && intval($data['budget_id']) == 0)) {
$warnings->add('budget_id', 'No budget selected.');
} else {
$budget = $this->_budgets->find(intval($data['budget_id']));
if (is_null($budget)) {
$errors->add('budget_id', 'This budget does not exist');
} else {
$successes->add('budget_id', 'OK!');
}
}
break;
}
if (count($errors->get('description')) == 0) {
$successes->add('description', 'OK!');
}
if (count($errors->get('amount')) == 0) {
$successes->add('amount', 'OK!');
}
return ['errors' => $errors, 'warnings' => $warnings, 'successes' => $successes];
/*
* Tests for deposit
*/
/*
* Tests for transfer
*/
}
/**
* Store a full transaction journal and associated stuff
*
* @param array $data
*
* @return MessageBag|\TransactionJournal
*
* @SuppressWarnings(PHPMD.ShortVariable)
*/
public function store(array $data)
{
/*
* save journal using repository
*/
$journal = $this->_journals->store($data);
/*
* If invalid, return the message bag:
*/
if (!$journal->validate()) {
return $journal->errors();
}
/*
* find budget using repository
*/
if (isset($data['budget_id'])) {
$budget = $this->_budgets->find($data['budget_id']);
}
/*
* find category using repository
*/
$category = $this->_categories->firstOrCreate($data['category']);
/*
* Find piggy bank using repository:
*/
$piggybank = null;
if (isset($data['piggybank_id'])) {
$piggybank = $this->_piggybanks->find($data['piggybank_id']);
}
/*
* save accounts using repositories
* this depends on the kind of transaction and i've yet to fix this.
*/
if (isset($data['account_id'])) {
$from = $this->_accounts->findAssetAccountById($data['account_id']);
}
if (isset($data['expense_account'])) {
$to = $this->_accounts->findExpenseAccountByName($data['expense_account']);
}
if (isset($data['revenue_account'])) {
$from = $this->_accounts->findRevenueAccountByName($data['revenue_account']);
$to = $this->_accounts->findAssetAccountById($data['account_id']);
}
if (isset($data['account_from_id'])) {
$from = $this->_accounts->findAssetAccountById($data['account_from_id']);
}
if (isset($data['account_to_id'])) {
$to = $this->_accounts->findAssetAccountById($data['account_to_id']);
}
/*
* Add a custom error when they are the same.
*/
if ($to->id == $from->id) {
$bag = new MessageBag;
$bag->add('account_from_id', 'The account from cannot be the same as the account to.');
return $bag;
}
/*
* Save transactions using repository. We try to connect the (possibly existing)
* piggy bank to either transaction, knowing it will only work with one of them.
*/
/** @var \Transaction $one */
$one = $this->_journals->saveTransaction($journal, $from, floatval($data['amount']) * -1);
$one->connectPiggybank($piggybank);
$two = $this->_journals->saveTransaction($journal, $to, floatval($data['amount']));
$two->connectPiggybank($piggybank);
/*
* Count for $journal is zero? Then there were errors!
*/
if ($journal->transactions()->count() < 2) {
/*
* Join message bags and return them:
*/
$bag = $one->errors();
$bag->merge($two->errors());
return $bag;
}
/*
* Connect budget, category and piggy bank:
*/
if (isset($budget) && !is_null($budget)) {
$journal->budgets()->save($budget);
}
if (!is_null($category)) {
$journal->categories()->save($category);
}
$journal->completed = true;
$journal->save();
/*
* Trigger recurring transaction event.
*/
\Event::fire('journals.store',[$journal]);
if (isset($data['return_journal']) && $data['return_journal'] == true) {
return ['journal' => $journal, 'messagebag' => $journal->errors()];
}
return $journal->errors();
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Firefly\Helper\Controllers;
use Illuminate\Support\MessageBag;
/**
* Interface TransactionInterface
*
* @package Firefly\Helper\Controllers
*/
interface TransactionInterface {
/**
* Store a full transaction journal and associated stuff
*
* @param array $data
*
* @return MessageBag|\TransactionJournal
*/
public function store(array $data);
/**
* Returns messages about the validation.
*
* @param array $data
*
* @return array
*/
public function validate(array $data);
/**
* @param \TransactionJournal $journal
* @param array $data
*
* @return MessageBag|\TransactionJournal
*/
public function update(\TransactionJournal $journal, array $data);
/**
* Overrule the user used when the class is created.
*
* @param \User $user
*
* @return mixed
*/
public function overruleUser(\User $user);
}

View File

@@ -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;

View File

@@ -26,6 +26,27 @@ class HelperServiceProvider extends ServiceProvider
'Firefly\Helper\Controllers\ChartInterface',
'Firefly\Helper\Controllers\Chart'
);
$this->app->bind(
'Firefly\Helper\Controllers\JsonInterface',
'Firefly\Helper\Controllers\Json'
);
$this->app->bind(
'Firefly\Helper\Controllers\RecurringInterface',
'Firefly\Helper\Controllers\Recurring'
);
$this->app->bind(
'Firefly\Helper\Controllers\SearchInterface',
'Firefly\Helper\Controllers\Search'
);
$this->app->bind(
'Firefly\Helper\Controllers\TransactionInterface',
'Firefly\Helper\Controllers\Transaction'
);
$this->app->bind(
'Firefly\Helper\Controllers\CategoryInterface',
'Firefly\Helper\Controllers\Category'

View File

@@ -3,140 +3,130 @@
namespace Firefly\Helper\Toolkit;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Firefly\Exception\FireflyException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
/**
* Class Toolkit
*
* @package Firefly\Helper\Toolkit
* @SuppressWarnings(PHPMD.CamelCaseMethodName)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
class Toolkit implements ToolkitInterface
{
/**
* @param Request $request
* Lots of code in Firefly III still depends on session['start'], session['end'] and
* session['range'] to be available, even though this feature has been removed from Firefly
* in favor of a new reporting feature. This reporting feature can show the user past and future
* date ranges instead of the dashboard (the dashboard always shows "right now").
*
* @return \Illuminate\Http\RedirectResponse|mixed|null
* The only actual choice the user is left with is the range, which can be changed using the Preferences pane.
*
* The start/end dates are set here, regardless of what the user might want to see.
*
* @return null
*/
public function getDateRange(Request $request)
public function getDateRange()
{
/*
* Get all data from the session:
*/
$range = $this->_getRange();
$start = $this->_getStartDate();
$end = $this->_getEndDate();
#\Log::debug('Range is: ' . $range);
$start = \Session::has('start') ? \Session::get('start') : new Carbon;
// update start only:
#\Log::debug('Session start is: ' . $start->format('Y-m-d'));
$end = \Session::has('end') ? \Session::get('end') : new Carbon;
#\Log::debug('Session end is : ' . $end->format('Y-m-d'));
/*
* Force start date to at the start of the $range.
* Ie. the start of the week, month, year.
*/
$start = $this->_updateStartDate($range, $start);
#\Log::debug('After update, session start is: ' . $start->format('Y-m-d'));
// update end only:
$end = $this->_updateEndDate($range, $start, $end);
/*
* Force end date to at the END of the $range. Always based on $start.
* Ie. the END of the week, month, year.
*/
$end = $this->_updateEndDate($range, $start);
#\Log::debug('After update, session end is : ' . $end->format('Y-m-d'));
if (\Input::get('action') == 'prev') {
$start = $this->_moveStartPrevious($range, $start);
$end = $this->_moveEndPrevious($range, $end);
}
if (\Input::get('action') == 'next') {
$start = $this->_moveStartNext($range, $start);
$end = $this->_moveEndNext($range, $end);
}
/*
* get the name of the month, depending on the range. Purely for astetics
*/
$period = $this->_periodName($range, $start);
// save in session:
/*
* Get the date for the previous and next period.
* Ie. next week, next month, etc.
*/
$prev = $this->_previous($range, clone $start);
$next = $this->_next($range, clone $start);
/*
* Save everything in the session:
*/
\Session::put('start', $start);
\Session::put('end', $end);
\Session::put('range', $range);
if (!is_null(\Input::get('action'))) {
return \Redirect::to($request->url());
}
\Session::put('period', $period);
\Session::put('prev', $this->_periodName($range, $prev));
\Session::put('next', $this->_periodName($range, $next));
return null;
}
/**
* @return array
*
*/
public function getDateRangeDates()
public function checkImportJobs()
{
return [\Session::get('start'), \Session::get('end')];
}
/**
* @return mixed
*/
public function getReminders()
{
// get reminders, for menu, mumble mumble:
$today = new Carbon;
$reminders = \Auth::user()->reminders()->where('class', 'PiggybankReminder')->validOn($today)->get();
/** @var \Reminder $reminder */
foreach ($reminders as $index => $reminder) {
if (\Session::has('dismissal-' . $reminder->id)) {
$time = \Session::get('dismissal-' . $reminder->id);
if ($time >= $today) {
unset($reminders[$index]);
}
}
}
\Session::put('reminderCount', count($reminders));
}
/**
* @return mixed
*/
protected function _getrange()
{
if (!is_null(\Input::get('range'))) {
$range = \Input::get('range');
/*
* Get all jobs.
*/
/** @var \Importmap $importJob */
$importJob = \Importmap::where('user_id', \Auth::user()->id)
->where('totaljobs', '>', \DB::Raw('`jobsdone`'))
->orderBy('created_at', 'DESC')
->first();
if (!is_null($importJob)) {
$diff = intval($importJob->totaljobs) - intval($importJob->jobsdone);
$date = new Carbon;
$today = new Carbon;
$date->addSeconds($diff);
\Session::put('job_pct', $importJob->pct());
\Session::put('job_text', $date->diffForHumans());
} else {
if (!is_null(\Session::get('range'))) {
$range = \Session::get('range');
} else {
/** @noinspection PhpUndefinedClassInspection */
$preferences = \App::make('Firefly\Helper\Preferences\PreferencesHelperInterface');
$viewRange = $preferences->get('viewRange', '1M');
// default range:
$range = $viewRange->data;
}
\Session::forget('job_pct');
\Session::forget('job_text');
}
}
/**
* @return mixed
*/
protected function _getRange()
{
if (!is_null(\Session::get('range'))) {
$range = \Session::get('range');
} else {
/** @noinspection PhpUndefinedClassInspection */
$preferences = \App::make('Firefly\Helper\Preferences\PreferencesHelperInterface');
$viewRange = $preferences->get('viewRange', '1M');
// default range:
$range = $viewRange->data;
\Session::put('range', $range);
}
return $range;
}
/**
* @return Carbon|mixed
*/
protected function _getStartDate()
{
$start = \Session::has('start') ? \Session::get('start') : new Carbon;
if (\Input::get('start') && \Input::get('end')) {
$start = new Carbon(\Input::get('start'));
}
return $start;
}
/**
* @return Carbon|mixed
*/
protected function _getEndDate()
{
$end = \Session::has('end') ? \Session::get('end') : new Carbon;
if (\Input::get('start') && \Input::get('end')) {
$end = new Carbon(\Input::get('end'));
}
return $end;
}
/**
* @param $range
* @param Carbon $start
@@ -145,7 +135,6 @@ class Toolkit implements ToolkitInterface
*/
protected function _updateStartDate($range, Carbon $start)
{
$today = new Carbon;
switch ($range) {
case '1D':
$start->startOfDay();
@@ -160,12 +149,15 @@ class Toolkit implements ToolkitInterface
$start->firstOfQuarter();
break;
case '6M':
if (intval($today->format('m')) >= 7) {
if (intval($start->format('m')) >= 7) {
$start->startOfYear()->addMonths(6);
} else {
$start->startOfYear();
}
break;
case '1Y':
$start->startOfYear();
break;
}
return $start;
@@ -179,150 +171,212 @@ class Toolkit implements ToolkitInterface
*
* @return Carbon
*/
protected function _updateEndDate($range, Carbon $start, Carbon $end)
protected function _updateEndDate($range, Carbon $start)
{
$today = new Carbon;
$end = clone $start;
switch ($range) {
case '1D':
$end = clone $start;
$end->endOfDay();
break;
case '1W':
$end = clone $start;
$end->endOfWeek();
break;
case '1M':
$end = clone $start;
$end->endOfMonth();
break;
case '3M':
$end = clone $start;
$end->lastOfQuarter();
break;
case '6M':
$end = clone $start;
if (intval($today->format('m')) >= 7) {
if (intval($start->format('m')) >= 7) {
$end->endOfYear();
} else {
$end->startOfYear()->addMonths(6);
}
break;
case '1Y':
$end->endOfYear();
break;
default:
throw new FireflyException('_updateEndDate cannot handle $range ' . $range);
break;
}
return $end;
}
/**
* @param $range
* @param Carbon $start
*
* @return Carbon
*/
protected function _moveStartPrevious($range, Carbon $start)
protected function _periodName($range, Carbon $date)
{
switch ($range) {
default:
throw new FireflyException('No _periodName() for range "' . $range . '"');
break;
case '1D':
return $date->format('jS F Y');
break;
case '1W':
return 'week ' . $date->format('W, Y');
break;
case '1M':
return $date->format('F Y');
break;
case '3M':
$month = intval($date->format('m'));
return 'Q' . ceil(($month / 12) * 4) . ' ' . $date->format('Y');
break;
case '6M':
$month = intval($date->format('m'));
$half = ceil(($month / 12) * 2);
$halfName = $half == 1 ? 'first' : 'second';
return $halfName . ' half of ' . $date->format('d-m-Y');
break;
case '1Y':
return $date->format('Y');
break;
}
}
protected function _previous($range, Carbon $date)
{
switch ($range) {
case '1D':
$start->subDay();
$date->startOfDay()->subDay();
break;
case '1W':
$start->subWeek();
$date->startOfWeek()->subWeek();
break;
case '1M':
$start->subMonth();
$date->startOfMonth()->subMonth();
break;
case '3M':
$start->subMonths(3)->firstOfQuarter();
$date->firstOfQuarter()->subMonths(3)->firstOfQuarter();
break;
case '6M':
$start->subMonths(6);
$month = intval($date->format('m'));
if ($month <= 6) {
$date->startOfYear()->subMonths(6);
} else {
$date->startOfYear();
}
break;
case '1Y':
$date->startOfYear()->subYear();
break;
default:
throw new FireflyException('Cannot do _previous() on ' . $range);
break;
}
return $start;
return $date;
}
/**
* @param $range
* @param Carbon $end
*
* @return Carbon
*/
protected function _moveEndPrevious($range, Carbon $end)
protected function _next($range, Carbon $date)
{
switch ($range) {
case '1D':
$end->subDay();
$date->endOfDay()->addDay();
break;
case '1W':
$end->subWeek();
$date->endOfWeek()->addDay()->startOfWeek();
break;
case '1M':
$end->startOfMonth()->subMonth()->endOfMonth();
$date->endOfMonth()->addDay()->startOfMonth();
break;
case '3M':
$end->subMonths(3)->lastOfQuarter();
$date->lastOfQuarter()->addDay();
break;
case '6M':
$end->subMonths(6);
if (intval($date->format('m')) >= 7) {
$date->startOfYear()->addYear();
} else {
$date->startOfYear()->addMonths(6);
}
break;
case '1Y':
$date->startOfYear()->addYear();
break;
default:
throw new FireflyException('Cannot do _next() on ' . $range);
break;
}
return $end;
return $date;
}
public function next()
{
/*
* Get the start date and the range from the session
*/
$range = $this->_getRange();
$start = \Session::get('start');
/*
* Add some period to $start.
*/
$next = $this->_next($range, clone $start);
/*
* Save in session:
*/
\Session::put('start', $next);
return true;
}
public function prev()
{
/*
* Get the start date and the range from the session
*/
$range = $this->_getRange();
$start = \Session::get('start');
/*
* Substract some period to $start.
*/
$prev = $this->_previous($range, clone $start);
/*
* Save in session:
*/
\Session::put('start', $prev);
return true;
}
/**
* @param $range
* @param Carbon $start
* Takes any collection and tries to make a sensible select list compatible array of it.
*
* @return Carbon
*/
protected function _moveStartNext($range, Carbon $start)
{
switch ($range) {
case '1D':
$start->addDay();
break;
case '1W':
$start->addWeek();
break;
case '1M':
$start->addMonth();
break;
case '3M':
$start->addMonths(3)->firstOfQuarter();
break;
case '6M':
$start->addMonths(6);
break;
}
return $start;
}
/**
* @param $range
* @param Carbon $end
* @param Collection $set
* @param null $titleField
*
* @return Carbon
* @return mixed
*/
protected function _moveEndNext($range, Carbon $end)
public function makeSelectList(Collection $set, $titleField = null)
{
switch ($range) {
case '1D':
$end->addDay();
break;
case '1W':
$end->addWeek();
break;
case '1M':
$end->addMonth();
break;
case '3M':
$end->addMonths(6)->lastOfQuarter();
break;
case '6M':
$end->addMonths(6);
break;
}
return $end;
}
$selectList = [];
/** @var Model $entry */
foreach ($set as $entry) {
$id = intval($entry->id);
$title = null;
if (is_null($titleField)) {
// try 'title' field.
if (isset($entry->title)) {
$title = $entry->title;
}
// try 'name' field
if (is_null($title)) {
$title = $entry->name;
}
}
// try 'description' field
if (is_null($title)) {
$title = $entry->description;
}
} else {
$title = $entry->$titleField;
}
$selectList[$id] = $title;
}
return $selectList;
}
}

View File

@@ -2,7 +2,7 @@
namespace Firefly\Helper\Toolkit;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
/**
* Interface ToolkitInterface
@@ -12,20 +12,25 @@ use Illuminate\Http\Request;
interface ToolkitInterface
{
/**
* @param Request $request
*
* @return null
*/
public function getDateRange();
/**
* Takes any collection and tries to make a sensible select list compatible array of it.
*
* @param Collection $set
* @param null $titleField
*
* @return mixed
*/
public function getDateRange(Request $request);
public function makeSelectList(Collection $set, $titleField = null);
/**
* @return mixed
*/
public function getDateRangeDates();
public function next();
/**
* @return mixed
*/
public function getReminders();
public function prev();
}
public function checkImportJobs();
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,8 @@
namespace Firefly\Storage\Account;
use Illuminate\Queue\Jobs\Job;
/**
* Interface AccountRepositoryInterface
*
@@ -11,25 +13,35 @@ namespace Firefly\Storage\Account;
interface AccountRepositoryInterface
{
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importAccount(Job $job, array $payload);
/**
* @return mixed
*/
public function count();
/**
* @param $name
* @param \AccountType $type
* Gets a list of accounts that have the mentioned type. Will automatically convert
* strings in this array to actual (model) account types.
*
* @return mixed
* @param array $types
*
* @return Collection
*/
public function createOrFind($name, \AccountType $type);
public function getOfTypes(array $types);
/**
* @param $name
* @param array $data
*
* @return mixed
*/
public function createOrFindBeneficiary($name);
public function firstOrCreate(array $data);
/**
* @param \Account $account
@@ -47,43 +59,49 @@ interface AccountRepositoryInterface
/**
* @param $type
*
* @return mixed
*/
public function findAccountType($type);
/**
* @param $name
* @param \AccountType $type
* Takes a transaction/account component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function findByName($name, \AccountType $type = null);
public function importUpdateTransaction(Job $job, array $payload);
/**
* @param $id
*
* @return |Account|null
*/
public function findAssetAccountById($id);
/**
* @param $name
* @return mixed
* @param $create
*
* @return |Account|null
*/
public function findByNameAny($name);
public function findExpenseAccountByName($name, $create = true);
/**
* @return mixed
* @param $name
* @param $create
*
* @return |Account|null
*/
public function get();
public function findRevenueAccountByName($name, $create = true);
/**
* @return mixed
*/
public function getActiveDefault();
/**
* @return mixed
*/
public function getActiveDefaultAsSelectList();
/**
* @return mixed
*/
public function getBeneficiaries();
/**
* @param $ids
*
@@ -91,11 +109,6 @@ interface AccountRepositoryInterface
*/
public function getByIds(array $ids);
/**
* @return mixed
*/
public function getCashAccount();
/**
* @return mixed
*/
@@ -103,12 +116,14 @@ interface AccountRepositoryInterface
/**
* @param \AccountType $type
*
* @return mixed
*/
public function getByAccountType(\AccountType $type);
/**
* @param \User $user
*
* @return mixed
*/
public function overruleUser(\User $user);

View File

@@ -4,6 +4,8 @@
namespace Firefly\Storage\Account;
use Carbon\Carbon;
use Illuminate\Database\QueryException;
use Illuminate\Queue\Jobs\Job;
/**
* Class EloquentAccountRepository
@@ -32,41 +34,6 @@ class EloquentAccountRepository implements AccountRepositoryInterface
}
/**
* @param $name
* @param \AccountType $type
*
* @return \Account|mixed
*/
public function createOrFind($name, \AccountType $type = null)
{
$account = $this->findByName($name, $type);
if (!$account) {
$data = [
'name' => $name,
'account_type' => $type
];
return $this->store($data);
}
return $account;
}
/**
* @param $name
*
* @return \Account|mixed|null
*/
public function createOrFindBeneficiary($name)
{
if (is_null($name) || strlen($name) == 0) {
return null;
}
$type = \AccountType::where('type', 'Beneficiary account')->first();
return $this->createOrFind($name, $type);
}
/**
* @param \Account $account
*
@@ -75,7 +42,7 @@ class EloquentAccountRepository implements AccountRepositoryInterface
public function destroy(\Account $account)
{
// find all transaction journals related to this account:
$journals = \TransactionJournal::withRelevantData()->account($account)->get(['transaction_journals.*']);
$journals = \TransactionJournal::withRelevantData()->accountIs($account)->get(['transaction_journals.*']);
$accountIDs = [];
/** @var \TransactionJournal $journal */
@@ -109,104 +76,163 @@ class EloquentAccountRepository implements AccountRepositoryInterface
}
/**
* @param $accountId
* @param $id
*
* @return mixed
* @return |Account|null
*/
public function find($accountId)
public function findAssetAccountById($id)
{
return $this->_user->accounts()->where('id', $accountId)->first();
return $this->_user->accounts()->find($id);
}
/**
* This method finds the expense account mentioned by name. This method is a sneaky little hobbits,
* because when you feed it "Import account" it will always return an import account of that type.
*
* @param $name
* @param $create
*
* @return null|\Account
*/
public function findExpenseAccountByName($name, $create = true)
{
$cashType = $this->findAccountType('Cash account');
$importType = $this->findAccountType('Import account');
// catch Import account:
if ($name == 'Import account') {
$import = $this->firstOrCreate(
[
'name' => 'Import account',
'user_id' => $this->_user->id,
'account_type_id' => $importType->id,
'active' => 1
]
);
return $import;
}
// find account:
$account = $this->_user->accounts()->where('name', $name)->accountTypeIn(
['Expense account', 'Beneficiary account']
)->first(['accounts.*']);
// create if not found:
if (strlen($name) > 0 && is_null($account) && $create === true) {
$type = $this->findAccountType('Expense account');
$set = [
'name' => $name,
'user_id' => $this->_user->id,
'active' => 1,
'account_type_id' => $type->id
];
$account = $this->firstOrCreate($set);
} else if (strlen($name) > 0 && is_null($account) && $create === false) {
return null;
}
// find cash account as fall back:
if (is_null($account)) {
$account = $this->_user->accounts()->where('account_type_id', $cashType->id)->first();
}
// create cash account as ultimate fall back:
if (is_null($account)) {
$set = [
'name' => 'Cash account',
'user_id' => $this->_user->id,
'active' => 1,
'account_type_id' => $cashType->id
];
$account = $this->firstOrCreate($set);
}
if ($account->active == 0 && $account->account_type_id != $cashType->id) {
return null;
}
return $account;
}
/**
* @param $type
* @return mixed
*
* @return \AccountType|null
*/
public function findAccountType($type)
{
return \AccountType::where('type', $type)->first();
}
public function firstOrCreate(array $data)
{
return \Account::firstOrCreate($data);
}
/**
* @param $name
* @param \AccountType $type
* @param $name
* @param $create
*
* @return mixed
* @return |Account|null
*/
public function findByName($name, \AccountType $type = null)
public function findRevenueAccountByName($name, $create = true)
{
$type = is_null($type) ? \AccountType::where('type', 'Default account')->first() : $type;
return $this->_user->accounts()->where('account_type_id', $type->id)
->where('name', 'like', '%' . $name . '%')
->first();
}
/**
* Used for import
*
* @param $name
*
* @return mixed
*/
public function findByNameAny($name)
{
return $this->_user->accounts()
->where('name', 'like', '%' . $name . '%')
->first();
}
/**
* @return mixed
*/
public function get()
{
return $this->_user->accounts()->with('accounttype')->orderBy('name', 'ASC')->get();
}
/**
* @return mixed
*/
public function getActiveDefault()
{
return $this->_user->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->where('account_types.type', 'Default account')->where('accounts.active', 1)
->get(['accounts.*']);
}
/**
* @return array|mixed
*/
public function getActiveDefaultAsSelectList()
{
$list = $this->_user->accounts()->leftJoin(
'account_types', 'account_types.id', '=', 'accounts.account_type_id'
)
->where('account_types.type', 'Default account')->where('accounts.active', 1)
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
$return = [];
foreach ($list as $entry) {
$return[intval($entry->id)] = $entry->name;
// catch Import account:
if ($name == 'Import account') {
$importType = $this->findAccountType('Import account');
$import = $this->firstOrCreate(
[
'name' => 'Import account',
'user_id' => $this->_user->id,
'account_type_id' => $importType->id,
'active' => 1
]
);
return $import;
}
return $return;
}
// find account:
$type = $this->findAccountType('Revenue account');
$account = $this->_user->accounts()->where('name', $name)->where('account_type_id', $type->id)->first();
/**
* @return mixed
*/
public function getBeneficiaries()
{
$list = $this->_user->accounts()->leftJoin(
'account_types', 'account_types.id', '=', 'accounts.account_type_id'
)
->where('account_types.type', 'Beneficiary account')->where('accounts.active', 1)
// create if not found:
if (strlen($name) > 0 && is_null($account) && $create === true) {
$set = [
'name' => $name,
'user_id' => $this->_user->id,
'active' => 1,
'account_type_id' => $type->id
];
$account = $this->firstOrCreate($set);
} else if (strlen($name) > 0 && is_null($account) && $create === false) {
return null;
}
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
// find cash account as fall back:
if (is_null($account)) {
$cashType = $this->findAccountType('Cash account');
$account = $this->_user->accounts()->where('account_type_id', $cashType->id)->first();
}
return $list;
// create cash account as ultimate fall back:
if (is_null($account)) {
$set = [
'name' => 'Cash account',
'user_id' => $this->_user->id,
'active' => 1,
'account_type_id' => $cashType->id
];
$account = $this->firstOrCreate($set);
}
if ($account->active == 0) {
return null;
}
return $account;
}
public function getByAccountType(\AccountType $type)
@@ -228,25 +254,66 @@ class EloquentAccountRepository implements AccountRepositoryInterface
return $this->getActiveDefault();
}
}
//
// /**
// * @param $name
// *
// * @return \Account|mixed|null
// */
// public function createOrFindBeneficiary($name)
// {
// if (is_null($name) || strlen($name) == 0) {
// return null;
// }
// $type = \AccountType::where('type', 'Expense account')->first();
// return $this->createOrFind($name, $type);
// }
//
// /**
// * @param $name
// * @param \AccountType $type
// *
// * @return \Account|mixed
// */
// public function createOrFind($name, \AccountType $type = null)
// {
// $account = $this->findByName($name, $type);
// if (!$account) {
// $data = [
// 'name' => $name,
// 'account_type' => $type
// ];
//
// return $this->store($data);
// }
//
// return $account;
// }
//
// /**
// * @param $name
// * @param \AccountType $type
// *
// * @return mixed
// */
// public function findByName($name, \AccountType $type = null)
// {
// $type = is_null($type) ? \AccountType::where('type', 'Asset account')->first() : $type;
//
// return $this->_user->accounts()->where('account_type_id', $type->id)
// ->where('name', 'like', '%' . $name . '%')
// ->first();
// }
/**
* @return mixed
*/
public function getCashAccount()
public function getActiveDefault()
{
$type = \AccountType::where('type', 'Cash account')->first();
$cash = $this->_user->accounts()->where('account_type_id', $type->id)->first();
if (is_null($cash)) {
$cash = new \Account;
$cash->accountType()->associate($type);
$cash->user()->associate($this->_user);
$cash->name = 'Cash account';
$cash->active = 1;
$cash->save();
}
return $cash;
return $this->_user->accounts()->accountTypeIn(['Default account', 'Asset account'])->where(
'accounts.active', 1
)
->get(['accounts.*']);
}
/**
@@ -254,22 +321,120 @@ class EloquentAccountRepository implements AccountRepositoryInterface
*/
public function getDefault()
{
return $this->_user->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
->where('account_types.type', 'Default account')
return $this->_user->accounts()->accountTypeIn(['Default account', 'Asset account'])
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
}
/**
* @param \User $user
* @return mixed|void
* Gets a list of accounts that have the mentioned type. Will automatically convert
* strings in this array to actual (model) account types.
*
* @param array $types
*
* @return Collection
*/
public function overruleUser(\User $user)
public function getOfTypes(array $types)
{
$this->_user = $user;
return true;
$accounts = $this->_user->accounts()->accountTypeIn($types)->get(['accounts.*']);
return $accounts;
}
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importAccount(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
/*
* maybe Account is already imported:
*/
$importEntry = $repository->findImportEntry($importMap, 'Account', intval($payload['data']['id']));
/*
* if so, delete job and return:
*/
if (!is_null($importEntry)) {
\Log::debug('Already imported ' . $payload['data']['name'] . ' of type ' . $payload['account_type']);
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/*
* find the payload's account type:
*/
$payload['account_type'] = isset($payload['account_type']) ? $payload['account_type'] : 'Expense account';
$type = $this->findAccountType($payload['account_type']);
/*
* Create data array for store() procedure.
*/
$data = [
'account_type' => $type,
'name' => $payload['data']['name'],
];
if (isset($payload['data']['openingbalance'])) {
$data['openingbalance'] = floatval($payload['data']['openingbalance']);
$data['openingbalancedate'] = $payload['data']['openingbalancedate'];
}
if (isset($payload['data']['inactive'])) {
$data['active'] = intval($payload['data']['inactive']) == 0 ? 1 : 0;
}
/*
* Try to store:
*/
$account = $this->store($data);
/*
* Check for failure.
*/
if (count($account->errors()) > 0) {
\Log::error('Account creation error: ' . $account->errors()->first());
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
\Log::debug('Imported ' . $payload['account_type'] . ': ' . $payload['data']['name']);
/*
* Save meta data
*/
$repository->store($importMap, 'Account', intval($payload['data']['id']), $account->id);
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed.
return;
}
// /**
// * Used for import
// *
// * @param $name
// *
// * @return mixed
// */
// public function findByNameAny($name)
// {
// return $this->_user->accounts()
// ->where('name', 'like', '%' . $name . '%')
// ->first();
// }
/**
* @param $data
*
@@ -286,11 +451,15 @@ class EloquentAccountRepository implements AccountRepositoryInterface
) {
$accountType = $data['account_type'];
} else if (isset($data['account_type']) && is_string($data['account_type'])) {
// if it isnt but set as string, find it:
$accountType = \AccountType::where('type', $data['account_type'])->first();
} else {
$accountType = \AccountType::where('type', 'Default account')->first();
$accountType = \AccountType::where('type', 'Asset account')->first();
}
$active = isset($data['active']) && intval($data['active']) >= 0 && intval($data['active']) <= 1 ? intval(
$data['active']
) : 1;
/**
* Create new account:
@@ -301,20 +470,24 @@ class EloquentAccountRepository implements AccountRepositoryInterface
$account->name = $data['name'];
$account->active
= isset($data['active']) && intval($data['active']) >= 0 && intval($data['active']) <= 1 ? intval(
= isset($data['active']) && intval($data['active']) >= 0 && intval($data['active']) <= 1 ? intval(
$data['active']
) : 1;
// try to save it:
if ($account->save()) {
// create initial balance, if necessary:
if (isset($data['openingbalance']) && isset($data['openingbalancedate'])) {
$amount = floatval($data['openingbalance']);
$date = new Carbon($data['openingbalancedate']);
if ($amount != 0) {
$this->_createInitialBalance($account, $amount, $date);
try {
if ($account->save()) {
// create initial balance, if necessary:
if (isset($data['openingbalance']) && isset($data['openingbalancedate'])) {
$amount = floatval($data['openingbalance']);
$date = new Carbon($data['openingbalancedate']);
if ($amount != 0) {
$this->_createInitialBalance($account, $amount, $date);
}
}
}
} catch (QueryException $e) {
// do nothing
}
@@ -322,6 +495,206 @@ class EloquentAccountRepository implements AccountRepositoryInterface
return $account;
}
/**
* @param \Account $account
* @param int $amount
* @param Carbon $date
*
* @return bool
* @SuppressWarnings(PHPMD.CamelCaseMethodName)
*/
protected function _createInitialBalance(\Account $account, $amount = 0, Carbon $date)
{
/*
* The repositories we need:
*/
/** @var \Firefly\Helper\Controllers\TransactionInterface $transactions */
$transactions = \App::make('Firefly\Helper\Controllers\TransactionInterface');
$transactions->overruleUser($this->_user);
/*
* get account type:
*/
$initialBalanceAT = $this->findAccountType('Initial balance account');
/*
* create new account
*/
$initial = new \Account;
$initial->accountType()->associate($initialBalanceAT);
$initial->user()->associate($this->_user);
$initial->name = $account->name . ' initial balance';
$initial->active = 0;
if ($initial->validate()) {
$initial->save();
/*
* create new transaction journal (and transactions):
*/
$set = [
'account_from_id' => $initial->id,
'account_to_id' => $account->id,
'description' => 'Initial Balance for ' . $account->name,
'what' => 'Opening balance',
'amount' => $amount,
'category' => '',
'date' => $date->format('Y-m-d')
];
$transactions->store($set);
return true;
}
return false;
}
/**
* Takes a transaction/account component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importUpdateTransaction(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
if ($job->attempts() > 10) {
\Log::error('Never found budget/account combination "' . $payload['data']['transaction_id'] . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed.
return;
}
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $journals */
$journals = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$journals->overruleUser($user);
/*
* Prep some vars from the payload
*/
$transactionId = intval($payload['data']['transaction_id']);
$componentId = intval($payload['data']['component_id']);
/*
* Find the import map for both:
*/
$accountMap = $repository->findImportEntry($importMap, 'Account', $componentId);
$transactionMap = $repository->findImportEntry($importMap, 'Transaction', $transactionId);
/*
* Either may be null:
*/
if (is_null($accountMap) || is_null($transactionMap)) {
\Log::notice('No map found in account/transaction mapper. Release.');
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Find the account and the transaction:
*/
$account = $this->find($accountMap->new);
/** @var \TransactionJournal $journal */
$journal = $journals->find($transactionMap->new);
/*
* If either is null, release:
*/
if (is_null($account) || is_null($journal)) {
\Log::notice('Map is incorrect in account/transaction mapper. Release.');
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Update one of the journal's transactions to have the right account:
*/
$importType = $this->findAccountType('Import account');
/** @var \Transaction $transaction */
$updated = false;
\Log::debug(
'Connect "' . $account->name . '" (#' . $account->id . ') to "' . $journal->description . '" (#'
. $journal->id . ')'
);
foreach ($journal->transactions as $index => $transaction) {
/*
* If it's of the right type, update it!
*/
\Log::debug(
'Transaction ' . $index . ' (#' . $transaction->id . '): [' . $transaction->account->account_type_id
. ' vs. ' . $importType->id . ']'
);
if ($transaction->account->account_type_id == $importType->id) {
$transaction->account()->associate($account);
$transaction->save();
$updated = true;
\Log::debug(
'Connected expense account "' . $account->name . '" to journal "' . $journal->description . '"'
);
}
}
if ($updated === false) {
\Log::error(
'Did not connect transactions of journal #' . $journal->id . ' to expense account ' . $account->id
);
}
$journal->save();
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
}
/**
* @param \User $user
*
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
/**
* @param $accountId
*
* @return mixed
*/
public function find($accountId)
{
return $this->_user->accounts()->where('id', $accountId)->first();
}
/**
* @param \Account $account
* @param $data
@@ -336,17 +709,17 @@ class EloquentAccountRepository implements AccountRepositoryInterface
$account->save();
}
// update initial balance if necessary:
if (floatval($data['openingbalance']) != 0) {
if (isset($data['openingbalance']) && floatval($data['openingbalance']) != 0) {
/** @var \Firefly\Helper\Controllers\AccountInterface $interface */
$interface = \App::make('Firefly\Helper\Controllers\AccountInterface');
if ($account->accounttype->type == 'Default account') {
if ($account->accounttype->type == 'Default account' || $account->accounttype->type == 'Asset account') {
$journal = $interface->openingBalanceTransaction($account);
if ($journal) {
$journal->date = new Carbon($data['openingbalancedate']);
$journal->date = new Carbon($data['openingbalancedate']);
$journal->transactions[0]->amount = floatval($data['openingbalance']) * -1;
$journal->transactions[1]->amount = floatval($data['openingbalance']);
$journal->transactions[0]->save();
@@ -359,42 +732,5 @@ class EloquentAccountRepository implements AccountRepositoryInterface
return $account;
}
/**
* @param \Account $account
* @param int $amount
* @param Carbon $date
*
* @return bool
* @SuppressWarnings(PHPMD.CamelCaseMethodName)
*/
protected function _createInitialBalance(\Account $account, $amount = 0, Carbon $date)
{
// get account type:
$initialBalanceAT = \AccountType::where('type', 'Initial balance account')->first();
// create new account:
$initial = new \Account;
$initial->accountType()->associate($initialBalanceAT);
$initial->user()->associate($this->_user);
$initial->name = $account->name . ' initial balance';
$initial->active = 0;
if ($initial->validate()) {
$initial->save();
// create new transaction journal (and transactions):
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $transactionJournal */
$transactionJournal = \App::make(
'Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface'
);
$transactionJournal->overruleUser($this->_user);
$transactionJournal->createSimpleJournal(
$initial, $account, 'Initial Balance for ' . $account->name, $amount, $date
);
return true;
}
return false;
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace Firefly\Storage\Budget;
use Illuminate\Queue\Jobs\Job;
/**
* Interface BudgetRepositoryInterface
@@ -9,6 +10,34 @@ namespace Firefly\Storage\Budget;
*/
interface BudgetRepositoryInterface
{
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importBudget(Job $job, array $payload);
/**
* Takes a transaction/budget component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importUpdateTransaction(Job $job, array $payload);
/**
* Takes a transfer/budget component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importUpdateTransfer(Job $job, array $payload);
/**
* @param \Budget $budget
*
@@ -41,11 +70,6 @@ interface BudgetRepositoryInterface
*/
public function get();
/**
* @return mixed
*/
public function getAsSelectList();
/**
* @param $data
*

View File

@@ -4,6 +4,7 @@ namespace Firefly\Storage\Budget;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Queue\Jobs\Job;
/**
* Class EloquentBudgetRepository
@@ -15,6 +16,7 @@ use Illuminate\Database\Eloquent\Collection;
*/
class EloquentBudgetRepository implements BudgetRepositoryInterface
{
protected $_user = null;
/**
@@ -26,79 +28,70 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
}
/**
* @param \Budget $budget
* @param Job $job
* @param array $payload
*
* @return bool
* @return mixed
*/
public function destroy(\Budget $budget)
public function importBudget(Job $job, array $payload)
{
$budget->delete();
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
return true;
}
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
/**
* @param $budgetId
*
* @return \Budget|null
*/
public function find($budgetId)
{
/*
* maybe Budget is already imported:
*/
$importEntry = $repository->findImportEntry($importMap, 'Budget', intval($payload['data']['id']));
return $this->_user->budgets()->find($budgetId);
}
/*
* if so, delete job and return:
*/
if (!is_null($importEntry)) {
\Log::debug('Already imported budget ' . $payload['data']['name']);
/**
* @param $budgetName
* @return \Budget|null
*/
public function findByName($budgetName)
{
$importMap->jobsdone++;
$importMap->save();
return $this->_user->budgets()->whereName($budgetName)->first();
}
/**
* @return Collection
*/
public function get()
{
$set = $this->_user->budgets()->with(
['limits' => function ($q) {
$q->orderBy('limits.startdate', 'DESC');
}, 'limits.limitrepetitions' => function ($q) {
$q->orderBy('limit_repetitions.startdate', 'ASC');
}]
)->orderBy('name', 'ASC')->get();
foreach ($set as $budget) {
foreach ($budget->limits as $limit) {
foreach ($limit->limitrepetitions as $rep) {
$rep->left = $rep->left();
}
}
$job->delete(); // count fixed
return;
}
return $set;
}
/*
* maybe Budget is already imported.
*/
$budget = $this->findByName($payload['data']['name']);
/**
* @return array
*/
public function getAsSelectList()
{
$list = $this->_user->budgets()->with(
['limits', 'limits.limitrepetitions']
)->orderBy('name', 'ASC')->get();
$return = [];
foreach ($list as $entry) {
$return[intval($entry->id)] = $entry->name;
if (is_null($budget)) {
/*
* Not imported yet.
*/
$budget = $this->store($payload['data']);
$repository->store($importMap, 'Budget', $payload['data']['id'], $budget->id);
\Log::debug('Imported budget "' . $payload['data']['name'] . '".');
} else {
/*
* already imported.
*/
$repository->store($importMap, 'Budget', $payload['data']['id'], $budget->id);
\Log::debug('Already had budget "' . $payload['data']['name'] . '".');
}
return $return;
// update map:
$importMap->jobsdone++;
$importMap->save();
// delete job.
$job->delete(); // count fixed
}
/**
* @param \User $user
*
* @return mixed|void
*/
public function overruleUser(\User $user)
@@ -107,6 +100,17 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
return true;
}
/**
* @param $budgetName
*
* @return \Budget|null
*/
public function findByName($budgetName)
{
return $this->_user->budgets()->whereName($budgetName)->first();
}
/**
* @param $data
*
@@ -135,7 +139,7 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
$limit = $limitRepository->store($limitData);
\Event::fire('limits.store', [$limit]);
}
if ($budget->validate()) {
$budget->save();
}
@@ -143,6 +147,240 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
return $budget;
}
/**
* Takes a transfer/budget component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importUpdateTransfer(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
if ($job->attempts() > 10) {
\Log::error('Never found budget/transfer combination "' . $payload['data']['transfer_id'] . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $journals */
$journals = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$journals->overruleUser($user);
/*
* Prep some vars from the payload
*/
$transferId = intval($payload['data']['transfer_id']);
$componentId = intval($payload['data']['component_id']);
/*
* Find the import map for both:
*/
$budgetMap = $repository->findImportEntry($importMap, 'Budget', $componentId);
$transferMap = $repository->findImportEntry($importMap, 'Transfer', $transferId);
/*
* Either may be null:
*/
if (is_null($budgetMap) || is_null($transferMap)) {
\Log::notice('No map found in budget/transfer mapper. Release.');
if(\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Find the budget and the transaction:
*/
$budget = $this->find($budgetMap->new);
/** @var \TransactionJournal $journal */
$journal = $journals->find($transferMap->new);
/*
* If either is null, release:
*/
if (is_null($budget) || is_null($journal)) {
\Log::notice('Map is incorrect in budget/transfer mapper. Release.');
if(\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Update journal to have budget:
*/
$journal->budgets()->save($budget);
$journal->save();
\Log::debug('Connected budget "' . $budget->name . '" to journal "' . $journal->description . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/**
* Takes a transaction/budget component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importUpdateTransaction(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
if ($job->attempts() > 10) {
\Log::error('Never found budget/transaction combination "' . $payload['data']['transaction_id'] . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $journals */
$journals = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$journals->overruleUser($user);
/*
* Prep some vars from the payload
*/
$transactionId = intval($payload['data']['transaction_id']);
$componentId = intval($payload['data']['component_id']);
/*
* Find the import map for both:
*/
$budgetMap = $repository->findImportEntry($importMap, 'Budget', $componentId);
$transactionMap = $repository->findImportEntry($importMap, 'Transaction', $transactionId);
/*
* Either may be null:
*/
if (is_null($budgetMap) || is_null($transactionMap)) {
\Log::notice('No map found in budget/transaction mapper. Release.');
if(\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Find the budget and the transaction:
*/
$budget = $this->find($budgetMap->new);
/** @var \TransactionJournal $journal */
$journal = $journals->find($transactionMap->new);
/*
* If either is null, release:
*/
if (is_null($budget) || is_null($journal)) {
\Log::notice('Map is incorrect in budget/transaction mapper. Release.');
if(\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Update journal to have budget:
*/
$journal->budgets()->save($budget);
$journal->save();
\Log::debug('Connected budget "' . $budget->name . '" to journal "' . $journal->description . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/**
* @param $budgetId
*
* @return \Budget|null
*/
public function find($budgetId)
{
return $this->_user->budgets()->find($budgetId);
}
/**
* @param \Budget $budget
*
* @return bool
*/
public function destroy(\Budget $budget)
{
$budget->delete();
return true;
}
/**
* @return Collection
*/
public function get()
{
$set = $this->_user->budgets()->with(
['limits' => function ($q) {
$q->orderBy('limits.startdate', 'DESC');
}, 'limits.limitrepetitions' => function ($q) {
$q->orderBy('limit_repetitions.startdate', 'ASC');
}]
)->orderBy('name', 'ASC')->get();
return $set;
}
/**
* @param \Budget $budget
* @param $data

View File

@@ -1,6 +1,7 @@
<?php
namespace Firefly\Storage\Category;
use Illuminate\Queue\Jobs\Job;
/**
* Interface CategoryRepositoryInterface
@@ -9,6 +10,33 @@ namespace Firefly\Storage\Category;
*/
interface CategoryRepositoryInterface
{
/**
* Takes a transaction/category component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importUpdateTransaction(Job $job, array $payload);
/**
* Takes a transfer/category component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importUpdateTransfer(Job $job, array $payload);
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importCategory(Job $job, array $payload);
/**
* @return mixed
@@ -27,7 +55,7 @@ interface CategoryRepositoryInterface
*
* @return mixed
*/
public function createOrFind($name);
public function firstOrCreate($name);
/**
* @param \User $user

View File

@@ -2,6 +2,8 @@
namespace Firefly\Storage\Category;
use Illuminate\Queue\Jobs\Job;
/**
* Class EloquentCategoryRepository
*
@@ -20,34 +22,112 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
}
/**
* @param $name
* Takes a transfer/category component and updates the transaction journal to match.
*
* @return \Category|mixed
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function createOrFind($name)
public function importUpdateTransfer(Job $job, array $payload)
{
if (strlen($name) == 0) {
return null;
}
$category = $this->findByName($name);
if (!$category) {
return $this->store(['name' => $name]);
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
if ($job->attempts() > 10) {
\Log::error('Never found category/transfer combination "' . $payload['data']['transfer_id'] . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed.
return;
}
return $category;
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $journals */
$journals = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$journals->overruleUser($user);
/*
* Prep some vars from the payload
*/
$transferId = intval($payload['data']['transfer_id']);
$componentId = intval($payload['data']['component_id']);
/*
* Find the import map for both:
*/
$categoryMap = $repository->findImportEntry($importMap, 'Category', $componentId);
$transferMap = $repository->findImportEntry($importMap, 'Transfer', $transferId);
/*
* Either may be null:
*/
if (is_null($categoryMap) || is_null($transferMap)) {
\Log::notice('No map found in category/transfer mapper. Release.');
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Find the budget and the transaction:
*/
$category = $this->find($categoryMap->new);
/** @var \TransactionJournal $journal */
$journal = $journals->find($transferMap->new);
/*
* If either is null, release:
*/
if (is_null($category) || is_null($journal)) {
\Log::notice('Map is incorrect in category/transfer mapper. Release.');
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Update journal to have budget:
*/
$journal->categories()->save($category);
$journal->save();
\Log::debug('Connected category "' . $category->name . '" to journal "' . $journal->description . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/**
* @param $category
* @param \User $user
*
* @return bool|mixed
* @return mixed|void
*/
public function destroy($category)
public function overruleUser(\User $user)
{
$category->delete();
$this->_user = $user;
return true;
}
@@ -61,6 +141,164 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
return $this->_user->categories()->find($categoryId);
}
/**
* Takes a transaction/category component and updates the transaction journal to match.
*
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importUpdateTransaction(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
if ($job->attempts() > 10) {
\Log::error('Never found category/transaction combination "' . $payload['data']['transaction_id'] . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed.
return;
}
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $journals */
$journals = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
$journals->overruleUser($user);
/*
* Prep some vars from the payload
*/
$transactionId = intval($payload['data']['transaction_id']);
$componentId = intval($payload['data']['component_id']);
/*
* Find the import map for both:
*/
$categoryMap = $repository->findImportEntry($importMap, 'Category', $componentId);
$transactionMap = $repository->findImportEntry($importMap, 'Transaction', $transactionId);
/*
* Either may be null:
*/
if (is_null($categoryMap) || is_null($transactionMap)) {
\Log::notice('No map found in category/transaction mapper. Release.');
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Find the budget and the transaction:
*/
$category = $this->find($categoryMap->new);
/** @var \TransactionJournal $journal */
$journal = $journals->find($transactionMap->new);
/*
* If either is null, release:
*/
if (is_null($category) || is_null($journal)) {
\Log::notice('Map is incorrect in category/transaction mapper. Release.');
if (\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Update journal to have budget:
*/
$journal->categories()->save($category);
$journal->save();
\Log::debug('Connected category "' . $category->name . '" to journal "' . $journal->description . '"');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importCategory(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
/*
* Maybe the category has already been imported
*/
$importEntry = $repository->findImportEntry($importMap, 'Category', intval($payload['data']['id']));
/*
* if so, delete job and return:
*/
if (!is_null($importEntry)) {
\Log::debug('Already imported category ' . $payload['data']['name']);
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/*
* try to find category first
*/
$current = $this->findByName($payload['data']['name']);
/*
* If not found, create it:
*/
if (is_null($current)) {
$category = $this->store($payload['data']);
$repository->store($importMap, 'Category', $payload['data']['id'], $category->id);
\Log::debug('Imported category "' . $payload['data']['name'] . '".');
} else {
$repository->store($importMap, 'Category', $payload['data']['id'], $current->id);
\Log::debug('Already had category "' . $payload['data']['name'] . '".');
}
// update map:
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/**
* @param $name
*
@@ -72,18 +310,10 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
return null;
}
return $this->_user->categories()->where('name', 'LIKE', '%' . $name . '%')->first();
return $this->_user->categories()->where('name', $name)->first();
}
/**
* @return mixed
*/
public function get()
{
return $this->_user->categories()->orderBy('name', 'ASC')->get();
}
/**
* @param $data
*
@@ -100,6 +330,44 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
return $category;
}
/**
* @param $name
*
* @return \Category|mixed
*/
public function firstOrCreate($name)
{
if (strlen($name) == 0) {
return null;
}
$data = [
'name' => $name,
'user_id' => $this->_user->id,
];
return \Category::firstOrCreate($data);
}
/**
* @param $category
*
* @return bool|mixed
*/
public function destroy($category)
{
$category->delete();
return true;
}
/**
* @return mixed
*/
public function get()
{
return $this->_user->categories()->orderBy('name', 'ASC')->get();
}
/**
* @param $category
* @param $data
@@ -116,14 +384,4 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
return $category;
}
/**
* @param \User $user
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
}

View File

@@ -1,37 +0,0 @@
<?php
namespace Firefly\Storage\Component;
/**
* Interface ComponentRepositoryInterface
*
* @package Firefly\Storage\Component
*/
interface ComponentRepositoryInterface
{
/**
* @return mixed
*/
public function count();
/**
* @return mixed
*/
public function get();
/**
* @param $data
*
* @return mixed
*/
public function store($data);
/**
* @param \User $user
* @return mixed
*/
public function overruleUser(\User $user);
}

View File

@@ -1,89 +0,0 @@
<?php
namespace Firefly\Storage\Component;
use Firefly\Exception\FireflyException;
use Illuminate\Database\QueryException;
/**
* Class EloquentComponentRepository
*
* @package Firefly\Storage\Component
*/
class EloquentComponentRepository implements ComponentRepositoryInterface
{
public $validator;
protected $_user = null;
/**
*
*/
public function __construct()
{
$this->_user = \Auth::user();
}
/**
* @return mixed
*/
public function count()
{
return $this->_user->components()->count();
}
/**
* @return mixed|void
* @throws \Firefly\Exception\FireflyException
*/
public function get()
{
throw new FireflyException('No implementation.');
}
/**
* @param \User $user
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
/**
* @param $data
*
* @return \Budget|\Category|mixed
* @throws \Firefly\Exception\FireflyException
*/
public function store($data)
{
if (!isset($data['class'])) {
throw new FireflyException('No class type present.');
}
switch ($data['class']) {
default:
case 'Budget':
$component = new \Budget;
break;
case 'Category':
$component = new \Category;
break;
}
$component->name = $data['name'];
$component->user()->associate($this->_user);
try {
$component->save();
} catch (QueryException $e) {
\Log::error('DB ERROR: ' . $e->getMessage());
throw new FireflyException('Could not save component ' . $data['name'] . ' of type'
. $data['class']);
}
return $component;
}
}

View File

@@ -19,10 +19,28 @@ interface ImportRepositoryInterface
*/
public function store(\Importmap $map, $class, $oldID, $newID);
/**
* @param $id
*
* @return mixed
*/
public function findImportMap($id);
/**
* @param \Importmap $map
* @param $class
* @param $oldID
*
* @return mixed
*/
public function findImportEntry(\Importmap $map, $class, $oldID);
/**
* @param \Importmap $map
* @param $oldComponentId
*
* @return mixed
*/
public function findImportComponentMap(\Importmap $map, $oldComponentId);
/**

View File

@@ -4,6 +4,7 @@ namespace Firefly\Storage\Limit;
use Carbon\Carbon;
use Illuminate\Queue\Jobs\Job;
/**
* Class EloquentLimitRepository
@@ -23,52 +24,103 @@ class EloquentLimitRepository implements LimitRepositoryInterface
}
/**
* @param \Limit $limit
*
* @return bool
*/
public function destroy(\Limit $limit)
{
$limit->delete();
return true;
}
/**
* @param $limitId
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function find($limitId)
public function importLimit(Job $job, array $payload)
{
return \Limit::with('limitrepetitions')->where('limits.id', $limitId)->leftJoin(
'components', 'components.id', '=', 'limits.component_id'
)
->where('components.user_id', $this->_user->id)->first(['limits.*']);
}
public function findByBudgetAndDate(\Budget $budget, Carbon $date)
{
return \Limit::whereComponentId($budget->id)->where('startdate', $date->format('Y-m-d'))->first();
}
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/**
* @param \Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return mixed
*/
public function getTJByBudgetAndDateRange(\Budget $budget, Carbon $start, Carbon $end)
{
$result = $budget->transactionjournals()->with('transactions')->after($start)->before($end)->get();
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
return $result;
if ($job->attempts() > 10) {
\Log::error(
'No budget found for limit #' . $payload['data']['id'] . '. Prob. for another component. KILL!'
);
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed.
return;
}
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgets */
$budgets = \App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
$budgets->overruleUser($user);
/*
* Find the budget this limit is part of:
*/
$importEntry = $repository->findImportEntry($importMap, 'Budget', intval($payload['data']['component_id']));
/*
* There is no budget (yet?)
*/
if (is_null($importEntry)) {
$componentId = intval($payload['data']['component_id']);
\Log::warning('Budget #' . $componentId . ' not found. Requeue import job.');
if(\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* Find budget import limit is for:
*/
$budget = $budgets->find($importEntry->new);
if (!is_null($budget)) {
/*
* Is actual limit already imported?
*/
$limit = $this->findByBudgetAndDate($budget, new Carbon($payload['data']['date']));
if (is_null($limit)) {
/*
* It isn't imported yet.
*/
$payload['data']['budget_id'] = $budget->id;
$payload['data']['startdate'] = $payload['data']['date'];
$payload['data']['period'] = 'monthly';
/*
* Store limit, and fire event for LimitRepetition.
*/
$limit = $this->store($payload['data']);
$repository->store($importMap, 'Limit', $payload['data']['id'], $limit->id);
\Event::fire('limits.store', [$limit]);
\Log::debug('Imported limit for budget ' . $budget->name);
} else {
/*
* Limit already imported:
*/
$repository->store($importMap, 'Budget', $payload['data']['id'], $limit->id);
}
} else {
\Log::error(print_r($importEntry,true));
\Log::error('Cannot import limit! Big bad error!');
}
// update map:
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
}
/**
* @param \User $user
*
* @return mixed|void
*/
public function overruleUser(\User $user)
@@ -77,6 +129,11 @@ class EloquentLimitRepository implements LimitRepositoryInterface
return true;
}
public function findByBudgetAndDate(\Budget $budget, Carbon $date)
{
return \Limit::whereComponentId($budget->id)->where('startdate', $date->format('Y-m-d'))->first();
}
/**
* @param $data
*
@@ -121,9 +178,9 @@ class EloquentLimitRepository implements LimitRepositoryInterface
// find existing:
$count = \Limit::
leftJoin('components', 'components.id', '=', 'limits.component_id')->where(
'components.user_id', $this->_user->id
'components.user_id', $this->_user->id
)->where('startdate', $date->format('Y-m-d'))->where('component_id', $data['budget_id'])->where(
'repeat_freq', $data['period']
'repeat_freq', $data['period']
)->count();
if ($count > 0) {
\Session::flash('error', 'There already is an entry for these parameters.');
@@ -144,6 +201,33 @@ class EloquentLimitRepository implements LimitRepositoryInterface
return $limit;
}
/**
* @param \Limit $limit
*
* @return bool
*/
public function destroy(\Limit $limit)
{
$limit->delete();
return true;
}
/**
* @param \Budget $budget
* @param Carbon $start
* @param Carbon $end
*
* @return mixed
*/
public function getTJByBudgetAndDateRange(\Budget $budget, Carbon $start, Carbon $end)
{
$result = $budget->transactionjournals()->with('transactions')->after($start)->before($end)->get();
return $result;
}
/**
* @param \Limit $limit
* @param $data

View File

@@ -3,6 +3,7 @@
namespace Firefly\Storage\Limit;
use Carbon\Carbon;
use Illuminate\Queue\Jobs\Job;
/**
* Interface LimitRepositoryInterface
@@ -12,6 +13,14 @@ use Carbon\Carbon;
interface LimitRepositoryInterface
{
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importLimit(Job $job, array $payload);
/**
* @param \Limit $limit
*
@@ -19,24 +28,19 @@ interface LimitRepositoryInterface
*/
public function destroy(\Limit $limit);
/**
* @param $limitId
*
* @return mixed
*/
public function find($limitId);
/**
* @param \Budget $budget
* @param Carbon $date
* @param Carbon $date
*
* @return mixed
*/
public function findByBudgetAndDate(\Budget $budget, Carbon $date);
/**
* @param \Budget $budget
* @param Carbon $start
* @param Carbon $end
* @param Carbon $start
* @param Carbon $end
*
* @return mixed
*/
@@ -59,6 +63,7 @@ interface LimitRepositoryInterface
/**
* @param \User $user
*
* @return mixed
*/
public function overruleUser(\User $user);

View File

@@ -4,6 +4,8 @@ namespace Firefly\Storage\Piggybank;
use Carbon\Carbon;
use Firefly\Exception\FireflyException;
use Illuminate\Queue\Jobs\Job;
use Illuminate\Support\Collection;
/**
@@ -25,19 +27,140 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
}
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function count()
public function importPiggybank(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
if ($job->attempts() > 10) {
\Log::error('No account available for piggy bank "' . $payload['data']['name'] . '". KILL!');
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$accounts->overruleUser($user);
/*
* Maybe the piggy bank has already been imported
*/
$importEntry = $repository->findImportEntry($importMap, 'Piggybank', intval($payload['data']['id']));
/*
* if so, delete job and return:
*/
if (!is_null($importEntry)) {
\Log::debug('Already imported piggy bank ' . $payload['data']['name']);
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
/*
* Try to find related piggybank:
*/
$piggyBank = $this->findByName($payload['data']['name']);
/*
* Find an account (any account, really, at this point).
*/
$accountType = $accounts->findAccountType('Asset account');
/** @var Collection $set */
$set = $accounts->getByAccountType($accountType);
/*
* If there is an account to attach to this piggy bank, simply use that one.
*/
if ($set->count() > 0) {
/** @var \Account $account */
$account = $set->first();
$payload['data']['account_id'] = $account->id;
} else {
\Log::notice('No account available yet for piggy bank "' . $payload['data']['name'] . '".');
if(\Config::get('queue.default') == 'sync') {
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
} else {
$job->release(300); // proper release.
}
return;
}
/*
* No existing piggy bank, create it:
*/
if (is_null($piggyBank)) {
$payload['data']['targetamount'] = floatval($payload['data']['target']);
$payload['data']['repeats'] = 0;
$payload['data']['rep_every'] = 1;
$payload['data']['reminder_skip'] = 1;
$payload['data']['rep_times'] = 1;
$piggyBank = $this->store($payload['data']);
/*
* Store and fire event.
*/
$repository->store($importMap, 'Piggybank', intval($payload['data']['id']), $piggyBank->id);
\Log::debug('Imported piggy "' . $payload['data']['name'] . '".');
\Event::fire('piggybanks.store', [$piggyBank]);
} else {
/*
* Already have a piggy bank with this name, we skip it.
*/
$repository->store($importMap, 'Piggybank', $payload['data']['id'], $piggyBank->id);
\Log::debug('Already imported piggy "' . $payload['data']['name'] . '".');
}
// update map:
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
}
/**
* @param \User $user
*
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
public function findByName($piggyBankName)
{
return \Piggybank::leftJoin('accounts', 'accounts.id', '=', 'piggybanks.account_id')->where(
'accounts.user_id', $this->_user->id
)->count();
'accounts.user_id', $this->_user->id
)->where('piggybanks.name', $piggyBankName)->first(['piggybanks.*']);
}
public function countNonrepeating()
{
return \Piggybank::leftJoin('accounts', 'accounts.id', '=', 'piggybanks.account_id')->where(
'accounts.user_id', $this->_user->id
'accounts.user_id', $this->_user->id
)->where('repeats', 0)->count();
}
@@ -45,7 +168,7 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
public function countRepeating()
{
return \Piggybank::leftJoin('accounts', 'accounts.id', '=', 'piggybanks.account_id')->where(
'accounts.user_id', $this->_user->id
'accounts.user_id', $this->_user->id
)->where('repeats', 1)->count();
}
@@ -69,17 +192,10 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
public function find($piggyBankId)
{
return \Piggybank::leftJoin('accounts', 'accounts.id', '=', 'piggybanks.account_id')->where(
'accounts.user_id', $this->_user->id
'accounts.user_id', $this->_user->id
)->where('piggybanks.id', $piggyBankId)->first(['piggybanks.*']);
}
public function findByName($piggyBankName)
{
return \Piggybank::leftJoin('accounts', 'accounts.id', '=', 'piggybanks.account_id')->where(
'accounts.user_id', $this->_user->id
)->where('piggybanks.name', $piggyBankName)->first(['piggybanks.*']);
}
/**
* @return mixed
*/
@@ -87,10 +203,6 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
{
$piggies = $this->_user->piggybanks()->with(['account', 'piggybankrepetitions'])->get();
foreach ($piggies as $pig) {
$pig->leftInAccount = $this->leftOnAccount($pig->account);
}
return $piggies;
}
@@ -130,16 +242,6 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
}
/**
* @param \User $user
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
/**
* @param $data
*
@@ -160,7 +262,7 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$accounts->overruleUser($this->_user);
$account = isset($data['account_id']) ? $accounts->find($data['account_id']) : null;
$account = isset($data['account_id']) ? $accounts->find($data['account_id']) : null;
$piggyBank = new \Piggybank($data);
@@ -216,7 +318,7 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
}
if ($firstReminder > $piggyBank->targetdate) {
$piggyBank->errors()->add(
'reminder', 'The reminder has been set to remind you after the piggy bank will expire.'
'reminder', 'The reminder has been set to remind you after the piggy bank will expire.'
);
\Log::error('PiggyBank create-error: ' . $piggyBank->errors()->first());
@@ -241,7 +343,7 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$accounts->overruleUser($this->_user);
$account = isset($data['account_id']) ? $accounts->find($data['account_id']) : null;
$account = isset($data['account_id']) ? $accounts->find($data['account_id']) : null;
if (!is_null($account)) {
$piggy->account()->associate($account);
@@ -253,7 +355,8 @@ class EloquentPiggybankRepository implements PiggybankRepositoryInterface
$piggy->reminder_skip = $data['reminder_skip'];
$piggy->targetdate = strlen($data['targetdate']) > 0 ? new Carbon($data['targetdate']) : null;
$piggy->startdate
= isset($data['startdate']) && strlen($data['startdate']) > 0 ? new Carbon($data['startdate']) : null;
=
isset($data['startdate']) && strlen($data['startdate']) > 0 ? new Carbon($data['startdate']) : null;
foreach ($piggy->piggybankrepetitions()->get() as $rep) {

View File

@@ -2,6 +2,8 @@
namespace Firefly\Storage\Piggybank;
use Illuminate\Queue\Jobs\Job;
/**
* Interface LimitRepositoryInterface
@@ -12,9 +14,12 @@ interface PiggybankRepositoryInterface
{
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function count();
public function importPiggybank(Job $job, array $payload);
/**
* @return mixed
@@ -81,6 +86,7 @@ interface PiggybankRepositoryInterface
/**
* @param \User $user
*
* @return mixed
*/
public function overruleUser(\User $user);

View File

@@ -4,6 +4,8 @@
namespace Firefly\Storage\RecurringTransaction;
use Carbon\Carbon;
use Illuminate\Queue\Jobs\Job;
use Illuminate\Support\MessageBag;
/**
* Class EloquentRecurringTransactionRepository
@@ -13,16 +15,6 @@ use Carbon\Carbon;
class EloquentRecurringTransactionRepository implements RecurringTransactionRepositoryInterface
{
/**
* @param \User $user
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
protected $_user = null;
/**
@@ -45,11 +37,6 @@ class EloquentRecurringTransactionRepository implements RecurringTransactionRepo
return true;
}
public function findByName($name)
{
return $this->_user->recurringtransactions()->where('name', 'LIKE', '%' . $name . '%')->first();
}
/**
* @return mixed
*/
@@ -58,70 +45,184 @@ class EloquentRecurringTransactionRepository implements RecurringTransactionRepo
return $this->_user->recurringtransactions()->get();
}
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importPredictable(Job $job, array $payload)
{
/** @var \Firefly\Storage\Import\ImportRepositoryInterface $repository */
$repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
/** @var \Importmap $importMap */
$importMap = $repository->findImportmap($payload['mapID']);
$user = $importMap->user;
$this->overruleUser($user);
/*
* maybe the recurring transaction is already imported:
*/
$oldId = intval($payload['data']['id']);
$description = $payload['data']['description'];
$importEntry = $repository->findImportEntry($importMap, 'RecurringTransaction', $oldId);
/*
* if so, delete job and return:
*/
if (!is_null($importEntry)) {
\Log::debug('Already imported recurring transaction #' . $payload['data']['id']);
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
return;
}
// try to find related recurring transaction:
$recurringTransaction = $this->findByName($payload['data']['description']);
if (is_null($recurringTransaction)) {
$amount = floatval($payload['data']['amount']);
$pct = intval($payload['data']['pct']);
$set = [
'name' => $description,
'match' => join(',', explode(' ', $description)),
'amount_min' => $amount * ($pct / 100) * -1,
'amount_max' => $amount * (1 + ($pct / 100)) * -1,
'date' => date('Y-m-') . $payload['data']['dom'],
'repeat_freq' => 'monthly',
'active' => intval($payload['data']['inactive']) == 1 ? 0 : 1,
'automatch' => 1,
];
$recurringTransaction = $this->store($set);
$this->store($importMap, 'RecurringTransaction', $oldId, $recurringTransaction->id);
\Log::debug('Imported predictable ' . $description);
} else {
$this->store($importMap, 'RecurringTransaction', $oldId, $recurringTransaction->id);
\Log::debug('Already had predictable ' . $description);
}
// update map:
$importMap->jobsdone++;
$importMap->save();
$job->delete(); // count fixed
}
/**
* @param \User $user
*
* @return mixed|void
*/
public function overruleUser(\User $user)
{
$this->_user = $user;
return true;
}
public function findByName($name)
{
return $this->_user->recurringtransactions()->where('name', 'LIKE', '%' . $name . '%')->first();
}
/**
* @param $data
*
* @return mixed|\RecurringTransaction
* @return MessageBag
*/
public function store($data)
{
$recurringTransaction = new \RecurringTransaction;
$recurringTransaction->user()->associate($this->_user);
$recurringTransaction->name = $data['name'];
$recurringTransaction->match = join(' ', explode(',', $data['match']));
$recurringTransaction->amount_max = floatval($data['amount_max']);
$recurringTransaction->amount_min = floatval($data['amount_min']);
$messageBag = new MessageBag;
$recurringTransaction = new \RecurringTransaction(
[
'user_id' => $this->_user->id,
'name' => $data['name'],
'match' => join(' ', explode(',', $data['match'])),
'amount_max' => floatval($data['amount_max']),
'amount_min' => floatval($data['amount_min']),
'date' => new Carbon($data['date']),
'active' => isset($data['active']) ? intval($data['active']) : 0,
'automatch' => isset($data['automatch']) ? intval($data['automatch']) : 0,
'skip' => isset($data['skip']) ? intval($data['skip']) : 0,
'repeat_freq' => $data['repeat_freq'],
]
);
// both amounts zero:
if ($recurringTransaction->amount_max == 0 && $recurringTransaction->amount_min == 0) {
$recurringTransaction->errors()->add('amount_max', 'Amount max and min cannot both be zero.');
return $recurringTransaction;
// unique name?
$count = $this->_user->recurringtransactions()->whereName($data['name'])->count();
if ($count > 0) {
$messageBag->add('name', 'A recurring transaction with this name already exists.');
return $messageBag;
}
// both amounts zero?:
if ($recurringTransaction->amount_max == 0 && $recurringTransaction->amount_min == 0) {
$messageBag->add('amount_max', 'Amount max and min cannot both be zero.');
return $messageBag;
}
if ($recurringTransaction->amount_max < $recurringTransaction->amount_min) {
$messageBag->add('amount_max', 'Amount max must be more than amount min.');
return $messageBag;
}
if ($recurringTransaction->amount_min > $recurringTransaction->amount_max) {
$messageBag->add('amount_max', 'Amount min must be less than amount max.');
return $messageBag;
}
if ($recurringTransaction->date < Carbon::now()) {
$messageBag->add('date', 'Must be in the future.');
return $messageBag;
}
$recurringTransaction->date = new Carbon($data['date']);
$recurringTransaction->active = isset($data['active']) ? intval($data['active']) : 0;
$recurringTransaction->automatch = isset($data['automatch']) ? intval($data['automatch']) : 0;
$recurringTransaction->skip = isset($data['skip']) ? intval($data['skip']) : 0;
$recurringTransaction->repeat_freq = $data['repeat_freq'];
if ($recurringTransaction->validate()) {
$recurringTransaction->save();
} else {
$messageBag = $recurringTransaction->errors();
}
return $recurringTransaction;
return $messageBag;
}
/**
* @param \RecurringTransaction $recurringTransaction
* @param $data
*
* @return mixed|void
* @return MessageBag
*/
public function update(\RecurringTransaction $recurringTransaction, $data)
{
$recurringTransaction->name = $data['name'];
$recurringTransaction->match = join(' ', explode(',', $data['match']));
$messageBag = new MessageBag;
$recurringTransaction->name = $data['name'];
$recurringTransaction->match = join(' ', explode(',', $data['match']));
$recurringTransaction->amount_max = floatval($data['amount_max']);
$recurringTransaction->amount_min = floatval($data['amount_min']);
// both amounts zero:
if ($recurringTransaction->amount_max == 0 && $recurringTransaction->amount_min == 0) {
$recurringTransaction->errors()->add('amount_max', 'Amount max and min cannot both be zero.');
$messageBag->add('amount_max', 'Amount max and min cannot both be zero.');
return $recurringTransaction;
return $messageBag;
}
$recurringTransaction->date = new Carbon($data['date']);
$recurringTransaction->active = isset($data['active']) ? intval($data['active']) : 0;
$recurringTransaction->automatch = isset($data['automatch']) ? intval($data['automatch']) : 0;
$recurringTransaction->skip = isset($data['skip']) ? intval($data['skip']) : 0;
$recurringTransaction->date = new Carbon($data['date']);
$recurringTransaction->active = isset($data['active']) ? intval($data['active']) : 0;
$recurringTransaction->automatch = isset($data['automatch']) ? intval($data['automatch']) : 0;
$recurringTransaction->skip = isset($data['skip']) ? intval($data['skip']) : 0;
$recurringTransaction->repeat_freq = $data['repeat_freq'];
if ($recurringTransaction->validate()) {
$recurringTransaction->save();
} else {
$messageBag = $recurringTransaction->errors();
}
return $recurringTransaction;
return $messageBag;
}

View File

@@ -2,6 +2,8 @@
namespace Firefly\Storage\RecurringTransaction;
use Illuminate\Queue\Jobs\Job;
use Illuminate\Support\MessageBag;
/**
* Interface RecurringTransactionRepositoryInterface
@@ -11,6 +13,14 @@ namespace Firefly\Storage\RecurringTransaction;
interface RecurringTransactionRepositoryInterface
{
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function importPredictable(Job $job, array $payload);
/**
* @return mixed
*/
@@ -25,7 +35,7 @@ interface RecurringTransactionRepositoryInterface
/**
* @param $data
*
* @return mixed
* @return MessageBag
*/
public function store($data);
@@ -40,7 +50,7 @@ interface RecurringTransactionRepositoryInterface
* @param \RecurringTransaction $recurringTransaction
* @param $data
*
* @return mixed
* @return MessageBag
*/
public function update(\RecurringTransaction $recurringTransaction, $data);

View File

@@ -44,38 +44,4 @@ class EloquentReminderRepository implements ReminderRepositoryInterface
return $reminder;
}
/**
* @param $id
*
* @return mixed|void
*/
public function find($id)
{
return \Reminder::find($id);
}
/**
* @return mixed
*/
public function get()
{
$today = new Carbon;
return $this->_user->reminders()->validOn($today)->get();
}
/**
*
*/
public function getCurrentRecurringReminders()
{
$today = new Carbon;
return $this->_user->reminders()->with('recurringtransaction')->validOn($today)->where(
'class', 'RecurringTransactionReminder'
)->get();
}
}
}

View File

@@ -1,11 +1,4 @@
<?php
/**
* Created by PhpStorm.
* User: sander
* Date: 23/08/14
* Time: 20:59
*/
namespace Firefly\Storage\Reminder;
@@ -24,20 +17,6 @@ interface ReminderRepositoryInterface
*/
public function deactivate(\Reminder $reminder);
/**
* @return mixed
*/
public function get();
/**
* @param $id
*
* @return mixed
*/
public function find($id);
public function getCurrentRecurringReminders();
/**
* @param \User $user

View File

@@ -55,11 +55,6 @@ class StorageServiceProvider extends ServiceProvider
'Firefly\Storage\TransactionJournal\EloquentTransactionJournalRepository'
);
$this->app->bind(
'Firefly\Storage\Component\ComponentRepositoryInterface',
'Firefly\Storage\Component\EloquentComponentRepository'
);
$this->app->bind(
'Firefly\Storage\Limit\LimitRepositoryInterface',
'Firefly\Storage\Limit\EloquentLimitRepository'

View File

@@ -3,6 +3,7 @@
namespace Firefly\Storage\TransactionJournal;
use Carbon\Carbon;
use Illuminate\Queue\Jobs\Job;
/**
* Interface TransactionJournalRepositoryInterface
@@ -12,40 +13,51 @@ use Carbon\Carbon;
interface TransactionJournalRepositoryInterface
{
/**
* @param \Account $from
* @param \Account $toAccount
* @param $description
* @param $amount
* @param Carbon $date
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function createSimpleJournal(\Account $from, \Account $toAccount, $description, $amount, Carbon $date);
public function importTransaction(Job $job, array $payload);
/**
* @param Job $job
* @param array $payload
*
* @return mixed
*/
public function get();
public function importTransfer(Job $job, array $payload);
/**
* @param \User $user
*
* @return mixed
*/
public function overruleUser(\User $user);
/**
* @param $what
* Store a new transaction journal.
*
* @param $data
*
* @return \TransactionJournal|null
*/
public function store(array $data);
/**
* @param \TransactionJournal $journal
* @param \Account $account
* @param $amount
*
* @return mixed
*/
public function store($what, $data);
public function saveTransaction(\TransactionJournal $journal, \Account $account, $amount);
/**
* @param \TransactionJournal $journal
* @param $data
*
* @return mixed
* @return \Transaction|null
*/
public function update(\TransactionJournal $journal, $data);
@@ -58,27 +70,11 @@ interface TransactionJournalRepositoryInterface
/**
* @param \Account $account
* @param int $count
* @param Carbon $start
* @param Carbon $end
* @param int $count
* @param Carbon $start
* @param Carbon $end
*
* @return mixed
*/
public function getByAccountInDateRange(\Account $account, $count = 25, Carbon $start, Carbon $end);
/**
* @param \Account $account
* @param Carbon $date
*
* @return mixed
*/
public function getByAccountAndDate(\Account $account, Carbon $date);
/**
* @param int $count
*
* @return mixed
*/
public function paginate($count = 25, Carbon $start = null, Carbon $end = null);
}

View File

@@ -17,22 +17,6 @@ class EloquentUserRepository implements UserRepositoryInterface
{
}
/**
* @param $array
*
* @return bool
*/
public function auth($array)
{
$user = \User::where('email', $array['email'])->first();
if (!is_null($user)) {
if (\Hash::check($array['password'], $user->password)) {
}
}
return false;
}
/**
* @param $email
*

View File

@@ -17,13 +17,6 @@ interface UserRepositoryInterface
*/
public function register($array);
/**
* @param $array
*
* @return mixed
*/
public function auth($array);
/**
* @param $reset
*

Some files were not shown because too many files have changed in this diff Show More