treasurer 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +3 -2
- data/VERSION +1 -1
- data/lib/treasurer/analysis.rb +55 -54
- data/lib/treasurer/commands.rb +4 -1
- data/lib/treasurer/local_customisations.rb +21 -15
- data/lib/treasurer/report.rb +292 -154
- data/test/otheraccountstatement.csv +1 -0
- data/test/test_treasurer.rb +14 -1
- data/treasurer.gemspec +41 -38
- metadata +21 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65f2e9677098bdac4956e3562dd33f66317c52df
|
4
|
+
data.tar.gz: 34118648a3281e1274a00f953887283937af5d76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 728db7d25466eb3f09bb2e47f0336dee0ea762624b899f7cc521584c17629de66818f584afbbefbdbeb4d351110381984b8075581c4df2da725a903bae864287
|
7
|
+
data.tar.gz: c1837491048206c26c5dc37104201ce2b5d232bbdb85135b0e2e3e482495d1ad52dcfbe17d2662fe860c4c0b06ac32be32c08fe9f3d8e16c90289339ff614f54
|
data/Gemfile
CHANGED
@@ -3,7 +3,7 @@ source "http://rubygems.org"
|
|
3
3
|
# Example:
|
4
4
|
# gem "activesupport", ">= 2.3.5"
|
5
5
|
gem "coderunner", ">= 0.14.16"
|
6
|
-
gem "budgetcrmod", ">= 0.
|
6
|
+
gem "budgetcrmod", ">= 0.2.0"
|
7
7
|
gem "command-line-flunky", ">= 1.0.0"
|
8
8
|
|
9
9
|
# Add dependencies to develop your gem here.
|
@@ -12,6 +12,7 @@ group :development do
|
|
12
12
|
gem "shoulda", ">= 0"
|
13
13
|
gem "rdoc", "~> 3.12"
|
14
14
|
gem "bundler", "~> 1.0"
|
15
|
-
gem "jeweler", "~> 2.
|
15
|
+
gem "jeweler", "~> 2.3.1"
|
16
16
|
gem "simplecov", ">= 0"
|
17
|
+
gem "ruby-prof", ">= 0.15"
|
17
18
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/treasurer/analysis.rb
CHANGED
@@ -2,39 +2,39 @@
|
|
2
2
|
class Treasurer::Reporter
|
3
3
|
module Analysis
|
4
4
|
# Within the range of the report, return a list
|
5
|
-
# of the dates of the beginning of each
|
5
|
+
# of the dates of the beginning of each account
|
6
6
|
# period, along with a list of the expenditures
|
7
7
|
# for each period and a list of the items within
|
8
8
|
# each period
|
9
|
-
def
|
9
|
+
def account_expenditure(account, account_info, options={})
|
10
10
|
dates = []
|
11
11
|
expenditures = []
|
12
|
-
|
13
|
-
date =
|
14
|
-
start_date = [(
|
12
|
+
account_items = []
|
13
|
+
date = account_info[:end]||@today
|
14
|
+
start_date = [(account_info[:start]||@start_date), @start_date].max
|
15
15
|
expenditure = 0
|
16
16
|
items_temp = []
|
17
|
-
items = @runner.component_run_list.values.find_all{|r| r.
|
17
|
+
items = @runner.component_run_list.values.find_all{|r| r.external_account == account and r.in_date(account_info)}
|
18
18
|
#ep ['items', items]
|
19
|
-
#ep ['
|
19
|
+
#ep ['account', account]
|
20
20
|
counter = 0
|
21
|
-
if not
|
21
|
+
if not account_info[:period]
|
22
22
|
dates.push date
|
23
|
-
|
24
|
-
expenditures.push (items.map{|r| r.
|
23
|
+
account_items.push items
|
24
|
+
expenditures.push (items.map{|r| (r.deposit - r.withdrawal) * (account_info[:external] ? -1 : 1)}+[0]).sum
|
25
25
|
else
|
26
26
|
|
27
|
-
case
|
27
|
+
case account_info[:period][1]
|
28
28
|
when :month
|
29
29
|
while date > @start_date
|
30
30
|
items_temp += items.find_all{|r| r.date == date}
|
31
|
-
if date.mday == (
|
31
|
+
if date.mday == (account_info[:monthday] or 1)
|
32
32
|
counter +=1
|
33
|
-
if counter %
|
34
|
-
expenditure = (items_temp.map{|r| r.
|
33
|
+
if counter % account_info[:period][0] == 0
|
34
|
+
expenditure = (items_temp.map{|r| (r.deposit - r.withdrawal) * (account_info[:external] ? -1 : 1)}+[0]).sum
|
35
35
|
dates.push date
|
36
36
|
expenditures.push expenditure
|
37
|
-
|
37
|
+
account_items.push items_temp
|
38
38
|
items_temp = []
|
39
39
|
expenditure = 0
|
40
40
|
end
|
@@ -44,13 +44,13 @@ module Analysis
|
|
44
44
|
when :day
|
45
45
|
while date > @start_date
|
46
46
|
items_temp += items.find_all{|r| r.date == date}
|
47
|
-
#expenditure += (
|
47
|
+
#expenditure += (account_items[-1].map{|r| r.debit}+[0]).sum
|
48
48
|
counter +=1
|
49
|
-
if counter %
|
50
|
-
expenditure = (items_temp.map{|r| r.
|
49
|
+
if counter % account_info[:period][0] == 0
|
50
|
+
expenditure = (items_temp.map{|r| (r.deposit - r.withdrawal) * (account_info[:external] ? -1 : 1)}+[0]).sum
|
51
51
|
dates.push date
|
52
52
|
expenditures.push expenditure
|
53
|
-
|
53
|
+
account_items.push items_temp
|
54
54
|
items_temp = []
|
55
55
|
expenditure = 0
|
56
56
|
end
|
@@ -59,45 +59,46 @@ module Analysis
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
[dates, expenditures,
|
62
|
+
[dates, expenditures, account_items]
|
63
63
|
|
64
64
|
end
|
65
|
-
# Work out the average spend from the
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
#
|
71
|
-
dates, expenditures, items =
|
72
|
-
|
65
|
+
# Work out the average spend from the account and include it in the account info
|
66
|
+
def accounts_with_averages(accounts, options={})
|
67
|
+
projected_accounts_info = accounts.dup
|
68
|
+
projected_accounts_info.each{|key,v| projected_accounts_info[key]=projected_accounts_info[key].dup}
|
69
|
+
projected_accounts_info.each do |account, account_info|
|
70
|
+
#account_info = accounts[account]
|
71
|
+
dates, expenditures, items = account_expenditure(account, account_info)
|
72
|
+
account_info[:average] = expenditures.mean rescue 0.0
|
73
73
|
end
|
74
|
-
|
74
|
+
projected_accounts_info
|
75
75
|
end
|
76
|
-
# Work out the projected spend from the
|
77
|
-
def
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
#
|
82
|
-
dates, expenditures, items =
|
83
|
-
|
76
|
+
# Work out the projected spend from the account and include it in the account info
|
77
|
+
def accounts_with_projections(accounts, options={})
|
78
|
+
projected_accounts_info = accounts.dup
|
79
|
+
projected_accounts_info.each{|key,v| projected_accounts_info[key]=projected_accounts_info[key].dup}
|
80
|
+
projected_accounts_info.each do |account, account_info|
|
81
|
+
#account_info = accounts[account]
|
82
|
+
dates, expenditures, items = account_expenditure(account, account_info)
|
83
|
+
account_info[:projection] = expenditures.mean rescue 0.0
|
84
84
|
end
|
85
|
-
|
85
|
+
projected_accounts_info
|
86
86
|
end
|
87
|
-
|
88
|
-
|
89
|
-
def
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
end
|
94
|
-
end
|
95
|
-
# Find all discretionary
|
96
|
-
# expenditure from that
|
87
|
+
## Get a list of accounts to be included in the report
|
88
|
+
## i.e. accounts with non-empty expenditure
|
89
|
+
#def get_actual_accounts
|
90
|
+
#@actual_accounts = ACCOUNT_INFO.dup
|
91
|
+
#ACCOUNT_INFO.keys.each do |account|
|
92
|
+
#@actual_accounts.delete(account) if account_expenditure(account, ACCOUNT_INFO[account])[0].size == 0
|
93
|
+
#end
|
94
|
+
#end
|
95
|
+
# Find all discretionary accounts and estimate the future
|
96
|
+
# expenditure from that account based on past
|
97
97
|
# expenditure (currently only a simple average)
|
98
|
-
def
|
99
|
-
@
|
100
|
-
@
|
98
|
+
def get_projected_accounts
|
99
|
+
@projected_accounts_info = Hash[ACCOUNT_INFO.dup.find_all{|k,v| v[:discretionary]}]
|
100
|
+
@projected_accounts_info = accounts_with_projections(@projected_accounts_info)
|
101
|
+
#@projected_accounts_info = @accounts.find_all{|acc| info = ACCOUNT_INFO[acc.name] and info[:discretionary]}
|
101
102
|
end
|
102
103
|
# Calculate the sum of all items within future
|
103
104
|
# items that fall before end_date
|
@@ -117,7 +118,7 @@ module Analysis
|
|
117
118
|
sum
|
118
119
|
end
|
119
120
|
# Sum every future occurence of the given
|
120
|
-
# regular items that falls within the
|
121
|
+
# regular items that falls within the account period
|
121
122
|
def sum_regular(regular_items, end_date, options={})
|
122
123
|
#end_date = @today + @days_ahead
|
123
124
|
sum = regular_items.inject(0) do |sum, (name, item)|
|
@@ -179,9 +180,9 @@ module Analysis
|
|
179
180
|
|
180
181
|
|
181
182
|
|
182
|
-
#ep ['name2234', name, info, @
|
183
|
+
#ep ['name2234', name, info, @projected_account_factor] if info[:discretionary]
|
183
184
|
|
184
|
-
value + nunits * (info[:size]||info[:projection]*(@
|
185
|
+
value + nunits * (info[:size]||info[:projection]*(@projected_account_factor||1.0))
|
185
186
|
|
186
187
|
end
|
187
188
|
sum + value
|
data/lib/treasurer/commands.rb
CHANGED
@@ -19,9 +19,12 @@ class << self
|
|
19
19
|
raise "This folder has not been set up to use with Treasurer; please initialise a folder with treasurer init" unless FileTest.exist? '.code_runner_script_defaults.rb' and eval(File.read('.code_runner_script_defaults.rb'))[:code] == 'budget'
|
20
20
|
end
|
21
21
|
def create_report(copts = {})
|
22
|
+
reporter = fetch_reporter(copts)
|
23
|
+
reporter.report()
|
24
|
+
end
|
25
|
+
def fetch_reporter(copts = {})
|
22
26
|
load_treasurer_folder
|
23
27
|
reporter = Reporter.new(CodeRunner.fetch_runner(h: :component), days_before: copts[:b]||360, days_ahead: copts[:a]||180, today: copts[:t])
|
24
|
-
reporter.report()
|
25
28
|
end
|
26
29
|
def init_root_folder(folder, copts={})
|
27
30
|
raise "Folder already exists" if FileTest.exist? folder
|
@@ -10,7 +10,7 @@ REGULAR_TRANSFERS = {
|
|
10
10
|
house: {size: 600, period: [1, :month], monthday: 20, end: Date.parse("01/07/2013")},
|
11
11
|
},
|
12
12
|
[:Income, :FirstBank] =>{
|
13
|
-
pay: {size:
|
13
|
+
pay: {size: 1200, period: [1, :month], monthday: 1, end: Date.parse("01/07/2014")},
|
14
14
|
},
|
15
15
|
|
16
16
|
}
|
@@ -19,7 +19,7 @@ REGULAR_TRANSFERS.default = {}
|
|
19
19
|
|
20
20
|
FUTURE_TRANSFERS = {
|
21
21
|
[:Income, :SecondBank] =>{
|
22
|
-
|
22
|
+
bonus: {size: 100, date: Date.parse("26/09/2010")},
|
23
23
|
},
|
24
24
|
[:FirstBank, :PersonalLoans] =>{
|
25
25
|
payfriend: {size: 640, date: Date.parse("25/09/2010")},
|
@@ -32,12 +32,16 @@ FUTURE_TRANSFERS.default = {}
|
|
32
32
|
|
33
33
|
|
34
34
|
|
35
|
-
|
36
|
-
Monthly: {
|
37
|
-
MonthlySecondBank: {
|
38
|
-
Weekly: {
|
39
|
-
WeeklySecondBank: {
|
40
|
-
MyHoliday: {
|
35
|
+
ACCOUNT_INFO = {
|
36
|
+
Monthly: {linked_account: :FirstBank, period: [1, :month], monthday: 1, start: nil, end: nil, discretionary: false},
|
37
|
+
MonthlySecondBank: {linked_account: :SecondBank, period: [1, :month], monthday: 1, start: nil, end: nil, discretionary: false},
|
38
|
+
Weekly: {linked_account: :FirstBank, period: [7, :day], monthday: nil, start: nil, end: nil, discretionary: true},
|
39
|
+
WeeklySecondBank: {linked_account: :SecondBank, period: [7, :day], monthday: nil, start: nil, end: nil, discretionary: true},
|
40
|
+
MyHoliday: {linked_account: :SecondBank, period: [1, :day], monthday: nil, start: Date.parse("02/12/2013"), end: Date.parse("2/01/2014"), discretionary: false},
|
41
|
+
PersonalLoans: {type: :Liability},
|
42
|
+
FirstBank: {type: :Asset},
|
43
|
+
SecondBank: {type: :Asset},
|
44
|
+
Pay: {linked_account: :FirstBank, type: :Income},
|
41
45
|
}
|
42
46
|
|
43
47
|
def in_date(item)
|
@@ -60,16 +64,16 @@ end
|
|
60
64
|
def red_line(account, date)
|
61
65
|
case account
|
62
66
|
when :FirstBank
|
63
|
-
|
67
|
+
-350
|
64
68
|
when :SecondBank
|
65
|
-
|
69
|
+
0
|
66
70
|
else
|
67
71
|
0
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
71
75
|
|
72
|
-
def
|
76
|
+
def sub_account
|
73
77
|
case description
|
74
78
|
when /co-op|sainsbury/i
|
75
79
|
:Food
|
@@ -85,19 +89,21 @@ def external_account
|
|
85
89
|
:Entertainment
|
86
90
|
when /blackwell/i
|
87
91
|
:Books
|
88
|
-
when /norries/i
|
89
|
-
:PersonalLoans
|
90
92
|
else
|
91
93
|
:Unknown
|
92
94
|
end
|
93
95
|
end
|
94
96
|
|
95
|
-
def
|
97
|
+
def external_account
|
96
98
|
case description
|
97
99
|
when /Vodafone/i
|
98
100
|
:Monthly
|
101
|
+
when /norries/i
|
102
|
+
:PersonalLoans
|
103
|
+
when /my employer/i
|
104
|
+
:Pay
|
99
105
|
else
|
100
|
-
case
|
106
|
+
case sub_account
|
101
107
|
when :Food, :Entertainment
|
102
108
|
:Weekly
|
103
109
|
when :Insurance, :Phone, :Rent
|
data/lib/treasurer/report.rb
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
class Float
|
2
|
+
def to_s
|
3
|
+
sprintf("%.2f", self)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
class Date
|
7
|
+
def inspect
|
8
|
+
"Date.parse('#{to_s}')"
|
9
|
+
end
|
10
|
+
end
|
1
11
|
# Some thoughts on double entry accounting:
|
2
12
|
#
|
3
13
|
# assets - liabilities = equity
|
@@ -48,10 +58,13 @@ class Treasurer
|
|
48
58
|
class Reporter
|
49
59
|
#include LocalCustomisations
|
50
60
|
attr_reader :today
|
51
|
-
attr_reader :
|
52
|
-
attr_reader :
|
53
|
-
attr_accessor :
|
54
|
-
|
61
|
+
attr_reader :in_limit_discretionary_account_factor
|
62
|
+
attr_reader :stable_discretionary_account_factor
|
63
|
+
attr_accessor :projected_account_factor
|
64
|
+
attr_reader :accounts
|
65
|
+
attr_reader :equity
|
66
|
+
attr_reader :projected_accounts_info
|
67
|
+
attr_reader :days_before
|
55
68
|
def initialize(runner, options)
|
56
69
|
@runner = runner
|
57
70
|
@days_ahead = options[:days_ahead]||180
|
@@ -59,48 +72,83 @@ class Reporter
|
|
59
72
|
@today = options[:today]||Date.today
|
60
73
|
@start_date = @today - @days_before
|
61
74
|
@runs = runner.component_run_list.values
|
75
|
+
|
76
|
+
if run = @runs.find{|r| not r.external_account}
|
77
|
+
raise "External_account not specified for #{run.data_line}"
|
78
|
+
end
|
62
79
|
@indateruns = @runs.find_all{|r| r.days_ago(@today) < @days_before}
|
63
|
-
p 'accounts256',@runs.size, @runs.map{|r| r.account}.uniq
|
80
|
+
#p 'accounts256',@runs.size, @runs.map{|r| r.account}.uniq
|
64
81
|
|
65
82
|
end
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
accounts = @runs.map{|r| r.account}.uniq.map{|acc| Account.new(acc, self, @runner, @runs, @projected_budgets, false)}
|
70
|
-
external_accounts = (@runs.map{|r| r.external_account}.uniq - accounts.map{|acc| acc.name}).map{|acc| Account.new(acc, self, @runner, @runs, @projected_budgets, true)}
|
83
|
+
def generate_accounts
|
84
|
+
accounts = @runs.map{|r| r.account}.uniq.map{|acc| Account.new(acc, self, @runner, @runs, false)}
|
85
|
+
external_accounts = (@runs.map{|r| r.external_account}.uniq - accounts.map{|acc| acc.name}).map{|acc| Account.new(acc, self, @runner, @runs, true)}
|
71
86
|
@accounts = accounts + external_accounts
|
87
|
+
@expense_accounts = @accounts.find_all{|acc| acc.type == :Expense}
|
88
|
+
get_projected_accounts
|
89
|
+
#p ['projected_accounts_info', @projected_accounts_info]
|
90
|
+
#exit
|
72
91
|
@accounts.unshift (@equity = Equity.new(self, @runner, @accounts))
|
73
|
-
|
74
|
-
|
92
|
+
end
|
93
|
+
def report
|
94
|
+
generate_accounts
|
95
|
+
#get_actual_accounts
|
96
|
+
get_in_limit_discretionary_account_factor
|
97
|
+
get_stable_discretionary_account_factor
|
75
98
|
report = ""
|
76
99
|
report << header
|
77
|
-
report << '\begin{multicols}{2}'
|
100
|
+
#report << '\begin{multicols}{2}'
|
78
101
|
report << account_summaries
|
79
|
-
report <<
|
102
|
+
report << discretionary_account_table
|
80
103
|
report << account_balance_graphs
|
81
104
|
report << expense_account_summary
|
82
|
-
report <<
|
83
|
-
report << '\end{multicols}'
|
84
|
-
|
85
|
-
report <<
|
105
|
+
report << account_expenditure_graphs
|
106
|
+
#report << '\end{multicols}'
|
107
|
+
##report << account_resolutions
|
108
|
+
#report << account_breakdown
|
109
|
+
|
110
|
+
report << assumptions
|
86
111
|
report << transactions_by_account
|
87
112
|
report << footer
|
88
113
|
|
89
114
|
File.open('report.tex', 'w'){|f| f.puts report}
|
90
|
-
system "
|
115
|
+
system "pdflatex report.tex && pdflatex report.tex"
|
116
|
+
end
|
117
|
+
class Account
|
118
|
+
end
|
119
|
+
class SubAccount < Account
|
120
|
+
def initialize(name, reporter, runner, runs, external)
|
121
|
+
@name = name
|
122
|
+
@reporter = reporter
|
123
|
+
@runner = runner
|
124
|
+
#@projected_accounts_info =Hash[projected_accounts_info.find_all{|k,v| v[:account] == name}]
|
125
|
+
@external = external
|
126
|
+
@runs = runs.find_all{|r| r.sub_account == name}
|
127
|
+
info[:external] = external if info
|
128
|
+
#ep ['sub_accounts333', name, @runs.size, runs.size]
|
129
|
+
end
|
91
130
|
end
|
92
131
|
class Account
|
93
132
|
attr_reader :name, :external, :runs
|
94
|
-
def initialize(name, reporter, runner, runs,
|
133
|
+
def initialize(name, reporter, runner, runs, external)
|
95
134
|
@name = name
|
96
135
|
@reporter = reporter
|
97
136
|
@runner = runner
|
98
|
-
|
137
|
+
#@projected_accounts_info =Hash[projected_accounts_info.find_all{|k,v| v[:account] == name}]
|
99
138
|
@external = external
|
100
139
|
@runs = runs.find_all{|r| (@external ? r.external_account : r.account) == name}
|
140
|
+
info[:external] = external if info
|
141
|
+
end
|
142
|
+
def sub_accounts
|
143
|
+
@sub_accounts ||= @runs.map{|r| r.sub_account}.uniq.compact.map{|acc| SubAccount.new(acc, @reporter, @runner, @runs, @external)}
|
101
144
|
end
|
102
145
|
def type
|
103
|
-
account_type(name)
|
146
|
+
#account_type(name)
|
147
|
+
if ACCOUNT_INFO[name] and type = ACCOUNT_INFO[name][:type]
|
148
|
+
type
|
149
|
+
else
|
150
|
+
:Expense
|
151
|
+
end
|
104
152
|
end
|
105
153
|
def red_line(date)
|
106
154
|
if Treasurer::LocalCustomisations.instance_methods.include? :red_line
|
@@ -109,35 +157,58 @@ class Reporter
|
|
109
157
|
0.0
|
110
158
|
end
|
111
159
|
end
|
160
|
+
def report_start
|
161
|
+
@reporter.today - @reporter.days_before
|
162
|
+
end
|
163
|
+
def opening_date
|
164
|
+
(info && info[:start]) || @runs.map{|r| r.date}.min
|
165
|
+
end
|
166
|
+
def opening_balance
|
167
|
+
(info && info[:opening_balance]) || 0.0
|
168
|
+
end
|
169
|
+
def has_balance?
|
170
|
+
not @runs.find{|r| not r.has_balance?}
|
171
|
+
end
|
112
172
|
def balance(date = @reporter.today)
|
173
|
+
date_i = date.to_datetime.to_time.to_i
|
113
174
|
#if !date
|
114
175
|
#@runs.sort_by{|r| r.date}[-1].balance
|
115
|
-
if @external
|
176
|
+
if @external or not has_balance?
|
116
177
|
#p ['name is ', name, type]
|
117
178
|
#
|
118
|
-
|
119
|
-
|
120
|
-
0.0
|
179
|
+
opening_balance + (@runs.find_all{|r| r.date <= date and r.date >= opening_date }.map{|r| money_in_sign * (r.deposit - r.withdrawal) * (@external ? -1 : 1)}.sum || 0.0)
|
180
|
+
#Temporary....
|
181
|
+
#0.0
|
121
182
|
else
|
122
|
-
@runs.
|
183
|
+
nearest_time = @runs.map{|r| (r.date_i - date_i).to_f.abs}.sort[0]
|
184
|
+
@runs.find_all{|r| (r.date_i - date_i).to_f.abs == nearest_time}.sort_by{|r| r.id}[-1].balance
|
123
185
|
end
|
124
186
|
end
|
125
|
-
def
|
126
|
-
p ['name22 is ', name, type]
|
127
|
-
|
187
|
+
def deposited(today, days_before, &block)
|
188
|
+
p ['name22 is ', name, type, @runs.size]
|
189
|
+
#@runs.find_all{|r| r.days_ago(today) < days_before and (!block or yield(r)) }.map{|r| (@external and not ([:Liability, :Income].include?(type))) ? r.withdrawal : r.deposit }.sum || 0
|
190
|
+
@runs.find_all{|r| r.days_ago(today) < days_before and (!block or yield(r)) }.map{|r| (@external) ? r.withdrawal : r.deposit }.sum || 0
|
128
191
|
end
|
129
|
-
def
|
130
|
-
|
192
|
+
def withdrawn(today, days_before)
|
193
|
+
#@runs.find_all{|r| r.days_ago(today) < days_before }.map{|r| (@external and not ([:Liability, :Income].include?(type))) ? r.deposit : r.withdrawal }.sum || 0
|
194
|
+
@runs.find_all{|r| r.days_ago(today) < days_before }.map{|r| (@external) ? r.deposit : r.withdrawal }.sum || 0
|
131
195
|
end
|
196
|
+
|
132
197
|
def summary_table(today, days_before)
|
133
198
|
|
134
199
|
<<EOF
|
135
200
|
\\subsubsection{#{name}}
|
136
201
|
\\begin{tabulary}{0.8\\textwidth}{ r | l}
|
137
202
|
Balance & #{balance} \\\\
|
138
|
-
Deposited & #{
|
139
|
-
Withdrawn & #{
|
203
|
+
Deposited & #{deposited(today, days_before)} \\\\
|
204
|
+
Withdrawn & #{withdrawn(today, days_before)} \\\\
|
140
205
|
\\end{tabulary}
|
206
|
+
EOF
|
207
|
+
end
|
208
|
+
def summary_line(today, days_before)
|
209
|
+
|
210
|
+
<<EOF
|
211
|
+
#{name} & #{balance} & #{deposited(today, days_before)} & #{withdrawn(today, days_before)}
|
141
212
|
EOF
|
142
213
|
end
|
143
214
|
def money_in_sign
|
@@ -148,10 +219,23 @@ EOF
|
|
148
219
|
1.0
|
149
220
|
end
|
150
221
|
end
|
222
|
+
def discretionary
|
223
|
+
info and info[:discretionary]
|
224
|
+
end
|
225
|
+
def info
|
226
|
+
ACCOUNT_INFO[name] ||= {}
|
227
|
+
end
|
151
228
|
def projected_balance(date)
|
152
|
-
return 0.0 if @external # Temporary Hack
|
229
|
+
#return 0.0 if @external # Temporary Hack
|
230
|
+
#ep ['projected', @reporter.projected_accounts_info]
|
231
|
+
raise "Only should be called for Asset and Liability accounts" unless [:Asset, :Liability].include? type
|
153
232
|
non_discretionary_projected_balance(date) -
|
154
|
-
@reporter.sum_regular(
|
233
|
+
@reporter.sum_regular(linked_projected_account_info, date)
|
234
|
+
|
235
|
+
#(discretionary ? @reporter.sum_regular({name => info}, date) : 0.0)
|
236
|
+
end
|
237
|
+
def linked_projected_account_info
|
238
|
+
Hash[@reporter.projected_accounts_info.find_all{|ac,inf| inf[:linked_account] == name}]
|
155
239
|
end
|
156
240
|
def cache
|
157
241
|
@cache ||={}
|
@@ -181,7 +265,7 @@ EOF
|
|
181
265
|
# balance of the account
|
182
266
|
def write_balance_graph(today, days_before, days_ahead)
|
183
267
|
#accshort = name.gsub(/\s/, '')
|
184
|
-
if not (@external or type == :Equity)
|
268
|
+
if not (@external or type == :Equity or not has_balance?)
|
185
269
|
kit = @runner.graphkit(['date.to_time.to_i', 'balance'], {conditions: "account == #{name.inspect} and days_ago(Date.parse(#{today.to_s.inspect})) < #{days_before} and days_ago(Date.parse(#{today.to_s.inspect})) > -1", sort: '[date, id]'})
|
186
270
|
else
|
187
271
|
pastdates = (today-days_before..today).to_a
|
@@ -193,23 +277,25 @@ EOF
|
|
193
277
|
kit2 = GraphKit.quick_create([futuredates.map{|d| d.to_time.to_i}, projection])
|
194
278
|
red = futuredates.map{|date| red_line(date)}
|
195
279
|
kit3 = GraphKit.quick_create([futuredates.map{|d| d.to_time.to_i}, red])
|
196
|
-
@reporter.
|
280
|
+
@reporter.projected_account_factor = @reporter.in_limit_discretionary_account_factor
|
197
281
|
limit = futuredates.map{|date| projected_balance(date)}
|
198
282
|
kit4 = GraphKit.quick_create([futuredates.map{|d| d.to_time.to_i}, limit])
|
199
|
-
@reporter.
|
200
|
-
#ep ['
|
283
|
+
@reporter.projected_account_factor = @reporter.stable_discretionary_account_factor
|
284
|
+
#ep ['projected_account_factor!!!!', @reporter.projected_account_factor]
|
201
285
|
stable = futuredates.map{|date| projected_balance(date)}
|
202
286
|
kit5 = GraphKit.quick_create([futuredates.map{|d| d.to_time.to_i}, stable])
|
203
287
|
#exit
|
204
|
-
@reporter.
|
288
|
+
@reporter.projected_account_factor = nil
|
205
289
|
kit += (kit2 + kit4 + kit5)
|
290
|
+
#kit += (kit2)
|
206
291
|
kit = kit3 + kit
|
207
292
|
kit.title = "Balance for #{name}"
|
208
293
|
kit.xlabel = %['Date' offset 0,-2]
|
209
294
|
kit.xlabel = nil
|
210
295
|
kit.ylabel = "Balance"
|
296
|
+
|
211
297
|
|
212
|
-
kit.data[0].gp.title = 'Limit'
|
298
|
+
#kit.data[0].gp.title = 'Limit'
|
213
299
|
kit.data[1].gp.title = 'Previous'
|
214
300
|
kit.data[2].gp.title = '0 GBP Discretionary'
|
215
301
|
kit.data[2].gp.title = 'Projection'
|
@@ -218,14 +304,18 @@ EOF
|
|
218
304
|
kit.data.each{|dk| dk.gp.with = "lp"}
|
219
305
|
kit.gp.key = ' bottom left '
|
220
306
|
|
307
|
+
#(p kit; STDIN.gets) if name == :LloydsCreditCard
|
221
308
|
CodeRunner::Budget.kit_time_format_x(kit)
|
222
309
|
|
223
|
-
(kit).gnuplot_write("#{name}_balance.eps", size: "4.0in,
|
310
|
+
(kit).gnuplot_write("#{name}_balance.eps", size: "4.0in,2.0in") #, latex: true)
|
311
|
+
#%x[epspdf #{name}_balance.eps]
|
224
312
|
end
|
225
313
|
# A string to include the balance graph in the document
|
226
314
|
def balance_graph_string
|
227
315
|
#accshort = name.gsub(/\s/, '')
|
228
|
-
"\\begin{center}\\includegraphics[width=3.0in]{#{name}_balance.eps}\\end{center}"
|
316
|
+
#"\\begin{center}\\includegraphics[width=3.0in]{#{name}_balance.eps}\\end{center}"
|
317
|
+
#"\\begin{center}\\includegraphics[width=0.9\\textwidth]{#{name}_balance.eps}\\end{center}"
|
318
|
+
"\\myfigure{#{name}_balance.eps}"
|
229
319
|
end
|
230
320
|
end
|
231
321
|
class Equity < Account
|
@@ -285,30 +375,35 @@ Balance & #{balance} \\\\
|
|
285
375
|
\\end{tabulary}
|
286
376
|
EOF
|
287
377
|
end
|
378
|
+
def summary_line(today, days_before)
|
379
|
+
"Equity & #{balance(today)} & & "
|
380
|
+
end
|
288
381
|
end
|
289
382
|
def account_summaries
|
290
383
|
#ep 'accounts', @accounts.map{|a| a.name}
|
291
384
|
|
292
385
|
<<EOF
|
293
386
|
\\section{Summary of Accounts}
|
294
|
-
|
295
|
-
#{
|
296
|
-
\\
|
297
|
-
|
298
|
-
\\
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
#{@accounts.find_all{|acc| acc.type == :Expense }.map{|acc| acc.summary_table(@today, @days_before)}.join("\n\n") }
|
387
|
+
#{[:Equity, :Asset, :Liability, :Income, :Expense].map{|type|
|
388
|
+
"\\subsection{#{type}}
|
389
|
+
\\begin{tabulary}{0.9\\textwidth}{ R | c | c | c}
|
390
|
+
Account & Balance & Deposited & Withdrawn \\\\
|
391
|
+
\\hline
|
392
|
+
\\Tstrut
|
393
|
+
#{@accounts.find_all{|acc| acc.type == type }.map{|acc| acc.summary_line(@today, @days_before)}.join("\\\\\n")}
|
394
|
+
\\end{tabulary}"
|
395
|
+
}.join("\n\n")}
|
304
396
|
EOF
|
305
397
|
end
|
306
398
|
def account_balance_graphs
|
307
399
|
<<EOF
|
308
400
|
\\section{Graphs of Recent Balances}
|
309
|
-
#{
|
310
|
-
|
311
|
-
acc.
|
401
|
+
#{[:Equity, :Asset, :Liability].map{|typ|
|
402
|
+
"\\subsection{#{typ}}\\vspace{3em}" +
|
403
|
+
@accounts.find_all{|acc| acc.type == typ}.map{|acc|
|
404
|
+
acc.write_balance_graph(@today, @days_before, @days_ahead)
|
405
|
+
acc.balance_graph_string
|
406
|
+
}.join("\n\n")
|
312
407
|
}.join("\n\n")
|
313
408
|
}
|
314
409
|
EOF
|
@@ -317,58 +412,66 @@ EOF
|
|
317
412
|
<<EOF
|
318
413
|
\\section{Expense Account Summary}
|
319
414
|
\\subsection{Budget Period}
|
320
|
-
#{expense_pie_chart('
|
415
|
+
#{expense_pie_chart('accountperiod', @expense_accounts){|r| r.days_ago(@today) < @days_before}}
|
321
416
|
\\subsection{Last Week}
|
322
|
-
#{expense_pie_chart('lastweekexpenses'){|r|
|
417
|
+
#{expense_pie_chart('lastweekexpenses', @expense_accounts){|r|
|
418
|
+
#p ['r.daysago', r.days_ago(@today)];
|
419
|
+
r.days_ago(@today) < 7}}
|
323
420
|
\\subsection{Last Month}
|
324
|
-
#{expense_pie_chart('lastmonthexpenses'){|r| r.days_ago(@today) < 30}}
|
421
|
+
#{expense_pie_chart('lastmonthexpenses', @expense_accounts){|r| r.days_ago(@today) < 30}}
|
325
422
|
\\subsection{Last Year}
|
326
|
-
#{expense_pie_chart('lastyearexpenses'){|r| r.days_ago(@today) < 365}}
|
327
|
-
\\section{Expense
|
328
|
-
#{@
|
329
|
-
|
330
|
-
|
423
|
+
#{expense_pie_chart('lastyearexpenses', @expense_accounts){|r| r.days_ago(@today) < 365}}
|
424
|
+
\\section{Expense Account Breakdown}
|
425
|
+
#{@expense_accounts.map{|account|
|
426
|
+
#ep ['sub_accounts2124', account.sub_accounts.map{|sa| sa.name}]
|
427
|
+
"\\subsection{#{account.name}} +
|
428
|
+
#{expense_pie_chart(account.name + 'breakdown', account.sub_accounts){|r|r.days_ago(@today) < @days_before }}"
|
331
429
|
}.join("\n\n")}
|
332
|
-
|
333
430
|
EOF
|
431
|
+
|
432
|
+
#EOF
|
334
433
|
end
|
335
|
-
def expense_pie_chart(name, &block)
|
336
|
-
expaccs =
|
337
|
-
labels =
|
338
|
-
exps =
|
434
|
+
def expense_pie_chart(name, accounts, &block)
|
435
|
+
#expaccs = accounts.find_all{|acc| acc.type == :Expense}
|
436
|
+
labels = accounts.map{|acc| acc.name}
|
437
|
+
exps = accounts.map{|acc| acc.deposited(@today, 50000, &block)}
|
339
438
|
labels, exps = [labels, exps].transpose.find_all{|l, e| e != 0.0}.transpose
|
340
|
-
|
341
|
-
|
439
|
+
#ep ['labels22539', name, labels, exps]
|
440
|
+
return "No expenditure in account period." if labels == nil
|
342
441
|
kit = GraphKit.quick_create([exps])
|
343
442
|
kit.data[0].gp.with = 'boxes'
|
344
443
|
kit.gp.style = "fill solid"
|
345
444
|
#pp ['kit222', kit, labels]
|
346
445
|
i = -1
|
347
446
|
kit.gp.xtics = "(#{labels.map{|l| %["#{l}" #{i+=1}]}.join(', ')}) rotate by 315"
|
348
|
-
|
447
|
+
kit.gnuplot_write("#{name}.eps", size: "4.0in,2.0in")
|
448
|
+
#%x[ps2eps #{name}.ps]
|
349
449
|
|
350
|
-
"\\begin{center}\\includegraphics[width=3.0in]{#{name}.eps}\\vspace{1em}\\end{center}"
|
450
|
+
#"\\begin{center}\\includegraphics[width=3.0in]{#{name}.eps}\\vspace{1em}\\end{center}"
|
451
|
+
#"\\begin{center}\\includegraphics[width=0.9\\textwidth]{#{name}.eps}\\vspace{1em}\\end{center}"
|
452
|
+
"\\myfigure{#{name}.eps}"
|
351
453
|
end
|
352
|
-
def
|
353
|
-
@
|
454
|
+
def get_in_limit_discretionary_account_factor
|
455
|
+
@projected_account_factor = 1.0
|
354
456
|
loop do
|
355
457
|
ok = true
|
356
458
|
date = @today
|
357
459
|
while date < @today + @days_ahead
|
358
460
|
ok = false if @equity.projected_balance(date) < @equity.red_line(date)
|
359
461
|
date += 1
|
360
|
-
#ep ['
|
462
|
+
#ep ['projected_account_factor', date, @equity.projected_balance(date), @equity.red_line(date), ok]
|
361
463
|
end
|
362
|
-
@
|
363
|
-
break if (@
|
364
|
-
@
|
365
|
-
@
|
464
|
+
@in_limit_discretionary_account_factor = @projected_account_factor
|
465
|
+
break if (@projected_account_factor == 0.0 or ok == true)
|
466
|
+
@projected_account_factor -= 0.01
|
467
|
+
@projected_account_factor -= 0.04
|
468
|
+
ep ['projected_account_factor', @projected_account_factor]
|
366
469
|
end
|
367
|
-
@
|
470
|
+
@projected_account_factor = nil
|
368
471
|
#exit
|
369
472
|
end
|
370
|
-
def
|
371
|
-
@
|
473
|
+
def get_stable_discretionary_account_factor
|
474
|
+
@projected_account_factor = 1.0
|
372
475
|
loop do
|
373
476
|
ok = true
|
374
477
|
date = @today
|
@@ -377,94 +480,102 @@ EOF
|
|
377
480
|
#ok = false if @equity.projected_balance(date) < @equity.red_line(date)
|
378
481
|
date += 1
|
379
482
|
balances.push @equity.projected_balance(date)
|
380
|
-
#ep ['
|
483
|
+
#ep ['projected_account_factor', date, @equity.projected_balance(date), @equity.red_line(date), ok]
|
381
484
|
end
|
382
485
|
ok = false if balances.mean < @equity.balance(@today)
|
383
|
-
@
|
384
|
-
break if (@
|
385
|
-
@
|
386
|
-
@
|
486
|
+
@stable_discretionary_account_factor = @projected_account_factor
|
487
|
+
break if (@projected_account_factor == 0.0 or ok == true)
|
488
|
+
@projected_account_factor -= 0.01
|
489
|
+
@projected_account_factor -= 0.1
|
387
490
|
end
|
388
|
-
@
|
491
|
+
@projected_account_factor = nil
|
389
492
|
#exit
|
390
493
|
end
|
391
|
-
def
|
392
|
-
|
494
|
+
def discretionary_account_table
|
495
|
+
discretionary_accounts = accounts_with_averages(@projected_accounts_info)
|
393
496
|
|
394
497
|
<<EOF
|
395
498
|
\\section{Discretionary Budget Summary}
|
396
|
-
\\begin{tabulary}{0.
|
499
|
+
\\begin{tabulary}{0.9\\textwidth}{ R | c c c c }
|
397
500
|
Budget & Average & Projection & Limit & Stable \\\\
|
398
|
-
#{
|
501
|
+
#{discretionary_accounts.map{|account, info|
|
399
502
|
#ep info
|
400
|
-
"#{
|
401
|
-
(info[:projection] * @
|
402
|
-
#{(info[:projection] * @
|
503
|
+
"#{account} & #{info[:average]} & #{info[:projection]} & #{
|
504
|
+
(info[:projection] * @in_limit_discretionary_account_factor).round(2)} &
|
505
|
+
#{(info[:projection] * @stable_discretionary_account_factor).round(2)} \\\\"
|
403
506
|
}.join("\n\n")
|
404
507
|
}
|
405
508
|
\\end{tabulary}
|
406
509
|
EOF
|
407
510
|
end
|
408
|
-
def
|
511
|
+
def account_expenditure_graphs
|
409
512
|
<<EOF
|
410
|
-
\\section{
|
411
|
-
#{
|
513
|
+
\\section{Expenditure by Account Period}
|
514
|
+
#{account_and_transfer_graphs(Hash[@expense_accounts.find_all{|acc| acc.info and acc.info[:period]}.map{|acc| [acc.name, acc.info]}], {})}
|
412
515
|
EOF
|
413
516
|
end
|
414
|
-
def
|
415
|
-
"#{
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
kit.gp.style = "fill solid"
|
421
|
-
kit.xlabel = nil
|
422
|
-
kit.ylabel = "Expenditure"
|
423
|
-
unless options[:transfers]
|
424
|
-
kits = budgets_with_averages({budget => budget_info}).map{|budget, budget_info|
|
425
|
-
#ep 'Budget is ', budget
|
426
|
-
kit2 = GraphKit.quick_create([
|
427
|
-
[dates[0], dates[-1]].map{|d| d.to_time.to_i},
|
428
|
-
[budget_info[:average], budget_info[:average]]
|
429
|
-
])
|
430
|
-
kit2.data[0].gp.with = 'lp lw 4'
|
431
|
-
kit2
|
432
|
-
}
|
433
|
-
#$debug_gnuplot = true
|
434
|
-
#kits.sum.gnuplot
|
435
|
-
kit += kits.sum
|
436
|
-
|
517
|
+
def account_and_transfer_graphs(accounts, options)
|
518
|
+
"#{accounts.map{|account, account_info|
|
519
|
+
#ep ['accountbadf', account, account_info]
|
520
|
+
dates, expenditures, items = account_expenditure(account, account_info)
|
521
|
+
if dates.size == 0
|
522
|
+
""
|
437
523
|
else
|
438
|
-
|
524
|
+
#ep ['account', account, dates, expenditures]
|
525
|
+
kit = GraphKit.quick_create([dates.map{|d| d.to_time.to_i}, expenditures])
|
526
|
+
kit.data.each{|dk| dk.gp.with="boxes"}
|
527
|
+
kit.gp.style = "fill solid"
|
528
|
+
kit.xlabel = nil
|
529
|
+
kit.ylabel = "Expenditure"
|
530
|
+
unless options[:transfers]
|
531
|
+
kits = accounts_with_averages({account => account_info}).map{|account, account_info|
|
532
|
+
#ep 'Budget is ', account
|
533
|
+
kit2 = GraphKit.quick_create([
|
534
|
+
[dates[0], dates[-1]].map{|d| d.to_time.to_i},
|
535
|
+
[account_info[:average], account_info[:average]]
|
536
|
+
])
|
537
|
+
kit2.data[0].gp.with = 'lp lw 4'
|
538
|
+
kit2
|
539
|
+
}
|
540
|
+
#$debug_gnuplot = true
|
541
|
+
#kits.sum.gnuplot
|
542
|
+
kit += kits.sum
|
543
|
+
|
544
|
+
else
|
545
|
+
kit.data[0].y.data.map!{|expen| expen*-1.0}
|
546
|
+
end
|
547
|
+
kit.title = "#{account} Expenditure with average (Total = #{kit.data[0].y.data.sum})"
|
548
|
+
CodeRunner::Budget.kit_time_format_x(kit)
|
549
|
+
#kit.gnuplot
|
550
|
+
#ep ['kit1122', account, kit]
|
551
|
+
kit.gnuplot_write("#{account}.eps", size: "4.0in,2.0in")
|
552
|
+
#%x[ps2eps #{account}.ps]
|
553
|
+
#"\\begin{center}\\includegraphics[width=3.0in]{#{account}.eps}\\vspace{1em}\\end{center}"
|
554
|
+
#"\\begin{center}\\includegraphics[width=0.9\\textwidth]{#{account}.eps}\\vspace{1em}\\end{center}"
|
555
|
+
"\\myfigure{#{account}.eps}"
|
439
556
|
end
|
440
|
-
kit.title = "#{budget} Expenditure with average (Total = #{kit.data[0].y.data.sum})"
|
441
|
-
CodeRunner::Budget.kit_time_format_x(kit)
|
442
|
-
#kit.gnuplot
|
443
|
-
#ep ['kit1122', budget, kit]
|
444
|
-
kit.gnuplot_write("#{budget}.eps")
|
445
|
-
"\\begin{center}\\includegraphics[width=3.0in]{#{budget}.eps}\\vspace{1em}\\end{center}"
|
446
557
|
}.join("\n\n")
|
447
558
|
}"
|
448
559
|
end
|
449
560
|
|
450
|
-
def
|
561
|
+
def account_resolutions
|
451
562
|
<<EOF
|
452
563
|
\\section{Budget Resolutions}
|
453
564
|
|
454
|
-
This section sums items from
|
565
|
+
This section sums items from accounts drawn from an alternate account, i.e. it determines how much should be transferred from one account to another as a result of expenditure from a given account.
|
455
566
|
|
456
|
-
#{@
|
567
|
+
#{@actual_accounts.map{|account, account_info|
|
457
568
|
|
458
|
-
"\\subsection{#{
|
569
|
+
"\\subsection{#{account} }
|
459
570
|
\\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{r l}
|
460
571
|
%\\hline
|
461
572
|
Account Owed & Amount \\\\
|
462
573
|
\\hline
|
463
574
|
\\Tstrut
|
464
|
-
#{
|
465
|
-
alternate_accounts =
|
575
|
+
#{account_items = @indateruns.find_all{|r| r.account == account}
|
576
|
+
alternate_accounts = account_items.map{|r| r.account}.uniq - [account_info[:account]]
|
466
577
|
alternate_accounts.map{|acc|
|
467
|
-
alternate_items =
|
578
|
+
alternate_items = account_items.find_all{|r| r.account == acc}
|
468
579
|
total = alternate_items.map{|r| r.withdrawal - r.deposit}.sum
|
469
580
|
"#{acc} & #{total} \\\\"
|
470
581
|
}.join("\n\n")
|
@@ -477,10 +588,10 @@ This section sums items from budgets drawn from an alternate account, i.e. it de
|
|
477
588
|
\\vspace{1em}\n\n
|
478
589
|
|
479
590
|
#{ alternate_accounts.map{|acc|
|
480
|
-
alternate_items =
|
591
|
+
alternate_items = account_items.find_all{|r| r.account == acc}
|
481
592
|
alternate_items.pieces((alternate_items.size.to_f/50.to_f).ceil).map{|piece|
|
482
593
|
"\\footnotesize\\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{ #{"c " * 4 + " L " + " r " * 3 }}
|
483
|
-
#{
|
594
|
+
#{account}: & #{account_info[:account]} & owes & #{acc} &&&&\\\\
|
484
595
|
\\hline
|
485
596
|
|
486
597
|
\\Tstrut
|
@@ -499,17 +610,30 @@ This section sums items from budgets drawn from an alternate account, i.e. it de
|
|
499
610
|
}
|
500
611
|
EOF
|
501
612
|
end
|
502
|
-
def
|
613
|
+
def assumptions
|
614
|
+
<<EOF
|
615
|
+
\\section{Assumptions for Projection}
|
616
|
+
\\subsection{Regular Transfers}
|
617
|
+
\\begin{lstlisting}[language=ruby]
|
618
|
+
#{REGULAR_TRANSFERS.pretty_inspect.latex_escape}
|
619
|
+
\\end{lstlisting}
|
620
|
+
\\subsection{One-off Transfers}
|
621
|
+
\\begin{lstlisting}[language=ruby]
|
622
|
+
#{FUTURE_TRANSFERS.pretty_inspect.latex_escape}
|
623
|
+
\\end{lstlisting}
|
624
|
+
EOF
|
625
|
+
end
|
626
|
+
def account_breakdown
|
503
627
|
<<EOF
|
504
|
-
\\section{
|
505
|
-
#{(@
|
506
|
-
dates, expenditures,
|
507
|
-
#pp
|
508
|
-
"\\subsection{#{
|
509
|
-
|
628
|
+
\\section{SubAccount Breakdown}
|
629
|
+
#{(@actual_accounts).map{|account, account_info|
|
630
|
+
dates, expenditures, account_items = account_expenditure(account, account_info)
|
631
|
+
#pp account, account_items.map{|items| items.map{|i| i.date.to_s}}
|
632
|
+
"\\subsection{#{account}}" +
|
633
|
+
account_items.zip(dates, expenditures).map{|items, date, expenditure|
|
510
634
|
if items.size > 0
|
511
635
|
"
|
512
|
-
\\
|
636
|
+
\\tiny
|
513
637
|
\\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{ #{"c " * 3 + " L " + " r " * 2 + " c " }}
|
514
638
|
%\\hline
|
515
639
|
#{date.to_s.latex_escape} & & & Total & #{expenditure} & \\\\
|
@@ -540,13 +664,13 @@ EOF
|
|
540
664
|
\\section{Recent Transactions}
|
541
665
|
#{@accounts.find_all{|acc| not acc.type == :Equity}.sort_by{|acc| acc.external ? 0 : 1}.map{|acc|
|
542
666
|
"\\subsection{#{acc.name}}
|
543
|
-
\\
|
544
|
-
#{all = acc.runs.find_all{|r| r.days_ago(@today) < @days_before}.sort_by{|r| [r.date, r.id]}.reverse
|
667
|
+
\\tiny
|
668
|
+
#{all = acc.runs.find_all{|r| r.days_ago(@today) < @days_before}.sort_by{|r| [r.sub_account, r.date, r.id]}.reverse
|
545
669
|
#ep ['acc', acc, 'ids', all.map{|r| r.id}, 'size', all.size]
|
546
670
|
all.pieces((all.size.to_f/50.to_f).ceil).map{|piece|
|
547
|
-
"\\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{ #{"c " * 3 + "
|
671
|
+
"\\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{ #{"c " * 3 + " l " + " r " * 3 + "l"}}
|
548
672
|
#{piece.map{|r|
|
549
|
-
(CodeRunner::Budget.rcp.component_results - [:sc] + [:
|
673
|
+
(CodeRunner::Budget.rcp.component_results - [:sc] + [:sub_account]).map{|res| r.send(res).to_s.latex_escape
|
550
674
|
#rcp.component_results.map{|res| r.send(res).to_s.gsub(/(.{20})/, '\1\\\\\\\\').latex_escape
|
551
675
|
}.join(" & ")
|
552
676
|
}.join("\\\\\n")}
|
@@ -557,13 +681,27 @@ EOF
|
|
557
681
|
|
558
682
|
def header
|
559
683
|
<<EOF
|
560
|
-
\\documentclass{article}
|
561
|
-
\\usepackage[
|
684
|
+
\\documentclass[a5paper]{article}
|
685
|
+
\\usepackage[scale=0.9]{geometry}
|
686
|
+
%\\usepackage[cm]{fullpage}
|
562
687
|
\\usepackage{tabulary}
|
563
688
|
\\usepackage{graphicx}
|
564
689
|
\\usepackage{multicol}
|
565
|
-
|
690
|
+
\\usepackage{hyperref}
|
691
|
+
\\usepackage{xcolor,listings}
|
566
692
|
\\newcommand\\Tstrut{\\rule{0pt}{2.8ex}}
|
693
|
+
\\newcommand\\myfigure[1]{\\vspace*{0em}\\begin{center}
|
694
|
+
|
695
|
+
\\includegraphics[width=0.9\\textwidth]{#1}
|
696
|
+
|
697
|
+
\\end{center}\\vspace*{0em}
|
698
|
+
|
699
|
+
}
|
700
|
+
\\lstset{%
|
701
|
+
basicstyle=\\ttfamily\\color{black},
|
702
|
+
identifierstyle = \\ttfamily\\color{purple},
|
703
|
+
keywordstyle=\\ttfamily\\color{blue},
|
704
|
+
stringstyle=\\color{orange}}
|
567
705
|
\\begin{document}
|
568
706
|
\\title{#{@days_before}-day Budget Report}
|
569
707
|
\\maketitle
|
@@ -7,3 +7,4 @@ Date,Type,Sort Code,Account Number,Description,In,Out,Balance,
|
|
7
7
|
01/09/2010,DEB,222222,88888888,THE MAGGIE ARMS,,48.00,1518.30
|
8
8
|
01/09/2010,DEB,222222,88888888,BARLEY MOW,,6.60,1566.30
|
9
9
|
30/08/2010,DD,222222,88888888,PAYPAL PAYMENT,,11.91,1572.90
|
10
|
+
30/08/2010,FPO,222222,88888888,MY EMPLOYER,1000.0,,1560.99
|
data/test/test_treasurer.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
require 'helper'
|
2
|
+
require 'ruby-prof'
|
3
|
+
#require 'ruby-prof/test'
|
2
4
|
|
3
|
-
class
|
5
|
+
class TestTreaurer < Test::Unit::TestCase
|
6
|
+
#include RubyProf::Test
|
7
|
+
#PROFILE_OPTIONS[:
|
4
8
|
def testfolder
|
5
9
|
'test/myaccount'
|
6
10
|
end
|
@@ -13,7 +17,16 @@ class TestTreasurer < Test::Unit::TestCase
|
|
13
17
|
Treasurer.add_file('../otheraccountstatement.csv', 'SecondBank', {})
|
14
18
|
Treasurer.add_folder_of_files('../multiple')
|
15
19
|
Treasurer.status h: :component
|
20
|
+
RubyProf.start
|
16
21
|
Treasurer.create_report t: Date.parse('2010-09-07'), b: 40, a: 35
|
22
|
+
result = RubyProf.stop
|
23
|
+
result.eliminate_methods!([/Array#map/, /Array#each/])
|
24
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
25
|
+
File.open('timing.html', 'w'){|f| printer.print(f, {})}
|
26
|
+
reporter = Treasurer.fetch_reporter(t: Date.parse('2010-09-07'), b: 40, a: 35)
|
27
|
+
reporter.generate_accounts
|
28
|
+
assert_equal(382.08, reporter.equity.balance.round(2))
|
29
|
+
assert_equal(724.33, reporter.equity.projected_balance(Date.parse('2010-10-09')).round(2))
|
17
30
|
end
|
18
31
|
#FileUtils.rm_r(testfolder) if FileTest.exist? testfolder
|
19
32
|
end
|
data/treasurer.gemspec
CHANGED
@@ -2,19 +2,19 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: treasurer 0.
|
5
|
+
# stub: treasurer 0.3.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
|
-
s.name = "treasurer"
|
9
|
-
s.version = "0.
|
8
|
+
s.name = "treasurer".freeze
|
9
|
+
s.version = "0.3.0"
|
10
10
|
|
11
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
-
s.require_paths = ["lib"]
|
13
|
-
s.authors = ["Edmund Highcock"]
|
14
|
-
s.date = "
|
15
|
-
s.description = "A simple command line tool for managing accounts and finances. Easily import internet banking spreadsheets and generate sophisticated reports and projections."
|
16
|
-
s.email = "edmundhighcock@users.sourceforge.net"
|
17
|
-
s.executables = ["treasurer"]
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib".freeze]
|
13
|
+
s.authors = ["Edmund Highcock".freeze]
|
14
|
+
s.date = "2018-02-27"
|
15
|
+
s.description = "A simple command line tool for managing accounts and finances. Easily import internet banking spreadsheets and generate sophisticated reports and projections.".freeze
|
16
|
+
s.email = "edmundhighcock@users.sourceforge.net".freeze
|
17
|
+
s.executables = ["treasurer".freeze]
|
18
18
|
s.extra_rdoc_files = [
|
19
19
|
"LICENSE.txt",
|
20
20
|
"README.rdoc"
|
@@ -40,42 +40,45 @@ Gem::Specification.new do |s|
|
|
40
40
|
"test/test_treasurer.rb",
|
41
41
|
"treasurer.gemspec"
|
42
42
|
]
|
43
|
-
s.homepage = "http://github.com/edmundhighcock/treasurer"
|
44
|
-
s.licenses = ["GPLv3"]
|
45
|
-
s.rubygems_version = "2.
|
46
|
-
s.summary = "A simple command line tool for managing accounts and finances."
|
43
|
+
s.homepage = "http://github.com/edmundhighcock/treasurer".freeze
|
44
|
+
s.licenses = ["GPLv3".freeze]
|
45
|
+
s.rubygems_version = "2.6.8".freeze
|
46
|
+
s.summary = "A simple command line tool for managing accounts and finances.".freeze
|
47
47
|
|
48
48
|
if s.respond_to? :specification_version then
|
49
49
|
s.specification_version = 4
|
50
50
|
|
51
51
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
52
|
-
s.add_runtime_dependency(%q<coderunner
|
53
|
-
s.add_runtime_dependency(%q<budgetcrmod
|
54
|
-
s.add_runtime_dependency(%q<command-line-flunky
|
55
|
-
s.add_development_dependency(%q<shoulda
|
56
|
-
s.add_development_dependency(%q<rdoc
|
57
|
-
s.add_development_dependency(%q<bundler
|
58
|
-
s.add_development_dependency(%q<jeweler
|
59
|
-
s.add_development_dependency(%q<simplecov
|
52
|
+
s.add_runtime_dependency(%q<coderunner>.freeze, [">= 0.14.16"])
|
53
|
+
s.add_runtime_dependency(%q<budgetcrmod>.freeze, [">= 0.2.0"])
|
54
|
+
s.add_runtime_dependency(%q<command-line-flunky>.freeze, [">= 1.0.0"])
|
55
|
+
s.add_development_dependency(%q<shoulda>.freeze, [">= 0"])
|
56
|
+
s.add_development_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
57
|
+
s.add_development_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
58
|
+
s.add_development_dependency(%q<jeweler>.freeze, ["~> 2.3.1"])
|
59
|
+
s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
|
60
|
+
s.add_development_dependency(%q<ruby-prof>.freeze, [">= 0.15"])
|
60
61
|
else
|
61
|
-
s.add_dependency(%q<coderunner
|
62
|
-
s.add_dependency(%q<budgetcrmod
|
63
|
-
s.add_dependency(%q<command-line-flunky
|
64
|
-
s.add_dependency(%q<shoulda
|
65
|
-
s.add_dependency(%q<rdoc
|
66
|
-
s.add_dependency(%q<bundler
|
67
|
-
s.add_dependency(%q<jeweler
|
68
|
-
s.add_dependency(%q<simplecov
|
62
|
+
s.add_dependency(%q<coderunner>.freeze, [">= 0.14.16"])
|
63
|
+
s.add_dependency(%q<budgetcrmod>.freeze, [">= 0.2.0"])
|
64
|
+
s.add_dependency(%q<command-line-flunky>.freeze, [">= 1.0.0"])
|
65
|
+
s.add_dependency(%q<shoulda>.freeze, [">= 0"])
|
66
|
+
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
67
|
+
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
68
|
+
s.add_dependency(%q<jeweler>.freeze, ["~> 2.3.1"])
|
69
|
+
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
70
|
+
s.add_dependency(%q<ruby-prof>.freeze, [">= 0.15"])
|
69
71
|
end
|
70
72
|
else
|
71
|
-
s.add_dependency(%q<coderunner
|
72
|
-
s.add_dependency(%q<budgetcrmod
|
73
|
-
s.add_dependency(%q<command-line-flunky
|
74
|
-
s.add_dependency(%q<shoulda
|
75
|
-
s.add_dependency(%q<rdoc
|
76
|
-
s.add_dependency(%q<bundler
|
77
|
-
s.add_dependency(%q<jeweler
|
78
|
-
s.add_dependency(%q<simplecov
|
73
|
+
s.add_dependency(%q<coderunner>.freeze, [">= 0.14.16"])
|
74
|
+
s.add_dependency(%q<budgetcrmod>.freeze, [">= 0.2.0"])
|
75
|
+
s.add_dependency(%q<command-line-flunky>.freeze, [">= 1.0.0"])
|
76
|
+
s.add_dependency(%q<shoulda>.freeze, [">= 0"])
|
77
|
+
s.add_dependency(%q<rdoc>.freeze, ["~> 3.12"])
|
78
|
+
s.add_dependency(%q<bundler>.freeze, ["~> 1.0"])
|
79
|
+
s.add_dependency(%q<jeweler>.freeze, ["~> 2.3.1"])
|
80
|
+
s.add_dependency(%q<simplecov>.freeze, [">= 0"])
|
81
|
+
s.add_dependency(%q<ruby-prof>.freeze, [">= 0.15"])
|
79
82
|
end
|
80
83
|
end
|
81
84
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: treasurer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Edmund Highcock
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-02-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: coderunner
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 0.
|
33
|
+
version: 0.2.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 0.
|
40
|
+
version: 0.2.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: command-line-flunky
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,14 +100,14 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - "~>"
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version: 2.
|
103
|
+
version: 2.3.1
|
104
104
|
type: :development
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version: 2.
|
110
|
+
version: 2.3.1
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: simplecov
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +122,20 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: ruby-prof
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.15'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.15'
|
125
139
|
description: A simple command line tool for managing accounts and finances. Easily
|
126
140
|
import internet banking spreadsheets and generate sophisticated reports and projections.
|
127
141
|
email: edmundhighcock@users.sourceforge.net
|
@@ -171,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
185
|
version: '0'
|
172
186
|
requirements: []
|
173
187
|
rubyforge_project:
|
174
|
-
rubygems_version: 2.
|
188
|
+
rubygems_version: 2.6.8
|
175
189
|
signing_key:
|
176
190
|
specification_version: 4
|
177
191
|
summary: A simple command line tool for managing accounts and finances.
|