treasurer 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|