Compare commits

...

131 Commits
3.0.3 ... 3.1

Author SHA1 Message Date
Sander Dorigo
81662473a6 Merge branch 'release/3.1' 2014-10-11 12:11:17 +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
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
242 changed files with 11029 additions and 8000 deletions

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

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

@@ -22,14 +22,73 @@ class AccountController extends \BaseController
{
$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
);
}
/**
@@ -40,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 . '"'
);
}
/**
@@ -50,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');
}
@@ -65,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 . '"');
}
/**
@@ -75,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');
}
/**
@@ -101,11 +174,26 @@ 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 . '"'
'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,7 @@
<?php
use Carbon\Carbon;
use Firefly\Exception\FireflyException;
use Firefly\Helper\Controllers\BudgetInterface as BI;
use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
@@ -25,6 +26,40 @@ class BudgetController extends BaseController
{
$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');
}
/**
@@ -34,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');
}
/**
@@ -45,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 . '"');
}
/**
@@ -76,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 . '"');
}
@@ -85,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');
}
@@ -97,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');
}
@@ -152,7 +191,7 @@ class BudgetController extends BaseController
->with('view', $view)
->with('highlight', Input::get('highlight'))
->with('useSessionDates', $useSessionDates)
->with('title', $title);
->with('subTitle', 'Overview for ' . $title);
}
/**

View File

@@ -21,6 +21,8 @@ class CategoryController extends BaseController
{
$this->_repository = $repository;
$this->_category = $category;
View::share('title','Categories');
View::share('mainTitleIcon', 'fa-bar-chart');
}
/**
@@ -28,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');
}
/**
@@ -39,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 . '"');
}
/**
@@ -62,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 . '"');
}
/**
@@ -73,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');
}
/**
@@ -91,7 +93,7 @@ class CategoryController extends BaseController
return View::make('categories.show')->with('category', $category)->with('journals', $journals)->with(
'highlight', Input::get('highlight')
)->with('title', 'Overview for category "' . $category->name . '"');
)->with('subTitle', 'Overview for category "' . $category->name . '"');
}
/**

View File

@@ -46,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)];
@@ -372,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

@@ -30,6 +30,35 @@ class HomeController extends BaseController
$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');
}
/**
* @return \Illuminate\Http\RedirectResponse
*/
@@ -71,15 +100,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);
}
// build the home screen:
return View::make('index')->with('count', $count)->with('transactions', $transactions);
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,9 +1,8 @@
<?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
@@ -12,32 +11,48 @@ use Firefly\Storage\Component\ComponentRepositoryInterface as CRI;
*/
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;
}
@@ -47,18 +62,105 @@ 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 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

@@ -22,6 +22,9 @@ class LimitController extends BaseController
{
$this->_budgets = $budgets;
$this->_limits = $limits;
View::share('title','Envelopes');
View::share('mainTitleIcon', 'fa-tasks');
}
/**
@@ -38,11 +41,13 @@ class LimitController extends BaseController
'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');
}
/**
@@ -52,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');
}
/**
@@ -84,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');
}
/**

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,13 +21,10 @@ 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);
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]);

View File

@@ -15,8 +15,8 @@ use Firefly\Storage\Piggybank\PiggybankRepositoryInterface as PRI;
class PiggybankController extends BaseController
{
protected $_repository;
protected $_accounts;
protected $_repository;
/**
* @param PRI $repository
@@ -49,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);
}
/**
@@ -60,8 +70,16 @@ class PiggybankController extends BaseController
*/
public function createRepeated()
{
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
$periods = Config::get('firefly.piggybank_periods');
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
$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);
}
@@ -73,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);
}
@@ -84,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);
}
/**
@@ -98,12 +132,28 @@ class PiggybankController extends BaseController
*/
public function edit(Piggybank $piggyBank)
{
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
/** @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);
}
@@ -111,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; 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])) {
$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
*
@@ -175,8 +196,47 @@ 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);
}
/**
@@ -195,6 +255,41 @@ 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);
}
/**
*
*/
@@ -203,6 +298,18 @@ class PiggybankController extends BaseController
$leftOnAccount = $this->_repository->leftOnAccount($piggyBank->account);
$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);
}
@@ -226,7 +333,7 @@ class PiggybankController extends BaseController
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 {
@@ -254,8 +361,7 @@ class PiggybankController extends BaseController
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());
@@ -274,10 +380,19 @@ class PiggybankController extends BaseController
{
$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

@@ -22,6 +22,8 @@ class PreferencesController extends BaseController
$this->_accounts = $accounts;
$this->_preferences = $preferences;
View::share('title','Preferences');
View::share('mainTitleIcon','fa-gear');
}
/**

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,6 +1,8 @@
<?php
use Firefly\Exception\FireflyException;
use Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface as RTR;
use Firefly\Helper\Controllers\RecurringInterface as RI;
/**
* Class RecurringController
@@ -10,13 +12,19 @@ use Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface
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');
}
/**
@@ -24,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);
@@ -36,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);
}
@@ -46,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.');
@@ -67,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
);
@@ -77,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');
}
/**
@@ -87,55 +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
*
* @return $this|\Illuminate\Http\RedirectResponse
*/
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

