mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-09-18 02:30:37 +00:00
Compare commits
201 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
42458ce11d | ||
|
aceb683d07 | ||
|
b7517b49ed | ||
|
849b711b79 | ||
|
25585b28c7 | ||
|
073da8fb2a | ||
|
a787ff3f3c | ||
|
733b6d7eb7 | ||
|
36d8dee853 | ||
|
65a2e07d24 | ||
|
7c97c558ab | ||
|
a6bb61050c | ||
|
b184aa2315 | ||
|
e4595333e7 | ||
|
41dd139bde | ||
|
c577dd302a | ||
|
0ab87de78b | ||
|
8a22509b41 | ||
|
b024c18441 | ||
|
d9ac681a68 | ||
|
637a5579ec | ||
|
4794156e80 | ||
|
5f4db7874c | ||
|
b4ea1839a5 | ||
|
6a6d889983 | ||
|
287c2e7af8 | ||
|
0fe6acc8cf | ||
|
7d2dab7ca0 | ||
|
f68c1aff26 | ||
|
81662473a6 | ||
|
d40645be68 | ||
|
a53550537f | ||
|
223ad16616 | ||
|
3f060979d7 | ||
|
2eac9081ea | ||
|
b3eef4f40b | ||
|
dd70fbad3f | ||
|
8cb7a1aef8 | ||
|
a687140056 | ||
|
3cba673a9c | ||
|
01de230785 | ||
|
e405d06f23 | ||
|
d9b70f7ad8 | ||
|
0ef5825d98 | ||
|
1e76a5fc3f | ||
|
1fbdb3d0ae | ||
|
d5bcf5497f | ||
|
28aaea1aa3 | ||
|
980d9ce885 | ||
|
ec601efa6e | ||
|
b3209d3b4d | ||
|
4ce978b9f3 | ||
|
a84064663a | ||
|
6798cea268 | ||
|
8e86196352 | ||
|
1b3d345fbd | ||
|
7d2627515f | ||
|
aa9eb8ca64 | ||
|
9015d6ca16 | ||
|
217483639d | ||
|
78e80530d3 | ||
|
3bbecfe830 | ||
|
9ab3679d49 | ||
|
fc44a52ba5 | ||
|
bb2b71bdc0 | ||
|
b23d2a9d95 | ||
|
eeb773fd7b | ||
|
53a582f374 | ||
|
73110f6a51 | ||
|
5667663fef | ||
|
fb664ba17d | ||
|
0c10190a8e | ||
|
183a323ef6 | ||
|
90bada5497 | ||
|
7c043e1923 | ||
|
2720ae3c46 | ||
|
401508577e | ||
|
b0a31cebc2 | ||
|
95adb428fa | ||
|
f92a0310dd | ||
|
84f0cb3765 | ||
|
d49e6787d6 | ||
|
0884853a6f | ||
|
1967c63006 | ||
|
9461e7b70a | ||
|
f1e5df566c | ||
|
fb02a0d5ad | ||
|
e438a02fa3 | ||
|
b112452aa1 | ||
|
1a2fc81af3 | ||
|
38bbda982c | ||
|
41ad6e64d1 | ||
|
efcad0b935 | ||
|
e892b69a96 | ||
|
5dfc04e777 | ||
|
c119a42d70 | ||
|
802541b796 | ||
|
0770c79777 | ||
|
5f4669341e | ||
|
f15fc80233 | ||
|
a7d75ea94a | ||
|
ba4bddf756 | ||
|
6a26408552 | ||
|
c39c59fff5 | ||
|
c1ba8dc6a7 | ||
|
f2825da878 | ||
|
c61f1307d8 | ||
|
9e88d7a60d | ||
|
406ae25162 | ||
|
dbfb342021 | ||
|
4632142e06 | ||
|
9ae036f297 | ||
|
497b8c48c8 | ||
|
5d11949313 | ||
|
a91c9f04c5 | ||
|
4f3493f9ff | ||
|
49b8742082 | ||
|
69cee59e23 | ||
|
19402b9022 | ||
|
62ba40b687 | ||
|
f9af9a4fbe | ||
|
c2ab43d0ab | ||
|
af28e6e7b9 | ||
|
114ad7f292 | ||
|
44eb67f94e | ||
|
0203fee174 | ||
|
a1ba340ead | ||
|
0ae9ff4575 | ||
|
5b501cb942 | ||
|
0255b7a4a0 | ||
|
15ef0bab1d | ||
|
decad6830b | ||
|
b6e0b985c2 | ||
|
c140f71878 | ||
|
87044e6b8e | ||
|
affa9014d2 | ||
|
4bbc3c3bd8 | ||
|
d296dbbc23 | ||
|
9bcd27b847 | ||
|
2a54b36db0 | ||
|
77fb02daa4 | ||
|
1963ac191f | ||
|
33da8aa987 | ||
|
0192484044 | ||
|
3c0863d8ea | ||
|
710d6dfb74 | ||
|
2359542d72 | ||
|
e1a2b4b9af | ||
|
0eadfa1c83 | ||
|
c8dd935460 | ||
|
e2227271b5 | ||
|
7a639a1d6e | ||
|
9edb9b91b2 | ||
|
b2adeb20d9 | ||
|
fa665de847 | ||
|
ab9e5f716d | ||
|
5788db9f07 | ||
|
3068a8d58d | ||
|
14aacf42b9 | ||
|
d1b97da309 | ||
|
867074e7b2 | ||
|
18748510b1 | ||
|
bcf71cdf85 | ||
|
3290ce85a9 | ||
|
60ef80c1a5 | ||
|
74e852b8bd | ||
|
90ae21d257 | ||
|
fdf03cd8e2 | ||
|
f6586be5e7 | ||
|
f9dc627d84 | ||
|
309177ca9c | ||
|
456d2342b6 | ||
|
0717aa22d7 | ||
|
b8e07ac38e | ||
|
c7273e4b60 | ||
|
33d4fd4af0 | ||
|
71b11e26d2 | ||
|
77f4111b09 | ||
|
9965297f36 | ||
|
5ca466a826 | ||
|
90f417facc | ||
|
eacbd038b7 | ||
|
5446e85424 | ||
|
77b4942691 | ||
|
824cf71e0b | ||
|
239bbd30c0 | ||
|
6f6b653d54 | ||
|
e4155ce735 | ||
|
7eaf307834 | ||
|
7db7950415 | ||
|
fcc184cd2a | ||
|
6423feff3a | ||
|
e97da25d5a | ||
|
f49a37a38e | ||
|
07e6b33095 | ||
|
9136b50e3c | ||
|
c3fd5c7136 | ||
|
98612dd253 | ||
|
4d7f5238dd | ||
|
f472a01a80 | ||
|
420b5790e3 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -11,4 +11,6 @@ tests/_output/*
|
||||
_ide_helper.php
|
||||
/build/logs/clover.xml
|
||||
index.html*
|
||||
app/storage/firefly-export*
|
||||
app/storage/firefly-export*
|
||||
.vagrant
|
||||
firefly-iii-import-*.json
|
||||
|
53
README.md
53
README.md
@@ -2,7 +2,6 @@ firefly-iii
|
||||
===========
|
||||
|
||||
[](https://travis-ci.org/JC5/firefly-iii)
|
||||
[](https://coveralls.io/r/JC5/firefly-iii?branch=master)
|
||||

|
||||
|
||||
[](https://packagist.org/packages/grumpydictator/firefly-iii)
|
||||
@@ -14,42 +13,52 @@ Firefly Mark III is a new version of Firefly built upon best practices and lesso
|
||||
from building [Firefly](https://github.com/JC5/Firefly). It's Mark III since the original Firefly never made it outside of my
|
||||
laptop and [Firefly II](https://github.com/JC5/Firefly) is live.
|
||||
|
||||
## Current features
|
||||
|
||||
- [A double-entry bookkeeping system](http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system).
|
||||
- You can store, edit and remove withdrawals, deposits and transfers. This allows you full financial management;
|
||||
- It's possible to create, change and manage money using _budgets_;
|
||||
- Organize transactions using categories;
|
||||
- Save towards a goal using piggy banks;
|
||||
- Predict and anticipate large expenses using "repeated expenses" (ie. yearly taxes);
|
||||
- Predict and anticipate bills using "recurring transactions" (rent for example).
|
||||
|
||||
Everything is organised:
|
||||
|
||||
- Clear views that should show you how you're doing;
|
||||
- Easy navigation through your records;
|
||||
- Browse back and forth to see previous months or even years;
|
||||
- Lots of help text in case you don't get it;
|
||||
- Lots of charts because we all love them.
|
||||
|
||||
## Changes
|
||||
|
||||
Firefly III will feature:
|
||||
Firefly III will feature, but does not feature yet:
|
||||
|
||||
- Double-entry bookkeeping system;
|
||||
- Better budgeting tools;
|
||||
- Better financial reporting;
|
||||
- Financial reporting showing you how well you are doing;
|
||||
- More control over other resources outside of personal finance
|
||||
- Accounts shared with a partner (household accounts)
|
||||
- Debts
|
||||
- Credit cards
|
||||
- More robust code base (mainly for my own peace of mind);
|
||||
- More test-coverage (aka: actual test coverage);
|
||||
|
||||
## More features
|
||||
|
||||
- Firefly will be able to split transactions; a single purchase can be split in multiple entries, for more fine-grained control.
|
||||
- Firefly will be able to join transactions.
|
||||
- Transfers and transactions will be combined into one internal datatype which is more consistent with what you're actually doing: moving money from A to B. The fact that A or B or both are yours should not matter. And it will not, in the future.
|
||||
- The nesting of budgets, categories and beneficiaries will be removed.
|
||||
- Firefly will be able to automatically login a specified account. Although this is pretty unsafe, it removes the need for you to login to your own tool.
|
||||
- Transfers and transactions are combined into one internal datatype which is more consistent with what you're actually doing: moving money from A to B. The fact that A or B or both are yours should not matter.
|
||||
- Any other features I might not have thought of.
|
||||
|
||||
## Not changed
|
||||
Some stuff has been removed:
|
||||
|
||||
- The nesting of budgets, categories and beneficiaries is removed because it was pretty pointless.
|
||||
- Firefly will not encrypt the content of the (MySQL) tables. Old versions of Firefly had this capability but it sucks when searching, sorting and organizing entries.
|
||||
|
||||
## Current state
|
||||
I have the basics up and running and test coverage is doing very well.
|
||||
I have the basics up and running. Test coverage is currently non-existent.
|
||||
|
||||
Current issues are the consistent look-and-feel of forms and likewise, the consistent inner workings of most of Firefly.
|
||||
Example: every "create"-action tends to be slightly different from the rest. Also is the fact that not all lists
|
||||
and forms are equally well thought of; some are not looking very well or miss feedback.
|
||||
Although I have not checked extensively, some forms and views have CSRF vulnerabilities. This is because not all
|
||||
views escape all characters by default. Will be fixed.
|
||||
|
||||
Most forms will not allow you to enter invalid data because the database cracks, not because it's actually checked.
|
||||
I'm still thinking about a way to build consistent forms. Laravel doesn't really cut it.
|
||||
The current layout / look & feel is a pretty basic Bootstrap3 template. I am currently working on a more consistent,
|
||||
expanded layout which will feature shiny AJAX things and data tables and all the Web 3.0 goodies you've come to expect
|
||||
from social media sites.
|
||||
|
||||
A lot of views have CSRF vulnerabilities. The general advice is NOT to use this tool in production.
|
||||
|
||||
Questions, ideas or other things to contribute? [Let me know](https://github.com/JC5/firefly-iii/issues/new)!
|
||||
Questions, ideas or other things to contribute? [Let me know](https://github.com/JC5/firefly-iii/issues/new)!
|
@@ -1 +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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
@@ -1,4 +0,0 @@
|
||||
$(function () {
|
||||
|
||||
|
||||
});
|
@@ -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
@@ -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;
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
@@ -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
|
||||
*/
|
@@ -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
|
||||
*/
|
@@ -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
|
||||
*/
|
@@ -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
7
app/breadcrumbs.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
/*
|
||||
* Back home.
|
||||
*/
|
||||
Breadcrumbs::register('home', function($breadcrumbs) {
|
||||
$breadcrumbs->push('Home', route('index'));
|
||||
});
|
3
app/config/.gitignore
vendored
3
app/config/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
local/
|
||||
laptop/
|
||||
laptop/
|
||||
vagrant/
|
@@ -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',
|
||||
|
||||
],
|
||||
|
||||
|
@@ -2,7 +2,7 @@
|
||||
use Carbon\Carbon;
|
||||
|
||||
return [
|
||||
'index_periods' => ['1D', '1W', '1M', '3M', '6M', 'custom'],
|
||||
'index_periods' => ['1D', '1W', '1M', '3M', '6M','1Y', 'custom'],
|
||||
'budget_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
|
||||
'piggybank_periods' => ['day', 'week', 'month', 'year'],
|
||||
'periods_to_text' => [
|
||||
@@ -21,6 +21,14 @@ return [
|
||||
'6M' => 'half year',
|
||||
'custom' => '(custom)'
|
||||
],
|
||||
'range_to_name' => [
|
||||
'1D' => 'one day',
|
||||
'1W' => 'one week',
|
||||
'1M' => 'one month',
|
||||
'3M' => 'three months',
|
||||
'6M' => 'six months',
|
||||
'1Y' => 'one year',
|
||||
],
|
||||
'range_to_repeat_freq' => [
|
||||
'1D' => 'weekly',
|
||||
'1W' => 'weekly',
|
||||
|
@@ -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' => [],
|
||||
],
|
||||
|
||||
];
|
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
'view' => 'laravel-breadcrumbs::bootstrap3',
|
||||
);
|
134
app/config/packages/rcrowe/twigbridge/extensions.php
Normal file
134
app/config/packages/rcrowe/twigbridge/extensions.php
Normal 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' => [],
|
||||
|
||||
];
|
88
app/config/packages/rcrowe/twigbridge/twig.php
Normal file
88
app/config/packages/rcrowe/twigbridge/twig.php
Normal 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' => [],
|
||||
|
||||
];
|
@@ -16,20 +16,79 @@ class AccountController extends \BaseController
|
||||
|
||||
/**
|
||||
* @param ARI $repository
|
||||
* @param AI $accounts
|
||||
* @param AI $accounts
|
||||
*/
|
||||
public function __construct(ARI $repository, AI $accounts)
|
||||
{
|
||||
$this->_accounts = $accounts;
|
||||
$this->_repository = $repository;
|
||||
View::share('mainTitleIcon', 'fa-credit-card');
|
||||
View::share('title', 'Accounts');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function create()
|
||||
public function create($what)
|
||||
{
|
||||
return View::make('accounts.create')->with('title', 'Create account');
|
||||
switch ($what) {
|
||||
case 'asset':
|
||||
View::share('subTitleIcon', 'fa-money');
|
||||
break;
|
||||
case 'expense':
|
||||
View::share('subTitleIcon', 'fa-shopping-cart');
|
||||
break;
|
||||
case 'revenue':
|
||||
View::share('subTitleIcon', 'fa-download');
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return View::make('accounts.create')->with('subTitle', 'Create a new ' . $what . ' account')->with(
|
||||
'what', $what
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function asset()
|
||||
{
|
||||
View::share('subTitleIcon', 'fa-money');
|
||||
|
||||
$accounts = $this->_repository->getOfTypes(['Asset account', 'Default account']);
|
||||
|
||||
return View::make('accounts.asset')->with('subTitle', 'Asset accounts')->with(
|
||||
'accounts', $accounts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function expense()
|
||||
{
|
||||
View::share('subTitleIcon', 'fa-shopping-cart');
|
||||
|
||||
$accounts = $this->_repository->getOfTypes(['Expense account', 'Beneficiary account']);
|
||||
|
||||
return View::make('accounts.expense')->with('subTitle', 'Expense accounts')->with(
|
||||
'accounts', $accounts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function revenue()
|
||||
{
|
||||
View::share('subTitleIcon', 'fa-download');
|
||||
|
||||
$accounts = $this->_repository->getOfTypes(['Revenue account']);
|
||||
|
||||
return View::make('accounts.revenue')->with('subTitle', 'Revenue accounts')->with(
|
||||
'accounts', $accounts
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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,8 +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.'"');
|
||||
return View::make('accounts.edit')->with('account', $account)->with('openingBalance', $openingBalance)
|
||||
|
||||
->with('subTitle', 'Edit ' . strtolower($account->accountType->type) . ' "' . $account->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,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');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,10 +174,27 @@ class AccountController extends \BaseController
|
||||
*/
|
||||
public function show(Account $account)
|
||||
{
|
||||
switch ($account->accountType->type) {
|
||||
case 'Asset account':
|
||||
case 'Default account':
|
||||
View::share('subTitleIcon', 'fa-money');
|
||||
break;
|
||||
case 'Expense account':
|
||||
case 'Beneficiary account':
|
||||
View::share('subTitleIcon', 'fa-shopping-cart');
|
||||
break;
|
||||
case 'Revenue account':
|
||||
View::share('subTitleIcon', 'fa-download');
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
$data = $this->_accounts->show($account, 40);
|
||||
|
||||
return View::make('accounts.show')->with('account', $account)->with('show', $data)->with('title',
|
||||
'Details for account "' . $account->name . '"');
|
||||
return View::make('accounts.show')->with('account', $account)->with('show', $data)->with(
|
||||
'subTitle',
|
||||
'Details for ' . strtolower($account->accountType->type) . ' "' . $account->name . '"'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -138,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());
|
||||
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
/**
|
||||
* Class BaseController
|
||||
*/
|
||||
@@ -11,8 +13,6 @@ class BaseController extends Controller
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
\Event::fire('limits.check');
|
||||
\Event::fire('piggybanks.check');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,11 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Firefly\Exception\FireflyException;
|
||||
use Firefly\Helper\Controllers\BudgetInterface as BI;
|
||||
use Firefly\Storage\Budget\BudgetRepositoryInterface as BRI;
|
||||
|
||||
/**
|
||||
* Class BudgetController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*
|
||||
*/
|
||||
class BudgetController extends BaseController
|
||||
{
|
||||
@@ -19,8 +24,42 @@ class BudgetController extends BaseController
|
||||
*/
|
||||
public function __construct(BI $budgets, BRI $repository)
|
||||
{
|
||||
$this->_budgets = $budgets;
|
||||
$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');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,7 +69,7 @@ class BudgetController extends BaseController
|
||||
{
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
|
||||
return View::make('budgets.create')->with('periods', $periods);
|
||||
return View::make('budgets.create')->with('periods', $periods)->with('subTitle', 'Create a new budget');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,7 +79,8 @@ class BudgetController extends BaseController
|
||||
*/
|
||||
public function delete(Budget $budget)
|
||||
{
|
||||
return View::make('budgets.delete')->with('budget', $budget);
|
||||
return View::make('budgets.delete')->with('budget', $budget)
|
||||
->with('subTitle', 'Delete budget "' . $budget->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,20 +90,16 @@ class BudgetController extends BaseController
|
||||
*/
|
||||
public function destroy(Budget $budget)
|
||||
{
|
||||
// remove budget
|
||||
Event::fire('budgets.destroy', [$budget]); // just before deletion.
|
||||
$result = $this->_repository->destroy($budget);
|
||||
if ($result === true) {
|
||||
Session::flash('success', 'The budget was deleted.');
|
||||
if (Input::get('from') == 'date') {
|
||||
return Redirect::route('budgets.index');
|
||||
} else {
|
||||
return Redirect::route('budgets.index.budget');
|
||||
}
|
||||
} else {
|
||||
Session::flash('error', 'Could not delete the budget. Check the logs to be sure.');
|
||||
}
|
||||
$this->_repository->destroy($budget);
|
||||
Session::flash('success', 'The budget was deleted.');
|
||||
|
||||
return Redirect::route('budgets.index');
|
||||
// redirect:
|
||||
if (Input::get('from') == 'date') {
|
||||
return Redirect::route('budgets.index');
|
||||
}
|
||||
return Redirect::route('budgets.index.budget');
|
||||
|
||||
}
|
||||
|
||||
@@ -74,7 +110,8 @@ class BudgetController extends BaseController
|
||||
*/
|
||||
public function edit(Budget $budget)
|
||||
{
|
||||
return View::make('budgets.edit')->with('budget', $budget);
|
||||
return View::make('budgets.edit')->with('budget', $budget)
|
||||
->with('subTitle', 'Edit budget "' . $budget->name . '"');
|
||||
|
||||
}
|
||||
|
||||
@@ -83,63 +120,78 @@ class BudgetController extends BaseController
|
||||
*/
|
||||
public function indexByBudget()
|
||||
{
|
||||
View::share('subTitleIcon', 'fa-folder-open');
|
||||
|
||||
$budgets = $this->_repository->get();
|
||||
$today = new Carbon;
|
||||
|
||||
|
||||
return View::make('budgets.indexByBudget')->with('budgets', $budgets)->with('today', $today);
|
||||
return View::make('budgets.indexByBudget')->with('budgets', $budgets)->with('today', new Carbon)
|
||||
->with('subTitle', 'Grouped by budget');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\Illuminate\View\View
|
||||
* @throws Firefly\Exception\FireflyException
|
||||
* @return $this
|
||||
*/
|
||||
public function indexByDate()
|
||||
{
|
||||
View::share('subTitleIcon', 'fa-calendar');
|
||||
|
||||
// get a list of dates by getting all repetitions:
|
||||
$set = $this->_repository->get();
|
||||
$set = $this->_repository->get();
|
||||
$budgets = $this->_budgets->organizeByDate($set);
|
||||
|
||||
|
||||
return View::make('budgets.indexByDate')->with('budgets', $budgets);
|
||||
return View::make('budgets.indexByDate')->with('budgets', $budgets)
|
||||
->with('subTitle', 'Grouped by date');
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Budget $budget
|
||||
* Three use cases for this view:
|
||||
*
|
||||
* - Show everything.
|
||||
* - Show a specific repetition.
|
||||
* - Show everything shows NO repetition.
|
||||
*
|
||||
* @param Budget $budget
|
||||
* @param LimitRepetition $repetition
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function show(Budget $budget)
|
||||
public function show(Budget $budget, \LimitRepetition $repetition = null)
|
||||
{
|
||||
/**
|
||||
* Use the
|
||||
*/
|
||||
$useSessionDates = Input::get('useSession') == 'true' ? true : false;
|
||||
|
||||
|
||||
$filters = [];
|
||||
|
||||
if (!is_null(Input::get('rep'))) {
|
||||
$repetitionId = intval(Input::get('rep'));
|
||||
$repetitions = $this->_budgets->organizeRepetition($repetitionId);
|
||||
$filters[] = $repetitions[0]['limit'];
|
||||
$filters[] = $repetitions[0]['limitrepetition'];
|
||||
} else {
|
||||
if (Input::get('noenvelope') == 'true') {
|
||||
$repetitions = $this->_budgets->outsideRepetitions($budget);
|
||||
$filters[] = 'no_envelope';
|
||||
} else {
|
||||
// grab all limit repetitions, order them, show them:
|
||||
$repetitions = $this->_budgets->organizeRepetitions($budget, $useSessionDates);
|
||||
}
|
||||
$view = null;
|
||||
$title = null;
|
||||
\Log::debug('Is envelope true? ' . (Input::get('noenvelope') == 'true'));
|
||||
switch (true) {
|
||||
case (!is_null($repetition)):
|
||||
$data = $this->_budgets->organizeRepetition($repetition);
|
||||
$view = 1;
|
||||
$title = $budget->name . ', ' . $repetition->periodShow() . ', ' . mf(
|
||||
$repetition->limit->amount,
|
||||
false
|
||||
);
|
||||
break;
|
||||
case (Input::get('noenvelope') == 'true'):
|
||||
$data = $this->_budgets->outsideRepetitions($budget);
|
||||
$view = 2;
|
||||
$title = $budget->name . ', transactions outside an envelope';
|
||||
break;
|
||||
default:
|
||||
$data = $this->_budgets->organizeRepetitions($budget, $useSessionDates);
|
||||
$view = $useSessionDates ? 3 : 4;
|
||||
$title = $useSessionDates ? $budget->name . ' in session period' : $budget->name;
|
||||
break;
|
||||
}
|
||||
|
||||
return View::make('budgets.show')->with('budget', $budget)->with('repetitions', $repetitions)->with(
|
||||
'filters', $filters
|
||||
)->with('highlight', Input::get('highlight'))->with('useSessionDates', $useSessionDates);
|
||||
return View::make('budgets.show')
|
||||
->with('budget', $budget)
|
||||
->with('repetitions', $data)
|
||||
->with('view', $view)
|
||||
->with('highlight', Input::get('highlight'))
|
||||
->with('useSessionDates', $useSessionDates)
|
||||
->with('subTitle', 'Overview for ' . $title);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -5,6 +5,8 @@ use Firefly\Storage\Category\CategoryRepositoryInterface as CRI;
|
||||
|
||||
/**
|
||||
* Class CategoryController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class CategoryController extends BaseController
|
||||
{
|
||||
@@ -18,7 +20,9 @@ class CategoryController extends BaseController
|
||||
public function __construct(CRI $repository, CI $category)
|
||||
{
|
||||
$this->_repository = $repository;
|
||||
$this->_category = $category;
|
||||
$this->_category = $category;
|
||||
View::share('title','Categories');
|
||||
View::share('mainTitleIcon', 'fa-bar-chart');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -26,7 +30,7 @@ class CategoryController extends BaseController
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
return View::make('categories.create');
|
||||
return View::make('categories.create')->with('subTitle', 'Create a new category');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,7 +40,8 @@ class CategoryController extends BaseController
|
||||
*/
|
||||
public function delete(Category $category)
|
||||
{
|
||||
return View::make('categories.delete')->with('category', $category);
|
||||
return View::make('categories.delete')->with('category', $category)
|
||||
->with('subTitle', 'Delete category "' . $category->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,13 +51,8 @@ class CategoryController extends BaseController
|
||||
*/
|
||||
public function destroy(Category $category)
|
||||
{
|
||||
$result = $this->_repository->destroy($category);
|
||||
if ($result === true) {
|
||||
Session::flash('success', 'The category was deleted.');
|
||||
} else {
|
||||
Session::flash('error', 'Could not delete the category. Check the logs to be sure.');
|
||||
}
|
||||
|
||||
$this->_repository->destroy($category);
|
||||
Session::flash('success', 'The category was deleted.');
|
||||
return Redirect::route('categories.index');
|
||||
}
|
||||
|
||||
@@ -63,7 +63,8 @@ class CategoryController extends BaseController
|
||||
*/
|
||||
public function edit(Category $category)
|
||||
{
|
||||
return View::make('categories.edit')->with('category', $category);
|
||||
return View::make('categories.edit')->with('category', $category)
|
||||
->with('subTitle', 'Edit category "' . $category->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,7 +74,8 @@ class CategoryController extends BaseController
|
||||
{
|
||||
$categories = $this->_repository->get();
|
||||
|
||||
return View::make('categories.index')->with('categories', $categories);
|
||||
return View::make('categories.index')->with('categories', $categories)
|
||||
->with('subTitle', 'All your categories');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,14 +86,14 @@ class CategoryController extends BaseController
|
||||
public function show(Category $category)
|
||||
{
|
||||
$start = \Session::get('start');
|
||||
$end = \Session::get('end');
|
||||
$end = \Session::get('end');
|
||||
|
||||
|
||||
$journals = $this->_category->journalsInRange($category, $start, $end);
|
||||
|
||||
return View::make('categories.show')->with('category', $category)->with('journals', $journals)->with(
|
||||
'highlight', Input::get('highlight')
|
||||
);
|
||||
)->with('subTitle', 'Overview for category "' . $category->name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,11 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Firefly\Exception\FireflyException;
|
||||
use Firefly\Helper\Controllers\ChartInterface;
|
||||
use Firefly\Storage\Account\AccountRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class ChartController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
*/
|
||||
class ChartController extends BaseController
|
||||
{
|
||||
@@ -15,66 +20,66 @@ class ChartController extends BaseController
|
||||
|
||||
|
||||
/**
|
||||
* @param ChartInterface $chart
|
||||
* @param ChartInterface $chart
|
||||
* @param AccountRepositoryInterface $accounts
|
||||
*/
|
||||
public function __construct(ChartInterface $chart, AccountRepositoryInterface $accounts)
|
||||
{
|
||||
$this->_chart = $chart;
|
||||
$this->_chart = $chart;
|
||||
$this->_accounts = $accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a budget, all limits and all their repetitions and displays three numbers per repetition:
|
||||
* the amount of money in the repetition (represented as "an envelope"), the amount spent and the spent percentage.
|
||||
*
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function budgetDefault(\Budget $budget)
|
||||
{
|
||||
$expense = [];
|
||||
$left = [];
|
||||
$expense = [];
|
||||
$left = [];
|
||||
$envelope = [];
|
||||
// get all limit repetitions for this budget.
|
||||
/** @var \Limit $limit */
|
||||
foreach ($budget->limits as $limit) {
|
||||
/** @var \LimitRepetition $rep */
|
||||
foreach ($limit->limitrepetitions as $rep) {
|
||||
$spentInRep = \Transaction::
|
||||
leftJoin(
|
||||
'transaction_journals', 'transaction_journals.id', '=',
|
||||
'transactions.transaction_journal_id'
|
||||
)
|
||||
->leftJoin(
|
||||
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id',
|
||||
'=',
|
||||
'transaction_journals.id'
|
||||
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
|
||||
'transaction_journals.date', '>=', $rep->startdate->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $rep->enddate->format('Y-m-d'))->where(
|
||||
'amount', '>', 0
|
||||
)->sum('amount');
|
||||
|
||||
|
||||
$pct = round(($spentInRep / $limit->amount) * 100, 2);
|
||||
$name = $rep->periodShow();
|
||||
$expense[] = [$name, floatval($spentInRep)];
|
||||
$left[] = [$name, $pct];
|
||||
// get the amount of money spent in this period on this budget.
|
||||
$spentInRep = $rep->amount - $rep->leftInRepetition();
|
||||
$pct = round((floatval($spentInRep) / floatval($limit->amount)) * 100, 2);
|
||||
$name = $rep->periodShow();
|
||||
$envelope[] = [$name, floatval($limit->amount)];
|
||||
$expense[] = [$name, floatval($spentInRep)];
|
||||
$left[] = [$name, $pct];
|
||||
}
|
||||
}
|
||||
|
||||
$return = [
|
||||
'chart_title' => 'Overview for budget ' . $budget->name,
|
||||
'subtitle' => 'All envelopes',
|
||||
'series' => [
|
||||
'subtitle' => 'All envelopes',
|
||||
'series' => [
|
||||
[
|
||||
'type' => 'line',
|
||||
'yAxis' => 1,
|
||||
'name' => 'Amount in envelope',
|
||||
'data' => $envelope
|
||||
],
|
||||
[
|
||||
'type' => 'column',
|
||||
'name' => 'Expenses in envelope',
|
||||
'data' => $expense
|
||||
],
|
||||
[
|
||||
'type' => 'line',
|
||||
'type' => 'line',
|
||||
'yAxis' => 1,
|
||||
'name' => 'Spent percentage for envelope',
|
||||
'data' => $left
|
||||
'name' => 'Spent percentage for envelope',
|
||||
'data' => $left
|
||||
]
|
||||
|
||||
|
||||
]
|
||||
];
|
||||
|
||||
@@ -82,42 +87,40 @@ class ChartController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a single limit repetition (so a single "envelope") and displays the amount of money spent
|
||||
* per day and subsequently how much money is left.
|
||||
*
|
||||
* @param LimitRepetition $rep
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function budgetLimit(\LimitRepetition $rep)
|
||||
{
|
||||
$budget = $rep->limit->budget;
|
||||
$current = clone $rep->startdate;
|
||||
$expense = [];
|
||||
$leftInLimit = [];
|
||||
$budget = $rep->limit->budget;
|
||||
$current = clone $rep->startdate;
|
||||
$expense = [];
|
||||
$leftInLimit = [];
|
||||
$currentLeftInLimit = floatval($rep->limit->amount);
|
||||
while ($current <= $rep->enddate) {
|
||||
$spent = \Transaction::
|
||||
leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin(
|
||||
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
|
||||
'transaction_journals.id'
|
||||
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
|
||||
'transaction_journals.date', $current->format('Y-m-d')
|
||||
)->where('amount', '>', 0)->sum('amount');
|
||||
$spent = floatval($spent) == 0 ? null : floatval($spent);
|
||||
$entry = [$current->timestamp * 1000, $spent];
|
||||
$expense[] = $entry;
|
||||
$spent = $this->_chart->spentOnDay($budget, $current);
|
||||
$spent = floatval($spent) == 0 ? null : floatval($spent);
|
||||
$entry = [$current->timestamp * 1000, $spent];
|
||||
$expense[] = $entry;
|
||||
$currentLeftInLimit = $currentLeftInLimit - $spent;
|
||||
$leftInLimit[] = [$current->timestamp * 1000, $currentLeftInLimit];
|
||||
$leftInLimit[] = [$current->timestamp * 1000, $currentLeftInLimit];
|
||||
$current->addDay();
|
||||
}
|
||||
|
||||
$return = [
|
||||
'chart_title' => 'Overview for budget ' . $budget->name,
|
||||
'subtitle' =>
|
||||
'subtitle' =>
|
||||
'Between ' . $rep->startdate->format('M jS, Y') . ' and ' . $rep->enddate->format('M jS, Y'),
|
||||
'series' => [
|
||||
'series' => [
|
||||
[
|
||||
'type' => 'column',
|
||||
'name' => 'Expenses per day',
|
||||
'type' => 'column',
|
||||
'name' => 'Expenses per day',
|
||||
'yAxis' => 1,
|
||||
'data' => $expense
|
||||
'data' => $expense
|
||||
],
|
||||
[
|
||||
'type' => 'line',
|
||||
@@ -132,180 +135,149 @@ class ChartController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* This method takes a budget and gets all transactions in it which haven't got an envelope (limit).
|
||||
*
|
||||
* Usually this means that very old and unorganized or very NEW transactions get displayed; there was never an
|
||||
* envelope or it hasn't been created (yet).
|
||||
*
|
||||
*
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function budgetNoLimits(\Budget $budget)
|
||||
{
|
||||
$inRepetitions = [];
|
||||
foreach ($budget->limits as $limit) {
|
||||
foreach ($limit->limitrepetitions as $repetition) {
|
||||
$set = $budget->transactionjournals()->leftJoin(
|
||||
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'
|
||||
)->where('transaction_types.type', 'Withdrawal')->where(
|
||||
'date', '>=', $repetition->startdate->format('Y-m-d')
|
||||
)->where('date', '<=', $repetition->enddate->format('Y-m-d'))->orderBy('date', 'DESC')->get(
|
||||
['transaction_journals.id']
|
||||
);
|
||||
foreach ($set as $item) {
|
||||
$inRepetitions[] = $item->id;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Firefly can go about this two ways. Either it finds all transactions which definitely are IN an envelope
|
||||
* and exclude them or it searches for transactions outside of the range of any of the envelopes there are.
|
||||
*
|
||||
* Since either is kinda shitty Firefly uses the first one because it's easier to build.
|
||||
*/
|
||||
$inRepetitions = $this->_chart->allJournalsInBudgetEnvelope($budget);
|
||||
|
||||
}
|
||||
|
||||
$query = $budget->transactionjournals()->whereNotIn(
|
||||
'transaction_journals.id', $inRepetitions
|
||||
)->orderBy('date', 'DESC')->orderBy(
|
||||
'transaction_journals.id', 'DESC'
|
||||
);
|
||||
/*
|
||||
* With this set of id's, Firefly can search for all journals NOT in that set.
|
||||
* BUT they have to be in the budget (duh).
|
||||
*/
|
||||
$set = $this->_chart->journalsNotInSet($budget, $inRepetitions);
|
||||
/*
|
||||
* Next step: get all transactions for those journals.
|
||||
*/
|
||||
$transactions = $this->_chart->transactionsByJournals($set);
|
||||
|
||||
|
||||
$result = $query->get(['transaction_journals.id']);
|
||||
$set = [];
|
||||
foreach ($result as $entry) {
|
||||
$set[] = $entry->id;
|
||||
}
|
||||
// all transactions for these journals, grouped by date and SUM
|
||||
$transactions = \Transaction::whereIn('transaction_journal_id', $set)->leftJoin(
|
||||
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
|
||||
)
|
||||
->groupBy('transaction_journals.date')->where('amount', '>', 0)->get(
|
||||
['transaction_journals.date', DB::Raw('SUM(`amount`) as `aggregate`')]
|
||||
);
|
||||
|
||||
|
||||
// this set builds the chart:
|
||||
/*
|
||||
* this set builds the chart:
|
||||
*/
|
||||
$expense = [];
|
||||
|
||||
foreach ($transactions as $t) {
|
||||
$date = new Carbon($t->date);
|
||||
$date = new Carbon($t->date);
|
||||
$expense[] = [$date->timestamp * 1000, floatval($t->aggregate)];
|
||||
}
|
||||
$return = [
|
||||
'chart_title' => 'Overview for ' . $budget->name,
|
||||
'subtitle' => 'Not organized by an envelope',
|
||||
'series' => [
|
||||
'subtitle' => 'Not organized by an envelope',
|
||||
'series' => [
|
||||
[
|
||||
'type' => 'spline',
|
||||
'type' => 'column',
|
||||
'name' => 'Expenses per day',
|
||||
'data' => $expense
|
||||
]
|
||||
|
||||
]
|
||||
];
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets all transactions within a budget within the period set by the current session
|
||||
* start and end date. It also includes any envelopes which might exist within this period.
|
||||
*
|
||||
* @param Budget $budget
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function budgetSession(\Budget $budget)
|
||||
{
|
||||
$expense = [];
|
||||
$repetitionSeries = [];
|
||||
$current = clone Session::get('start');
|
||||
$end = clone Session::get('end');
|
||||
while ($current <= $end) {
|
||||
$spent = \Transaction::
|
||||
leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin(
|
||||
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
|
||||
'transaction_journals.id'
|
||||
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
|
||||
'transaction_journals.date', $current->format('Y-m-d')
|
||||
)->where('amount', '>', 0)->sum('amount');
|
||||
$spent = floatval($spent) == 0 ? null : floatval($spent);
|
||||
if (!is_null($spent)) {
|
||||
$expense[] = [$current->timestamp * 1000, $spent];
|
||||
}
|
||||
$series = [];
|
||||
$end = clone Session::get('end');
|
||||
$start = clone Session::get('start');
|
||||
|
||||
|
||||
/*
|
||||
* Expenses per day in the session's period. That's easy.
|
||||
*/
|
||||
$expense = [];
|
||||
$current = clone Session::get('start');
|
||||
while ($current <= $end) {
|
||||
$spent = $this->_chart->spentOnDay($budget, $current);
|
||||
$spent = floatval($spent) == 0 ? null : floatval($spent);
|
||||
$expense[] = [$current->timestamp * 1000, $spent];
|
||||
$current->addDay();
|
||||
}
|
||||
|
||||
// find all limit repetitions (for this budget) between start and end.
|
||||
$start = clone Session::get('start');
|
||||
$repetitionSeries[] = [
|
||||
$series[] = [
|
||||
'type' => 'column',
|
||||
'name' => 'Expenses per day',
|
||||
'data' => $expense
|
||||
];
|
||||
unset($expense, $spent, $current);
|
||||
|
||||
/*
|
||||
* Find all limit repetitions (for this budget) between start and end. This is
|
||||
* quite a complex query.
|
||||
*/
|
||||
$reps = $this->_chart->limitsInRange($budget, $start, $end);
|
||||
|
||||
/** @var \Limit $limit */
|
||||
foreach ($budget->limits as $limit) {
|
||||
$reps = $limit->limitrepetitions()->where(
|
||||
function ($q) use ($start, $end) {
|
||||
// startdate is between range
|
||||
$q->where(
|
||||
function ($q) use ($start, $end) {
|
||||
$q->where('startdate', '>=', $start->format('Y-m-d'));
|
||||
$q->where('startdate', '<=', $end->format('Y-m-d'));
|
||||
}
|
||||
/*
|
||||
* For each limitrepetition Firefly creates a serie that contains the amount left in
|
||||
* the limitrepetition for its entire date-range. Entries are only actually included when they
|
||||
* fall into the charts date range.
|
||||
*
|
||||
* So example: the user has a session date from Jan 15 to Jan 30. The limitrepetition
|
||||
* starts at 1 Jan until 1 Feb.
|
||||
*
|
||||
* Firefly loops from 1 Jan to 1 Feb but only includes Jan 15 / Jan 30.
|
||||
* But it does keep count of the amount outside of these dates because otherwise the line might be wrong.
|
||||
*/
|
||||
/** @var \LimitRepetition $repetition */
|
||||
foreach ($reps as $repetition) {
|
||||
$limitAmount = $repetition->limit->amount;
|
||||
|
||||
// create a serie for the repetition.
|
||||
$currentSerie = [
|
||||
'type' => 'spline',
|
||||
'id' => 'rep-' . $repetition->id,
|
||||
'yAxis' => 1,
|
||||
'name' => 'Envelope #' . $repetition->id . ' in ' . $repetition->periodShow(),
|
||||
'data' => []
|
||||
];
|
||||
$current = clone $repetition->startdate;
|
||||
while ($current <= $repetition->enddate) {
|
||||
if ($current >= $start && $current <= $end) {
|
||||
// spent on limit:
|
||||
$spentSoFar = $this->_chart->spentOnLimitRepetitionBetweenDates(
|
||||
$repetition, $repetition->startdate, $current
|
||||
);
|
||||
$leftInLimit = floatval($limitAmount) - floatval($spentSoFar);
|
||||
|
||||
// or enddate is between range.
|
||||
$q->orWhere(
|
||||
function ($q) use ($start, $end) {
|
||||
$q->where('enddate', '>=', $start->format('Y-m-d'));
|
||||
$q->where('enddate', '<=', $end->format('Y-m-d'));
|
||||
}
|
||||
);
|
||||
$currentSerie['data'][] = [$current->timestamp * 1000, $leftInLimit];
|
||||
}
|
||||
)
|
||||
->get();
|
||||
$currentLeftInLimit = floatval($limit->amount);
|
||||
/** @var \LimitRepetition $repetition */
|
||||
foreach ($reps as $repetition) {
|
||||
// create a serie for the repetition.
|
||||
$currentSerie = [
|
||||
'type' => 'spline',
|
||||
'id' => 'rep-' . $repetition->id,
|
||||
'yAxis' => 1,
|
||||
'name' => 'Envelope in ' . $repetition->periodShow(),
|
||||
'data' => []
|
||||
];
|
||||
$current = clone $repetition->startdate;
|
||||
while ($current <= $repetition->enddate) {
|
||||
if ($current >= Session::get('start') && $current <= Session::get('end')) {
|
||||
// spent on limit:
|
||||
$spentSoFar = \Transaction::
|
||||
leftJoin(
|
||||
'transaction_journals', 'transaction_journals.id', '=',
|
||||
'transactions.transaction_journal_id'
|
||||
)
|
||||
->leftJoin(
|
||||
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id',
|
||||
'=',
|
||||
'transaction_journals.id'
|
||||
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
|
||||
'transaction_journals.date', '>=', $repetition->startdate->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $current->format('Y-m-d'))->where(
|
||||
'amount', '>', 0
|
||||
)->sum('amount');
|
||||
$spent = floatval($spent) == 0 ? null : floatval($spent);
|
||||
$currentLeftInLimit = floatval($limit->amount) - floatval($spentSoFar);
|
||||
|
||||
$currentSerie['data'][] = [$current->timestamp * 1000, $currentLeftInLimit];
|
||||
}
|
||||
$current->addDay();
|
||||
}
|
||||
|
||||
// do something here.
|
||||
$repetitionSeries[] = $currentSerie;
|
||||
|
||||
$current->addDay();
|
||||
}
|
||||
|
||||
// do something here.
|
||||
$series[] = $currentSerie;
|
||||
}
|
||||
|
||||
|
||||
$return = [
|
||||
'chart_title' => 'Overview for budget ' . $budget->name,
|
||||
'subtitle' =>
|
||||
'subtitle' =>
|
||||
'Between ' . Session::get('start')->format('M jS, Y') . ' and ' . Session::get('end')->format(
|
||||
'M jS, Y'
|
||||
),
|
||||
'series' => $repetitionSeries
|
||||
'series' => $series
|
||||
];
|
||||
|
||||
return Response::json($return);
|
||||
@@ -320,14 +292,14 @@ class ChartController extends BaseController
|
||||
public function categoryShowChart(Category $category)
|
||||
{
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$end = Session::get('end');
|
||||
$range = Session::get('range');
|
||||
|
||||
$serie = $this->_chart->categoryShowChart($category, $range, $start, $end);
|
||||
$data = [
|
||||
$data = [
|
||||
'chart_title' => $category->name,
|
||||
'subtitle' => '<a href="' . route('categories.show', [$category->id]) . '">View more</a>',
|
||||
'series' => $serie
|
||||
'subtitle' => '<a href="' . route('categories.show', [$category->id]) . '">View more</a>',
|
||||
'series' => $serie
|
||||
];
|
||||
|
||||
return Response::json($data);
|
||||
@@ -344,34 +316,33 @@ class ChartController extends BaseController
|
||||
{
|
||||
// get preferences and accounts (if necessary):
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$end = Session::get('end');
|
||||
|
||||
if (is_null($account)) {
|
||||
// get, depending on preferences:
|
||||
/** @var \Firefly\Helper\Preferences\PreferencesHelperInterface $prefs */
|
||||
$prefs = \App::make('Firefly\Helper\Preferences\PreferencesHelperInterface');
|
||||
$pref = $prefs->get('frontpageAccounts', []);
|
||||
$pref = $prefs->get('frontpageAccounts', []);
|
||||
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $acct */
|
||||
$acct = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$acct = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$accounts = $acct->getByIds($pref->data);
|
||||
} else {
|
||||
$accounts = [$account];
|
||||
}
|
||||
// loop and get array data.
|
||||
|
||||
$url = count($accounts) == 1 && is_array($accounts)
|
||||
$url = count($accounts) == 1 && is_array($accounts)
|
||||
? '<a href="' . route('accounts.show', [$account->id]) . '">View more</a>'
|
||||
:
|
||||
'<a href="' . route('accounts.index') . '">View more</a>';
|
||||
$data = [
|
||||
'chart_title' => count($accounts) == 1 ? $accounts[0]->name : 'All accounts',
|
||||
'subtitle' => $url,
|
||||
'series' => []
|
||||
'subtitle' => $url,
|
||||
'series' => []
|
||||
];
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
\Log::debug('Now building series for ' . $account->name);
|
||||
$data['series'][] = $this->_chart->account($account, $start, $end);
|
||||
}
|
||||
|
||||
@@ -390,7 +361,7 @@ class ChartController extends BaseController
|
||||
{
|
||||
$account = $this->_accounts->findByName($name);
|
||||
|
||||
$date = Carbon::createFromDate($year, $month, $day);
|
||||
$date = Carbon::createFromDate($year, $month, $day);
|
||||
$result = $this->_chart->accountDailySummary($account, $date);
|
||||
|
||||
return View::make('charts.info')->with('rows', $result['rows'])->with('sum', $result['sum'])->with(
|
||||
@@ -403,9 +374,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));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -414,10 +478,126 @@ class ChartController extends BaseController
|
||||
public function homeCategories()
|
||||
{
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$end = Session::get('end');
|
||||
|
||||
return Response::json($this->_chart->categories($start, $end));
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method checks all recurring transactions, calculates the current "moment" and returns
|
||||
* a list of yet to be paid (and paid) recurring transactions. This list can be used to show a beautiful chart
|
||||
* to the end user who will love it and cherish it.
|
||||
*
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function homeRecurring()
|
||||
{
|
||||
/** @var \Firefly\Helper\Toolkit\ToolkitInterface $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface');
|
||||
|
||||
/*
|
||||
* Set of paid transaction journals.
|
||||
* Set of unpaid recurring transactions.
|
||||
*/
|
||||
$paid = [];
|
||||
$unpaid = [];
|
||||
|
||||
/*
|
||||
* Loop the recurring transactions.
|
||||
*/
|
||||
|
||||
/** @var \RecurringTransaction $recurring */
|
||||
foreach (\Auth::user()->recurringtransactions()->get() as $recurring) {
|
||||
/*
|
||||
* Start another loop starting at the $date.
|
||||
*/
|
||||
$start = clone $recurring->date;
|
||||
$end = Carbon::now();
|
||||
|
||||
/*
|
||||
* The jump we make depends on the $repeat_freq
|
||||
*/
|
||||
$current = clone $start;
|
||||
|
||||
while ($current <= $end) {
|
||||
/*
|
||||
* Get end of period for $current:
|
||||
*/
|
||||
$currentEnd = clone $current;
|
||||
$toolkit->endOfPeriod($currentEnd, $recurring->repeat_freq);
|
||||
|
||||
/*
|
||||
* In the current session range?
|
||||
*/
|
||||
if (\Session::get('end') >= $current and $currentEnd >= \Session::get('start')) {
|
||||
/*
|
||||
* Lets see if we've already spent money on this recurring transaction (it hath recurred).
|
||||
*/
|
||||
/** @var TransactionJournal $set */
|
||||
$transaction = \Auth::user()->transactionjournals()->where('recurring_transaction_id', $recurring->id)->after($current)->before($currentEnd)->first();
|
||||
|
||||
if(is_null($transaction)) {
|
||||
$unpaid[] = $recurring;
|
||||
} else {
|
||||
$paid[] = $transaction;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Add some time for the next loop!
|
||||
*/
|
||||
$toolkit->addPeriod($current, $recurring->repeat_freq, intval($recurring->skip));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* Get some colors going.
|
||||
*/
|
||||
$unPaidColours = $toolkit->colorRange('AA4643', 'FFFFFF', count($unpaid) == 0 ? 1 : count($unpaid));
|
||||
$paidColours = $toolkit->colorRange('4572A7', 'FFFFFF', count($paid) == 0 ? 1 : count($paid));
|
||||
|
||||
/*
|
||||
* The chart serie:
|
||||
*/
|
||||
$serie = [
|
||||
'type' => 'pie',
|
||||
'name' => 'Amount',
|
||||
'data' => []
|
||||
];
|
||||
|
||||
/*
|
||||
* Loop paid and unpaid to make two haves for a pie chart.
|
||||
*/
|
||||
/** @var TransactionJournal $entry */
|
||||
foreach ($paid as $index => $entry) {
|
||||
$transactions = $entry->transactions()->get();
|
||||
$amount = max(floatval($transactions[0]->amount), floatval($transactions[1]->amount));
|
||||
$serie['data'][] = [
|
||||
'name' => 'Already paid "'.$entry->description.'"',
|
||||
'y' => $amount,
|
||||
'url' => route('transactions.show',$entry->id),
|
||||
'objType' => 'paid',
|
||||
'color' => $paidColours[$index]
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/** @var RecurringTransaction $entry */
|
||||
foreach ($unpaid as $index => $entry) {
|
||||
$amount = (floatval($entry->amount_max) + floatval($entry->amount_min)) / 2;
|
||||
$serie['data'][] = [
|
||||
'name' => 'Still due: '.$entry->name,
|
||||
'y' => $amount,
|
||||
'url' => route('recurring.show',$entry->id),
|
||||
'objType' => 'unpaid',
|
||||
'color' => $unPaidColours[$index]
|
||||
];
|
||||
}
|
||||
|
||||
return Response::json([$serie]);
|
||||
|
||||
}
|
||||
}
|
@@ -1,26 +1,73 @@
|
||||
<?php
|
||||
use Carbon\Carbon;
|
||||
use Firefly\Helper\Preferences\PreferencesHelperInterface as PHI;
|
||||
use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
|
||||
use Firefly\Storage\Reminder\ReminderRepositoryInterface as RRI;
|
||||
use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as TJRI;
|
||||
|
||||
/**
|
||||
* Class HomeController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class HomeController extends BaseController
|
||||
{
|
||||
protected $_accounts;
|
||||
protected $_preferences;
|
||||
protected $_journal;
|
||||
protected $_reminders;
|
||||
|
||||
public function __construct(ARI $accounts, PHI $preferences, TJRI $journal, RRI $reminders)
|
||||
/**
|
||||
* @param ARI $accounts
|
||||
* @param PHI $preferences
|
||||
* @param TJRI $journal
|
||||
*/
|
||||
public function __construct(ARI $accounts, PHI $preferences, TJRI $journal)
|
||||
{
|
||||
$this->_accounts = $accounts;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_preferences = $preferences;
|
||||
$this->_journal = $journal;
|
||||
$this->_reminders = $reminders;
|
||||
$this->_journal = $journal;
|
||||
}
|
||||
|
||||
public function jobDev()
|
||||
{
|
||||
$fullName = storage_path() . DIRECTORY_SEPARATOR . 'firefly-export-2014-07-23.json';
|
||||
\Log::notice('Pushed start job.');
|
||||
Queue::push('Firefly\Queue\Import@start', ['file' => $fullName, 'user' => 1]);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public function sessionPrev()
|
||||
{
|
||||
/** @var \Firefly\Helper\Toolkit\ToolkitInterface $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface');
|
||||
$toolkit->prev();
|
||||
return Redirect::back();
|
||||
//return Redirect::route('index');
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
public function sessionNext()
|
||||
{
|
||||
/** @var \Firefly\Helper\Toolkit\ToolkitInterface $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\ToolkitInterface');
|
||||
$toolkit->next();
|
||||
return Redirect::back();
|
||||
//return Redirect::route('index');
|
||||
}
|
||||
|
||||
public function rangeJump($range)
|
||||
{
|
||||
|
||||
$valid = ['1D', '1W', '1M', '3M', '6M', '1Y',];
|
||||
|
||||
if (in_array($range, $valid)) {
|
||||
$this->_preferences->set('viewRange', $range);
|
||||
Session::forget('range');
|
||||
}
|
||||
return Redirect::back();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,16 +85,14 @@ class HomeController extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
Event::fire('limits.check');
|
||||
Event::fire('piggybanks.check');
|
||||
Event::fire('recurring.check');
|
||||
|
||||
\Event::fire('limits.check');
|
||||
\Event::fire('piggybanks.check');
|
||||
\Event::fire('recurring.check');
|
||||
|
||||
|
||||
// count, maybe we need some introducing text to show:
|
||||
// count, maybe Firefly needs some introducing text to show:
|
||||
$count = $this->_accounts->count();
|
||||
$start = Session::get('start');
|
||||
$end = Session::get('end');
|
||||
$end = Session::get('end');
|
||||
|
||||
|
||||
// get the preference for the home accounts to show:
|
||||
@@ -66,21 +111,8 @@ class HomeController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
if (count($transactions) % 2 == 0) {
|
||||
$transactions = array_chunk($transactions, 2);
|
||||
} elseif (count($transactions) == 1) {
|
||||
$transactions = array_chunk($transactions, 3);
|
||||
} else {
|
||||
$transactions = array_chunk($transactions, 3);
|
||||
}
|
||||
|
||||
// get the users reminders:
|
||||
|
||||
$reminders = $this->_reminders->getCurrentRecurringReminders();
|
||||
|
||||
// build the home screen:
|
||||
return View::make('index')->with('count', $count)->with('transactions', $transactions)->with(
|
||||
'reminders', $reminders
|
||||
);
|
||||
return View::make('index')->with('count', $count)->with('transactions', $transactions)->with('title', 'Firefly')
|
||||
->with('subTitle', 'What\'s playing?')->with('mainTitleIcon', 'fa-fire');
|
||||
}
|
||||
}
|
@@ -1,41 +1,57 @@
|
||||
<?php
|
||||
|
||||
use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
|
||||
use Firefly\Storage\Budget\BudgetRepositoryInterface as Bud;
|
||||
use Firefly\Storage\Category\CategoryRepositoryInterface as Cat;
|
||||
use Firefly\Storage\Component\ComponentRepositoryInterface as CRI;
|
||||
use Firefly\Helper\Controllers\JsonInterface as JI;
|
||||
use Illuminate\Support\Collection;
|
||||
use LaravelBook\Ardent\Builder;
|
||||
|
||||
/**
|
||||
* Class JsonController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class JsonController extends BaseController
|
||||
{
|
||||
protected $_accounts;
|
||||
protected $_components;
|
||||
protected $_categories;
|
||||
protected $_budgets;
|
||||
/** @var \Firefly\Helper\Controllers\JsonInterface $helper */
|
||||
protected $helper;
|
||||
|
||||
public function __construct(JI $helper)
|
||||
{
|
||||
$this->helper = $helper;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ARI $accounts
|
||||
* @param CRI $components
|
||||
* @param Cat $categories
|
||||
* @param Bud $budgets
|
||||
* Returns a list of categories.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function __construct(ARI $accounts, CRI $components, Cat $categories, Bud $budgets)
|
||||
public function categories()
|
||||
{
|
||||
$this->_components = $components;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_categories = $categories;
|
||||
$this->_budgets = $budgets;
|
||||
/** @var \Firefly\Storage\Category\EloquentCategoryRepository $categories */
|
||||
$categories = App::make('Firefly\Storage\Category\CategoryRepositoryInterface');
|
||||
$list = $categories->get();
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
}
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function beneficiaries()
|
||||
public function expenseAccounts()
|
||||
{
|
||||
$list = $this->_accounts->getBeneficiaries();
|
||||
$return = [];
|
||||
/** @var \Firefly\Storage\Account\EloquentAccountRepository $accounts */
|
||||
$accounts = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$list = $accounts->getOfTypes(['Expense account', 'Beneficiary account']);
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
}
|
||||
@@ -45,18 +61,126 @@ class JsonController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds some JSON for typeahead fields.
|
||||
* Returns a list of transactions, expenses only, using the given parameters.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function categories()
|
||||
public function expenses()
|
||||
{
|
||||
$list = $this->_categories->get();
|
||||
$return = [];
|
||||
|
||||
/*
|
||||
* Gets most parameters from the Input::all() array:
|
||||
*/
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
|
||||
/*
|
||||
* Add some more parameters to fine tune the query:
|
||||
*/
|
||||
$parameters['transactionTypes'] = ['Withdrawal'];
|
||||
$parameters['amount'] = 'negative';
|
||||
|
||||
/*
|
||||
* Get the query:
|
||||
*/
|
||||
$query = $this->helper->journalQuery($parameters);
|
||||
|
||||
/*
|
||||
* Build result set:
|
||||
*/
|
||||
$resultSet = $this->helper->journalDataset($parameters, $query);
|
||||
|
||||
|
||||
/*
|
||||
* Build return data:
|
||||
*/
|
||||
return Response::json($resultSet);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function recurringjournals(RecurringTransaction $recurringTransaction)
|
||||
{
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
$parameters['transactionTypes'] = ['Withdrawal'];
|
||||
$parameters['amount'] = 'negative';
|
||||
|
||||
$query = $this->helper->journalQuery($parameters);
|
||||
|
||||
$query->where('recurring_transaction_id', $recurringTransaction->id);
|
||||
$resultSet = $this->helper->journalDataset($parameters, $query);
|
||||
|
||||
|
||||
/*
|
||||
* Build return data:
|
||||
*/
|
||||
return Response::json($resultSet);
|
||||
}
|
||||
|
||||
public function recurring()
|
||||
{
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
$query = $this->helper->recurringTransactionsQuery($parameters);
|
||||
$resultSet = $this->helper->recurringTransactionsDataset($parameters, $query);
|
||||
return Response::json($resultSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Http\JsonResponse|string
|
||||
*/
|
||||
public function revenue()
|
||||
{
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
$parameters['transactionTypes'] = ['Deposit'];
|
||||
$parameters['amount'] = 'positive';
|
||||
|
||||
$query = $this->helper->journalQuery($parameters);
|
||||
$resultSet = $this->helper->journalDataset($parameters, $query);
|
||||
|
||||
|
||||
/*
|
||||
* Build return data:
|
||||
*/
|
||||
return Response::json($resultSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all revenue accounts.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function revenueAccounts()
|
||||
{
|
||||
/** @var \Firefly\Storage\Account\EloquentAccountRepository $accounts */
|
||||
$accounts = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$list = $accounts->getOfTypes(['Revenue account']);
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[] = $entry->name;
|
||||
}
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all transfers.
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function transfers()
|
||||
{
|
||||
$parameters = $this->helper->dataTableParameters();
|
||||
$parameters['transactionTypes'] = ['Transfer'];
|
||||
$parameters['amount'] = 'positive';
|
||||
|
||||
$query = $this->helper->journalQuery($parameters);
|
||||
$resultSet = $this->helper->journalDataset($parameters, $query);
|
||||
|
||||
|
||||
/*
|
||||
* Build return data:
|
||||
*/
|
||||
return Response::json($resultSet);
|
||||
}
|
||||
}
|
@@ -5,6 +5,8 @@ use Firefly\Storage\Limit\LimitRepositoryInterface as LRI;
|
||||
|
||||
/**
|
||||
* Class LimitController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class LimitController extends BaseController
|
||||
{
|
||||
@@ -19,7 +21,10 @@ class LimitController extends BaseController
|
||||
public function __construct(BRI $budgets, LRI $limits)
|
||||
{
|
||||
$this->_budgets = $budgets;
|
||||
$this->_limits = $limits;
|
||||
$this->_limits = $limits;
|
||||
|
||||
View::share('title','Envelopes');
|
||||
View::share('mainTitleIcon', 'fa-tasks');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,18 +34,20 @@ class LimitController extends BaseController
|
||||
*/
|
||||
public function create(\Budget $budget = null)
|
||||
{
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
$prefilled = [
|
||||
'startdate' => \Input::get('startdate') ? : date('Y-m-d'),
|
||||
'repeat_freq' => \Input::get('repeat_freq') ? : 'monthly',
|
||||
'budget_id' => $budget ? $budget->id : null
|
||||
];
|
||||
|
||||
$budgets = $this->_budgets->getAsSelectList();
|
||||
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
|
||||
$budgets = $toolkit->makeSelectList($this->_budgets->get());
|
||||
|
||||
return View::make('limits.create')->with('budgets', $budgets)->with(
|
||||
'periods', $periods
|
||||
)->with('prefilled', $prefilled);
|
||||
)->with('prefilled', $prefilled)->with('subTitle','New envelope');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +57,7 @@ class LimitController extends BaseController
|
||||
*/
|
||||
public function delete(\Limit $limit)
|
||||
{
|
||||
return View::make('limits.delete')->with('limit', $limit);
|
||||
return View::make('limits.delete')->with('limit', $limit)->with('subTitle','Delete envelope');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,12 +89,15 @@ class LimitController extends BaseController
|
||||
*/
|
||||
public function edit(Limit $limit)
|
||||
{
|
||||
$budgets = $this->_budgets->getAsSelectList();
|
||||
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
|
||||
|
||||
$budgets = $toolkit->makeSelectList($this->_budgets->get());
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
|
||||
return View::make('limits.edit')->with('limit', $limit)->with('budgets', $budgets)->with(
|
||||
'periods', $periods
|
||||
);
|
||||
)->with('subTitle','Edit envelope');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +108,7 @@ class LimitController extends BaseController
|
||||
public function store(Budget $budget = null)
|
||||
{
|
||||
|
||||
// find a limit with these properties, as we might already have one:
|
||||
// find a limit with these properties, as Firefly might already have one:
|
||||
$limit = $this->_limits->store(Input::all());
|
||||
if ($limit->validate()) {
|
||||
Session::flash('success', 'Envelope created!');
|
||||
@@ -110,7 +120,7 @@ class LimitController extends BaseController
|
||||
}
|
||||
} else {
|
||||
Session::flash('error', 'Could not save new envelope.');
|
||||
$budgetId = $budget ? $budget->id : null;
|
||||
$budgetId = $budget ? $budget->id : null;
|
||||
$parameters = [$budgetId, 'from' => Input::get('from')];
|
||||
|
||||
return Redirect::route('budgets.limits.create', $parameters)->withInput()
|
||||
|
52
app/controllers/MigrateController.php
Normal file
52
app/controllers/MigrateController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Class MigrateController
|
||||
*/
|
||||
class MigrateController extends BaseController
|
||||
{
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return View::make('migrate.index')->with('index', 'Migration')->with('title','Migrate')->
|
||||
with('subTitle','From Firefly II to Firefly III');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function upload()
|
||||
{
|
||||
if (Input::hasFile('file') && Input::file('file')->isValid()) {
|
||||
$path = storage_path();
|
||||
$fileName = 'firefly-iii-import-' . date('Y-m-d-H-i') . '.json';
|
||||
$fullName = $path . DIRECTORY_SEPARATOR . $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]);
|
||||
if (Config::get('queue.default') == 'sync') {
|
||||
Session::flash('success', 'Your data has been imported!');
|
||||
} else {
|
||||
Session::flash(
|
||||
'success',
|
||||
'The import job has been queued. Please be patient. Data will appear slowly. Please be patient.'
|
||||
);
|
||||
}
|
||||
|
||||
return Redirect::route('index');
|
||||
}
|
||||
Session::flash('error', 'Could not save file to storage.');
|
||||
return Redirect::route('migrate.index');
|
||||
|
||||
} else {
|
||||
Session::flash('error', 'Please upload a file.');
|
||||
return Redirect::route('migrate.index');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -7,12 +7,16 @@ use Firefly\Storage\Piggybank\PiggybankRepositoryInterface as PRI;
|
||||
/**
|
||||
* Class PiggybankController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
|
||||
* @SuppressWarnings(PHPMD.TooManyMethods)
|
||||
*
|
||||
*/
|
||||
class PiggybankController extends BaseController
|
||||
{
|
||||
|
||||
protected $_repository;
|
||||
protected $_accounts;
|
||||
protected $_repository;
|
||||
|
||||
/**
|
||||
* @param PRI $repository
|
||||
@@ -21,16 +25,18 @@ class PiggybankController extends BaseController
|
||||
public function __construct(PRI $repository, ARI $accounts)
|
||||
{
|
||||
$this->_repository = $repository;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_accounts = $accounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Piggybank $piggyBank
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addMoney(Piggybank $piggyBank)
|
||||
{
|
||||
$what = 'add';
|
||||
$maxAdd = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$what = 'add';
|
||||
$maxAdd = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$maxRemove = null;
|
||||
|
||||
return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with(
|
||||
@@ -43,10 +49,20 @@ class PiggybankController extends BaseController
|
||||
*/
|
||||
public function createPiggybank()
|
||||
{
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
|
||||
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
|
||||
|
||||
return View::make('piggybanks.create-piggybank')->with('accounts', $accounts)->with('periods', $periods);
|
||||
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$list = $this->_accounts->getActiveDefault();
|
||||
$accounts = $toolkit->makeSelectList($list);
|
||||
|
||||
View::share('title', 'Piggy banks');
|
||||
View::share('subTitle', 'Create new');
|
||||
View::share('mainTitleIcon', 'fa-sort-amount-asc');
|
||||
|
||||
return View::make('piggybanks.create-piggybank')->with('accounts', $accounts)
|
||||
->with('periods', $periods);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,8 +70,16 @@ class PiggybankController extends BaseController
|
||||
*/
|
||||
public function createRepeated()
|
||||
{
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
|
||||
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
|
||||
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
$list = $this->_accounts->getActiveDefault();
|
||||
$accounts = $toolkit->makeSelectList($list);
|
||||
|
||||
View::share('title', 'Repeated expenses');
|
||||
View::share('subTitle', 'Create new');
|
||||
View::share('mainTitleIcon', 'fa-rotate-right');
|
||||
|
||||
return View::make('piggybanks.create-repeated')->with('accounts', $accounts)->with('periods', $periods);
|
||||
}
|
||||
@@ -67,6 +91,15 @@ class PiggybankController extends BaseController
|
||||
*/
|
||||
public function delete(Piggybank $piggyBank)
|
||||
{
|
||||
View::share('subTitle', 'Delete "' . $piggyBank->name . '"');
|
||||
if ($piggyBank->repeats == 1) {
|
||||
View::share('title', 'Repeated expenses');
|
||||
View::share('mainTitleIcon', 'fa-rotate-right');
|
||||
} else {
|
||||
View::share('title', 'Piggy banks');
|
||||
View::share('mainTitleIcon', 'fa-sort-amount-asc');
|
||||
}
|
||||
|
||||
return View::make('piggybanks.delete')->with('piggybank', $piggyBank);
|
||||
}
|
||||
|
||||
@@ -78,11 +111,18 @@ class PiggybankController extends BaseController
|
||||
public function destroy(Piggybank $piggyBank)
|
||||
{
|
||||
Event::fire('piggybanks.destroy', [$piggyBank]);
|
||||
if ($piggyBank->repeats == 1) {
|
||||
$route = 'piggybanks.index.repeated';
|
||||
$message = 'Repeated expense';
|
||||
} else {
|
||||
$route = 'piggybanks.index.piggybanks';
|
||||
$message = 'Piggybank';
|
||||
}
|
||||
$this->_repository->destroy($piggyBank);
|
||||
|
||||
Session::flash('success', 'Piggy bank deleted.');
|
||||
Session::flash('success', $message . ' deleted.');
|
||||
|
||||
return Redirect::route('piggybanks.index');
|
||||
return Redirect::route($route);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,12 +132,28 @@ class PiggybankController extends BaseController
|
||||
*/
|
||||
public function edit(Piggybank $piggyBank)
|
||||
{
|
||||
$accounts = $this->_accounts->getActiveDefaultAsSelectList();
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
|
||||
|
||||
$list = $this->_accounts->getActiveDefault();
|
||||
$accounts = $toolkit->makeSelectList($list);
|
||||
$periods = Config::get('firefly.piggybank_periods');
|
||||
|
||||
|
||||
View::share('subTitle', 'Edit "' . $piggyBank->name . '"');
|
||||
|
||||
|
||||
if ($piggyBank->repeats == 1) {
|
||||
View::share('title', 'Repeated expenses');
|
||||
View::share('mainTitleIcon', 'fa-rotate-left');
|
||||
|
||||
return View::make('piggybanks.edit-repeated')->with('piggybank', $piggyBank)->with('accounts', $accounts)
|
||||
->with('periods', $periods);
|
||||
} else {
|
||||
// piggy bank.
|
||||
View::share('title', 'Piggy banks');
|
||||
View::share('mainTitleIcon', 'fa-sort-amount-asc');
|
||||
|
||||
return View::make('piggybanks.edit-piggybank')->with('piggybank', $piggyBank)->with('accounts', $accounts)
|
||||
->with('periods', $periods);
|
||||
}
|
||||
@@ -105,35 +161,6 @@ class PiggybankController extends BaseController
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$countRepeating = $this->_repository->countRepeating();
|
||||
$countNonRepeating = $this->_repository->countNonrepeating();
|
||||
|
||||
$piggybanks = $this->_repository->get();
|
||||
|
||||
// get the accounts with each piggy bank and check their balance; we might need to
|
||||
// show the user a correction.
|
||||
|
||||
$accounts = [];
|
||||
/** @var \Piggybank $piggybank */
|
||||
foreach ($piggybanks as $piggybank) {
|
||||
$account = $piggybank->account;
|
||||
$id = $account->id;
|
||||
if (!isset($accounts[$id])) {
|
||||
$accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)];
|
||||
}
|
||||
}
|
||||
|
||||
return View::make('piggybanks.index')->with('piggybanks', $piggybanks)
|
||||
->with('countRepeating', $countRepeating)
|
||||
->with('countNonRepeating', $countNonRepeating)
|
||||
->with('accounts', $accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Piggybank $piggyBank
|
||||
*
|
||||
@@ -158,7 +185,7 @@ class PiggybankController extends BaseController
|
||||
}
|
||||
break;
|
||||
case 'remove':
|
||||
$rep = $piggyBank->currentRelevantRep();
|
||||
$rep = $piggyBank->currentRelevantRep();
|
||||
$maxRemove = $rep->currentamount;
|
||||
if (round($amount, 2) <= round($maxRemove, 2)) {
|
||||
Session::flash('success', 'Amount updated!');
|
||||
@@ -169,17 +196,58 @@ class PiggybankController extends BaseController
|
||||
}
|
||||
break;
|
||||
}
|
||||
if($piggyBank->repeats == 1) {
|
||||
$route = 'piggybanks.index.repeated';
|
||||
|
||||
return Redirect::route('piggybanks.index');
|
||||
} else {
|
||||
$route = 'piggybanks.index.piggybanks';
|
||||
}
|
||||
return Redirect::route($route);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function piggybanks()
|
||||
{
|
||||
$countRepeating = $this->_repository->countRepeating();
|
||||
$countNonRepeating = $this->_repository->countNonrepeating();
|
||||
|
||||
$piggybanks = $this->_repository->get();
|
||||
|
||||
// get the accounts with each piggy bank and check their balance; Fireflyy might needs to
|
||||
// show the user a correction.
|
||||
|
||||
$accounts = [];
|
||||
/** @var \Piggybank $piggybank */
|
||||
foreach ($piggybanks as $piggybank) {
|
||||
$account = $piggybank->account;
|
||||
$id = $account->id;
|
||||
if (!isset($accounts[$id])) {
|
||||
$account->leftOnAccount = $this->_repository->leftOnAccount($account);
|
||||
$accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)];
|
||||
}
|
||||
}
|
||||
|
||||
View::share('title', 'Piggy banks');
|
||||
View::share('subTitle', 'Save for big expenses');
|
||||
View::share('mainTitleIcon', 'fa-sort-amount-asc');
|
||||
|
||||
return View::make('piggybanks.index')->with('piggybanks', $piggybanks)
|
||||
->with('countRepeating', $countRepeating)
|
||||
->with('countNonRepeating', $countNonRepeating)
|
||||
->with('accounts', $accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Piggybank $piggyBank
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function removeMoney(Piggybank $piggyBank)
|
||||
{
|
||||
$what = 'remove';
|
||||
$maxAdd = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$what = 'remove';
|
||||
$maxAdd = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$maxRemove = $piggyBank->currentRelevantRep()->currentamount;
|
||||
|
||||
return View::make('piggybanks.modifyAmount')->with('what', $what)->with('maxAdd', $maxAdd)->with(
|
||||
@@ -187,13 +255,60 @@ class PiggybankController extends BaseController
|
||||
)->with('piggybank', $piggyBank);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function repeated()
|
||||
{
|
||||
$countRepeating = $this->_repository->countRepeating();
|
||||
$countNonRepeating = $this->_repository->countNonrepeating();
|
||||
|
||||
$piggybanks = $this->_repository->get();
|
||||
|
||||
// get the accounts with each piggy bank and check their balance; Fireflyy might needs to
|
||||
// show the user a correction.
|
||||
|
||||
$accounts = [];
|
||||
/** @var \Piggybank $piggybank */
|
||||
foreach ($piggybanks as $piggybank) {
|
||||
$account = $piggybank->account;
|
||||
$id = $account->id;
|
||||
if (!isset($accounts[$id])) {
|
||||
$account->leftOnAccount = $this->_repository->leftOnAccount($account);
|
||||
$accounts[$id] = ['account' => $account, 'left' => $this->_repository->leftOnAccount($account)];
|
||||
}
|
||||
}
|
||||
|
||||
View::share('title', 'Repeated expenses');
|
||||
View::share('subTitle', 'Save for returning bills');
|
||||
View::share('mainTitleIcon', 'fa-rotate-left');
|
||||
|
||||
|
||||
return View::make('piggybanks.index')->with('piggybanks', $piggybanks)
|
||||
->with('countRepeating', $countRepeating)
|
||||
->with('countNonRepeating', $countNonRepeating)
|
||||
->with('accounts', $accounts);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function show(Piggybank $piggyBank)
|
||||
{
|
||||
$leftOnAccount = $this->_repository->leftOnAccount($piggyBank->account);
|
||||
$balance = $piggyBank->account->balance();
|
||||
$balance = $piggyBank->account->balance();
|
||||
|
||||
View::share('subTitle', $piggyBank->name);
|
||||
|
||||
if ($piggyBank->repeats == 1) {
|
||||
// repeated expense.
|
||||
View::share('title', 'Repeated expenses');
|
||||
View::share('mainTitleIcon', 'fa-rotate-left');
|
||||
} else {
|
||||
// piggy bank.
|
||||
View::share('title', 'Piggy banks');
|
||||
View::share('mainTitleIcon', 'fa-sort-amount-asc');
|
||||
}
|
||||
|
||||
return View::make('piggybanks.show')->with('piggyBank', $piggyBank)->with('leftOnAccount', $leftOnAccount)
|
||||
->with('balance', $balance);
|
||||
@@ -208,17 +323,17 @@ class PiggybankController extends BaseController
|
||||
unset($data['_token']);
|
||||
|
||||
// extend the data array with the settings needed to create a piggy bank:
|
||||
$data['repeats'] = 0;
|
||||
$data['repeats'] = 0;
|
||||
$data['rep_times'] = 1;
|
||||
$data['rep_every'] = 1;
|
||||
$data['order'] = 0;
|
||||
$data['order'] = 0;
|
||||
|
||||
$piggyBank = $this->_repository->store($data);
|
||||
if (!is_null($piggyBank->id)) {
|
||||
Session::flash('success', 'New piggy bank "' . $piggyBank->name . '" created!');
|
||||
Event::fire('piggybanks.store', [$piggyBank]);
|
||||
|
||||
return Redirect::route('piggybanks.index');
|
||||
return Redirect::route('piggybanks.index.piggybanks');
|
||||
|
||||
|
||||
} else {
|
||||
@@ -240,14 +355,13 @@ class PiggybankController extends BaseController
|
||||
|
||||
// extend the data array with the settings needed to create a repeated:
|
||||
$data['repeats'] = 1;
|
||||
$data['order'] = 0;
|
||||
$data['order'] = 0;
|
||||
|
||||
$piggyBank = $this->_repository->store($data);
|
||||
if ($piggyBank->id) {
|
||||
Session::flash('success', 'New piggy bank "' . $piggyBank->name . '" created!');
|
||||
Event::fire('piggybanks.store', [$piggyBank]);
|
||||
|
||||
return Redirect::route('piggybanks.index');
|
||||
return Redirect::route('piggybanks.index.repeated');
|
||||
|
||||
} else {
|
||||
Session::flash('error', 'Could not save piggy bank: ' . $piggyBank->errors()->first());
|
||||
@@ -258,16 +372,27 @@ class PiggybankController extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Piggybank $piggyBank
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(Piggybank $piggyBank)
|
||||
{
|
||||
$piggyBank = $this->_repository->update($piggyBank, Input::all());
|
||||
if ($piggyBank->validate()) {
|
||||
Session::flash('success', 'Piggy bank "' . $piggyBank->name . '" updated.');
|
||||
if ($piggyBank->repeats == 1) {
|
||||
$route = 'piggybanks.index.repeated';
|
||||
$message = 'Repeated expense';
|
||||
} else {
|
||||
$route = 'piggybanks.index.piggybanks';
|
||||
$message = 'Piggy bank';
|
||||
}
|
||||
|
||||
|
||||
Session::flash('success', $message . ' "' . $piggyBank->name . '" updated.');
|
||||
Event::fire('piggybanks.update', [$piggyBank]);
|
||||
|
||||
return Redirect::route('piggybanks.index');
|
||||
return Redirect::route($route);
|
||||
} else {
|
||||
Session::flash('error', 'Could not update piggy bank: ' . $piggyBank->errors()->first());
|
||||
|
||||
|
@@ -5,6 +5,8 @@ use Firefly\Storage\Account\AccountRepositoryInterface as ARI;
|
||||
|
||||
/**
|
||||
* Class PreferencesController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class PreferencesController extends BaseController
|
||||
{
|
||||
@@ -18,8 +20,10 @@ class PreferencesController extends BaseController
|
||||
public function __construct(ARI $accounts, PHI $preferences)
|
||||
{
|
||||
|
||||
$this->_accounts = $accounts;
|
||||
$this->_accounts = $accounts;
|
||||
$this->_preferences = $preferences;
|
||||
View::share('title','Preferences');
|
||||
View::share('mainTitleIcon','fa-gear');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,7 +33,7 @@ class PreferencesController extends BaseController
|
||||
{
|
||||
$accounts = $this->_accounts->getDefault();
|
||||
|
||||
$viewRange = $this->_preferences->get('viewRange', '1M');
|
||||
$viewRange = $this->_preferences->get('viewRange', '1M');
|
||||
$viewRangeValue = $viewRange->data;
|
||||
|
||||
// pref:
|
||||
|
@@ -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');
|
||||
}
|
||||
|
||||
|
@@ -1,20 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Firefly\Exception\FireflyException;
|
||||
use Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface as RTR;
|
||||
use Firefly\Helper\Controllers\RecurringInterface as RI;
|
||||
|
||||
/**
|
||||
* Class RecurringController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class RecurringController extends BaseController
|
||||
{
|
||||
protected $_repository;
|
||||
protected $_helper;
|
||||
|
||||
/**
|
||||
* @param RTR $repository
|
||||
* @param RI $helper
|
||||
*/
|
||||
public function __construct(RTR $repository)
|
||||
public function __construct(RTR $repository, RI $helper)
|
||||
{
|
||||
$this->_repository = $repository;
|
||||
$this->_helper = $helper;
|
||||
|
||||
View::share('title', 'Recurring transactions');
|
||||
View::share('mainTitleIcon', 'fa-rotate-right');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,6 +32,7 @@ class RecurringController extends BaseController
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
View::share('subTitle', 'Create new');
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
|
||||
return View::make('recurring.create')->with('periods', $periods);
|
||||
@@ -34,6 +45,7 @@ class RecurringController extends BaseController
|
||||
*/
|
||||
public function delete(RecurringTransaction $recurringTransaction)
|
||||
{
|
||||
View::share('subTitle', 'Delete "' . $recurringTransaction->name . '"');
|
||||
return View::make('recurring.delete')->with('recurringTransaction', $recurringTransaction);
|
||||
}
|
||||
|
||||
@@ -44,7 +56,7 @@ class RecurringController extends BaseController
|
||||
*/
|
||||
public function destroy(RecurringTransaction $recurringTransaction)
|
||||
{
|
||||
Event::fire('recurring.destroy', [$recurringTransaction]);
|
||||
//Event::fire('recurring.destroy', [$recurringTransaction]);
|
||||
$result = $this->_repository->destroy($recurringTransaction);
|
||||
if ($result === true) {
|
||||
Session::flash('success', 'The recurring transaction was deleted.');
|
||||
@@ -65,6 +77,8 @@ class RecurringController extends BaseController
|
||||
{
|
||||
$periods = \Config::get('firefly.periods_to_text');
|
||||
|
||||
View::share('subTitle', 'Edit "' . $recurringTransaction->name . '"');
|
||||
|
||||
return View::make('recurring.edit')->with('periods', $periods)->with(
|
||||
'recurringTransaction', $recurringTransaction
|
||||
);
|
||||
@@ -75,9 +89,7 @@ class RecurringController extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$list = $this->_repository->get();
|
||||
|
||||
return View::make('recurring.index')->with('list', $list);
|
||||
return View::make('recurring.index');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,53 +97,142 @@ 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
|
||||
* @param RecurringTransaction $recurringTransaction
|
||||
* @return mixed
|
||||
*/
|
||||
public function rescan(RecurringTransaction $recurringTransaction)
|
||||
{
|
||||
if (intval($recurringTransaction->active) == 0) {
|
||||
Session::flash('warning', 'Inactive recurring transactions cannot be scanned.');
|
||||
return Redirect::back();
|
||||
}
|
||||
// do something!
|
||||
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $repo */
|
||||
$repo = App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
|
||||
$set = $repo->get();
|
||||
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
Event::fire('recurring.rescan', [$recurringTransaction, $journal]);
|
||||
}
|
||||
Session::flash('success', 'Rescanned everything.');
|
||||
return Redirect::back();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$recurringTransaction = $this->_repository->store(Input::all());
|
||||
if ($recurringTransaction->validate()) {
|
||||
Session::flash('success', 'Recurring transaction "' . $recurringTransaction->name . '" saved!');
|
||||
Event::fire('recurring.store', [$recurringTransaction]);
|
||||
if (Input::get('create') == '1') {
|
||||
return Redirect::route('recurring.create')->withInput();
|
||||
} else {
|
||||
return Redirect::route('recurring.index');
|
||||
}
|
||||
} else {
|
||||
Session::flash(
|
||||
'error', 'Could not save the recurring transaction: ' . $recurringTransaction->errors()->first()
|
||||
);
|
||||
$data = Input::except(['_token', 'post_submit_action']);
|
||||
switch (Input::get('post_submit_action')) {
|
||||
default:
|
||||
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
|
||||
break;
|
||||
case 'store':
|
||||
case 'create_another':
|
||||
/*
|
||||
* Try to store:
|
||||
*/
|
||||
$messageBag = $this->_repository->store($data);
|
||||
|
||||
return Redirect::route('recurring.create')->withInput()->withErrors($recurringTransaction->errors());
|
||||
/*
|
||||
* Failure!
|
||||
*/
|
||||
if ($messageBag->count() > 0) {
|
||||
Session::flash('error', 'Could not save recurring transaction: ' . $messageBag->first());
|
||||
return Redirect::route('recurring.create')->withInput()->withErrors($messageBag);
|
||||
}
|
||||
|
||||
/*
|
||||
* Success!
|
||||
*/
|
||||
Session::flash('success', 'Recurring transaction "' . e(Input::get('name')) . '" saved!');
|
||||
|
||||
/*
|
||||
* Redirect to original location or back to the form.
|
||||
*/
|
||||
if (Input::get('post_submit_action') == 'create_another') {
|
||||
return Redirect::route('recurring.create')->withInput();
|
||||
} else {
|
||||
return Redirect::route('recurring.index');
|
||||
}
|
||||
break;
|
||||
case 'validate_only':
|
||||
$messageBags = $this->_helper->validate($data);
|
||||
|
||||
Session::flash('warnings', $messageBags['warnings']);
|
||||
Session::flash('successes', $messageBags['successes']);
|
||||
Session::flash('errors', $messageBags['errors']);
|
||||
return Redirect::route('recurring.create')->withInput();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param RecurringTransaction $recurringTransaction
|
||||
*/
|
||||
public function update(RecurringTransaction $recurringTransaction)
|
||||
{
|
||||
/** @var \RecurringTransaction $recurringTransaction */
|
||||
$recurringTransaction = $this->_repository->update($recurringTransaction, Input::all());
|
||||
if ($recurringTransaction->errors()->count() == 0) {
|
||||
Session::flash('success', 'The recurring transaction has been updated.');
|
||||
Event::fire('recurring.update', [$recurringTransaction]);
|
||||
$data = Input::except(['_token', 'post_submit_action']);
|
||||
switch (Input::get('post_submit_action')) {
|
||||
case 'update':
|
||||
case 'return_to_edit':
|
||||
$messageBag = $this->_repository->update($recurringTransaction, $data);
|
||||
if ($messageBag->count() == 0) {
|
||||
// has been saved, return to index:
|
||||
Session::flash('success', 'Recurring transaction updated!');
|
||||
|
||||
return Redirect::route('recurring.index');
|
||||
} else {
|
||||
Session::flash(
|
||||
'error', 'Could not update the recurring transaction: ' . $recurringTransaction->errors()->first()
|
||||
);
|
||||
if (Input::get('post_submit_action') == 'return_to_edit') {
|
||||
return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput();
|
||||
} else {
|
||||
return Redirect::route('recurring.index');
|
||||
}
|
||||
} else {
|
||||
Session::flash('error', 'Could not update recurring transaction: ' . $messageBag->first());
|
||||
|
||||
return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput()->withErrors(
|
||||
$recurringTransaction->errors()
|
||||
);
|
||||
return Redirect::route('transactions.edit', $recurringTransaction->id)->withInput()
|
||||
->withErrors($messageBag);
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
case 'validate_only':
|
||||
$data = Input::all();
|
||||
$data['id'] = $recurringTransaction->id;
|
||||
$messageBags = $this->_helper->validate($data);
|
||||
|
||||
Session::flash('warnings', $messageBags['warnings']);
|
||||
Session::flash('successes', $messageBags['successes']);
|
||||
Session::flash('errors', $messageBags['errors']);
|
||||
return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput();
|
||||
|
||||
break;
|
||||
// update
|
||||
default:
|
||||
throw new FireflyException('Method ' . Input::get('post_submit_action') . ' not implemented yet.');
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// /** @var \RecurringTransaction $recurringTransaction */
|
||||
// $recurringTransaction = $this->_repository->update($recurringTransaction, Input::all());
|
||||
// if ($recurringTransaction->errors()->count() == 0) {
|
||||
// Session::flash('success', 'The recurring transaction has been updated.');
|
||||
// //Event::fire('recurring.update', [$recurringTransaction]);
|
||||
//
|
||||
// return Redirect::route('recurring.index');
|
||||
// } else {
|
||||
// Session::flash(
|
||||
// 'error', 'Could not update the recurring transaction: ' . $recurringTransaction->errors()->first()
|
||||
// );
|
||||
//
|
||||
// return Redirect::route('recurring.edit', $recurringTransaction->id)->withInput()->withErrors(
|
||||
// $recurringTransaction->errors()
|
||||
// );
|
||||
// }
|
||||
}
|
||||
}
|
@@ -1,89 +1,10 @@
|
||||
<?php
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Firefly\Storage\Reminder\ReminderRepositoryInterface as RRI;
|
||||
|
||||
/**
|
||||
* Class ReminderController
|
||||
*
|
||||
*/
|
||||
class ReminderController extends BaseController
|
||||
{
|
||||
|
||||
protected $_repository;
|
||||
|
||||
public function __construct(RRI $repository)
|
||||
{
|
||||
$this->_repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Reminder $reminder
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function dismiss(\Reminder $reminder)
|
||||
{
|
||||
$reminder = $this->_repository->deactivate($reminder);
|
||||
|
||||
return Response::json($reminder->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reminders currently active for the modal dialog.
|
||||
*/
|
||||
public function modalDialog()
|
||||
{
|
||||
$today = new Carbon;
|
||||
$reminders = $this->_repository->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]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return View::make('reminders.popup')->with('reminders', $reminders);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Reminder $reminder
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function postpone(\Reminder $reminder)
|
||||
{
|
||||
$now = new Carbon;
|
||||
$now->addDay();
|
||||
Session::put('dismissal-' . $reminder->id, $now);
|
||||
|
||||
return Response::json($reminder->id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Reminder $reminder
|
||||
*/
|
||||
public function redirect(\Reminder $reminder)
|
||||
{
|
||||
if ($reminder instanceof PiggybankReminder) {
|
||||
// fields to prefill:
|
||||
$parameters = [
|
||||
'account_to_id' => $reminder->piggybank->account->id,
|
||||
'amount' => round($reminder->amountToSave(), 2),
|
||||
'description' => 'Money for ' . $reminder->piggybank->name,
|
||||
'piggybank_id' => $reminder->piggybank->id,
|
||||
'reminder_id' => $reminder->id
|
||||
];
|
||||
|
||||
return Redirect::to(
|
||||
route('transactions.create', ['what' => 'transfer']) . '?' . http_build_query($parameters)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@ class ReportController extends BaseController
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
|
||||
return View::make('reports.index')->with('title','Reports')->with('mainTitleIcon','fa-line-chart');
|
||||
}
|
||||
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -1,61 +1,103 @@
|
||||
<?php
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Firefly\Exception\FireflyException;
|
||||
use Firefly\Helper\Controllers\TransactionInterface as TI;
|
||||
use Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface as TJRI;
|
||||
use Illuminate\Support\MessageBag;
|
||||
|
||||
/**
|
||||
* Class TransactionController
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*
|
||||
*/
|
||||
class TransactionController extends BaseController
|
||||
{
|
||||
|
||||
protected $_helper;
|
||||
protected $_repository;
|
||||
|
||||
/**
|
||||
* Construct a new transaction controller with two of the most often used helpers.
|
||||
*
|
||||
* @param TJRI $repository
|
||||
* @param TI $helper
|
||||
*/
|
||||
public function __construct(TJRI $repository)
|
||||
public function __construct(TJRI $repository, TI $helper)
|
||||
{
|
||||
$this->_repository = $repository;
|
||||
$this->_helper = $helper;
|
||||
View::share('title', 'Transactions');
|
||||
View::share('mainTitleIcon', 'fa-repeat');
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the view helping the user to create a new transaction journal.
|
||||
*
|
||||
* @param string $what
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function create($what = 'deposit')
|
||||
{
|
||||
// get accounts with names and id's.
|
||||
/*
|
||||
* The repositories we need:
|
||||
*/
|
||||
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
|
||||
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$accounts = $accountRepository->getActiveDefaultAsSelectList();
|
||||
|
||||
// get budgets as a select list.
|
||||
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgetRepository */
|
||||
$budgetRepository = App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
|
||||
$budgets = $budgetRepository->getAsSelectList();
|
||||
|
||||
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
|
||||
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
|
||||
|
||||
// get asset accounts with names and id's.
|
||||
$assetAccounts = $toolkit->makeSelectList($accountRepository->getActiveDefault());
|
||||
|
||||
// get budgets as a select list.
|
||||
$budgets = $toolkit->makeSelectList($budgetRepository->get());
|
||||
$budgets[0] = '(no budget)';
|
||||
|
||||
// get the piggy banks.
|
||||
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
|
||||
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
|
||||
$piggies = $piggyRepository->get();
|
||||
$piggies = $toolkit->makeSelectList($piggyRepository->get());
|
||||
$piggies[0] = '(no piggy bank)';
|
||||
|
||||
return View::make('transactions.create')->with('accounts', $accounts)->with('budgets', $budgets)->with(
|
||||
/*
|
||||
* respond to a possible given values in the URL.
|
||||
*/
|
||||
$prefilled = Session::has('prefilled') ? Session::get('prefilled') : [];
|
||||
$respondTo = ['account_id', 'account_from_id'];
|
||||
foreach ($respondTo as $r) {
|
||||
if (!is_null(Input::get($r))) {
|
||||
$prefilled[$r] = Input::get($r);
|
||||
}
|
||||
}
|
||||
Session::put('prefilled', $prefilled);
|
||||
|
||||
return View::make('transactions.create')->with('accounts', $assetAccounts)->with('budgets', $budgets)->with(
|
||||
'what', $what
|
||||
)->with('piggies', $piggies);
|
||||
)->with('piggies', $piggies)->with('subTitle', 'Add a new ' . $what);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the form that allows a user to delete a transaction journal.
|
||||
*
|
||||
* @param TransactionJournal $transactionJournal
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function delete(TransactionJournal $transactionJournal)
|
||||
{
|
||||
return View::make('transactions.delete')->with('journal', $transactionJournal);
|
||||
$type = strtolower($transactionJournal->transactionType->type);
|
||||
|
||||
return View::make('transactions.delete')->with('journal', $transactionJournal)->with(
|
||||
'subTitle', 'Delete ' . $type . ' "' . $transactionJournal->description . '"'
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
@@ -68,103 +110,142 @@ class TransactionController extends BaseController
|
||||
*/
|
||||
public function destroy(TransactionJournal $transactionJournal)
|
||||
{
|
||||
$type = $transactionJournal->transactionType->type;
|
||||
$transactionJournal->delete();
|
||||
|
||||
return Redirect::route('transactions.index');
|
||||
|
||||
switch ($type) {
|
||||
case 'Withdrawal':
|
||||
return Redirect::route('transactions.expenses');
|
||||
break;
|
||||
case 'Deposit':
|
||||
return Redirect::route('transactions.revenue');
|
||||
break;
|
||||
case 'Transfer':
|
||||
return Redirect::route('transactions.transfers');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the view to edit a transaction.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function edit(TransactionJournal $journal)
|
||||
{
|
||||
/*
|
||||
* All the repositories we need:
|
||||
*/
|
||||
/** @var \Firefly\Helper\Toolkit\Toolkit $toolkit */
|
||||
$toolkit = App::make('Firefly\Helper\Toolkit\Toolkit');
|
||||
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
|
||||
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgetRepository */
|
||||
$budgetRepository = App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
|
||||
|
||||
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
|
||||
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
|
||||
|
||||
// type is useful for display:
|
||||
$what = strtolower($journal->transactiontype->type);
|
||||
|
||||
// some lists prefilled:
|
||||
// get accounts with names and id's.
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accountRepository */
|
||||
$accountRepository = App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$accounts = $accountRepository->getActiveDefaultAsSelectList();
|
||||
// get asset accounts with names and id's.
|
||||
$accounts = $toolkit->makeSelectList($accountRepository->getActiveDefault());
|
||||
|
||||
// get budgets as a select list.
|
||||
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgetRepository */
|
||||
$budgetRepository = App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
|
||||
$budgets = $budgetRepository->getAsSelectList();
|
||||
$budgets = $toolkit->makeSelectList($budgetRepository->get());
|
||||
$budgets[0] = '(no budget)';
|
||||
|
||||
/*
|
||||
* Get all piggy banks plus (if any) the relevant piggy bank. Since just one
|
||||
* of the transactions in the journal has this field, it should all fill in nicely.
|
||||
*/
|
||||
// get the piggy banks.
|
||||
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface $piggyRepository */
|
||||
$piggyRepository = App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
|
||||
$piggies = $piggyRepository->get();
|
||||
// piggy bank id?
|
||||
$piggyBankId = null;
|
||||
$piggies = $toolkit->makeSelectList($piggyRepository->get());
|
||||
$piggies[0] = '(no piggy bank)';
|
||||
$piggyBankId = 0;
|
||||
foreach ($journal->transactions as $t) {
|
||||
$piggyBankId = $t->piggybank_id;
|
||||
if (!is_null($t->piggybank_id)) {
|
||||
$piggyBankId = $t->piggybank_id;
|
||||
}
|
||||
}
|
||||
|
||||
// data to properly display form:
|
||||
$data = [
|
||||
/*
|
||||
* Data to properly display the edit form.
|
||||
*/
|
||||
$prefilled = [
|
||||
'date' => $journal->date->format('Y-m-d'),
|
||||
'category' => '',
|
||||
'budget_id' => 0,
|
||||
'piggybank_id' => $piggyBankId
|
||||
];
|
||||
|
||||
/*
|
||||
* Fill in the category.
|
||||
*/
|
||||
$category = $journal->categories()->first();
|
||||
if (!is_null($category)) {
|
||||
$data['category'] = $category->name;
|
||||
$prefilled['category'] = $category->name;
|
||||
}
|
||||
switch ($journal->transactiontype->type) {
|
||||
case 'Withdrawal':
|
||||
$data['account_id'] = $journal->transactions[0]->account->id;
|
||||
$data['beneficiary'] = $journal->transactions[1]->account->name;
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
|
||||
/*
|
||||
* Switch on the type of transaction edited by the user and fill in other
|
||||
* relevant fields:
|
||||
*/
|
||||
switch ($what) {
|
||||
case 'withdrawal':
|
||||
$prefilled['account_id'] = $journal->transactions[0]->account->id;
|
||||
$prefilled['expense_account'] = $journal->transactions[1]->account->name;
|
||||
$prefilled['amount'] = floatval($journal->transactions[1]->amount);
|
||||
$budget = $journal->budgets()->first();
|
||||
if (!is_null($budget)) {
|
||||
$data['budget_id'] = $budget->id;
|
||||
$prefilled['budget_id'] = $budget->id;
|
||||
}
|
||||
break;
|
||||
case 'Deposit':
|
||||
$data['account_id'] = $journal->transactions[1]->account->id;
|
||||
$data['beneficiary'] = $journal->transactions[0]->account->name;
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
case 'deposit':
|
||||
$prefilled['account_id'] = $journal->transactions[1]->account->id;
|
||||
$prefilled['revenue_account'] = $journal->transactions[0]->account->name;
|
||||
$prefilled['amount'] = floatval($journal->transactions[1]->amount);
|
||||
break;
|
||||
case 'Transfer':
|
||||
$data['account_from_id'] = $journal->transactions[1]->account->id;
|
||||
$data['account_to_id'] = $journal->transactions[0]->account->id;
|
||||
$data['amount'] = floatval($journal->transactions[1]->amount);
|
||||
case 'transfer':
|
||||
$prefilled['account_from_id'] = $journal->transactions[1]->account->id;
|
||||
$prefilled['account_to_id'] = $journal->transactions[0]->account->id;
|
||||
$prefilled['amount'] = floatval($journal->transactions[1]->amount);
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Show the view.
|
||||
*/
|
||||
return View::make('transactions.edit')->with('journal', $journal)->with('accounts', $accounts)->with(
|
||||
'what', $what
|
||||
)->with('budgets', $budgets)->with('data', $data)->with('piggies', $piggies);
|
||||
)->with('budgets', $budgets)->with('data', $prefilled)->with('piggies', $piggies)->with(
|
||||
'subTitle', 'Edit ' . $what . ' "' . $journal->description . '"'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this|\Illuminate\View\View
|
||||
* @return $this
|
||||
*/
|
||||
public function index()
|
||||
public function expenses()
|
||||
{
|
||||
$start = is_null(Input::get('startdate')) ? null : new Carbon(Input::get('startdate'));
|
||||
$end = is_null(Input::get('enddate')) ? null : new Carbon(Input::get('enddate'));
|
||||
if ($start <= $end && !is_null($start) && !is_null($end)) {
|
||||
$journals = $this->_repository->paginate(25, $start, $end);
|
||||
$filtered = true;
|
||||
$filters = ['start' => $start, 'end' => $end];
|
||||
} else {
|
||||
$journals = $this->_repository->paginate(25);
|
||||
$filtered = false;
|
||||
$filters = null;
|
||||
}
|
||||
return View::make('transactions.list')->with('subTitle', 'Expenses')->with(
|
||||
'subTitleIcon', 'fa-long-arrow-left'
|
||||
)->with('what', 'expenses');
|
||||
}
|
||||
|
||||
|
||||
return View::make('transactions.index')->with('journals', $journals)->with('filtered', $filtered)->with(
|
||||
'filters', $filters
|
||||
);
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function revenue()
|
||||
{
|
||||
return View::make('transactions.list')->with('subTitle', 'Revenue')->with(
|
||||
'subTitleIcon', 'fa-long-arrow-right'
|
||||
)->with('what', 'revenue');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,63 +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.
|
||||
/*
|
||||
* 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!');
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -17,7 +17,7 @@ class UserController extends BaseController
|
||||
*/
|
||||
public function __construct(URI $user, EHI $email)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->user = $user;
|
||||
$this->email = $email;
|
||||
|
||||
}
|
||||
@@ -41,14 +41,12 @@ class UserController extends BaseController
|
||||
public function postLogin()
|
||||
{
|
||||
$rememberMe = Input::get('remember_me') == '1';
|
||||
$data = [
|
||||
$data = [
|
||||
'email' => Input::get('email'),
|
||||
'password' => Input::get('password')
|
||||
];
|
||||
$result = Auth::attempt($data, $rememberMe);
|
||||
$result = Auth::attempt($data, $rememberMe);
|
||||
if ($result) {
|
||||
Session::flash('success', 'Logged in!');
|
||||
|
||||
return Redirect::route('index');
|
||||
}
|
||||
|
||||
|
@@ -25,8 +25,8 @@ class CreateRecurringTransactionsTable extends Migration
|
||||
$table->integer('user_id')->unsigned();
|
||||
$table->string('name', 50);
|
||||
$table->string('match', 255);
|
||||
$table->decimal('amount_max', 10, 2);
|
||||
$table->decimal('amount_min', 10, 2);
|
||||
$table->decimal('amount_max', 10, 2);
|
||||
$table->date('date');
|
||||
$table->boolean('active');
|
||||
|
@@ -24,6 +24,7 @@ class CreateTransactionJournalsTable extends Migration
|
||||
$table->timestamps();
|
||||
$table->integer('user_id')->unsigned();
|
||||
$table->integer('transaction_type_id')->unsigned();
|
||||
$table->integer('recurring_transaction_id')->unsigned()->nullable();
|
||||
$table->integer('transaction_currency_id')->unsigned();
|
||||
$table->string('description', 255)->nullable();
|
||||
$table->boolean('completed');
|
||||
@@ -34,6 +35,11 @@ class CreateTransactionJournalsTable extends Migration
|
||||
->references('id')->on('transaction_types')
|
||||
->onDelete('cascade');
|
||||
|
||||
// connect transaction journals to recurring transactions
|
||||
$table->foreign('recurring_transaction_id')
|
||||
->references('id')->on('recurring_transactions')
|
||||
->onDelete('set null');
|
||||
|
||||
// connect transaction journals to transaction currencies
|
||||
$table->foreign('transaction_currency_id')
|
||||
->references('id')->on('transaction_currencies')
|
||||
|
@@ -27,26 +27,11 @@ class CreateRemindersTable extends Migration
|
||||
'reminders', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->timestamps();
|
||||
$table->string('class', 40);
|
||||
$table->integer('piggybank_id')->unsigned()->nullable();
|
||||
$table->integer('recurring_transaction_id')->unsigned()->nullable();
|
||||
$table->integer('user_id')->unsigned();
|
||||
$table->date('startdate');
|
||||
$table->date('enddate');
|
||||
$table->date('enddate')->nullable();
|
||||
$table->boolean('active');
|
||||
|
||||
|
||||
// connect reminders to piggy banks.
|
||||
$table->foreign('piggybank_id')
|
||||
->references('id')->on('piggybanks')
|
||||
->onDelete('set null');
|
||||
|
||||
// connect reminders to recurring transactions.
|
||||
$table->foreign('recurring_transaction_id')
|
||||
->references('id')->on('recurring_transactions')
|
||||
->onDelete('set null');
|
||||
|
||||
|
||||
// connect reminders to users
|
||||
$table->foreign('user_id')
|
||||
->references('id')->on('users')
|
||||
|
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateImportmapsTable extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('importmaps', function(Blueprint $table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$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')
|
||||
->references('id')->on('users')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('importmaps');
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class CreateImportentriesTable extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('importentries', function(Blueprint $table)
|
||||
{
|
||||
$table->increments('id');
|
||||
$table->timestamps();
|
||||
$table->string('class',200);
|
||||
$table->integer('importmap_id')->unsigned();
|
||||
$table->integer('old')->unsigned();
|
||||
$table->integer('new')->unsigned();
|
||||
|
||||
// map import entries to import map.
|
||||
// connect accounts to account_types
|
||||
$table->foreign('importmap_id')
|
||||
->references('id')->on('importmaps')
|
||||
->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('importentries');
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
class CreateFailedJobsTable extends Migration
|
||||
{
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop('failed_jobs');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
$table->increments('id');
|
||||
$table->text('connection');
|
||||
$table->text('queue');
|
||||
$table->text('payload');
|
||||
$table->timestamp('failed_at');
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@@ -11,16 +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]
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -6,10 +6,11 @@ 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();
|
||||
Event::fire('recurring.verify');
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,7 +1,4 @@
|
||||
<?php
|
||||
|
||||
|
||||
|
||||
namespace Firefly\Database;
|
||||
|
||||
use LaravelBook\Ardent\Ardent;
|
||||
@@ -73,7 +70,7 @@ abstract class SingleTableInheritanceEntity extends Ardent
|
||||
// newEloquentBuilder() was added in 4.1
|
||||
$builder = $this->newEloquentBuilder($this->newBaseQueryBuilder());
|
||||
|
||||
// Once we have the query builders, we will set the model instances so the
|
||||
// Once Firefly has the query builders, it will set the model instances so the
|
||||
// builder can easily access any information it may need from the model
|
||||
// while it is constructing and executing various queries against it.
|
||||
$builder->setModel($this)->with($this->with);
|
||||
|
@@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Firefly\Helper;
|
||||
|
||||
|
||||
/**
|
||||
* Class MigrationException
|
||||
*
|
||||
* @package Firefly\Helper
|
||||
*/
|
||||
class MigrationException extends \Exception
|
||||
{
|
||||
|
||||
}
|
11
app/lib/Firefly/Exception/ValidationException.php
Normal file
11
app/lib/Firefly/Exception/ValidationException.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
namespace Firefly\Exception;
|
||||
|
||||
/**
|
||||
* Class ValidationException
|
||||
*
|
||||
* @package Firefly\Exception
|
||||
*/
|
||||
class ValidationException extends \Exception {
|
||||
|
||||
}
|
327
app/lib/Firefly/Form/Form.php
Normal file
327
app/lib/Firefly/Form/Form.php
Normal 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">€</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;
|
||||
|
||||
}
|
||||
}
|
@@ -2,8 +2,6 @@
|
||||
|
||||
namespace Firefly\Helper\Controllers;
|
||||
|
||||
use Firefly\Exception\FireflyException;
|
||||
|
||||
/**
|
||||
* Class Account
|
||||
*
|
||||
@@ -11,15 +9,15 @@ use Firefly\Exception\FireflyException;
|
||||
*/
|
||||
class Account implements AccountInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param \Account $account
|
||||
*
|
||||
* @return mixed
|
||||
* @return \TransactionJournal|null
|
||||
*/
|
||||
public function openingBalanceTransaction(\Account $account)
|
||||
{
|
||||
return \TransactionJournal::
|
||||
withRelevantData()->account($account)
|
||||
return \TransactionJournal::withRelevantData()
|
||||
->accountIs($account)
|
||||
->leftJoin('transaction_types', 'transaction_types.id', '=',
|
||||
'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', 'Opening balance')
|
||||
@@ -27,55 +25,57 @@ class Account implements AccountInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Account $account
|
||||
* @param $perPage
|
||||
* Since it is entirely possible the database is messed up somehow it might be that a transaction
|
||||
* journal has only one transaction. This is mainly caused by wrong deletions and other artefacts from the past.
|
||||
*
|
||||
* @return mixed|void
|
||||
* If it is the case, Firefly removes $item and continues like nothing ever happened. This will however,
|
||||
* mess up some statisics but it's decided everybody should learn to live with that.
|
||||
*
|
||||
* Firefly might be needing some cleanup routine in the future.
|
||||
*
|
||||
* For now, Firefly simply warns the user of this.
|
||||
*
|
||||
* @param \Account $account
|
||||
* @param $perPage
|
||||
*
|
||||
* @return array|mixed
|
||||
* @throws \Firefly\Exception\FireflyException
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
|
||||
*/
|
||||
public function show(\Account $account, $perPage)
|
||||
{
|
||||
$start = \Session::get('start');
|
||||
$end = \Session::get('end');
|
||||
$stats = [
|
||||
'budgets' => [],
|
||||
'categories' => [],
|
||||
'accounts' => []
|
||||
'accounts' => []
|
||||
];
|
||||
$items = [];
|
||||
|
||||
|
||||
// build a query:
|
||||
$query = \TransactionJournal::withRelevantData()->defaultSorting()->account($account)->after($start)
|
||||
$query = \TransactionJournal::withRelevantData()
|
||||
->defaultSorting()
|
||||
->accountIs($account)
|
||||
->after($start)
|
||||
->before($end);
|
||||
// filter some:
|
||||
if (\Input::get('type')) {
|
||||
switch (\Input::get('type')) {
|
||||
case 'transactions':
|
||||
$query->transactionTypes(['Deposit', 'Withdrawal']);
|
||||
break;
|
||||
case 'transfers':
|
||||
$query->transactionTypes(['Transfer']);
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException('No case for type "' . \Input::get('type') . '"!');
|
||||
break;
|
||||
}
|
||||
switch (\Input::get('type')) {
|
||||
case 'transactions':
|
||||
$query->transactionTypes(['Deposit', 'Withdrawal']);
|
||||
break;
|
||||
case 'transfers':
|
||||
$query->transactionTypes(['Transfer']);
|
||||
break;
|
||||
}
|
||||
|
||||
if (\Input::get('show')) {
|
||||
switch (\Input::get('show')) {
|
||||
case 'expenses':
|
||||
case 'out':
|
||||
$query->lessThan(0);
|
||||
break;
|
||||
case 'income':
|
||||
case 'in':
|
||||
$query->moreThan(0);
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException('No case for show "' . \Input::get('show') . '"!');
|
||||
break;
|
||||
}
|
||||
switch (\Input::get('show')) {
|
||||
case 'expenses':
|
||||
case 'out':
|
||||
$query->lessThan(0);
|
||||
break;
|
||||
case 'income':
|
||||
case 'in':
|
||||
$query->moreThan(0);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,26 +91,11 @@ class Account implements AccountInterface
|
||||
foreach ($result as $index => $item) {
|
||||
|
||||
foreach ($item->components as $component) {
|
||||
if ($component->class == 'Budget') {
|
||||
$stats['budgets'][$component->id] = $component;
|
||||
}
|
||||
if ($component->class == 'Category') {
|
||||
$stats['categories'][$component->id] = $component;
|
||||
}
|
||||
$stats[$component->class][$component->id] = $component;
|
||||
}
|
||||
// since it is entirely possible the database is messed up somehow
|
||||
// it might be that a transaction journal has only one transaction.
|
||||
// this is mainly caused by wrong deletions and other artefacts from the past.
|
||||
// if it is the case, we remove $item and continue like nothing ever happened.
|
||||
|
||||
// this will however, mess up some statisics but we can live with that.
|
||||
// we might be needing some cleanup routine in the future.
|
||||
|
||||
// for now, we simply warn the user of this.
|
||||
|
||||
if (count($item->transactions) < 2) {
|
||||
\Session::flash('warning',
|
||||
'Some transactions are incomplete; they will not be shown. Statistics may differ.');
|
||||
\Session::flash('warning', 'Some transactions are incomplete; they will not be shown.');
|
||||
unset($result[$index]);
|
||||
continue;
|
||||
}
|
||||
@@ -125,20 +110,19 @@ 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;
|
||||
|
||||
|
||||
$stats['period'] = [
|
||||
'in' => $trIn,
|
||||
'out' => $trOut,
|
||||
@@ -155,7 +139,5 @@ class Account implements AccountInterface
|
||||
];
|
||||
|
||||
return $return;
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -13,6 +13,11 @@ class Budget implements BudgetInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* First, loop all budgets, all of their limits and all repetitions to get an overview per period
|
||||
* and some basic information about that repetition's data.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param Collection $budgets
|
||||
*
|
||||
* @return mixed|void
|
||||
@@ -21,32 +26,30 @@ class Budget implements BudgetInterface
|
||||
{
|
||||
$return = [];
|
||||
|
||||
/** @var \Budget $budget */
|
||||
foreach ($budgets as $budget) {
|
||||
|
||||
/** @var \Limit $limit */
|
||||
foreach ($budget->limits as $limit) {
|
||||
|
||||
/** @var \LimitRepetition $rep */
|
||||
foreach ($limit->limitrepetitions as $rep) {
|
||||
$periodOrder = $rep->periodOrder();
|
||||
$period = $rep->periodShow();
|
||||
$return[$periodOrder] = isset($return[$periodOrder])
|
||||
? $return[$periodOrder]
|
||||
: ['date' => $period,
|
||||
'dateObject' => $rep->startdate,
|
||||
'start' => $rep->startdate,
|
||||
'end' => $rep->enddate,
|
||||
'budget_id' => $limit->budget_id];
|
||||
/** @var \LimitRepetition $repetition */
|
||||
foreach ($limit->limitrepetitions as $repetition) {
|
||||
$repetition->left = $repetition->leftInRepetition();
|
||||
$periodOrder = $repetition->periodOrder();
|
||||
$period = $repetition->periodShow();
|
||||
if (!isset($return[$periodOrder])) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// put all the budgets under their respective date:
|
||||
foreach ($budgets as $budget) {
|
||||
foreach ($budget->limits as $limit) {
|
||||
foreach ($limit->limitrepetitions as $rep) {
|
||||
$rep->left = $rep->left();
|
||||
$return[$periodOrder] = [
|
||||
'date' => $period,
|
||||
'start' => $repetition->startdate,
|
||||
'end' => $repetition->enddate,
|
||||
'budget_id' => $budget->id,
|
||||
'limitrepetitions' => [$repetition]
|
||||
];
|
||||
} else {
|
||||
$return[$periodOrder]['limitrepetitions'][] = $repetition;
|
||||
}
|
||||
|
||||
$month = $rep->periodOrder();
|
||||
$return[$month]['limitrepetitions'][] = $rep;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,30 +59,24 @@ class Budget implements BudgetInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a repetition (complex because of user check)
|
||||
* and then get the transactions in it.
|
||||
* @param $repetitionId
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function organizeRepetition($repetitionId)
|
||||
public function organizeRepetition(\LimitRepetition $repetition)
|
||||
{
|
||||
$result = [];
|
||||
$repetition = \LimitRepetition::with('limit', 'limit.budget')->leftJoin(
|
||||
'limits', 'limit_repetitions.limit_id', '=', 'limits.id'
|
||||
)->leftJoin('components', 'limits.component_id', '=', 'components.id')->where(
|
||||
'components.user_id', \Auth::user()->id
|
||||
)
|
||||
->where('limit_repetitions.id', $repetitionId)->first(['limit_repetitions.*']);
|
||||
|
||||
// get transactions:
|
||||
$set = $repetition->limit->budget->transactionjournals()->with(
|
||||
'transactions', 'transactions.account', 'components', 'transactiontype'
|
||||
)->leftJoin(
|
||||
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'
|
||||
)->where('transaction_types.type', 'Withdrawal')->where(
|
||||
'date', '>=', $repetition->startdate->format('Y-m-d')
|
||||
)->where('date', '<=', $repetition->enddate->format('Y-m-d'))->orderBy('date', 'DESC')->orderBy(
|
||||
'id', 'DESC'
|
||||
)->get(['transaction_journals.*']);
|
||||
$set = $repetition->limit->budget
|
||||
->transactionjournals()
|
||||
->withRelevantData()
|
||||
->transactionTypes(['Withdrawal'])
|
||||
->after($repetition->startdate)
|
||||
->before($repetition->enddate)
|
||||
->defaultSorting()
|
||||
->get(['transaction_journals.*']);
|
||||
|
||||
$result[0] = [
|
||||
'date' => $repetition->periodShow(),
|
||||
@@ -93,8 +90,10 @@ class Budget implements BudgetInterface
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param \Budget $budget
|
||||
* @param bool $useSessionDates
|
||||
* @param bool $useSessionDates
|
||||
*
|
||||
* @return array|mixed
|
||||
* @throws \Firefly\Exception\FireflyException
|
||||
@@ -102,15 +101,15 @@ class Budget implements BudgetInterface
|
||||
public function organizeRepetitions(\Budget $budget, $useSessionDates = false)
|
||||
{
|
||||
$sessionStart = \Session::get('start');
|
||||
$sessionEnd = \Session::get('end');
|
||||
$sessionEnd = \Session::get('end');
|
||||
|
||||
$result = [];
|
||||
$result = [];
|
||||
$inRepetition = [];
|
||||
|
||||
// get the limits:
|
||||
if ($useSessionDates) {
|
||||
$limits = $budget->limits()->where('startdate', '>=', $sessionStart->format('Y-m-d'))->where(
|
||||
'startdate', '<=', $sessionEnd->format('Y-m-d')
|
||||
'startdate', '<=', $sessionEnd->format('Y-m-d')
|
||||
)->get();
|
||||
} else {
|
||||
$limits = $budget->limits;
|
||||
@@ -119,7 +118,7 @@ class Budget implements BudgetInterface
|
||||
/** @var \Limit $limit */
|
||||
foreach ($limits as $limit) {
|
||||
foreach ($limit->limitrepetitions as $repetition) {
|
||||
$order = $repetition->periodOrder();
|
||||
$order = $repetition->periodOrder();
|
||||
$result[$order] = [
|
||||
'date' => $repetition->periodShow(),
|
||||
'limitrepetition' => $repetition,
|
||||
@@ -127,16 +126,14 @@ class Budget implements BudgetInterface
|
||||
'journals' => [],
|
||||
'paginated' => false
|
||||
];
|
||||
$transactions = [];
|
||||
$set = $budget->transactionjournals()->with(
|
||||
'transactions', 'transactions.account', 'components', 'transactiontype'
|
||||
)->leftJoin(
|
||||
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'
|
||||
)->where('transaction_types.type', 'Withdrawal')->where(
|
||||
'date', '>=', $repetition->startdate->format('Y-m-d')
|
||||
)->where('date', '<=', $repetition->enddate->format('Y-m-d'))->orderBy('date', 'DESC')->orderBy(
|
||||
'id', 'DESC'
|
||||
)->get(['transaction_journals.*']);
|
||||
$transactions = [];
|
||||
$set = $budget->transactionjournals()
|
||||
->withRelevantData()
|
||||
->transactionTypes(['Withdrawal'])
|
||||
->after($repetition->startdate)
|
||||
->before($repetition->enddate)
|
||||
->defaultSorting()
|
||||
->get(['transaction_journals.*']);
|
||||
foreach ($set as $entry) {
|
||||
$transactions[] = $entry;
|
||||
$inRepetition[] = $entry->id;
|
||||
@@ -146,30 +143,17 @@ class Budget implements BudgetInterface
|
||||
|
||||
}
|
||||
if ($useSessionDates === false) {
|
||||
$query = $budget->transactionjournals()->withRelevantData()->defaultSorting();
|
||||
if (count($inRepetition) > 0) {
|
||||
$query = $budget->transactionjournals()->with(
|
||||
'transactions', 'transactions.account', 'components', 'transactiontype',
|
||||
'transactions.account.accounttype'
|
||||
)->whereNotIn(
|
||||
'transaction_journals.id', $inRepetition
|
||||
)->orderBy('date', 'DESC')->orderBy(
|
||||
'transaction_journals.id', 'DESC'
|
||||
);
|
||||
} else {
|
||||
$query = $budget->transactionjournals()->with(
|
||||
'transactions', 'transactions.account', 'components', 'transactiontype',
|
||||
'transactions.account.accounttype'
|
||||
)->orderBy('date', 'DESC')->orderBy(
|
||||
'transaction_journals.id', 'DESC'
|
||||
);
|
||||
$query->whereNotIn('transaction_journals.id', $inRepetition);
|
||||
}
|
||||
|
||||
// build paginator:
|
||||
$perPage = 25;
|
||||
$perPage = 25;
|
||||
$totalItems = $query->count();
|
||||
$page = intval(\Input::get('page')) > 1 ? intval(\Input::get('page')) : 1;
|
||||
$skip = ($page - 1) * $perPage;
|
||||
$set = $query->skip($skip)->take($perPage)->get();
|
||||
$page = intval(\Input::get('page')) > 1 ? intval(\Input::get('page')) : 1;
|
||||
$skip = ($page - 1) * $perPage;
|
||||
$set = $query->skip($skip)->take($perPage)->get();
|
||||
|
||||
// stupid paginator!
|
||||
$items = [];
|
||||
@@ -177,9 +161,12 @@ class Budget implements BudgetInterface
|
||||
foreach ($set as $item) {
|
||||
$items[] = $item;
|
||||
}
|
||||
$paginator = \Paginator::make($items, $totalItems, $perPage);
|
||||
$result['0000'] = ['date' => 'Not in an envelope', 'limit' => null, 'paginated' => true,
|
||||
'journals' => $paginator];
|
||||
$paginator = \Paginator::make($items, $totalItems, $perPage);
|
||||
$result['0000'] = [
|
||||
'date' => 'Not in an envelope',
|
||||
'limit' => null,
|
||||
'paginated' => true,
|
||||
'journals' => $paginator];
|
||||
}
|
||||
krsort($result);
|
||||
|
||||
@@ -196,13 +183,12 @@ class Budget implements BudgetInterface
|
||||
$inRepetitions = [];
|
||||
foreach ($budget->limits as $limit) {
|
||||
foreach ($limit->limitrepetitions as $repetition) {
|
||||
$set = $budget->transactionjournals()->leftJoin(
|
||||
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'
|
||||
)->where('transaction_types.type', 'Withdrawal')->where(
|
||||
'date', '>=', $repetition->startdate->format('Y-m-d')
|
||||
)->where('date', '<=', $repetition->enddate->format('Y-m-d'))->orderBy('date', 'DESC')->get(
|
||||
['transaction_journals.id']
|
||||
);
|
||||
$set = $budget->transactionjournals()
|
||||
->transactionTypes(['Withdrawal'])
|
||||
->after($repetition->startdate)
|
||||
->before($repetition->enddate)
|
||||
->defaultSorting()
|
||||
->get(['transaction_journals.id']);
|
||||
foreach ($set as $item) {
|
||||
$inRepetitions[] = $item->id;
|
||||
}
|
||||
@@ -210,21 +196,17 @@ class Budget implements BudgetInterface
|
||||
|
||||
}
|
||||
|
||||
$query = $budget->transactionjournals()->with(
|
||||
'transactions', 'transactions.account', 'components', 'transactiontype',
|
||||
'transactions.account.accounttype'
|
||||
)->whereNotIn(
|
||||
'transaction_journals.id', $inRepetitions
|
||||
)->orderBy('date', 'DESC')->orderBy(
|
||||
'transaction_journals.id', 'DESC'
|
||||
);
|
||||
$query = $budget->transactionjournals()
|
||||
->withRelevantData()
|
||||
->whereNotIn('transaction_journals.id', $inRepetitions)
|
||||
->defaultSorting();
|
||||
|
||||
// build paginator:
|
||||
$perPage = 25;
|
||||
$perPage = 25;
|
||||
$totalItems = $query->count();
|
||||
$page = intval(\Input::get('page')) > 1 ? intval(\Input::get('page')) : 1;
|
||||
$skip = ($page - 1) * $perPage;
|
||||
$set = $query->skip($skip)->take($perPage)->get();
|
||||
$page = intval(\Input::get('page')) > 1 ? intval(\Input::get('page')) : 1;
|
||||
$skip = ($page - 1) * $perPage;
|
||||
$set = $query->skip($skip)->take($perPage)->get();
|
||||
|
||||
// stupid paginator!
|
||||
$items = [];
|
||||
@@ -233,8 +215,12 @@ class Budget implements BudgetInterface
|
||||
$items[] = $item;
|
||||
}
|
||||
$paginator = \Paginator::make($items, $totalItems, $perPage);
|
||||
$result = [0 => ['date' => 'Not in an envelope', 'limit' => null, 'paginated' => true,
|
||||
'journals' => $paginator]];
|
||||
$result = [0 => [
|
||||
'date' => 'Not in an envelope',
|
||||
'limit' => null,
|
||||
'paginated' => true,
|
||||
'journals' => $paginator
|
||||
]];
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ interface BudgetInterface
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function organizeRepetition($repetitionId);
|
||||
public function organizeRepetition(\LimitRepetition $repetition);
|
||||
|
||||
|
||||
/**
|
||||
|
@@ -4,6 +4,7 @@ namespace Firefly\Helper\Controllers;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Firefly\Exception\FireflyException;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class Chart
|
||||
@@ -23,14 +24,21 @@ class Chart implements ChartInterface
|
||||
public function account(\Account $account, Carbon $start, Carbon $end)
|
||||
{
|
||||
$current = clone $start;
|
||||
$today = new Carbon;
|
||||
$return = ['name' => $account->name, 'id' => $account->id, 'data' => []];
|
||||
$today = new Carbon;
|
||||
$return = [
|
||||
'name' => $account->name,
|
||||
'id' => $account->id,
|
||||
'type' => 'spline',
|
||||
'pointStart' => $start->timestamp * 1000,
|
||||
'pointInterval' => 24 * 3600 * 1000, // one day
|
||||
'data' => []
|
||||
];
|
||||
|
||||
while ($current <= $end) {
|
||||
if ($current > $today) {
|
||||
$return['data'][] = [$current->timestamp * 1000, $account->predict(clone $current)];
|
||||
$return['data'][] = $account->predict(clone $current);
|
||||
} else {
|
||||
$return['data'][] = [$current->timestamp * 1000, $account->balance(clone $current)];
|
||||
$return['data'][] = $account->balance(clone $current);
|
||||
}
|
||||
|
||||
$current->addDay();
|
||||
@@ -94,92 +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 = '';
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$data['series'] = [
|
||||
[
|
||||
'name' => $limitInPeriod,
|
||||
'data' => []
|
||||
],
|
||||
[
|
||||
'name' => $spentInPeriod,
|
||||
'data' => []
|
||||
],
|
||||
];
|
||||
|
||||
|
||||
foreach ($budgets as $budget) {
|
||||
if ($budget->count > 0) {
|
||||
$data['labels'][] = wordwrap($budget->name, 12, "<br>");
|
||||
}
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
@@ -211,10 +133,10 @@ class Chart implements ChartInterface
|
||||
. ' transactions!');
|
||||
}
|
||||
$transaction = $journal->transactions[0];
|
||||
$amount = floatval($transaction->amount);
|
||||
$amount = floatval($transaction->amount);
|
||||
|
||||
// get budget from journal:
|
||||
$category = $journal->categories()->first();
|
||||
$category = $journal->categories()->first();
|
||||
$categoryName = is_null($category) ? '(no category)' : $category->name;
|
||||
|
||||
$result[$categoryName] = isset($result[$categoryName]) ? $result[$categoryName] + floatval($amount)
|
||||
@@ -335,7 +257,7 @@ class Chart implements ChartInterface
|
||||
|
||||
|
||||
// get sum for current range:
|
||||
$journals = \TransactionJournal::
|
||||
$journals = \TransactionJournal::
|
||||
with(
|
||||
['transactions' => function ($q) {
|
||||
$q->where('amount', '>', 0);
|
||||
@@ -360,7 +282,7 @@ class Chart implements ChartInterface
|
||||
. ' transactions!');
|
||||
}
|
||||
$transaction = $journal->transactions[0];
|
||||
$amount = floatval($transaction->amount);
|
||||
$amount = floatval($transaction->amount);
|
||||
$currentSum += $amount;
|
||||
|
||||
}
|
||||
@@ -401,4 +323,153 @@ class Chart implements ChartInterface
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function spentOnDay(\Budget $budget, Carbon $date)
|
||||
{
|
||||
return floatval(
|
||||
\Transaction::
|
||||
leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin(
|
||||
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
|
||||
'transaction_journals.id'
|
||||
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
|
||||
'transaction_journals.date', $date->format('Y-m-d')
|
||||
)->where('amount', '>', 0)->sum('amount')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function allJournalsInBudgetEnvelope(\Budget $budget)
|
||||
{
|
||||
$inRepetitions = [];
|
||||
|
||||
foreach ($budget->limits as $limit) {
|
||||
foreach ($limit->limitrepetitions as $repetition) {
|
||||
$set = $budget
|
||||
->transactionjournals()
|
||||
->transactionTypes(['Withdrawal'])
|
||||
->after($repetition->startdate)
|
||||
->before($repetition->enddate)
|
||||
->get(['transaction_journals.id']);
|
||||
|
||||
foreach ($set as $item) {
|
||||
$inRepetitions[] = $item->id;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $inRepetitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
* @param array $ids
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function journalsNotInSet(\Budget $budget, array $ids)
|
||||
{
|
||||
$query = $budget->transactionjournals()
|
||||
->whereNotIn('transaction_journals.id', $ids)
|
||||
->orderBy('date', 'DESC')
|
||||
->orderBy('transaction_journals.id', 'DESC');
|
||||
|
||||
$result = $query->get(['transaction_journals.id']);
|
||||
$set = [];
|
||||
foreach ($result as $entry) {
|
||||
$set[] = $entry->id;
|
||||
}
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $set
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function transactionsByJournals(array $set)
|
||||
{
|
||||
$transactions = \Transaction::whereIn('transaction_journal_id', $set)
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->groupBy('transaction_journals.date')
|
||||
->where('amount', '>', 0)->get(['transaction_journals.date', \DB::Raw('SUM(`amount`) as `aggregate`')]);
|
||||
return $transactions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all limit (LimitRepetitions) for a budget falling in a certain date range.
|
||||
*
|
||||
* @param \Budget $budget
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function limitsInRange(\Budget $budget, Carbon $start, Carbon $end)
|
||||
{
|
||||
$reps = new Collection;
|
||||
/** @var \Limit $limit */
|
||||
foreach ($budget->limits as $limit) {
|
||||
$set = $limit->limitrepetitions()->where(
|
||||
function ($q) use ($start, $end) {
|
||||
// startdate is between range
|
||||
$q->where(
|
||||
function ($q) use ($start, $end) {
|
||||
$q->where('startdate', '>=', $start->format('Y-m-d'));
|
||||
$q->where('startdate', '<=', $end->format('Y-m-d'));
|
||||
}
|
||||
);
|
||||
|
||||
// or enddate is between range.
|
||||
$q->orWhere(
|
||||
function ($q) use ($start, $end) {
|
||||
$q->where('enddate', '>=', $start->format('Y-m-d'));
|
||||
$q->where('enddate', '<=', $end->format('Y-m-d'));
|
||||
}
|
||||
);
|
||||
}
|
||||
)->get();
|
||||
|
||||
$reps = $reps->merge($set);
|
||||
}
|
||||
return $reps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Firefly checks how much money has been spend on the limitrepetition (aka: the current envelope) in
|
||||
* the period denoted. Aka, the user has a certain amount of money in an envelope and wishes to know how
|
||||
* much he has spent between the dates entered. This date range can be a partial match with the date range
|
||||
* of the envelope or no match at all.
|
||||
*
|
||||
* @param \LimitRepetition $repetition
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function spentOnLimitRepetitionBetweenDates(\LimitRepetition $repetition, Carbon $start, Carbon $end)
|
||||
{
|
||||
return floatval(
|
||||
\Transaction::
|
||||
leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
|
||||
->leftJoin(
|
||||
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
|
||||
'transaction_journals.id'
|
||||
)->where('component_transaction_journal.component_id', '=', $repetition->limit->budget->id)->where(
|
||||
'transaction_journals.date', '>=', $start->format('Y-m-d')
|
||||
)->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->where(
|
||||
'amount', '>', 0
|
||||
)->sum('amount')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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
|
||||
@@ -54,4 +47,62 @@ interface ChartInterface
|
||||
* @return mixed
|
||||
*/
|
||||
public function categoryShowChart(\Category $category, $range, Carbon $start, Carbon $end);
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function spentOnDay(\Budget $budget, Carbon $date);
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function allJournalsInBudgetEnvelope(\Budget $budget);
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
* @param array $ids
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function journalsNotInSet(\Budget $budget, array $ids);
|
||||
|
||||
/**
|
||||
* @param array $set
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function transactionsByJournals(array $set);
|
||||
|
||||
/**
|
||||
* Get all limit (LimitRepetitions) for a budget falling in a certain date range.
|
||||
*
|
||||
* @param \Budget $budget
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function limitsInRange(\Budget $budget, Carbon $start, Carbon $end);
|
||||
|
||||
|
||||
/**
|
||||
* Firefly checks how much money has been spend on the limitrepetition (aka: the current envelope) in
|
||||
* the period denoted. Aka, the user has a certain amount of money in an envelope and wishes to know how
|
||||
* much he has spent between the dates entered. This date range can be a partial match with the date range
|
||||
* of the envelope or no match at all.
|
||||
*
|
||||
* @param \LimitRepetition $repetition
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function spentOnLimitRepetitionBetweenDates(\LimitRepetition $repetition, Carbon $start, Carbon $end);
|
||||
|
||||
|
||||
}
|
403
app/lib/Firefly/Helper/Controllers/Json.php
Normal file
403
app/lib/Firefly/Helper/Controllers/Json.php
Normal file
@@ -0,0 +1,403 @@
|
||||
<?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) {
|
||||
$set = [
|
||||
|
||||
'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)
|
||||
]
|
||||
];
|
||||
if (intval($entry->skip) > 0) {
|
||||
$set['repeat_freq'] = $entry->repeat_freq . ' (skip ' . $entry->skip . ')';
|
||||
}
|
||||
$data['data'][] = $set;
|
||||
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
64
app/lib/Firefly/Helper/Controllers/JsonInterface.php
Normal file
64
app/lib/Firefly/Helper/Controllers/JsonInterface.php
Normal 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);
|
||||
}
|
120
app/lib/Firefly/Helper/Controllers/Recurring.php
Normal file
120
app/lib/Firefly/Helper/Controllers/Recurring.php
Normal 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];
|
||||
|
||||
}
|
||||
}
|
15
app/lib/Firefly/Helper/Controllers/RecurringInterface.php
Normal file
15
app/lib/Firefly/Helper/Controllers/RecurringInterface.php
Normal 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);
|
||||
}
|
101
app/lib/Firefly/Helper/Controllers/Search.php
Normal file
101
app/lib/Firefly/Helper/Controllers/Search.php
Normal 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;
|
||||
}
|
||||
}
|
36
app/lib/Firefly/Helper/Controllers/SearchInterface.php
Normal file
36
app/lib/Firefly/Helper/Controllers/SearchInterface.php
Normal 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);
|
||||
|
||||
}
|
485
app/lib/Firefly/Helper/Controllers/Transaction.php
Normal file
485
app/lib/Firefly/Helper/Controllers/Transaction.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
48
app/lib/Firefly/Helper/Controllers/TransactionInterface.php
Normal file
48
app/lib/Firefly/Helper/Controllers/TransactionInterface.php
Normal 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);
|
||||
|
||||
}
|
@@ -38,7 +38,7 @@ class EmailHelper implements EmailHelperInterface
|
||||
{
|
||||
|
||||
$password = \Str::random(12);
|
||||
$user->password = \Hash::make($password);
|
||||
$user->password = $password;
|
||||
$user->reset = \Str::random(32); // new one.
|
||||
$user->forceSave();
|
||||
$email = $user->email;
|
||||
|
@@ -1,47 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Firefly\Helper\Form;
|
||||
|
||||
/**
|
||||
* Class FormHelper
|
||||
*
|
||||
* @package Firefly\Form
|
||||
*/
|
||||
class FormHelper
|
||||
{
|
||||
|
||||
/**
|
||||
* @param null $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function budget($value = null)
|
||||
{
|
||||
|
||||
$str = '<select name="budget_id" class="form-control">';
|
||||
|
||||
$str .= '<option value="0" label="(no budget)"';
|
||||
if (is_null($value) || intval($value) == 0) {
|
||||
$str .= ' selected="selected"';
|
||||
}
|
||||
$str .= '</option>';
|
||||
|
||||
/** @var \Firefly\Storage\Budget\BudgetRepositoryInterface $budgets */
|
||||
$budgets = \App::make('Firefly\Storage\Budget\BudgetRepositoryInterface');
|
||||
$list = $budgets->getAsSelectList();
|
||||
foreach ($list as $id => $name) {
|
||||
$str .= '<option value="' . e($id) . '" label="' . e($name) . '"';
|
||||
if ($id == intval($value)) {
|
||||
$str .= ' selected="selected"';
|
||||
}
|
||||
$str .= '>' . e($name) . '</option>';
|
||||
}
|
||||
|
||||
|
||||
$str .= '</select>';
|
||||
|
||||
return $str;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Firefly\Helper\Form;
|
||||
|
||||
use Illuminate\Events\Dispatcher;
|
||||
|
||||
/**
|
||||
* Class FormTrigger
|
||||
*
|
||||
* @package Firefly\Helper\Form
|
||||
*/
|
||||
class FormTrigger
|
||||
{
|
||||
|
||||
public function registerFormExtensions()
|
||||
{
|
||||
\Form::macro(
|
||||
'budget', function () {
|
||||
$helper = new FormHelper;
|
||||
|
||||
return $helper->budget();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Dispatcher $events
|
||||
*/
|
||||
public function subscribe(Dispatcher $events)
|
||||
{
|
||||
$events->listen('laravel.booted', 'Firefly\Helper\Form\FormTrigger@registerFormExtensions');
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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'
|
||||
|
@@ -1,327 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Firefly\Helper\Migration;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Firefly\Exception\FireflyException;
|
||||
|
||||
/**
|
||||
* Class MigrationHelper
|
||||
*
|
||||
* @package Firefly\Helper\Migration
|
||||
*/
|
||||
class MigrationHelper implements MigrationHelperInterface
|
||||
{
|
||||
protected $path;
|
||||
protected $JSON;
|
||||
protected $map = [];
|
||||
|
||||
/**
|
||||
* @param $path
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function loadFile($path)
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function validFile()
|
||||
{
|
||||
// file does not exist:
|
||||
if (!file_exists($this->path)) {
|
||||
\Log::error('Migration file ' . $this->path . ' does not exist!');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// load the content:
|
||||
$content = file_get_contents($this->path);
|
||||
if ($content === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse the content
|
||||
$this->JSON = json_decode($content);
|
||||
if (is_null($this->JSON)) {
|
||||
return false;
|
||||
}
|
||||
\Log::info('Migration file ' . $this->path . ' is valid!');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function migrate()
|
||||
{
|
||||
\Log::info('Start of migration.');
|
||||
\DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// create cash account:
|
||||
$this->_createCashAccount();
|
||||
|
||||
$this->_importAccounts();
|
||||
$this->_importComponents();
|
||||
//$this->_importPiggybanks();
|
||||
|
||||
// create transactions:
|
||||
$this->_importTransactions();
|
||||
|
||||
// create transfers:
|
||||
$this->_importTransfers();
|
||||
|
||||
// create limits:
|
||||
$this->_importLimits();
|
||||
|
||||
|
||||
} catch (FireflyException $e) {
|
||||
\DB::rollBack();
|
||||
\Log::error('Rollback because of error!');
|
||||
\Log::error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
\DB::commit();
|
||||
\Log::info('Done!');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function _createCashAccount()
|
||||
{
|
||||
$cashAT = \AccountType::where('description', 'Cash account')->first();
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
|
||||
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$cash = $accounts->store(['name' => 'Cash account', 'account_type' => $cashAT, 'active' => 0]);
|
||||
\Log::info('Created cash account (#' . $cash->id . ')');
|
||||
$this->map['cash'] = $cash;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function _importAccounts()
|
||||
{
|
||||
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
|
||||
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
\Log::info('Going to import ' . count($this->JSON->accounts) . ' accounts.');
|
||||
foreach ($this->JSON->accounts as $entry) {
|
||||
// create account:
|
||||
if ($entry->openingbalance == 0) {
|
||||
$account = $accounts->store(['name' => $entry->name]);
|
||||
} else {
|
||||
$account = $accounts->storeWithInitialBalance(
|
||||
['name' => $entry->name],
|
||||
new Carbon($entry->openingbalancedate),
|
||||
floatval($entry->openingbalance)
|
||||
);
|
||||
}
|
||||
$this->map['accounts'][$entry->id] = $account;
|
||||
\Log::info('Imported account "' . $entry->name . '" with balance ' . $entry->openingbalance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function _importComponents()
|
||||
{
|
||||
$beneficiaryAT = \AccountType::where('description', 'Beneficiary account')->first();
|
||||
foreach ($this->JSON->components as $entry) {
|
||||
switch ($entry->type->type) {
|
||||
case 'beneficiary':
|
||||
/** @noinspection PhpParamsInspection */
|
||||
$beneficiary = $this->_importBeneficiary($entry, $beneficiaryAT);
|
||||
$this->map['accounts'][$entry->id] = $beneficiary;
|
||||
break;
|
||||
case 'category':
|
||||
$component = $this->_importCategory($entry);
|
||||
$this->map['categories'][$entry->id] = $component;
|
||||
break;
|
||||
case 'budget':
|
||||
$component = $this->_importBudget($entry);
|
||||
$this->map['budgets'][$entry->id] = $component;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $component
|
||||
* @param \AccountType $beneficiaryAT
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _importBeneficiary($component, \AccountType $beneficiaryAT)
|
||||
{
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $accounts */
|
||||
$accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
|
||||
return $accounts->store(
|
||||
[
|
||||
'name' => $component->name,
|
||||
'account_type' => $beneficiaryAT
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $component
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _importCategory($component)
|
||||
{
|
||||
/** @var \Firefly\Storage\Component\ComponentRepositoryInterface $components */
|
||||
$components = \App::make('Firefly\Storage\Component\ComponentRepositoryInterface');
|
||||
|
||||
return $components->store(['name' => $component->name, 'class' => 'Category']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $component
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _importBudget($component)
|
||||
{
|
||||
/** @var \Firefly\Storage\Component\ComponentRepositoryInterface $components */
|
||||
$components = \App::make('Firefly\Storage\Component\ComponentRepositoryInterface');
|
||||
|
||||
return $components->store(['name' => $component->name, 'class' => 'Budget']);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function _importTransactions()
|
||||
{
|
||||
|
||||
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $journals */
|
||||
$journals = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
|
||||
|
||||
// loop component_transaction to find beneficiaries, categories and budgets:
|
||||
$beneficiaries = [];
|
||||
$categories = [];
|
||||
$budgets = [];
|
||||
foreach ($this->JSON->component_transaction as $entry) {
|
||||
// beneficiaries
|
||||
if (isset($this->map['accounts'][$entry->component_id])) {
|
||||
$beneficiaries[$entry->transaction_id] = $this->map['accounts'][$entry->component_id];
|
||||
}
|
||||
|
||||
// categories
|
||||
if (isset($this->map['categories'][$entry->component_id])) {
|
||||
$categories[$entry->transaction_id] = $this->map['categories'][$entry->component_id];
|
||||
}
|
||||
|
||||
// budgets:
|
||||
if (isset($this->map['budgets'][$entry->component_id])) {
|
||||
$budgets[$entry->transaction_id] = $this->map['budgets'][$entry->component_id];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->JSON->transactions as $entry) {
|
||||
|
||||
// to properly save the amount, do it times -1:
|
||||
$amount = $entry->amount * -1;
|
||||
|
||||
/** @var \Account $fromAccount */
|
||||
$fromAccount = isset($this->map['accounts'][$entry->account_id])
|
||||
? $this->map['accounts'][$entry->account_id] : false;
|
||||
|
||||
/** @var \Account $toAccount */
|
||||
$toAccount = isset($beneficiaries[$entry->id]) ? $beneficiaries[$entry->id] : $this->map['cash'];
|
||||
|
||||
$date = new Carbon($entry->date);
|
||||
$journal = $journals->createSimpleJournal($fromAccount, $toAccount, $entry->description, $amount, $date);
|
||||
|
||||
// save budgets and categories, on the journal
|
||||
if (isset($budgets[$entry->id])) {
|
||||
$budget = $budgets[$entry->id];
|
||||
$journal->budgets()->save($budget);
|
||||
}
|
||||
if (isset($categories[$entry->id])) {
|
||||
$category = $categories[$entry->id];
|
||||
$journal->categories()->save($category);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function _importTransfers()
|
||||
{
|
||||
/** @var \Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface $journals */
|
||||
$journals = \App::make('Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface');
|
||||
|
||||
foreach ($this->JSON->transfers as $entry) {
|
||||
|
||||
// to properly save the amount, do it times 1 (?):
|
||||
$amount = $entry->amount * -1;
|
||||
|
||||
/** @var \Account $fromAccount */
|
||||
$fromAccount = isset($this->map['accounts'][$entry->accountfrom_id])
|
||||
? $this->map['accounts'][$entry->accountto_id] : false;
|
||||
|
||||
/** @var \Account $toAccount */
|
||||
$toAccount = isset($this->map['accounts'][$entry->accountto_id])
|
||||
? $this->map['accounts'][$entry->accountfrom_id] : false;
|
||||
|
||||
$date = new Carbon($entry->date);
|
||||
$journals->createSimpleJournal($fromAccount, $toAccount, $entry->description, $amount, $date);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function _importLimits()
|
||||
{
|
||||
\Log::info('Importing limits');
|
||||
foreach ($this->JSON->limits as $entry) {
|
||||
\Log::debug(
|
||||
'Now at #' . $entry->id . ': EUR ' . $entry->amount . ' for month ' . $entry->date
|
||||
. ' and componentID: ' . $entry->component_id
|
||||
);
|
||||
$budget = isset($this->map['budgets'][$entry->component_id]) ? $this->map['budgets'][$entry->component_id]
|
||||
: null;
|
||||
if (!is_null($budget)) {
|
||||
\Log::debug('Found budget for this limit: #' . $budget->id . ', ' . $budget->name);
|
||||
|
||||
$limit = new \Limit;
|
||||
$limit->budget()->associate($budget);
|
||||
$limit->startdate = new Carbon($entry->date);
|
||||
$limit->amount = floatval($entry->amount);
|
||||
$limit->repeats = 0;
|
||||
$limit->repeat_freq = 'monthly';
|
||||
try {
|
||||
$limit->save();
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
} else {
|
||||
\Log::warning('No budget for this limit!');
|
||||
}
|
||||
|
||||
|
||||
// create repeat thing should not be necessary.
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Firefly\Helper\Migration;
|
||||
|
||||
/**
|
||||
* Interface MigrationHelperInterface
|
||||
*
|
||||
* @package Firefly\Helper\Migration
|
||||
*/
|
||||
interface MigrationHelperInterface
|
||||
{
|
||||
/**
|
||||
* @param $path
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function loadFile($path);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function validFile();
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function migrate();
|
||||
|
||||
}
|
@@ -3,140 +3,130 @@
|
||||
namespace Firefly\Helper\Toolkit;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Firefly\Exception\FireflyException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class Toolkit
|
||||
*
|
||||
* @package Firefly\Helper\Toolkit
|
||||
* @SuppressWarnings(PHPMD.CamelCaseMethodName)
|
||||
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
|
||||
*/
|
||||
class Toolkit implements ToolkitInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* Lots of code in Firefly III still depends on session['start'], session['end'] and
|
||||
* session['range'] to be available, even though this feature has been removed from Firefly
|
||||
* in favor of a new reporting feature. This reporting feature can show the user past and future
|
||||
* date ranges instead of the dashboard (the dashboard always shows "right now").
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|mixed|null
|
||||
* The only actual choice the user is left with is the range, which can be changed using the Preferences pane.
|
||||
*
|
||||
* The start/end dates are set here, regardless of what the user might want to see.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function getDateRange(Request $request)
|
||||
public function getDateRange()
|
||||
{
|
||||
/*
|
||||
* Get all data from the session:
|
||||
*/
|
||||
$range = $this->_getRange();
|
||||
$start = $this->_getStartDate();
|
||||
$end = $this->_getEndDate();
|
||||
#\Log::debug('Range is: ' . $range);
|
||||
$start = \Session::has('start') ? \Session::get('start') : new Carbon;
|
||||
|
||||
// update start only:
|
||||
#\Log::debug('Session start is: ' . $start->format('Y-m-d'));
|
||||
$end = \Session::has('end') ? \Session::get('end') : new Carbon;
|
||||
#\Log::debug('Session end is : ' . $end->format('Y-m-d'));
|
||||
|
||||
/*
|
||||
* Force start date to at the start of the $range.
|
||||
* Ie. the start of the week, month, year.
|
||||
*/
|
||||
$start = $this->_updateStartDate($range, $start);
|
||||
#\Log::debug('After update, session start is: ' . $start->format('Y-m-d'));
|
||||
|
||||
// update end only:
|
||||
$end = $this->_updateEndDate($range, $start, $end);
|
||||
/*
|
||||
* Force end date to at the END of the $range. Always based on $start.
|
||||
* Ie. the END of the week, month, year.
|
||||
*/
|
||||
$end = $this->_updateEndDate($range, $start);
|
||||
#\Log::debug('After update, session end is : ' . $end->format('Y-m-d'));
|
||||
|
||||
if (\Input::get('action') == 'prev') {
|
||||
$start = $this->_moveStartPrevious($range, $start);
|
||||
$end = $this->_moveEndPrevious($range, $end);
|
||||
}
|
||||
if (\Input::get('action') == 'next') {
|
||||
$start = $this->_moveStartNext($range, $start);
|
||||
$end = $this->_moveEndNext($range, $end);
|
||||
}
|
||||
/*
|
||||
* get the name of the month, depending on the range. Purely for astetics
|
||||
*/
|
||||
$period = $this->_periodName($range, $start);
|
||||
|
||||
// save in session:
|
||||
/*
|
||||
* Get the date for the previous and next period.
|
||||
* Ie. next week, next month, etc.
|
||||
*/
|
||||
$prev = $this->_previous($range, clone $start);
|
||||
$next = $this->_next($range, clone $start);
|
||||
|
||||
/*
|
||||
* Save everything in the session:
|
||||
*/
|
||||
\Session::put('start', $start);
|
||||
\Session::put('end', $end);
|
||||
\Session::put('range', $range);
|
||||
if (!is_null(\Input::get('action'))) {
|
||||
return \Redirect::to($request->url());
|
||||
|
||||
}
|
||||
|
||||
\Session::put('period', $period);
|
||||
\Session::put('prev', $this->_periodName($range, $prev));
|
||||
\Session::put('next', $this->_periodName($range, $next));
|
||||
return null;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*
|
||||
*/
|
||||
public function getDateRangeDates()
|
||||
public function checkImportJobs()
|
||||
{
|
||||
return [\Session::get('start'), \Session::get('end')];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getReminders()
|
||||
{
|
||||
// get reminders, for menu, mumble mumble:
|
||||
$today = new Carbon;
|
||||
$reminders = \Auth::user()->reminders()->where('class', 'PiggybankReminder')->validOn($today)->get();
|
||||
|
||||
/** @var \Reminder $reminder */
|
||||
foreach ($reminders as $index => $reminder) {
|
||||
if (\Session::has('dismissal-' . $reminder->id)) {
|
||||
$time = \Session::get('dismissal-' . $reminder->id);
|
||||
if ($time >= $today) {
|
||||
unset($reminders[$index]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
\Session::put('reminderCount', count($reminders));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _getrange()
|
||||
{
|
||||
if (!is_null(\Input::get('range'))) {
|
||||
$range = \Input::get('range');
|
||||
/*
|
||||
* Get all jobs.
|
||||
*/
|
||||
/** @var \Importmap $importJob */
|
||||
$importJob = \Importmap::where('user_id', \Auth::user()->id)
|
||||
->where('totaljobs', '>', \DB::Raw('`jobsdone`'))
|
||||
->orderBy('created_at', 'DESC')
|
||||
->first();
|
||||
if (!is_null($importJob)) {
|
||||
$diff = intval($importJob->totaljobs) - intval($importJob->jobsdone);
|
||||
$date = new Carbon;
|
||||
$today = new Carbon;
|
||||
$date->addSeconds($diff);
|
||||
\Session::put('job_pct', $importJob->pct());
|
||||
\Session::put('job_text', $date->diffForHumans());
|
||||
} else {
|
||||
if (!is_null(\Session::get('range'))) {
|
||||
$range = \Session::get('range');
|
||||
} else {
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
$preferences = \App::make('Firefly\Helper\Preferences\PreferencesHelperInterface');
|
||||
$viewRange = $preferences->get('viewRange', '1M');
|
||||
|
||||
// default range:
|
||||
$range = $viewRange->data;
|
||||
}
|
||||
\Session::forget('job_pct');
|
||||
\Session::forget('job_text');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _getRange()
|
||||
{
|
||||
if (!is_null(\Session::get('range'))) {
|
||||
$range = \Session::get('range');
|
||||
} else {
|
||||
/** @noinspection PhpUndefinedClassInspection */
|
||||
$preferences = \App::make('Firefly\Helper\Preferences\PreferencesHelperInterface');
|
||||
$viewRange = $preferences->get('viewRange', '1M');
|
||||
|
||||
// default range:
|
||||
$range = $viewRange->data;
|
||||
\Session::put('range', $range);
|
||||
}
|
||||
return $range;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Carbon|mixed
|
||||
*/
|
||||
protected function _getStartDate()
|
||||
{
|
||||
$start = \Session::has('start') ? \Session::get('start') : new Carbon;
|
||||
if (\Input::get('start') && \Input::get('end')) {
|
||||
$start = new Carbon(\Input::get('start'));
|
||||
}
|
||||
|
||||
return $start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Carbon|mixed
|
||||
*/
|
||||
protected function _getEndDate()
|
||||
{
|
||||
$end = \Session::has('end') ? \Session::get('end') : new Carbon;
|
||||
if (\Input::get('start') && \Input::get('end')) {
|
||||
$end = new Carbon(\Input::get('end'));
|
||||
}
|
||||
|
||||
return $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $range
|
||||
* @param Carbon $start
|
||||
@@ -145,7 +135,6 @@ class Toolkit implements ToolkitInterface
|
||||
*/
|
||||
protected function _updateStartDate($range, Carbon $start)
|
||||
{
|
||||
$today = new Carbon;
|
||||
switch ($range) {
|
||||
case '1D':
|
||||
$start->startOfDay();
|
||||
@@ -160,12 +149,15 @@ class Toolkit implements ToolkitInterface
|
||||
$start->firstOfQuarter();
|
||||
break;
|
||||
case '6M':
|
||||
if (intval($today->format('m')) >= 7) {
|
||||
if (intval($start->format('m')) >= 7) {
|
||||
$start->startOfYear()->addMonths(6);
|
||||
} else {
|
||||
$start->startOfYear();
|
||||
}
|
||||
break;
|
||||
case '1Y':
|
||||
$start->startOfYear();
|
||||
break;
|
||||
}
|
||||
|
||||
return $start;
|
||||
@@ -179,150 +171,337 @@ class Toolkit implements ToolkitInterface
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
protected function _updateEndDate($range, Carbon $start, Carbon $end)
|
||||
protected function _updateEndDate($range, Carbon $start)
|
||||
{
|
||||
$today = new Carbon;
|
||||
$end = clone $start;
|
||||
switch ($range) {
|
||||
default:
|
||||
throw new FireflyException('_updateEndDate cannot handle $range ' . $range);
|
||||
break;
|
||||
case '1D':
|
||||
$end = clone $start;
|
||||
$end->endOfDay();
|
||||
break;
|
||||
case '1W':
|
||||
$end = clone $start;
|
||||
$end->endOfWeek();
|
||||
break;
|
||||
case '1M':
|
||||
$end = clone $start;
|
||||
$end->endOfMonth();
|
||||
break;
|
||||
case '3M':
|
||||
$end = clone $start;
|
||||
$end->lastOfQuarter();
|
||||
break;
|
||||
case '6M':
|
||||
$end = clone $start;
|
||||
if (intval($today->format('m')) >= 7) {
|
||||
if (intval($start->format('m')) >= 7) {
|
||||
$end->endOfYear();
|
||||
} else {
|
||||
$end->startOfYear()->addMonths(6);
|
||||
}
|
||||
break;
|
||||
case '1Y':
|
||||
$end->endOfYear();
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $range
|
||||
* @param Carbon $start
|
||||
*
|
||||
* @return Carbon
|
||||
*/
|
||||
protected function _moveStartPrevious($range, Carbon $start)
|
||||
protected function _periodName($range, Carbon $date)
|
||||
{
|
||||
switch ($range) {
|
||||
default:
|
||||
throw new FireflyException('No _periodName() for range "' . $range . '"');
|
||||
break;
|
||||
case '1D':
|
||||
return $date->format('jS F Y');
|
||||
break;
|
||||
case '1W':
|
||||
return 'week ' . $date->format('W, Y');
|
||||
break;
|
||||
case '1M':
|
||||
return $date->format('F Y');
|
||||
break;
|
||||
case '3M':
|
||||
$month = intval($date->format('m'));
|
||||
return 'Q' . ceil(($month / 12) * 4) . ' ' . $date->format('Y');
|
||||
break;
|
||||
case '6M':
|
||||
$month = intval($date->format('m'));
|
||||
$half = ceil(($month / 12) * 2);
|
||||
$halfName = $half == 1 ? 'first' : 'second';
|
||||
return $halfName . ' half of ' . $date->format('d-m-Y');
|
||||
break;
|
||||
case '1Y':
|
||||
return $date->format('Y');
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
protected function _previous($range, Carbon $date)
|
||||
{
|
||||
switch ($range) {
|
||||
default:
|
||||
throw new FireflyException('Cannot do _previous() on ' . $range);
|
||||
break;
|
||||
case '1D':
|
||||
$date->startOfDay()->subDay();
|
||||
break;
|
||||
case '1W':
|
||||
$date->startOfWeek()->subWeek();
|
||||
break;
|
||||
case '1M':
|
||||
$date->startOfMonth()->subMonth();
|
||||
break;
|
||||
case '3M':
|
||||
$date->firstOfQuarter()->subMonths(3)->firstOfQuarter();
|
||||
break;
|
||||
case '6M':
|
||||
$month = intval($date->format('m'));
|
||||
if ($month <= 6) {
|
||||
$date->startOfYear()->subMonths(6);
|
||||
} else {
|
||||
$date->startOfYear();
|
||||
}
|
||||
break;
|
||||
case '1Y':
|
||||
$date->startOfYear()->subYear();
|
||||
break;
|
||||
|
||||
}
|
||||
return $date;
|
||||
}
|
||||
|
||||
protected function _next($range, Carbon $date)
|
||||
{
|
||||
switch ($range) {
|
||||
case '1D':
|
||||
$start->subDay();
|
||||
$date->endOfDay()->addDay();
|
||||
break;
|
||||
case '1W':
|
||||
$start->subWeek();
|
||||
$date->endOfWeek()->addDay()->startOfWeek();
|
||||
break;
|
||||
case '1M':
|
||||
$start->subMonth();
|
||||
$date->endOfMonth()->addDay()->startOfMonth();
|
||||
break;
|
||||
case '3M':
|
||||
$start->subMonths(3)->firstOfQuarter();
|
||||
$date->lastOfQuarter()->addDay();
|
||||
break;
|
||||
case '6M':
|
||||
$start->subMonths(6);
|
||||
if (intval($date->format('m')) >= 7) {
|
||||
$date->startOfYear()->addYear();
|
||||
} else {
|
||||
$date->startOfYear()->addMonths(6);
|
||||
}
|
||||
break;
|
||||
case '1Y':
|
||||
$date->startOfYear()->addYear();
|
||||
break;
|
||||
default:
|
||||
throw new FireflyException('Cannot do _next() on ' . $range);
|
||||
break;
|
||||
}
|
||||
return $start;
|
||||
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 $end
|
||||
* Takes any collection and tries to make a sensible select list compatible array of it.
|
||||
*
|
||||
* @return Carbon
|
||||
* @param Collection $set
|
||||
* @param null $titleField
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function _moveEndPrevious($range, Carbon $end)
|
||||
public function makeSelectList(Collection $set, $titleField = null)
|
||||
{
|
||||
switch ($range) {
|
||||
case '1D':
|
||||
$end->subDay();
|
||||
break;
|
||||
case '1W':
|
||||
$end->subWeek();
|
||||
break;
|
||||
case '1M':
|
||||
$end->startOfMonth()->subMonth()->endOfMonth();
|
||||
break;
|
||||
case '3M':
|
||||
$end->subMonths(3)->lastOfQuarter();
|
||||
break;
|
||||
case '6M':
|
||||
$end->subMonths(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $range
|
||||
* @param Carbon $start
|
||||
*
|
||||
* @return Carbon
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @param int $steps
|
||||
*/
|
||||
protected function _moveStartNext($range, Carbon $start)
|
||||
public function colorRange($start, $end, $steps = 5)
|
||||
{
|
||||
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;
|
||||
if (strlen($start) != 6) {
|
||||
throw new FireflyException('Start, ' . e($start) . ' should be a six character HTML colour.');
|
||||
}
|
||||
return $start;
|
||||
if (strlen($end) != 6) {
|
||||
throw new FireflyException('End, ' . e($end) . ' should be a six character HTML colour.');
|
||||
}
|
||||
if ($steps < 1) {
|
||||
throw new FireflyException('Steps must be > 1');
|
||||
}
|
||||
|
||||
$start = '#' . $start;
|
||||
$end = '#' . $end;
|
||||
/*
|
||||
* Split html colours.
|
||||
*/
|
||||
list($rs, $gs, $bs) = sscanf($start, "#%02x%02x%02x");
|
||||
list($re, $ge, $be) = sscanf($end, "#%02x%02x%02x");
|
||||
|
||||
$stepr = ($re - $rs) / $steps;
|
||||
$stepg = ($ge - $gs) / $steps;
|
||||
$stepb = ($be - $bs) / $steps;
|
||||
|
||||
$return = [];
|
||||
for ($i = 0; $i <= $steps; $i++) {
|
||||
$cr = $rs + ($stepr * $i);
|
||||
$cg = $gs + ($stepg * $i);
|
||||
$cb = $bs + ($stepb * $i);
|
||||
|
||||
$return[] = $this->rgb2html($cr, $cg, $cb);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
protected function rgb2html($r, $g = -1, $b = -1)
|
||||
{
|
||||
$r = dechex($r < 0 ? 0 : ($r > 255 ? 255 : $r));
|
||||
$g = dechex($g < 0 ? 0 : ($g > 255 ? 255 : $g));
|
||||
$b = dechex($b < 0 ? 0 : ($b > 255 ? 255 : $b));
|
||||
|
||||
$color = (strlen($r) < 2 ? '0' : '') . $r;
|
||||
$color .= (strlen($g) < 2 ? '0' : '') . $g;
|
||||
$color .= (strlen($b) < 2 ? '0' : '') . $b;
|
||||
return '#' . $color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $range
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Carbon
|
||||
* @param Carbon $currentEnd
|
||||
* @param $repeatFreq
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function _moveEndNext($range, Carbon $end)
|
||||
public function endOfPeriod(Carbon $currentEnd, $repeatFreq)
|
||||
{
|
||||
switch ($range) {
|
||||
case '1D':
|
||||
$end->addDay();
|
||||
switch ($repeatFreq) {
|
||||
default:
|
||||
throw new FireflyException('Cannot do getFunctionForRepeatFreq for $repeat_freq ' . $repeatFreq);
|
||||
break;
|
||||
case '1W':
|
||||
$end->addWeek();
|
||||
case 'daily':
|
||||
$currentEnd->addDay();
|
||||
break;
|
||||
case '1M':
|
||||
$end->addMonth();
|
||||
case 'weekly':
|
||||
$currentEnd->addWeek()->subDay();
|
||||
break;
|
||||
case '3M':
|
||||
$end->addMonths(6)->lastOfQuarter();
|
||||
case 'monthly':
|
||||
$currentEnd->addMonth()->subDay();
|
||||
break;
|
||||
case '6M':
|
||||
$end->addMonths(6);
|
||||
case 'quarterly':
|
||||
$currentEnd->addMonths(3)->subDay();
|
||||
break;
|
||||
case 'half-year':
|
||||
$currentEnd->addMonths(6)->subDay();
|
||||
break;
|
||||
case 'yearly':
|
||||
$currentEnd->addYear()->subDay();
|
||||
break;
|
||||
}
|
||||
return $end;
|
||||
}
|
||||
|
||||
}
|
||||
/**
|
||||
* @param Carbon $date
|
||||
* @param $repeatFreq
|
||||
* @param $skip
|
||||
* @return Carbon
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function addPeriod(Carbon $date, $repeatFreq, $skip)
|
||||
{
|
||||
$add = ($skip + 1);
|
||||
switch ($repeatFreq) {
|
||||
default:
|
||||
throw new FireflyException('Cannot do getFunctionForRepeatFreq for $repeat_freq ' . $repeatFreq);
|
||||
break;
|
||||
case 'daily':
|
||||
$date->addDays($add);
|
||||
break;
|
||||
case 'weekly':
|
||||
$date->addWeeks($add);
|
||||
break;
|
||||
case 'monthly':
|
||||
$date->addMonths($add);
|
||||
break;
|
||||
case 'quarterly':
|
||||
$months = $add * 3;
|
||||
$date->addMonths($months);
|
||||
break;
|
||||
case 'half-year':
|
||||
$months = $add * 6;
|
||||
$date->addMonths($months);
|
||||
break;
|
||||
case 'yearly':
|
||||
$date->addYears($add);
|
||||
break;
|
||||
}
|
||||
return $date;
|
||||
}
|
||||
}
|
@@ -2,7 +2,8 @@
|
||||
|
||||
namespace Firefly\Helper\Toolkit;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface ToolkitInterface
|
||||
@@ -12,20 +13,40 @@ 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);
|
||||
|
||||
public function next();
|
||||
|
||||
public function prev();
|
||||
|
||||
public function checkImportJobs();
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @param string $start
|
||||
* @param string $end
|
||||
* @param int $steps
|
||||
*/
|
||||
public function getDateRangeDates();
|
||||
public function colorRange($start, $end, $steps = 5);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @param Carbon $date
|
||||
* @param $repeatFreq
|
||||
* @param $skip
|
||||
* @return Carbon
|
||||
*/
|
||||
public function getReminders();
|
||||
public function addPeriod(Carbon $date, $repeatFreq, $skip);
|
||||
|
||||
}
|
||||
}
|
596
app/lib/Firefly/Queue/Import.php
Normal file
596
app/lib/Firefly/Queue/Import.php
Normal file
@@ -0,0 +1,596 @@
|
||||
<?php
|
||||
|
||||
namespace Firefly\Queue;
|
||||
|
||||
use Illuminate\Queue\Jobs\Job;
|
||||
|
||||
/**
|
||||
* Class Import
|
||||
*
|
||||
* @package Firefly\Queue
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
class Import
|
||||
{
|
||||
/** @var \Firefly\Storage\Account\AccountRepositoryInterface */
|
||||
protected $_accounts;
|
||||
|
||||
/** @var \Firefly\Storage\Import\ImportRepositoryInterface */
|
||||
protected $_repository;
|
||||
|
||||
/** @var \Firefly\Storage\Piggybank\PiggybankRepositoryInterface */
|
||||
protected $_piggybanks;
|
||||
|
||||
/**
|
||||
* This constructs the import handler and initiates all the relevant interfaces / classes.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_accounts = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
|
||||
$this->_repository = \App::make('Firefly\Storage\Import\ImportRepositoryInterface');
|
||||
$this->_piggybanks = \App::make('Firefly\Storage\Piggybank\PiggybankRepositoryInterface');
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The final step in the import routine is to get all transactions which have one of their accounts
|
||||
* still set to "import", which means it is a cash transaction. This routine will set them all to cash instead.
|
||||
*
|
||||
* If there was no account present for these accounts in the import routine (no beneficiary set), Firefly
|
||||
* II would fall back to the import account.
|
||||
*
|
||||
* @param Job $job
|
||||
* @param array $payload
|
||||
*/
|
||||
public function cleanImportAccount(Job $job, array $payload)
|
||||
{
|
||||
|
||||
/** @var \Importmap $importMap */
|
||||
$importMap = $this->_repository->findImportmap($payload['mapID']);
|
||||
$user = $importMap->user;
|
||||
$this->overruleUser($user);
|
||||
|
||||
// two import account types.
|
||||
$importAccountType = $this->_accounts->findAccountType('Import account');
|
||||
$cashAccountType = $this->_accounts->findAccountType('Cash account');
|
||||
|
||||
// find or create import account:
|
||||
$importAccount = $this->_accounts->firstOrCreate(
|
||||
[
|
||||
'name' => 'Import account',
|
||||
'account_type_id' => $importAccountType->id,
|
||||
'active' => 1,
|
||||
'user_id' => $user->id,
|
||||
]
|
||||
);
|
||||
|
||||
// find or create cash account:
|
||||
$cashAccount = $this->_accounts->firstOrCreate(
|
||||
[
|
||||
'name' => 'Cash account',
|
||||
'account_type_id' => $cashAccountType->id,
|
||||
'active' => 1,
|
||||
'user_id' => $user->id,
|
||||
]
|
||||
);
|
||||
|
||||
// update all users transactions:
|
||||
$count = \DB::table('transactions')
|
||||
->where('account_id', $importAccount->id)->count();
|
||||
|
||||
\DB::table('transactions')
|
||||
->where('account_id', $importAccount->id)
|
||||
->update(['account_id' => $cashAccount->id]);
|
||||
|
||||
\Log::debug('Updated ' . $count . ' transactions from Import Account to cash.');
|
||||
$job->delete(); // no count fix
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \User $user
|
||||
*/
|
||||
protected function overruleUser(\User $user)
|
||||
{
|
||||
$this->_accounts->overruleUser($user);
|
||||
$this->_repository->overruleUser($user);
|
||||
$this->_piggybanks->overruleUser($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* This job queues new jobs that will connect components to their proper transactions and updates the
|
||||
* expense account: categories, budgets an beneficiaries used to be components.
|
||||
*
|
||||
* @param Job $job
|
||||
* @param array $payload
|
||||
*/
|
||||
public function importComponentTransaction(Job $job, array $payload)
|
||||
{
|
||||
/** @var \Importmap $importMap */
|
||||
$importMap = $this->_repository->findImportmap($payload['mapID']);
|
||||
$user = $importMap->user;
|
||||
$this->overruleUser($user);
|
||||
|
||||
/*
|
||||
* Took too long to fix this:
|
||||
*/
|
||||
if ($job->attempts() > 10) {
|
||||
\Log::error('Could not map transaction to component after 10 tries. KILL');
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
$job->delete(); // count fixed
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prep some vars from the payload
|
||||
*/
|
||||
$transactionId = intval($payload['data']['transaction_id']);
|
||||
$componentId = intval($payload['data']['component_id']);
|
||||
|
||||
/*
|
||||
* We don't know what kind of component we have. So we search for it. We have a specific function
|
||||
* for this:
|
||||
*/
|
||||
$oldComponentMap = $this->_repository->findImportComponentMap($importMap, $componentId);
|
||||
|
||||
/*
|
||||
* If the map is null, the component (whatever it is) is not imported yet, and we release the job.
|
||||
*/
|
||||
if (is_null($oldComponentMap)) {
|
||||
\Log::notice('No map for this component, release transaction/component import.');
|
||||
|
||||
/*
|
||||
* When in sync, its pointless to release jobs. Simply remove them.
|
||||
*/
|
||||
if (\Config::get('queue.default') == 'sync') {
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
$job->delete(); // count fixed
|
||||
} else {
|
||||
$job->release(300); // proper release.
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Switch on the class found in the map, and push a new job to update the transaction journal:
|
||||
*/
|
||||
switch ($oldComponentMap->class) {
|
||||
default:
|
||||
\Log::error('Cannot handle "' . $oldComponentMap->class . '" in component<>transaction routine!');
|
||||
$job->delete();
|
||||
break;
|
||||
case 'Budget':
|
||||
\Log::debug('Push job to connect budget to transaction #' . $transactionId);
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Budget\BudgetRepositoryInterface@importUpdateTransaction', $payload
|
||||
);
|
||||
$importMap->totaljobs++;
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
$job->delete(); // count fixed
|
||||
break;
|
||||
case 'Category':
|
||||
\Log::debug('Push job to connect category to transaction #' . $transactionId);
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Category\CategoryRepositoryInterface@importUpdateTransaction', $payload
|
||||
);
|
||||
|
||||
$importMap->totaljobs++;
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
$job->delete(); // count fixed
|
||||
break;
|
||||
case 'Account':
|
||||
\Log::debug('Push job to connect account to transaction #' . $transactionId);
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Account\AccountRepositoryInterface@importUpdateTransaction', $payload
|
||||
);
|
||||
|
||||
$importMap->totaljobs++;
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
$job->delete(); // count fixed
|
||||
break;
|
||||
}
|
||||
return;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This job queues new jobs that will connect components to their proper transfers and updates the
|
||||
* expense account: categories, budgets an beneficiaries used to be components. Even though not all
|
||||
* of the transfers used to have these components, we check for them all.
|
||||
*
|
||||
* @param Job $job
|
||||
* @param array $payload
|
||||
*/
|
||||
public function importComponentTransfer(Job $job, array $payload)
|
||||
{
|
||||
/** @var \Importmap $importMap */
|
||||
$importMap = $this->_repository->findImportmap($payload['mapID']);
|
||||
$user = $importMap->user;
|
||||
$this->overruleUser($user);
|
||||
|
||||
/*
|
||||
* Took too long to fix this:
|
||||
*/
|
||||
if ($job->attempts() > 10) {
|
||||
\Log::error('Could not map transaction to component after 10 tries. KILL');
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
$job->delete(); // count fixed
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Prep some vars from the payload
|
||||
*/
|
||||
$transferId = intval($payload['data']['transfer_id']);
|
||||
$componentId = intval($payload['data']['component_id']);
|
||||
|
||||
/*
|
||||
* We don't know what kind of component we have. So we search for it. We have a specific function
|
||||
* for this:
|
||||
*/
|
||||
$oldComponentMap = $this->_repository->findImportComponentMap($importMap, $componentId);
|
||||
|
||||
/*
|
||||
* If the map is null, the component (whatever it is) is not imported yet, and we release the job.
|
||||
*/
|
||||
if (is_null($oldComponentMap)) {
|
||||
\Log::notice('No map for this component, release transfer/component import.');
|
||||
/*
|
||||
* When in sync, its pointless to release jobs. Simply remove them.
|
||||
*/
|
||||
if (\Config::get('queue.default') == 'sync') {
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
$job->delete(); // count fixed
|
||||
} else {
|
||||
$job->release(300); // proper release.
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Switch on the class found in the map, and push a new job to update the transaction journal:
|
||||
*/
|
||||
switch ($oldComponentMap->class) {
|
||||
default:
|
||||
\Log::error('Cannot handle "' . $oldComponentMap->class . '" in component<>transfer routine!');
|
||||
$job->delete();
|
||||
break;
|
||||
case 'Category':
|
||||
\Log::debug('Push job to connect category to transfer #' . $transferId);
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Category\CategoryRepositoryInterface@importUpdateTransfer', $payload
|
||||
);
|
||||
|
||||
$importMap->totaljobs++;
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
$job->delete(); // count fixed
|
||||
break;
|
||||
case 'Budget':
|
||||
\Log::debug('Push job to connect budget to transfer #' . $transferId);
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Budget\BudgetRepositoryInterface@importUpdateTransfer', $payload
|
||||
);
|
||||
$importMap->totaljobs++;
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
$job->delete(); // count fixed
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This job will see if the particular setting is a 'piggyAccount' setting,
|
||||
* one we need to fix all imported piggy banks.
|
||||
*
|
||||
* @param Job $job
|
||||
* @param array $payload
|
||||
*/
|
||||
public function importSetting(Job $job, array $payload)
|
||||
{
|
||||
/** @var \Importmap $importMap */
|
||||
$importMap = $this->_repository->findImportmap($payload['mapID']);
|
||||
$user = $importMap->user;
|
||||
$this->overruleUser($user);
|
||||
|
||||
if ($job->attempts() > 10) {
|
||||
\Log::error('No account found for piggyAccount setting after 10 tries. KILL!');
|
||||
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
$job->delete(); // count fixed
|
||||
return;
|
||||
}
|
||||
$name = $payload['data']['name'];
|
||||
switch ($name) {
|
||||
default:
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
$job->delete(); // count fixed.
|
||||
return;
|
||||
break;
|
||||
case 'piggyAccount':
|
||||
|
||||
/*
|
||||
* If user has this account, update all piggy banks:
|
||||
*/
|
||||
$accountID = intval($payload['data']['value']);
|
||||
|
||||
/*
|
||||
* Is account imported already?
|
||||
*/
|
||||
$importEntry = $this->_repository->findImportEntry($importMap, 'Account', $accountID);
|
||||
|
||||
/*
|
||||
* We imported this account already.
|
||||
*/
|
||||
if ($importEntry) {
|
||||
$all = $this->_piggybanks->get();
|
||||
$account = $this->_accounts->find($importEntry->new);
|
||||
/*
|
||||
* Update all piggy banks.
|
||||
*/
|
||||
if (!is_null($account)) {
|
||||
\Log::debug('Updating all piggybanks, found the right setting.');
|
||||
foreach ($all as $piggy) {
|
||||
$piggy->account()->associate($account);
|
||||
unset($piggy->leftInAccount);
|
||||
$piggy->save();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
\Log::notice('Account not yet imported, hold or 5 minutes.');
|
||||
/*
|
||||
* When in sync, its pointless to release jobs. Simply remove them.
|
||||
*/
|
||||
if (\Config::get('queue.default') == 'sync') {
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
$job->delete(); // count fixed
|
||||
} else {
|
||||
$job->release(300); // proper release.
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// update map:
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
$job->delete(); // count fixed.
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This job will loop and queue jobs for the import file; almost every set of records will be imported.
|
||||
*
|
||||
* @param Job $job
|
||||
* @param $payload
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*/
|
||||
public function start(Job $job, array $payload)
|
||||
{
|
||||
\Log::debug('Start with job "start"');
|
||||
$user = \User::find($payload['user']);
|
||||
$filename = $payload['file'];
|
||||
if (file_exists($filename) && !is_null($user)) {
|
||||
/*
|
||||
* Make an import map. Need it to refer back to import.
|
||||
*/
|
||||
$importMap = new \Importmap;
|
||||
$importMap->user()->associate($user);
|
||||
$importMap->file = $filename;
|
||||
$importMap->totaljobs = 0;
|
||||
$importMap->jobsdone = 0;
|
||||
$importMap->save();
|
||||
|
||||
$totalJobs = 0;
|
||||
|
||||
/*
|
||||
* Loop over all data in the JSON file, then create jobs.
|
||||
*/
|
||||
$raw = file_get_contents($filename);
|
||||
$JSON = json_decode($raw);
|
||||
|
||||
// first import all asset accounts:
|
||||
foreach ($JSON->accounts as $entry) {
|
||||
\Log::debug('Create job to import asset account');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Account\AccountRepositoryInterface@importAccount', [
|
||||
'data' => $entry,
|
||||
'class' => 'Account',
|
||||
'account_type' => 'Asset account',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
// then import all beneficiaries:
|
||||
foreach ($JSON->components as $entry) {
|
||||
if ($entry->type->type == 'beneficiary') {
|
||||
\Log::debug('Create job to import expense account');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Account\AccountRepositoryInterface@importAccount', [
|
||||
'data' => $entry,
|
||||
'class' => 'Account',
|
||||
'account_type' => 'Expense account',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
}
|
||||
|
||||
// then import all categories.
|
||||
foreach ($JSON->components as $entry) {
|
||||
if ($entry->type->type == 'category') {
|
||||
\Log::debug('Create job to import category');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Category\CategoryRepositoryInterface@importCategory', [
|
||||
'data' => $entry,
|
||||
'class' => 'Category',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
}
|
||||
|
||||
// then import all budgets:
|
||||
foreach ($JSON->components as $entry) {
|
||||
if ($entry->type->type == 'budget') {
|
||||
\Log::debug('Create job to import budget');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Budget\BudgetRepositoryInterface@importBudget', [
|
||||
'data' => $entry,
|
||||
'class' => 'Budget',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
}
|
||||
|
||||
// then import all limits.
|
||||
foreach ($JSON->limits as $entry) {
|
||||
\Log::debug('Create job to import limit');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Limit\LimitRepositoryInterface@importLimit', [
|
||||
'data' => $entry,
|
||||
'class' => 'Limit',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
// all piggy banks
|
||||
foreach ($JSON->piggybanks as $entry) {
|
||||
\Log::debug('Create job to import piggy bank');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\Piggybank\PiggybankRepositoryInterface@importPiggybank', [
|
||||
'data' => $entry,
|
||||
'class' => 'Piggybank',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
// all predictables.
|
||||
foreach ($JSON->predictables as $entry) {
|
||||
\Log::debug('Create job to import predictable');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\RecurringTransaction\RecurringTransactionRepositoryInterface@importPredictable', [
|
||||
'data' => $entry,
|
||||
'class' => 'Predictable',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
// all settings (to fix the piggy banks)
|
||||
foreach ($JSON->settings as $entry) {
|
||||
\Log::debug('Create job to import setting');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Queue\Import@importSetting', [
|
||||
'data' => $entry,
|
||||
'class' => 'Setting',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
// all transactions
|
||||
foreach ($JSON->transactions as $entry) {
|
||||
|
||||
\Log::debug('Create job to import transaction');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface@importTransaction', [
|
||||
'data' => $entry,
|
||||
'class' => 'Transaction',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
// all transfers
|
||||
foreach ($JSON->transfers as $entry) {
|
||||
\Log::debug('Create job to import transfer');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Storage\TransactionJournal\TransactionJournalRepositoryInterface@importTransfer', [
|
||||
'data' => $entry,
|
||||
'class' => 'Transfer',
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
// then, fix all component <> transaction links
|
||||
foreach ($JSON->component_transaction as $entry) {
|
||||
\Log::debug('Create job to import components_transaction');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Queue\Import@importComponentTransaction',
|
||||
[
|
||||
'data' => $entry,
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
|
||||
// then, fix all component <> transfer links
|
||||
foreach ($JSON->component_transfer as $entry) {
|
||||
\Log::debug('Create job to import components_transfer');
|
||||
\Queue::push( // count fixed
|
||||
'Firefly\Queue\Import@importComponentTransfer',
|
||||
[
|
||||
'data' => $entry,
|
||||
'mapID' => $importMap->id
|
||||
]
|
||||
);
|
||||
$totalJobs++;
|
||||
}
|
||||
|
||||
$importMap->totaljobs = $totalJobs;
|
||||
$importMap->save();
|
||||
/*
|
||||
* We save the import map which now holds the number of jobs we've got planned.
|
||||
*/
|
||||
|
||||
\Queue::push('Firefly\Queue\Import@cleanImportAccount', ['mapID' => $importMap->id]);
|
||||
|
||||
$job->delete(); // count fixed
|
||||
|
||||
\Log::debug('Done with job "start"');
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
@@ -46,32 +58,50 @@ interface AccountRepositoryInterface
|
||||
public function find($accountId);
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $type
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function findByName($name);
|
||||
public function findAccountType($type);
|
||||
|
||||
/**
|
||||
* Takes a transaction/account component and updates the transaction journal to match.
|
||||
*
|
||||
* @param Job $job
|
||||
* @param array $payload
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get();
|
||||
public function importUpdateTransaction(Job $job, array $payload);
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
*
|
||||
* @return |Account|null
|
||||
*/
|
||||
public function findAssetAccountById($id);
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $create
|
||||
*
|
||||
* @return |Account|null
|
||||
*/
|
||||
public function findExpenseAccountByName($name, $create = true);
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param $create
|
||||
*
|
||||
* @return |Account|null
|
||||
*/
|
||||
public function findRevenueAccountByName($name, $create = true);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getActiveDefault();
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getActiveDefaultAsSelectList();
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getBeneficiaries();
|
||||
|
||||
/**
|
||||
* @param $ids
|
||||
*
|
||||
@@ -82,12 +112,21 @@ interface AccountRepositoryInterface
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCashAccount();
|
||||
public function getDefault();
|
||||
|
||||
/**
|
||||
* @param \AccountType $type
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDefault();
|
||||
public function getByAccountType(\AccountType $type);
|
||||
|
||||
/**
|
||||
* @param \User $user
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function overruleUser(\User $user);
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
|
@@ -4,6 +4,8 @@
|
||||
namespace Firefly\Storage\Account;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Queue\Jobs\Job;
|
||||
|
||||
/**
|
||||
* Class EloquentAccountRepository
|
||||
@@ -12,11 +14,15 @@ use Carbon\Carbon;
|
||||
*/
|
||||
class EloquentAccountRepository implements AccountRepositoryInterface
|
||||
{
|
||||
|
||||
protected $_user = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_user = \Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,45 +30,10 @@ class EloquentAccountRepository implements AccountRepositoryInterface
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return \Auth::user()->accounts()->count();
|
||||
return $this->_user->accounts()->count();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*
|
||||
@@ -71,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 */
|
||||
@@ -86,8 +57,8 @@ class EloquentAccountRepository implements AccountRepositoryInterface
|
||||
$accountIDs = array_unique($accountIDs);
|
||||
if (count($accountIDs) > 0) {
|
||||
// find the "initial balance" type accounts in this list. Should be just 1.
|
||||
$query = \Auth::user()->accounts()->accountTypeIn(['Initial balance account'])
|
||||
->whereIn('accounts.id', $accountIDs);
|
||||
$query = $this->_user->accounts()->accountTypeIn(['Initial balance account'])
|
||||
->whereIn('accounts.id', $accountIDs);
|
||||
if ($query->count() == 1) {
|
||||
$iba = $query->first(['accounts.*']);
|
||||
$iba->delete();
|
||||
@@ -105,81 +76,169 @@ class EloquentAccountRepository implements AccountRepositoryInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $accountId
|
||||
* @param $id
|
||||
*
|
||||
* @return mixed
|
||||
* @return |Account|null
|
||||
*/
|
||||
public function find($accountId)
|
||||
public function findAssetAccountById($id)
|
||||
{
|
||||
return \Auth::user()->accounts()->where('id', $accountId)->first();
|
||||
return $this->_user->accounts()->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param \AccountType $type
|
||||
* 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.
|
||||
*
|
||||
* @return mixed
|
||||
* @param $name
|
||||
* @param $create
|
||||
*
|
||||
* @return null|\Account
|
||||
*/
|
||||
public function findByName($name, \AccountType $type = null)
|
||||
public function findExpenseAccountByName($name, $create = true)
|
||||
{
|
||||
$type = is_null($type) ? \AccountType::where('type', 'Default account')->first() : $type;
|
||||
$cashType = $this->findAccountType('Cash account');
|
||||
$importType = $this->findAccountType('Import account');
|
||||
// catch Import account:
|
||||
if ($name == 'Import account') {
|
||||
|
||||
return \Auth::user()->accounts()->where('account_type_id', $type->id)
|
||||
->where('name', 'like', '%' . $name . '%')
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return \Auth::user()->accounts()->with('accounttype')->orderBy('name', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getActiveDefault()
|
||||
{
|
||||
return \Auth::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 = \Auth::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;
|
||||
$import = $this->firstOrCreate(
|
||||
[
|
||||
'name' => 'Import account',
|
||||
'user_id' => $this->_user->id,
|
||||
'account_type_id' => $importType->id,
|
||||
'active' => 1
|
||||
]
|
||||
);
|
||||
return $import;
|
||||
}
|
||||
|
||||
return $return;
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @param $type
|
||||
*
|
||||
* @return \AccountType|null
|
||||
*/
|
||||
public function getBeneficiaries()
|
||||
public function findAccountType($type)
|
||||
{
|
||||
$list = \Auth::user()->accounts()->leftJoin(
|
||||
'account_types', 'account_types.id', '=', 'accounts.account_type_id'
|
||||
)
|
||||
->where('account_types.type', 'Beneficiary account')->where('accounts.active', 1)
|
||||
return \AccountType::where('type', $type)->first();
|
||||
}
|
||||
|
||||
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
|
||||
public function firstOrCreate(array $data)
|
||||
{
|
||||
return \Account::firstOrCreate($data);
|
||||
}
|
||||
|
||||
return $list;
|
||||
/**
|
||||
* @param $name
|
||||
* @param $create
|
||||
*
|
||||
* @return |Account|null
|
||||
*/
|
||||
public function findRevenueAccountByName($name, $create = true)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
|
||||
// find account:
|
||||
$type = $this->findAccountType('Revenue account');
|
||||
$account = $this->_user->accounts()->where('name', $name)->where('account_type_id', $type->id)->first();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
return $this->_user->accounts()->with('accounttype')->orderBy('name', 'ASC')
|
||||
->where('account_type_id', $type->id)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,22 +249,71 @@ class EloquentAccountRepository implements AccountRepositoryInterface
|
||||
public function getByIds(array $ids)
|
||||
{
|
||||
if (count($ids) > 0) {
|
||||
return \Auth::user()->accounts()->with('accounttype')->whereIn('id', $ids)->orderBy('name', 'ASC')->get();
|
||||
return $this->_user->accounts()->with('accounttype')->whereIn('id', $ids)->orderBy('name', 'ASC')->get();
|
||||
} else {
|
||||
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 = \Auth::user()->accounts()->where('account_type_id', $type->id)->first();
|
||||
|
||||
return $cash;
|
||||
|
||||
return $this->_user->accounts()->accountTypeIn(['Default account', 'Asset account'])->where(
|
||||
'accounts.active', 1
|
||||
)
|
||||
->get(['accounts.*']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,12 +321,120 @@ class EloquentAccountRepository implements AccountRepositoryInterface
|
||||
*/
|
||||
public function getDefault()
|
||||
{
|
||||
return \Auth::user()->accounts()->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->where('account_types.type', 'Default account')
|
||||
|
||||
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
|
||||
return $this->_user->accounts()->accountTypeIn(['Default account', 'Asset account'])
|
||||
->orderBy('accounts.name', 'ASC')->get(['accounts.*']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 getOfTypes(array $types)
|
||||
{
|
||||
$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
|
||||
*
|
||||
@@ -234,30 +450,44 @@ class EloquentAccountRepository implements AccountRepositoryInterface
|
||||
&& get_class($data['account_type']) == 'AccountType'
|
||||
) {
|
||||
$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:
|
||||
*/
|
||||
$account = new \Account;
|
||||
$account->accountType()->associate($accountType);
|
||||
$account->user()->associate(\Auth::user());
|
||||
$account->user()->associate($this->_user);
|
||||
|
||||
$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']);
|
||||
$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
|
||||
}
|
||||
|
||||
|
||||
@@ -265,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
|
||||
@@ -279,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();
|
||||
@@ -302,41 +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(\Auth::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->createSimpleJournal(
|
||||
$initial, $account, 'Initial Balance for ' . $account->name, $amount, $date
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@@ -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
|
||||
*
|
||||
@@ -23,15 +52,23 @@ interface BudgetRepositoryInterface
|
||||
*/
|
||||
public function find($budgetId);
|
||||
|
||||
|
||||
/**
|
||||
* @param $budgetName
|
||||
* @return mixed
|
||||
*/
|
||||
public function get();
|
||||
public function findByName($budgetName);
|
||||
|
||||
/**
|
||||
* @param \User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function overruleUser(\User $user);
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getAsSelectList();
|
||||
public function get();
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
|
@@ -3,19 +3,361 @@
|
||||
namespace Firefly\Storage\Budget;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Queue\Jobs\Job;
|
||||
|
||||
/**
|
||||
* Class EloquentBudgetRepository
|
||||
*
|
||||
* @package Firefly\Storage\Budget
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CamelCasePropertyName)
|
||||
*
|
||||
*/
|
||||
class EloquentBudgetRepository implements BudgetRepositoryInterface
|
||||
{
|
||||
|
||||
protected $_user = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_user = \Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Job $job
|
||||
* @param array $payload
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function importBudget(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 Budget is already imported:
|
||||
*/
|
||||
$importEntry = $repository->findImportEntry($importMap, 'Budget', intval($payload['data']['id']));
|
||||
|
||||
/*
|
||||
* if so, delete job and return:
|
||||
*/
|
||||
if (!is_null($importEntry)) {
|
||||
\Log::debug('Already imported budget ' . $payload['data']['name']);
|
||||
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
$job->delete(); // count fixed
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* maybe Budget is already imported.
|
||||
*/
|
||||
$budget = $this->findByName($payload['data']['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'] . '".');
|
||||
}
|
||||
|
||||
// update map:
|
||||
$importMap->jobsdone++;
|
||||
$importMap->save();
|
||||
|
||||
// delete job.
|
||||
$job->delete(); // count fixed
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \User $user
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function overruleUser(\User $user)
|
||||
{
|
||||
$this->_user = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $budgetName
|
||||
*
|
||||
* @return \Budget|null
|
||||
*/
|
||||
public function findByName($budgetName)
|
||||
{
|
||||
|
||||
return $this->_user->budgets()->whereName($budgetName)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
* @return \Budget
|
||||
*/
|
||||
public function store($data)
|
||||
{
|
||||
$budget = new \Budget;
|
||||
$budget->name = $data['name'];
|
||||
$budget->user()->associate($this->_user);
|
||||
$budget->save();
|
||||
|
||||
// if limit, create limit (repetition itself will be picked up elsewhere).
|
||||
if (isset($data['amount']) && floatval($data['amount']) > 0) {
|
||||
$startDate = new Carbon;
|
||||
$limitData = [
|
||||
'budget_id' => $budget->id,
|
||||
'startdate' => $startDate->format('Y-m-d'),
|
||||
'period' => $data['repeat_freq'],
|
||||
'amount' => floatval($data['amount']),
|
||||
'repeats' => 0
|
||||
];
|
||||
/** @var \Firefly\Storage\Limit\LimitRepositoryInterface $limitRepository */
|
||||
$limitRepository = \App::make('Firefly\Storage\Limit\LimitRepositoryInterface');
|
||||
$limitRepository->overruleUser($this->_user);
|
||||
$limit = $limitRepository->store($limitData);
|
||||
\Event::fire('limits.store', [$limit]);
|
||||
}
|
||||
|
||||
if ($budget->validate()) {
|
||||
$budget->save();
|
||||
}
|
||||
|
||||
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|mixed
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy(\Budget $budget)
|
||||
{
|
||||
@@ -25,112 +367,20 @@ class EloquentBudgetRepository implements BudgetRepositoryInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $budgetId
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($budgetId)
|
||||
{
|
||||
|
||||
return \Auth::user()->budgets()->find($budgetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @return Collection
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
$set = \Auth::user()->budgets()->with(
|
||||
$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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|mixed
|
||||
*/
|
||||
public function getAsSelectList()
|
||||
{
|
||||
$list = \Auth::user()->budgets()->with(
|
||||
['limits', 'limits.limitrepetitions']
|
||||
)->orderBy('name', 'ASC')->get();
|
||||
$return = [];
|
||||
foreach ($list as $entry) {
|
||||
$return[intval($entry->id)] = $entry->name;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
* @return \Budget|mixed
|
||||
*/
|
||||
public function store($data)
|
||||
{
|
||||
$budget = new \Budget;
|
||||
$budget->name = $data['name'];
|
||||
$budget->user()->associate(\Auth::user());
|
||||
$budget->save();
|
||||
|
||||
// if limit, create limit (repetition itself will be picked up elsewhere).
|
||||
if (floatval($data['amount']) > 0) {
|
||||
$limit = new \Limit;
|
||||
$limit->budget()->associate($budget);
|
||||
$startDate = new Carbon;
|
||||
switch ($data['repeat_freq']) {
|
||||
case 'daily':
|
||||
$startDate->startOfDay();
|
||||
break;
|
||||
case 'weekly':
|
||||
$startDate->startOfWeek();
|
||||
break;
|
||||
case 'monthly':
|
||||
$startDate->startOfMonth();
|
||||
break;
|
||||
case 'quarterly':
|
||||
$startDate->firstOfQuarter();
|
||||
break;
|
||||
case 'half-year':
|
||||
$startDate->startOfYear();
|
||||
if (intval($startDate->format('m')) >= 7) {
|
||||
$startDate->addMonths(6);
|
||||
}
|
||||
break;
|
||||
case 'yearly':
|
||||
$startDate->startOfYear();
|
||||
break;
|
||||
}
|
||||
$limit->startdate = $startDate;
|
||||
$limit->amount = $data['amount'];
|
||||
$limit->repeats = isset($data['repeats']) ? $data['repeats'] : 0;
|
||||
$limit->repeat_freq = $data['repeat_freq'];
|
||||
if ($limit->validate()) {
|
||||
$limit->save();
|
||||
\Event::fire('limits.store', [$limit]);
|
||||
}
|
||||
}
|
||||
if ($budget->validate()) {
|
||||
$budget->save();
|
||||
}
|
||||
|
||||
return $budget;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
* @param $data
|
||||
|
@@ -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,13 @@ interface CategoryRepositoryInterface
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function createOrFind($name);
|
||||
public function firstOrCreate($name);
|
||||
|
||||
/**
|
||||
* @param \User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function overruleUser(\User $user);
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Firefly\Storage\Category;
|
||||
|
||||
use Illuminate\Queue\Jobs\Job;
|
||||
|
||||
/**
|
||||
* Class EloquentCategoryRepository
|
||||
*
|
||||
@@ -9,23 +11,340 @@ namespace Firefly\Storage\Category;
|
||||
*/
|
||||
class EloquentCategoryRepository implements CategoryRepositoryInterface
|
||||
{
|
||||
protected $_user = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_user = \Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
/** @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;
|
||||
}
|
||||
|
||||
|
||||
/** @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 \User $user
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function overruleUser(\User $user)
|
||||
{
|
||||
$this->_user = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $categoryId
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($categoryId)
|
||||
{
|
||||
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
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function findByName($name)
|
||||
{
|
||||
if ($name == '' || strlen($name) == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->_user->categories()->where('name', $name)->first();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
* @return \Category|mixed
|
||||
*/
|
||||
public function store($data)
|
||||
{
|
||||
$category = new \Category;
|
||||
$category->name = $data['name'];
|
||||
|
||||
$category->user()->associate($this->_user);
|
||||
$category->save();
|
||||
|
||||
return $category;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @return \Category|mixed
|
||||
*/
|
||||
public function createOrFind($name)
|
||||
public function firstOrCreate($name)
|
||||
{
|
||||
if (strlen($name) == 0) {
|
||||
return null;
|
||||
}
|
||||
$category = $this->findByName($name);
|
||||
if (!$category) {
|
||||
return $this->store(['name' => $name]);
|
||||
}
|
||||
|
||||
return $category;
|
||||
|
||||
$data = [
|
||||
'name' => $name,
|
||||
'user_id' => $this->_user->id,
|
||||
];
|
||||
return \Category::firstOrCreate($data);
|
||||
|
||||
}
|
||||
|
||||
@@ -41,53 +360,12 @@ class EloquentCategoryRepository implements CategoryRepositoryInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $categoryId
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($categoryId)
|
||||
{
|
||||
return \Auth::user()->categories()->find($categoryId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function findByName($name)
|
||||
{
|
||||
if ($name == '' || strlen($name) == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return \Auth::user()->categories()->where('name', 'LIKE', '%' . $name . '%')->first();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
return \Auth::user()->categories()->orderBy('name', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
* @return \Category|mixed
|
||||
*/
|
||||
public function store($data)
|
||||
{
|
||||
$category = new \Category;
|
||||
$category->name = $data['name'];
|
||||
|
||||
$category->user()->associate(\Auth::user());
|
||||
$category->save();
|
||||
|
||||
return $category;
|
||||
return $this->_user->categories()->orderBy('name', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,31 +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);
|
||||
|
||||
}
|
@@ -1,77 +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;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return \Auth::user()->components()->count();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed|void
|
||||
* @throws \Firefly\Exception\FireflyException
|
||||
*/
|
||||
public function get()
|
||||
{
|
||||
throw new FireflyException('No implementation.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(\Auth::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;
|
||||
}
|
||||
|
||||
}
|
58
app/lib/Firefly/Storage/Import/EloquentImportRepository.php
Normal file
58
app/lib/Firefly/Storage/Import/EloquentImportRepository.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Firefly\Storage\Import;
|
||||
|
||||
|
||||
class EloquentImportRepository implements ImportRepositoryInterface
|
||||
{
|
||||
protected $_user = null;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->_user = \Auth::user();
|
||||
}
|
||||
|
||||
public function findImportComponentMap(\Importmap $map, $oldComponentId)
|
||||
{
|
||||
$entry = \Importentry::where('importmap_id', $map->id)
|
||||
->whereIn('class', ['Budget', 'Category', 'Account', 'Component'])
|
||||
->where('old', intval($oldComponentId))->first();
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
public function findImportEntry(\Importmap $map, $class, $oldID)
|
||||
{
|
||||
|
||||
return \Importentry::where('importmap_id', $map->id)->where('class', $class)->where('old', $oldID)->first();
|
||||
}
|
||||
|
||||
public function findImportMap($id)
|
||||
{
|
||||
return \Importmap::find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \User $user
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function overruleUser(\User $user)
|
||||
{
|
||||
$this->_user = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
public function store(\Importmap $map, $class, $oldID, $newID)
|
||||
{
|
||||
$entry = new \Importentry;
|
||||
$entry->importmap()->associate($map);
|
||||
$entry->class = $class;
|
||||
$entry->old = intval($oldID);
|
||||
$entry->new = intval($newID);
|
||||
$entry->save();
|
||||
}
|
||||
|
||||
}
|
51
app/lib/Firefly/Storage/Import/ImportRepositoryInterface.php
Normal file
51
app/lib/Firefly/Storage/Import/ImportRepositoryInterface.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Firefly\Storage\Import;
|
||||
|
||||
/**
|
||||
* Interface ImportRepositoryInterface
|
||||
* @package Firefly\Storage\Import
|
||||
*/
|
||||
interface ImportRepositoryInterface
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
* @param \Importmap $map
|
||||
* @param $class
|
||||
* @param $oldID
|
||||
* @param $newID
|
||||
* @return mixed
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @param \User $user
|
||||
* @return mixed
|
||||
*/
|
||||
public function overruleUser(\User $user);
|
||||
}
|
@@ -4,6 +4,7 @@ namespace Firefly\Storage\Limit;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Queue\Jobs\Job;
|
||||
|
||||
/**
|
||||
* Class EloquentLimitRepository
|
||||
@@ -12,64 +13,125 @@ use Carbon\Carbon;
|
||||
*/
|
||||
class EloquentLimitRepository implements LimitRepositoryInterface
|
||||
{
|
||||
|
||||
protected $_user = null;
|
||||
|
||||
/**
|
||||
* @param \Limit $limit
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy(\Limit $limit)
|
||||
public function __construct()
|
||||
{
|
||||
$limit->delete();
|
||||
|
||||
return true;
|
||||
$this->_user = \Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Limit $limit
|
||||
* @param $data
|
||||
* @param Job $job
|
||||
* @param array $payload
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function importLimit(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 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 update(\Limit $limit, $data)
|
||||
public function overruleUser(\User $user)
|
||||
{
|
||||
$limit->startdate = new Carbon($data['startdate']);
|
||||
$limit->repeat_freq = $data['period'];
|
||||
$limit->repeats = isset($data['repeats']) && $data['repeats'] == '1' ? 1 : 0;
|
||||
$limit->amount = floatval($data['amount']);
|
||||
|
||||
$limit->save();
|
||||
|
||||
return $limit;
|
||||
$this->_user = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $limitId
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($limitId)
|
||||
public function findByBudgetAndDate(\Budget $budget, Carbon $date)
|
||||
{
|
||||
return \Limit::with('limitrepetitions')->where('limits.id', $limitId)->leftJoin(
|
||||
'components', 'components.id', '=', 'limits.component_id'
|
||||
)
|
||||
->where('components.user_id', \Auth::user()->id)->first(['limits.*']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
return \Limit::whereComponentId($budget->id)->where('startdate', $date->format('Y-m-d'))->first();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,7 +178,7 @@ class EloquentLimitRepository implements LimitRepositoryInterface
|
||||
// find existing:
|
||||
$count = \Limit::
|
||||
leftJoin('components', 'components.id', '=', 'limits.component_id')->where(
|
||||
'components.user_id', \Auth::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']
|
||||
)->count();
|
||||
@@ -128,9 +190,9 @@ class EloquentLimitRepository implements LimitRepositoryInterface
|
||||
// create new limit:
|
||||
$limit = new \Limit;
|
||||
$limit->budget()->associate($budget);
|
||||
$limit->startdate = $date;
|
||||
$limit->amount = floatval($data['amount']);
|
||||
$limit->repeats = isset($data['repeats']) ? intval($data['repeats']) : 0;
|
||||
$limit->startdate = $date;
|
||||
$limit->amount = floatval($data['amount']);
|
||||
$limit->repeats = isset($data['repeats']) ? intval($data['repeats']) : 0;
|
||||
$limit->repeat_freq = $data['period'];
|
||||
if (!$limit->save()) {
|
||||
\Session::flash('error', 'Could not save: ' . $limit->errors()->first());
|
||||
@@ -139,4 +201,49 @@ 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
|
||||
*
|
||||
* @return mixed|void
|
||||
*/
|
||||
public function update(\Limit $limit, $data)
|
||||
{
|
||||
$limit->startdate = new Carbon($data['startdate']);
|
||||
$limit->repeat_freq = $data['period'];
|
||||
$limit->repeats = isset($data['repeats']) && $data['repeats'] == '1' ? 1 : 0;
|
||||
$limit->amount = floatval($data['amount']);
|
||||
|
||||
$limit->save();
|
||||
|
||||
return $limit;
|
||||
}
|
||||
|
||||
}
|
@@ -3,6 +3,7 @@
|
||||
namespace Firefly\Storage\Limit;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Queue\Jobs\Job;
|
||||
|
||||
/**
|
||||
* Interface LimitRepositoryInterface
|
||||
@@ -12,6 +13,39 @@ use Carbon\Carbon;
|
||||
interface LimitRepositoryInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* @param Job $job
|
||||
* @param array $payload
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function importLimit(Job $job, array $payload);
|
||||
|
||||
/**
|
||||
* @param \Limit $limit
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function destroy(\Limit $limit);
|
||||
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
* @param Carbon $date
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function findByBudgetAndDate(\Budget $budget, Carbon $date);
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTJByBudgetAndDateRange(\Budget $budget, Carbon $start, Carbon $end);
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
*
|
||||
@@ -28,25 +62,9 @@ interface LimitRepositoryInterface
|
||||
public function update(\Limit $limit, $data);
|
||||
|
||||
/**
|
||||
* @param \Budget $budget
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
* @param \User $user
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTJByBudgetAndDateRange(\Budget $budget, Carbon $start, Carbon $end);
|
||||
|
||||
/**
|
||||
* @param $limitId
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function find($limitId);
|
||||
|
||||
/**
|
||||
* @param \Limit $limit
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function destroy(\Limit $limit);
|
||||
public function overruleUser(\User $user);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user