@@ -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,63 +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();
$budgets[0] = '(no budget)';
// get the piggy banks.
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
$piggies = $piggyRepository->get();
return View::make('transactions.create')->with('accounts', $accounts)->with('budgets', $budgets)->with(
// 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.
$piggies = $toolkit->makeSelectList($piggyRepository->get());
$piggies[0] = '(no piggy bank)';
/*
* 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 . '"'
);
}
@@ -70,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[0] = '(no budget)';
$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);
$budget = $journal->budgets()->first();
/*
* 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');
}
/**
@@ -176,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

@@ -47,8 +47,6 @@ class UserController extends BaseController
];
$result = Auth::attempt($data, $rememberMe);
if ($result) {
Session::flash('success', 'Logged in!');
return Redirect::route('index');
}

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;

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')
@@ -54,7 +54,7 @@ class Account implements AccountInterface
// build a query:
$query = \TransactionJournal::withRelevantData()
->defaultSorting()
->account($account)
->accountIs($account)
->after($start)
->before($end);
// filter some:
@@ -110,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

@@ -102,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

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

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

@@ -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,129 @@
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()
{
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');
public function checkImportJobs() {
/*
* 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
@@ -179,9 +168,8 @@ class Toolkit implements ToolkitInterface
*
* @return Carbon
*/
protected function _updateEndDate($range, Carbon $start, Carbon $end)
protected function _updateEndDate($range, Carbon $start)
{
$today = new Carbon;
switch ($range) {
case '1D':
$end = clone $start;
@@ -201,128 +189,158 @@ class Toolkit implements ToolkitInterface
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;
default:
throw new FireflyException('Nothing happened with $end!');
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 '1M':
return $date->format('F 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);
if (intval($date->format('m')) >= 7) {
$date->startOfYear();
} else {
$date->startOfYear()->subMonths(6);
}
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();
break;
case '6M':
$end->subMonths(6);
if (intval($date->format('m')) >= 7) {
$date->startOfYear()->addYear();
} else {
$date->startOfYear()->addMonths(6);
}
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,48 +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();
}
/**
* @return mixed
*/
public function getPiggybankReminders()
{
$today = new Carbon;
return $this->_user->reminders()->where('class','PiggybankReminder')->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
*

View File

@@ -20,53 +20,46 @@ class EloquentJournalTrigger
*/
public function store(\TransactionJournal $journal)
{
// select all reminders for recurring transactions:
if ($journal->transaction_type->type == 'Withdrawal') {
\Log::debug('Trigger on the creation of a withdrawal');
$transaction = $journal->transactions()->orderBy('amount', 'DESC')->first();
$amount = floatval($transaction->amount);
$description = strtolower($journal->description);
$beneficiary = strtolower($transaction->account->name);
/*
* Grab all recurring events.
*/
$set = $journal->user()->first()->recurringtransactions()->get();
$result = [];
/*
* Prep vars
*/
$description = strtolower($journal->description);
// make an array of parts:
$parts = explode(' ', $description);
$parts[] = $beneficiary;
$today = new Carbon;
$set = \RecurringTransactionReminder::
leftJoin(
'recurring_transactions', 'recurring_transactions.id', '=', 'reminders.recurring_transaction_id'
)
->where('startdate', '<', $today->format('Y-m-d'))
->where('enddate', '>', $today->format('Y-m-d'))
->where('amount_min', '<=', $amount)
->where('amount_max', '>=', $amount)->get(['reminders.*']);
/** @var \RecurringTransctionReminder $reminder */
\Log::debug('Have these parts to search for: ' . join('/',$parts));
\Log::debug('Found ' . count($set).' possible matching recurring transactions');
foreach ($set as $index => $reminder) {
/** @var \RecurringTransaction $RT */
$RT = $reminder->recurring_transaction;
$matches = explode(' ', strtolower($RT->match));
\Log::debug($index.': ' . join('/',$matches));
$matchCount = 0;
foreach ($parts as $part) {
if (in_array($part, $matches)) {
$matchCount++;
}
}
if ($matchCount >= count($matches)) {
// we have a match!
\Log::debug(
'Match between new journal "' . join('/', $parts) . '" and RT ' . join('/', $matches) . '.'
);
$journal->recurringTransaction()->associate($RT);
$journal->save();
// also update the reminder.
$reminder->active = 0;
$reminder->save();
return true;
/** @var \RecurringTransaction $recurring */
foreach ($set as $recurring) {
$matches = explode(' ', $recurring->match);
/*
* Count the number of matches.
*/
$count = 0;
foreach ($matches as $word) {
if (!(strpos($description, $word) === false)) {
$count++;
\Log::debug('Recurring transaction #' . $recurring->id . ': word "' . $word . '" found in "' . $description . '".');
}
}
$result[$recurring->id] = $count;
}
/*
* The one with the highest value is the winrar!
*/
$index = array_search(max($result), $result);
/*
* Find the recurring transaction:
*/
if (count($result[$index]) > 0) {
$winner = $journal->user()->first()->recurringtransactions()->find($index);
if ($winner) {
$journal->recurringTransaction()->associate($winner);
$journal->save();
}
}
return true;

View File

@@ -85,7 +85,7 @@ class EloquentLimitTrigger
*/
public function madeRepetition(\LimitRepetition $repetition)
{
\Log::info('TRIGGER: Created a limit repetition (#' . $repetition->id . ')');
\Log::debug('TRIGGER: Created a limit repetition (#' . $repetition->id . ')');
}
/**

View File

@@ -18,14 +18,6 @@ class EloquentRecurringTrigger
*/
public function destroy(\RecurringTransaction $recurring)
{
$reminders = $recurring->recurringtransactionreminders()->get();
/** @var \RecurringTransactionReminder $reminder */
foreach ($reminders as $reminder) {
$reminder->delete();
}
return true;
}
/**
@@ -33,67 +25,11 @@ class EloquentRecurringTrigger
*/
public function store(\RecurringTransaction $recurring)
{
$this->createReminders();
}
public function createReminders()
{
$entries = \Auth::user()->recurringtransactions()->where('active', 1)->get();
// for each entry, check for existing reminders during their period:
/** @var \RecurringTransaction $entry */
foreach ($entries as $entry) {
$start = clone $entry->date;
$end = clone $entry->date;
switch ($entry->repeat_freq) {
case 'weekly':
$start->startOfWeek();
$end->endOfWeek();
break;
case 'monthly':
$start->startOfMonth();
$end->endOfMonth();
break;
case 'quarterly':
$start->firstOfQuarter();
$end->lastOfQuarter();
break;
case 'half-year':
// start of half-year:
if (intval($start->format('m')) >= 7) {
$start->startOfYear();
$start->addMonths(6);
} else {
$start->startOfYear();
}
$end = clone $start;
$end->addMonths(6);
break;
case 'yearly':
$start->startOfYear();
$end->endOfYear();
break;
}
// check if exists.
$count = $entry->reminders()->where('startdate', $start->format('Y-m-d'))->where(
'enddate', $end->format('Y-m-d')
)->count();
if ($count == 0) {
// create reminder:
$reminder = new \RecurringTransactionReminder;
$reminder->recurringtransaction()->associate($entry);
$reminder->startdate = $start;
$reminder->enddate = $end;
$reminder->active = 1;
$reminder->user()->associate(\Auth::user());
$reminder->save();
}
}
}
/**
@@ -103,10 +39,10 @@ class EloquentRecurringTrigger
*/
public function subscribe(Dispatcher $events)
{
$events->listen('recurring.destroy', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@destroy');
$events->listen('recurring.store', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@store');
$events->listen('recurring.update', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@update');
$events->listen('recurring.check', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@createReminders');
// $events->listen('recurring.destroy', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@destroy');
// $events->listen('recurring.store', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@store');
// $events->listen('recurring.update', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@update');
// $events->listen('recurring.check', 'Firefly\Trigger\Recurring\EloquentRecurringTrigger@createReminders');
}
/**
@@ -114,14 +50,5 @@ class EloquentRecurringTrigger
*/
public function update(\RecurringTransaction $recurring)
{
// remove old active reminders
$reminders = $recurring->reminders()->validOnOrAfter(new Carbon)->get();
foreach ($reminders as $r) {
$r->delete();
}
$this->createReminders();
// create new reminder for the current period.
// and now create new one(s)!
}
}

View File

@@ -12,7 +12,7 @@ class FireflyValidator extends Validator
{
public function validateAlphabasic($attribute, $value, $parameters)
{
$pattern = '/[^a-z_\-0-9 ]/i';
$pattern = '/[^[:alnum:]_\-\.\& \(\)\'"]/iu';
if (preg_match($pattern, $value)) {
return false;
} else {

View File

@@ -42,6 +42,8 @@ class Account extends Ardent
];
protected $fillable = ['name','user_id','account_type_id','active'];
/**
* Account type.
*

View File

@@ -27,11 +27,13 @@ class Component extends SingleTableInheritanceEntity
public static $rules
= [
'user_id' => 'exists:users,id|required',
'name' => ['required', 'between:1,100', 'alphabasic'],
'name' => ['required', 'between:1,100','min:1', 'alphabasic'],
'class' => 'required',
];
protected $table = 'components';
protected $subclassField = 'class';
protected $fillable = ['name','user_id'];
/**
* @return \Illuminate\Database\Eloquent\Relations\HasMany

View File

@@ -1,22 +1,36 @@
<?php
use Illuminate\Database\Eloquent\Model as Eloquent;
use LaravelBook\Ardent\Ardent as Ardent;
/**
* Class Importmap
*
* @property-read \User $user
* @property integer $id
* @property-read \User $user
* @property integer $id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property integer $user_id
* @property string $file
* @property integer $user_id
* @property string $file
* @method static \Illuminate\Database\Query\Builder|\Importmap whereId($value)
* @method static \Illuminate\Database\Query\Builder|\Importmap whereCreatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Importmap whereUpdatedAt($value)
* @method static \Illuminate\Database\Query\Builder|\Importmap whereUserId($value)
* @method static \Illuminate\Database\Query\Builder|\Importmap whereFile($value)
* @property integer $totaljobs
* @property integer $jobsdone
* @method static \Illuminate\Database\Query\Builder|\Importmap whereTotaljobs($value)
* @method static \Illuminate\Database\Query\Builder|\Importmap whereJobsdone($value)
*/
class Importmap extends Eloquent
class Importmap extends Ardent
{
public static $rules
= [
'user_id' => 'required|exists:users,id',
'file' => 'required',
'totaljobs' => 'numeric|required|min:0',
'jobsdone' => 'numeric|required|min:0',
];
/**
* User
*
@@ -26,4 +40,13 @@ class Importmap extends Eloquent
{
return $this->belongsTo('User');
}
public function pct()
{
if ($this->jobsdone == 0 || $this->totaljobs == 0) {
return 0;
} else {
return round((($this->jobsdone / $this->totaljobs) * 100), 1);
}
}
}

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