treasurer 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f597627694776187be3b9d975e9eda13233ceca2
4
- data.tar.gz: ec019032db5c6584d6d02f0e0760a6cffcf71279
3
+ metadata.gz: 7093dd3826d606b0ba1f76ae379ea9ff02d74211
4
+ data.tar.gz: 54f3cde9ccd02559074114f691dc03d48f44d55f
5
5
  SHA512:
6
- metadata.gz: 380882f78a65ba1c1f444ec3af4525906ee845b89f4b85c43ca27f5e4658ad2d200afb07bb2ba9cf204ae2e0e5c26a421d78ddcd909331e2fb8572b63e53f479
7
- data.tar.gz: fbd3b25ed54211d825d8f0bfc252f44db052d041afc911586318ef583d0662f26cd69bdf433378b2ac80bb6d0dffbd0e2054dbe866c4f3d1ef7da93f5ca1d312
6
+ metadata.gz: cbcec24e8990da5ed2131023662c5cfee4d65332f90ca1547f96d648865dbc5051a511dde77c78f27580b56832ff827b3bdc8d483efc65e351673304ccf568da
7
+ data.tar.gz: d78965e3d3b81edb9fec098aa33a2b58e17a254c71c85e1ad490ed4407434e1279cc9f187d16a6f8cec3539d1758114bb914c3751886ac56c7267bd944c59daf
data/Gemfile CHANGED
@@ -2,6 +2,9 @@ source "http://rubygems.org"
2
2
  # Add dependencies required to use your gem here.
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
+ gem "coderunner", ">= 0.14.16"
6
+ gem "budgetcrmod", ">= 0.0.0"
7
+ gem "command-line-flunky", ">= 1.0.0"
5
8
 
6
9
  # Add dependencies to develop your gem here.
7
10
  # Include everything needed to run rake, tests, features, etc.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.1.0
data/bin/treasurer ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'treasurer'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'treasurer'
8
+ end
9
+
10
+ CommandLineFlunky.run_script
@@ -0,0 +1,194 @@
1
+
2
+ class Treasurer::Reporter
3
+ module Analysis
4
+ # Within the range of the report, return a list
5
+ # of the dates of the beginning of each budget
6
+ # period, along with a list of the expenditures
7
+ # for each period and a list of the items within
8
+ # each period
9
+ def budget_expenditure(budget, budget_info, options={})
10
+ dates = []
11
+ expenditures = []
12
+ budget_items = []
13
+ date = budget_info[:end]||@today
14
+ start_date = [(budget_info[:start]||@start_date), @start_date].max
15
+ expenditure = 0
16
+ items_temp = []
17
+ items = @runner.component_run_list.values.find_all{|r| r.budget == budget and r.in_date(budget_info)}
18
+ #ep ['items', items]
19
+ #ep ['budget', budget]
20
+ counter = 0
21
+ if not budget_info[:period]
22
+ dates.push date
23
+ budget_items.push items
24
+ expenditures.push (items.map{|r| r.debit - r.credit}+[0]).sum
25
+ else
26
+
27
+ case budget_info[:period][1]
28
+ when :month
29
+ while date > @start_date
30
+ items_temp += items.find_all{|r| r.date == date}
31
+ if date.mday == (budget_info[:monthday] or 1)
32
+ counter +=1
33
+ if counter % budget_info[:period][0] == 0
34
+ expenditure = (items_temp.map{|r| r.debit - r.credit}+[0]).sum
35
+ dates.push date
36
+ expenditures.push expenditure
37
+ budget_items.push items_temp
38
+ items_temp = []
39
+ expenditure = 0
40
+ end
41
+ end
42
+ date-=1
43
+ end
44
+ when :day
45
+ while date > @start_date
46
+ items_temp += items.find_all{|r| r.date == date}
47
+ #expenditure += (budget_items[-1].map{|r| r.debit}+[0]).sum
48
+ counter +=1
49
+ if counter % budget_info[:period][0] == 0
50
+ expenditure = (items_temp.map{|r| r.debit - r.credit}+[0]).sum
51
+ dates.push date
52
+ expenditures.push expenditure
53
+ budget_items.push items_temp
54
+ items_temp = []
55
+ expenditure = 0
56
+ end
57
+ date-=1
58
+ end
59
+ end
60
+ end
61
+
62
+ [dates, expenditures, budget_items]
63
+
64
+ end
65
+ # Work out the average spend from the budget and include it in the budget info
66
+ def budgets_with_averages(budgets, options={})
67
+ projected_budgets = budgets.dup
68
+ projected_budgets.each{|key,v| projected_budgets[key]=projected_budgets[key].dup}
69
+ projected_budgets.each do |budget, budget_info|
70
+ #budget_info = budgets[budget]
71
+ dates, expenditures, items = budget_expenditure(budget, budget_info)
72
+ budget_info[:average] = expenditures.mean rescue 0.0
73
+ end
74
+ projected_budgets
75
+ end
76
+ # Work out the projected spend from the budget and include it in the budget info
77
+ def budgets_with_projections(budgets, options={})
78
+ projected_budgets = budgets.dup
79
+ projected_budgets.each{|key,v| projected_budgets[key]=projected_budgets[key].dup}
80
+ projected_budgets.each do |budget, budget_info|
81
+ #budget_info = budgets[budget]
82
+ dates, expenditures, items = budget_expenditure(budget, budget_info)
83
+ budget_info[:projection] = expenditures.mean rescue 0.0
84
+ end
85
+ projected_budgets
86
+ end
87
+ # Get a list of budgets to be included in the report
88
+ # i.e. budgets with non-empty expenditure
89
+ def get_actual_budgets
90
+ @actual_budgets = BUDGETS.dup
91
+ BUDGETS.keys.each do |budget|
92
+ @actual_budgets.delete(budget) if budget_expenditure(budget, BUDGETS[budget])[0].size == 0
93
+ end
94
+ end
95
+ # Find all discretionary budgets and estimate the future
96
+ # expenditure from that budget based on past
97
+ # expenditure (currently only a simple average)
98
+ def get_projected_budgets
99
+ @projected_budgets = Hash[@actual_budgets.dup.find_all{|k,v| v[:discretionary]}]
100
+ @projected_budgets = budgets_with_projections(@projected_budgets)
101
+ end
102
+ # Calculate the sum of all items within future
103
+ # items that fall before end_date
104
+ def sum_future(future_items, end_date, options={})
105
+ #end_date = @today + @days_ahead
106
+ sum = future_items.inject(0.0) do |sum, (name, item)|
107
+ item = [item] unless item.kind_of? Array
108
+ value = item.inject(0.0) do |value,info|
109
+ value += info[:size] unless ((@today||Date.today) > info[:date]) or (info[:date] > end_date) # add unless we have already passed that date
110
+ value
111
+
112
+ end
113
+ #ep ['name2223', name, item, value, end_date, @today, (@today||Date.today > item[0][:date]), (item[0][:date] > end_date)]
114
+ sum + value
115
+ #rcp.excluding.include?(name) ? sum : sum + value
116
+ end
117
+ sum
118
+ end
119
+ # Sum every future occurence of the given
120
+ # regular items that falls within the budget period
121
+ def sum_regular(regular_items, end_date, options={})
122
+ #end_date = @today + @days_ahead
123
+ sum = regular_items.inject(0) do |sum, (name, item)|
124
+ item = [item] unless item.kind_of? Array
125
+ # ep item
126
+ value = item.inject(0) do |value,info|
127
+ finish = (info[:end] and info[:end] < end_date) ? info[:end] : end_date
128
+ #today = (Time.now.to_i / (24.0*3600.0)).round
129
+
130
+ nunits = 0
131
+ counter = info[:period][0] == 1 ? 0 : nil
132
+ unless counter
133
+ date = @today
134
+ counter = 0
135
+ case info[:period][1]
136
+ when :month
137
+ while date >= (info[:start] or Date.today)
138
+ counter +=1 if date.mday == (info[:monthday] or 1)
139
+ date -= 1
140
+ end
141
+ when :year
142
+ while date >= (info[:start] or Date.today)
143
+ counter +=1 if date.yday == (info[:yearday] or 1)
144
+ date -= 1
145
+ end
146
+ when :day
147
+ while date > (info[:start] or Date.today)
148
+ counter +=1
149
+ date -= 1
150
+ end
151
+ end
152
+ end
153
+ date = @today
154
+ case info[:period][1]
155
+ when :month
156
+ #p date, info
157
+ while date <= finish
158
+ if date.mday == (info[:monthday] or 1)
159
+ nunits += 1 if counter % info[:period][0] == 0
160
+ counter +=1
161
+ end
162
+ date += 1
163
+ end
164
+ when :year
165
+ while date <= finish
166
+ if date.yday == (info[:yearday] or 1)
167
+ nunits += 1 if counter % info[:period][0] == 0
168
+ counter +=1
169
+ end
170
+ date += 1
171
+ end
172
+ when :day
173
+ while date <= finish
174
+ nunits += 1 if counter % info[:period][0] == 0
175
+ counter +=1
176
+ date += 1
177
+ end
178
+ end
179
+
180
+
181
+
182
+ #ep ['name2234', name, info, @projected_budget_factor] if info[:discretionary]
183
+
184
+ value + nunits * (info[:size]||info[:projection]*(@projected_budget_factor||1.0))
185
+
186
+ end
187
+ sum + value
188
+ #(rcp.excluding? and rcp.excluding.include?(name)) ? sum : sum + value
189
+ end
190
+ sum
191
+ end
192
+ end
193
+ include Analysis
194
+ end
@@ -0,0 +1,48 @@
1
+
2
+ class Treasurer
3
+ class << self
4
+ def add_file(file, account, copts={})
5
+ load_treasurer_folder
6
+ ep 'entries', Dir.entries
7
+ CodeRunner.submit(p: "{data_file: '#{File.expand_path(file)}', account: :#{account}}")
8
+ end
9
+ def add_folder(folder, copts={})
10
+ #Dir.chdir(folder) do
11
+ files = Dir.entries(folder).grep(/\.csv$/)
12
+ accounts = files.map{|f| f.sub(/\.csv/, '')}
13
+ files = files.map{|f| folder + '/' + f}
14
+ p ['files789', files, accounts]
15
+ files.zip(accounts).each{|f,a| add_file(f, a, copts)}
16
+ #end
17
+ end
18
+ def check_is_treasurer_folder
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
+ end
21
+ def init_root_folder(folder, copts={})
22
+ raise "Folder already exists" if FileTest.exist? folder
23
+ FileUtils.makedirs(folder)
24
+ FileUtils.cp(SCRIPT_FOLDER + '/treasurer/local_customisations.rb', folder + '/local_customisations.rb')
25
+ CodeRunner.fetch_runner(Y: folder, C: 'budget', X: '/dev/null')
26
+ eputs "\n\n Your treasurer folder '#{folder}' has been set up. All further treasurer commands should be run from within this folder.\n"
27
+ end
28
+ def load_treasurer_folder
29
+ check_is_treasurer_folder
30
+ Treasurer.send(:remove_const, :LocalCustomisations) if defined? Treasurer::LocalCustomisations
31
+ load 'local_customisations.rb'
32
+ Treasurer::Reporter.send(:include, Treasurer::LocalCustomisations)
33
+ Treasurer::Reporter::Account.send(:include, Treasurer::LocalCustomisations)
34
+ Treasurer::Reporter::Analysis.send(:include, Treasurer::LocalCustomisations)
35
+ CodeRunner::Budget.send(:include, Treasurer::LocalCustomisations)
36
+ runner = CodeRunner.fetch_runner
37
+ end
38
+ def report(copts = {})
39
+ load_treasurer_folder
40
+ reporter = Reporter.new(CodeRunner.fetch_runner(h: :component), days_before: copts[:b]||360, days_ahead: copts[:a]||180, today: copts[:t])
41
+ reporter.report()
42
+ end
43
+
44
+ def method_missing(meth, *args)
45
+ CodeRunner.send(meth, *args)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,111 @@
1
+ #class CodeRunner::Budget
2
+ module Treasurer::LocalCustomisations
3
+
4
+
5
+ REGULAR_TRANSFERS = {
6
+ [:FirstBank, :SecondBank] =>{
7
+ topup: {size: 200, period: [1, :month], monthday: 1, end: Date.parse("01/10/2014")},
8
+ },
9
+ [:FirstBank, :Rent] =>{
10
+ house: {size: 600, period: [1, :month], monthday: 20, end: Date.parse("01/07/2013")},
11
+ },
12
+ [:Income, :FirstBank] =>{
13
+ pay: {size: 1000, period: [1, :month], monthday: 1, end: Date.parse("01/07/2014")},
14
+ },
15
+
16
+ }
17
+
18
+ REGULAR_TRANSFERS.default = {}
19
+
20
+ FUTURE_TRANSFERS = {
21
+ [:Income, :SecondBank] =>{
22
+ topup: {size: 100, date: Date.parse("26/09/2010")},
23
+ },
24
+ [:FirstBank, :PersonalLoans] =>{
25
+ payfriend: {size: 640, date: Date.parse("25/09/2010")},
26
+ borrowfromfriend: {size: -840, date: Date.parse("28/09/2010")},
27
+ },
28
+
29
+ }
30
+
31
+ FUTURE_TRANSFERS.default = {}
32
+
33
+
34
+
35
+ BUDGETS = {
36
+ Monthly: {account: :FirstBank, period: [1, :month], monthday: 1, start: nil, end: nil, discretionary: false},
37
+ MonthlySecondBank: {account: :SecondBank, period: [1, :month], monthday: 1, start: nil, end: nil, discretionary: false},
38
+ Weekly: {account: :FirstBank, period: [7, :day], monthday: nil, start: nil, end: nil, discretionary: true},
39
+ WeeklySecondBank: {account: :SecondBank, period: [7, :day], monthday: nil, start: nil, end: nil, discretionary: true},
40
+ MyHoliday: {account: :SecondBank, period: [1, :day], monthday: nil, start: Date.parse("02/12/2013"), end: Date.parse("2/01/2014"), discretionary: false},
41
+ }
42
+
43
+ def in_date(item)
44
+ (!item[:start] or date >= item[:start]) and (!item[:end] || date <= item[:end])
45
+ end
46
+
47
+ def account_type(account)
48
+ case account
49
+ when :Food, :Phone, :Rent, :Cash, :Entertainment, :Books, :Insurance
50
+ :Expense
51
+ when :FirstBank, :SecondBank
52
+ :Asset
53
+ when :PersonalLoans
54
+ :Liability
55
+ end
56
+ end
57
+
58
+ def red_line(account, date)
59
+ case account
60
+ when :FirstBank
61
+ 300
62
+ when :SecondBank
63
+ 500
64
+ else
65
+ 0
66
+ end
67
+ end
68
+
69
+
70
+ def external_account
71
+ case description
72
+ when /co-op|sainsbury/i
73
+ :Food
74
+ when /insurance/i
75
+ :Insurance
76
+ when /Vodafone/i
77
+ :Phone
78
+ when /Adams/i
79
+ :Rent
80
+ when /Carfax|Lnk/i
81
+ :Cash
82
+ when /andalus/i, /angels/i, /maggie arms/i, /barley mow/i
83
+ :Entertainment
84
+ when /blackwell/i
85
+ :Books
86
+ when /norries/i
87
+ :PersonalLoans
88
+ else
89
+ :Unknown
90
+ end
91
+ end
92
+
93
+ def budget
94
+ case description
95
+ when /Vodafone/i
96
+ :Monthly
97
+ else
98
+ case external_account
99
+ when :Food, :Entertainment
100
+ :Weekly
101
+ when :Insurance, :Phone, :Rent
102
+ :Monthly
103
+ when :Books, :Cash
104
+ :WeeklySecondBank
105
+ else
106
+ :Unknown
107
+ end
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,570 @@
1
+ # Some thoughts on double entry accounting:
2
+ #
3
+ # assets - liabilities = equity
4
+ # where equity = equity_at_start + income - expenses
5
+ #
6
+ # so
7
+ #
8
+ # assets - liabilities = equity_at_start + income - expenses
9
+ #
10
+ # or alternatively
11
+ #
12
+ # assets + expenses = equity_at_start + income + liabilities (1)
13
+ #
14
+ # Good things:
15
+ # Positive equity_at_start, positive assets, positive income, negative liabilities, negative expenses
16
+ #
17
+ # A debit on the left of (1) must be matched by a credit on the right of (1) and
18
+ # vice versa.
19
+ #
20
+ #
21
+ # A debit to an asset account increases the value of the asset. This means buying some land
22
+ # or supplies or depositing some cash in a bank account. You can think of it as a debit because
23
+ # you are locking up your equity in a way that may not be realisable. A credit to the asset account
24
+ # means drawing down on the asset, for example selling a bit of land or taking money out of a
25
+ # bank account.
26
+ #
27
+ # Similarly, a debit to an expense account, effectively, spending money on that expense,
28
+ # increases the value of that account. Debits here are clearly negative things from
29
+ # the point of view of your wealth! (Credits to expense accounts would be something like
30
+ # travel reimbursements).
31
+ #
32
+ # A credit to income increases the value of the income account... this seems obvious. If
33
+ # you credit income you must debit assets (you have to put your income somewhere, for
34
+ # example a bank account, i.e. you must effectively spend it by buying an asset: remember
35
+ # a bank may fail... a bank account is an asset with risk just as much as a painting).
36
+ #
37
+ # A credit to liabilities increases the value of the liability, for example taking out a
38
+ # loan. Once you credit a liability you have to either buy (debit) an asset, or buy (debit)
39
+ # an expense directly (for example a loan to pay some fees).
40
+ #
41
+ # In any accounting period, the sum of all debits and credits should be 0. Also, at the end
42
+ # of the accounting period,
43
+ #
44
+ # equity_at_end = assets - liabilities = equity_at_start + income - expenses
45
+ #
46
+ # This seems obvious to me!!
47
+ class Treasurer
48
+ class Reporter
49
+ #include LocalCustomisations
50
+ attr_reader :today
51
+ attr_reader :in_limit_discretionary_budget_factor
52
+ attr_reader :stable_discretionary_budget_factor
53
+ attr_accessor :projected_budget_factor
54
+ attr_accessor :equity
55
+ def initialize(runner, options)
56
+ @runner = runner
57
+ @days_ahead = options[:days_ahead]||180
58
+ @days_before = options[:days_before]||360
59
+ @today = options[:today]||Date.today
60
+ @start_date = @today - @days_before
61
+ @runs = runner.component_run_list.values
62
+ @indateruns = @runs.find_all{|r| r.days_ago(@today) < @days_before}
63
+ p 'accounts256',@runs.size, @runs.map{|r| r.account}.uniq
64
+
65
+ end
66
+ def report
67
+ get_actual_budgets
68
+ get_projected_budgets
69
+ @accounts = @runs.map{|r| r.account}.uniq.map{|acc| Account.new(acc, self, @runner, @runs, @projected_budgets, false)} +
70
+ @runs.map{|r| r.external_account}.uniq.map{|acc| Account.new(acc, self, @runner, @runs, @projected_budgets, true)}
71
+ @accounts.unshift (@equity = Equity.new(self, @runner, @accounts))
72
+ get_in_limit_discretionary_budget_factor
73
+ get_stable_discretionary_budget_factor
74
+ report = ""
75
+ report << header
76
+ report << '\begin{multicols}{2}'
77
+ report << account_summaries
78
+ report << discretionary_budget_table
79
+ report << account_balance_graphs
80
+ report << expense_account_summary
81
+ report << budget_expenditure_graphs
82
+ report << '\end{multicols}'
83
+ report << budget_resolutions
84
+ report << budget_breakdown
85
+ report << transactions_by_account
86
+ report << footer
87
+
88
+ File.open('report.tex', 'w'){|f| f.puts report}
89
+ system "latex report.tex && latex report.tex"
90
+ end
91
+ class Account
92
+ attr_reader :name, :external, :runs
93
+ def initialize(name, reporter, runner, runs, projected_budgets, external)
94
+ @name = name
95
+ @reporter = reporter
96
+ @runner = runner
97
+ @projected_budgets =Hash[projected_budgets.find_all{|k,v| v[:account] == name}]
98
+ @external = external
99
+ @runs = runs.find_all{|r| (@external ? r.external_account : r.account) == name}
100
+ end
101
+ def type
102
+ account_type(name)
103
+ end
104
+ def red_line(date)
105
+ if Treasurer::LocalCustomisations.instance_methods.include? :red_line
106
+ super(name, date)
107
+ else
108
+ 0.0
109
+ end
110
+ end
111
+ def balance(date = @reporter.today)
112
+ #if !date
113
+ #@runs.sort_by{|r| r.date}[-1].balance
114
+ if @external
115
+ @runs.find_all{|r| r.date < date}.map{|r| (r.deposit - r.withdrawal) * (@external ? -1 : 1)}.sum || 0.0
116
+ else
117
+ @runs.sort_by{|r| (r.date.to_datetime.to_time.to_i - date.to_datetime.to_time.to_i).to_f.abs}[0].balance
118
+ end
119
+ end
120
+ def expenditure(today, days_before, &block)
121
+ @runs.find_all{|r| r.days_ago(today) < days_before and (!block or yield(r)) }.map{|r| @external ? r.withdrawal : r.deposit }.sum || 0
122
+ end
123
+ def income(today, days_before)
124
+ @runs.find_all{|r| r.days_ago(today) < days_before }.map{|r| @external ? r.deposit : r.withdrawal }.sum || 0
125
+ end
126
+ def summary_table(today, days_before)
127
+
128
+ <<EOF
129
+ \\subsubsection{#{name}}
130
+ \\begin{tabulary}{0.8\\textwidth}{ r | l}
131
+ Balance & #{balance} \\\\
132
+ Deposited & #{expenditure(today, days_before)} \\\\
133
+ Withdrawn & #{income(today, days_before)} \\\\
134
+ \\end{tabulary}
135
+ EOF
136
+ end
137
+ def money_in_sign
138
+ case type
139
+ when :Liability, :Income
140
+ -1.0
141
+ else
142
+ 1.0
143
+ end
144
+ end
145
+ def projected_balance(date)
146
+ non_discretionary_projected_balance(date) -
147
+ @reporter.sum_regular(@projected_budgets, date)
148
+ end
149
+ def cache
150
+ @cache ||={}
151
+ end
152
+ def non_discretionary_projected_balance(date)
153
+ #ep ['FUTURE_INCOME', FUTURE_INCOME, name] if FUTURE_INCOME.size > 0
154
+ cache[[:non_discretionary_projected_balance, date]] ||=
155
+ balance +
156
+ #@reporter.sum_regular(REGULAR_EXPENDITURE[name], date) +
157
+ #@reporter.sum_regular(REGULAR_INCOME[name], date) -
158
+ #@reporter.sum_future(FUTURE_EXPENDITURE[name], date) +
159
+ #@reporter.sum_future(FUTURE_INCOME[name], date) +
160
+ (FUTURE_TRANSFERS.keys.find_all{|from,to| to == name}.map{|key|
161
+ @reporter.sum_future(FUTURE_TRANSFERS[key], date) * money_in_sign
162
+ }.sum||0) -
163
+ (FUTURE_TRANSFERS.keys.find_all{|from,to| from == name}.map{|key|
164
+ @reporter.sum_future( FUTURE_TRANSFERS[key], date) * money_in_sign
165
+ }.sum||0) +
166
+ (REGULAR_TRANSFERS.keys.find_all{|from,to| to == name}.map{|key|
167
+ @reporter.sum_regular(REGULAR_TRANSFERS[key], date) * money_in_sign
168
+ }.sum||0) -
169
+ (REGULAR_TRANSFERS.keys.find_all{|from,to| from == name}.map{|key|
170
+ @reporter.sum_regular( REGULAR_TRANSFERS[key], date) * money_in_sign
171
+ }.sum||0)
172
+ end
173
+ # Write an eps graph to disk of past and projected
174
+ # balance of the account
175
+ def write_balance_graph(today, days_before, days_ahead)
176
+ #accshort = name.gsub(/\s/, '')
177
+ if not (@external or type == :Equity)
178
+ 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]'})
179
+ else
180
+ pastdates = (today-days_before..today).to_a
181
+ balances = pastdates.map{|date| balance(date)}
182
+ kit = GraphKit.quick_create([pastdates.map{|d| d.to_time.to_i}, balances])
183
+ end
184
+ futuredates = (today..today+days_ahead).to_a
185
+ projection = futuredates.map{|date| projected_balance(date) }
186
+ kit2 = GraphKit.quick_create([futuredates.map{|d| d.to_time.to_i}, projection])
187
+ red = futuredates.map{|date| red_line(date)}
188
+ kit3 = GraphKit.quick_create([futuredates.map{|d| d.to_time.to_i}, red])
189
+ @reporter.projected_budget_factor = @reporter.in_limit_discretionary_budget_factor
190
+ limit = futuredates.map{|date| projected_balance(date)}
191
+ kit4 = GraphKit.quick_create([futuredates.map{|d| d.to_time.to_i}, limit])
192
+ @reporter.projected_budget_factor = @reporter.stable_discretionary_budget_factor
193
+ #ep ['projected_budget_factor!!!!', @reporter.projected_budget_factor]
194
+ stable = futuredates.map{|date| projected_balance(date)}
195
+ kit5 = GraphKit.quick_create([futuredates.map{|d| d.to_time.to_i}, stable])
196
+ #exit
197
+ @projected_budget_factor = nil
198
+ kit += (kit2 + kit4 + kit5)
199
+ kit = kit3 + kit
200
+ kit.title = "Balance for #{name}"
201
+ kit.xlabel = %['Date' offset 0,-2]
202
+ kit.xlabel = nil
203
+ kit.ylabel = "Balance"
204
+
205
+ kit.data[0].gp.title = 'Limit'
206
+ kit.data[1].gp.title = 'Previous'
207
+ kit.data[2].gp.title = '0 GBP Discretionary'
208
+ kit.data[2].gp.title = 'Projection'
209
+ kit.data[3].gp.title = 'Limit'
210
+ kit.data[4].gp.title = 'Stable'
211
+ kit.data.each{|dk| dk.gp.with = "lp"}
212
+ kit.gp.key = ' bottom left '
213
+
214
+ CodeRunner::Budget.kit_time_format_x(kit)
215
+
216
+ (kit).gnuplot_write("#{name}_balance.eps", size: "4.0in,3.0in")
217
+ end
218
+ # A string to include the balance graph in the document
219
+ def balance_graph_string
220
+ #accshort = name.gsub(/\s/, '')
221
+ "\\begin{center}\\includegraphics[width=3.0in]{#{name}_balance.eps}\\end{center}"
222
+ end
223
+ end
224
+ class Equity < Account
225
+ def initialize(reporter, runner, accounts)
226
+ @reporter = reporter
227
+ @runner = runner
228
+ @accounts = accounts #.find_all{|acc| not acc.external}
229
+ end
230
+ def type
231
+ :Equity
232
+ end
233
+ def name
234
+ :Equity
235
+ end
236
+ def red_line(date)
237
+ @accounts.map{|acc|
238
+ case acc.type
239
+ when :Asset
240
+ acc.red_line(date)
241
+ when :Liability
242
+ -acc.red_line(date)
243
+ else
244
+ 0.0
245
+ end
246
+ }.sum
247
+ end
248
+ def balance(date=@reporter.today)
249
+ @accounts.map{|acc|
250
+ case acc.type
251
+ when :Asset
252
+ acc.balance(date)
253
+ when :Liability
254
+ -acc.balance(date)
255
+ else
256
+ 0.0
257
+ end
258
+ }.sum
259
+ end
260
+ def projected_balance(date=@reporter.today)
261
+ @accounts.map{|acc|
262
+ case acc.type
263
+ when :Asset
264
+ acc.projected_balance(date)
265
+ when :Liability
266
+ -acc.projected_balance(date)
267
+ else
268
+ 0.0
269
+ end
270
+ }.sum
271
+ end
272
+ def summary_table(today, days_before)
273
+
274
+ <<EOF
275
+ \\subsubsection{#{name}}
276
+ \\begin{tabulary}{0.8\\textwidth}{ r | l}
277
+ Balance & #{balance} \\\\
278
+ \\end{tabulary}
279
+ EOF
280
+ end
281
+ end
282
+ def account_summaries
283
+ #ep 'accounts', @accounts.map{|a| a.name}
284
+
285
+ <<EOF
286
+ \\section{Summary of Accounts}
287
+ \\subsection{Equity}
288
+ #{@accounts.find{|acc| acc.type == :Equity }.summary_table(@today, @days_before)}
289
+ \\subsection{Assets}
290
+ #{@accounts.find_all{|acc| account_type(acc.name) == :Asset }.map{|acc| acc.summary_table(@today, @days_before)}.join("\n\n") }
291
+ \\subsection{Liabilities}
292
+ #{@accounts.find_all{|acc| account_type(acc.name) == :Liability }.map{|acc| acc.summary_table(@today, @days_before)}.join("\n\n") }
293
+ \\subsection{Income}
294
+ #{@accounts.find_all{|acc| account_type(acc.name) == :Income }.map{|acc| acc.summary_table(@today, @days_before)}.join("\n\n") }
295
+ \\subsection{Expenses}
296
+ #{@accounts.find_all{|acc| account_type(acc.name) == :Expense }.map{|acc| acc.summary_table(@today, @days_before)}.join("\n\n") }
297
+ EOF
298
+ end
299
+ def account_balance_graphs
300
+ <<EOF
301
+ \\section{Graphs of Recent Balances}
302
+ #{@accounts.find_all{|acc| account_type(acc.name) != :Expense}.map{|acc|
303
+ acc.write_balance_graph(@today, @days_before, @days_ahead)
304
+ acc.balance_graph_string
305
+ }.join("\n\n")
306
+ }
307
+ EOF
308
+ end
309
+ def expense_account_summary
310
+ <<EOF
311
+ \\section{Expense Account Summary}
312
+ \\subsection{Budget Period}
313
+ #{expense_pie_chart('budgetperiod'){|r| r.days_ago(@today) < @days_before}}
314
+ \\subsection{Last Week}
315
+ #{expense_pie_chart('lastweekexpenses'){|r| p ['r.daysago', r.days_ago(@today)]; r.days_ago(@today) < 7}}
316
+ \\subsection{Last Month}
317
+ #{expense_pie_chart('lastmonthexpenses'){|r| r.days_ago(@today) < 30}}
318
+ \\subsection{Last Year}
319
+ #{expense_pie_chart('lastyearexpenses'){|r| r.days_ago(@today) < 365}}
320
+ \\section{Expense Accounts by Budget}
321
+ #{@actual_budgets.map{|budget, budget_info|
322
+ "\\subsection{#{budget}}
323
+ #{expense_pie_chart(budget + 'expenses'){|r|r.days_ago(@today) < @days_before and r.budget == budget}}"
324
+ }.join("\n\n")}
325
+
326
+ EOF
327
+ end
328
+ def expense_pie_chart(name, &block)
329
+ expaccs = @accounts.find_all{|acc| account_type(acc.name) == :Expense}
330
+ labels = expaccs.map{|acc| acc.name}
331
+ exps = expaccs.map{|acc| acc.expenditure(@today, 50000, &block)}
332
+ kit = GraphKit.quick_create([exps])
333
+ kit.data[0].gp.with = 'boxes'
334
+ kit.gp.style = "fill solid"
335
+ #pp ['kit222', kit, labels]
336
+ i = -1
337
+ kit.gp.xtics = "(#{labels.map{|l| %["#{l}" #{i+=1}]}.join(', ')}) rotate by 315"
338
+ kit.gnuplot_write("#{name}.eps")
339
+
340
+ "\\begin{center}\\includegraphics[width=3.0in]{#{name}.eps}\\vspace{1em}\\end{center}"
341
+ end
342
+ def get_in_limit_discretionary_budget_factor
343
+ @projected_budget_factor = 1.0
344
+ loop do
345
+ ok = true
346
+ date = @today
347
+ while date < @today + @days_ahead
348
+ ok = false if @equity.projected_balance(date) < @equity.red_line(date)
349
+ date += 1
350
+ #ep ['projected_budget_factor', date, @equity.projected_balance(date), @equity.red_line(date), ok]
351
+ end
352
+ @in_limit_discretionary_budget_factor = @projected_budget_factor
353
+ break if (@projected_budget_factor == 0.0 or ok == true)
354
+ @projected_budget_factor -= 0.01
355
+ @projected_budget_factor -= 0.1
356
+ end
357
+ @projected_budget_factor = nil
358
+ #exit
359
+ end
360
+ def get_stable_discretionary_budget_factor
361
+ @projected_budget_factor = 1.0
362
+ loop do
363
+ ok = true
364
+ date = @today
365
+ balances = []
366
+ while date < @today + @days_ahead
367
+ #ok = false if @equity.projected_balance(date) < @equity.red_line(date)
368
+ date += 1
369
+ balances.push @equity.projected_balance(date)
370
+ #ep ['projected_budget_factor', date, @equity.projected_balance(date), @equity.red_line(date), ok]
371
+ end
372
+ ok = false if balances.mean < @equity.balance(@today)
373
+ @stable_discretionary_budget_factor = @projected_budget_factor
374
+ break if (@projected_budget_factor == 0.0 or ok == true)
375
+ @projected_budget_factor -= 0.01
376
+ @projected_budget_factor -= 0.1
377
+ end
378
+ @projected_budget_factor = nil
379
+ #exit
380
+ end
381
+ def discretionary_budget_table
382
+ discretionary_budgets = budgets_with_averages(@projected_budgets)
383
+
384
+ <<EOF
385
+ \\section{Discretionary Budget Summary}
386
+ \\begin{tabulary}{0.5\\textwidth}{ R | c c c c }
387
+ Budget & Average & Projection & Limit & Stable \\\\
388
+ #{discretionary_budgets.map{|budget, info|
389
+ #ep info
390
+ "#{budget} & #{info[:average]} & #{info[:projection]} & #{
391
+ (info[:projection] * @in_limit_discretionary_budget_factor).round(2)} &
392
+ #{(info[:projection] * @stable_discretionary_budget_factor).round(2)} \\\\"
393
+ }.join("\n\n")
394
+ }
395
+ \\end{tabulary}
396
+ EOF
397
+ end
398
+ def budget_expenditure_graphs
399
+ <<EOF
400
+ \\section{Budget Expenditure}
401
+ #{budget_and_transfer_graphs(@actual_budgets, {})}
402
+ EOF
403
+ end
404
+ def budget_and_transfer_graphs(budgets, options)
405
+ "#{budgets.map{|budget, budget_info|
406
+ dates, expenditures, items = budget_expenditure(budget, budget_info)
407
+ #ep ['budget', budget, dates, expenditures]
408
+ kit = GraphKit.quick_create([dates.map{|d| d.to_time.to_i}, expenditures])
409
+ kit.data.each{|dk| dk.gp.with="boxes"}
410
+ kit.gp.style = "fill solid"
411
+ kit.xlabel = nil
412
+ kit.ylabel = "Expenditure"
413
+ unless options[:transfers]
414
+ kits = budgets_with_averages({budget => budget_info}).map{|budget, budget_info|
415
+ #ep 'Budget is ', budget
416
+ kit2 = GraphKit.quick_create([
417
+ [dates[0], dates[-1]].map{|d| d.to_time.to_i},
418
+ [budget_info[:average], budget_info[:average]]
419
+ ])
420
+ kit2.data[0].gp.with = 'lp lw 4'
421
+ kit2
422
+ }
423
+ #$debug_gnuplot = true
424
+ #kits.sum.gnuplot
425
+ kit += kits.sum
426
+
427
+ else
428
+ kit.data[0].y.data.map!{|expen| expen*-1.0}
429
+ end
430
+ kit.title = "#{budget} Expenditure with average (Total = #{kit.data[0].y.data.sum})"
431
+ CodeRunner::Budget.kit_time_format_x(kit)
432
+ #kit.gnuplot
433
+ #ep ['kit1122', budget, kit]
434
+ kit.gnuplot_write("#{budget}.eps")
435
+ "\\begin{center}\\includegraphics[width=3.0in]{#{budget}.eps}\\vspace{1em}\\end{center}"
436
+ }.join("\n\n")
437
+ }"
438
+ end
439
+
440
+ def budget_resolutions
441
+ <<EOF
442
+ \\section{Budget Resolutions}
443
+
444
+ This section sums items from budgets 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 budget.
445
+
446
+ #{@actual_budgets.map{|budget, budget_info|
447
+
448
+ "\\subsection{#{budget} }
449
+ \\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{r l}
450
+ %\\hline
451
+ Account Owed & Amount \\\\
452
+ \\hline
453
+ \\Tstrut
454
+ #{budget_items = @indateruns.find_all{|r| r.budget == budget}
455
+ alternate_accounts = budget_items.map{|r| r.account}.uniq - [budget_info[:account]]
456
+ alternate_accounts.map{|acc|
457
+ alternate_items = budget_items.find_all{|r| r.account == acc}
458
+ total = alternate_items.map{|r| r.withdrawal - r.deposit}.sum
459
+ "#{acc} & #{total} \\\\"
460
+ }.join("\n\n")
461
+ }
462
+
463
+ \\\\
464
+ \\hline
465
+ \\end{tabulary}
466
+ \\normalsize
467
+ \\vspace{1em}\n\n
468
+
469
+ #{ alternate_accounts.map{|acc|
470
+ alternate_items = budget_items.find_all{|r| r.account == acc}
471
+ alternate_items.pieces((alternate_items.size.to_f/50.to_f).ceil).map{|piece|
472
+ "\\footnotesize\\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{ #{"c " * 4 + " L " + " r " * 3 }}
473
+ #{budget}: & #{budget_info[:account]} & owes & #{acc} &&&&\\\\
474
+ \\hline
475
+
476
+ \\Tstrut
477
+
478
+ #{piece.map{|r|
479
+ ([:id] + CodeRunner::Budget.rcp.component_results - [:sc]).map{|res|
480
+ r.send(res).to_s.latex_escape
481
+ #rcp.component_results.map{|res| r.send(res).to_s.gsub(/(.{20})/, '\1\\\\\\\\').latex_escape
482
+ }.join(" & ")
483
+ }.join("\\\\\n")
484
+ }
485
+ \\end{tabulary}\\normalsize"}.join("\n\n")
486
+ }.join("\n\n")}
487
+ "
488
+ }.join("\n\n")
489
+ }
490
+ EOF
491
+ end
492
+ def budget_breakdown
493
+ <<EOF
494
+ \\section{Budget and Transfer Breakdown}
495
+ #{(@actual_budgets).map{|budget, budget_info|
496
+ dates, expenditures, budget_items = budget_expenditure(budget, budget_info)
497
+ #pp budget, budget_items.map{|items| items.map{|i| i.date.to_s}}
498
+ "\\subsection{#{budget}}" +
499
+ budget_items.zip(dates, expenditures).map{|items, date, expenditure|
500
+ if items.size > 0
501
+ "
502
+ \\footnotesize
503
+ \\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{ #{"c " * 4 + " L " + " r " * 2 }}
504
+ %\\hline
505
+ & #{date.to_s.latex_escape} & & & Total & #{expenditure} & \\\\
506
+ \\hline
507
+ \\Tstrut
508
+ #{items.map{|r|
509
+ ([:id] + CodeRunner::Budget.rcp.component_results - [:sc, :balance]).map{|res|
510
+ r.send(res).to_s.latex_escape
511
+ }.join(" & ")
512
+ }.join("\\\\\n")
513
+ }
514
+ \\\\
515
+ \\hline
516
+ \\end{tabulary}
517
+ \\normalsize
518
+ \\vspace{1em}\n\n"
519
+ else
520
+ ""
521
+ end
522
+ }.join("\n\n")
523
+ }.join("\n\n")
524
+ }
525
+ EOF
526
+ end
527
+
528
+ def transactions_by_account
529
+ <<EOF
530
+ \\section{Recent Transactions}
531
+ #{@accounts.find_all{|acc| not acc.type == :Equity}.sort_by{|acc| acc.external ? 0 : 1}.map{|acc|
532
+ "\\subsection{#{acc.name}}
533
+ \\footnotesize
534
+ #{all = acc.runs.find_all{|r| r.days_ago(@today) < @days_before}.sort_by{|r| [r.date, r.id]}.reverse
535
+ #ep ['acc', acc, 'ids', all.map{|r| r.id}, 'size', all.size]
536
+ all.pieces((all.size.to_f/50.to_f).ceil).map{|piece|
537
+ "\\setlength{\\parindent}{0cm}\n\n\\begin{tabulary}{0.99\\textwidth}{ #{"c " * 4 + " L " + " r " * 3 + "l"}}
538
+ #{piece.map{|r|
539
+ ([:id] + CodeRunner::Budget.rcp.component_results - [:sc] + [:budget]).map{|res| r.send(res).to_s.latex_escape
540
+ #rcp.component_results.map{|res| r.send(res).to_s.gsub(/(.{20})/, '\1\\\\\\\\').latex_escape
541
+ }.join(" & ")
542
+ }.join("\\\\\n")}
543
+ \\end{tabulary}"}.join("\n\n")}"
544
+ }.join("\n\n")}
545
+ EOF
546
+ end
547
+
548
+ def header
549
+ <<EOF
550
+ \\documentclass{article}
551
+ \\usepackage[cm]{fullpage}
552
+ \\usepackage{tabulary}
553
+ \\usepackage{graphicx}
554
+ \\usepackage{multicol}
555
+ %\\usepackage{hyperlink}
556
+ \\newcommand\\Tstrut{\\rule{0pt}{2.8ex}}
557
+ \\begin{document}
558
+ \\title{#{@days_before}-day Budget Report}
559
+ \\maketitle
560
+ \\tableofcontents
561
+ EOF
562
+ end
563
+ def footer
564
+ <<EOF
565
+ \\end{document}
566
+ EOF
567
+ end
568
+
569
+ end
570
+ end
data/lib/treasurer.rb CHANGED
@@ -0,0 +1,113 @@
1
+
2
+ # A tool for analysing finances and making budget projections
3
+
4
+ require 'getoptlong'
5
+
6
+ module CommandLineFlunky
7
+
8
+ STARTUP_MESSAGE = "\n------Treasurer Financial Utility (c) Edmund Highcock------"
9
+
10
+ MANUAL_HEADER = <<EOF
11
+
12
+ -------------Treasurer Financial Utility Manual---------------
13
+
14
+ Written by Edmund Highcock (2014)
15
+
16
+ NAME
17
+
18
+ treasurer
19
+
20
+
21
+ SYNOPSIS
22
+
23
+ treasurer <command> [arguments] [options]
24
+
25
+
26
+ DESCRIPTION
27
+
28
+ Generate a financial report from one or more bank accounts, credit cards
29
+ etc by analysing internet banking sheets. Simple local files can be used to
30
+ customise the reports by adding budgets and categories
31
+
32
+ EXAMPLES
33
+
34
+ treasurer init my_accounts_folder
35
+
36
+ treasurer add my_bank_statement.csv
37
+
38
+ treasurer report
39
+
40
+ treasurer add_folder folder_of_bank_statements
41
+
42
+
43
+ EOF
44
+
45
+ COMMANDS_WITH_HELP = [
46
+ ['add_file', 'add', 2, 'Import a new internet banking spreadsheet for the given account.', ['csv spreadsheet filename', 'account name'], []],
47
+ ['add_folder_of_files', 'addf', 1, 'Import all internet banking spreadsheets within the given folder .', ['folder'], []],
48
+ ['init_root_folder', 'init', 1, 'Create a new folder and initialise it for storing treasurer data.', ['folder'], []],
49
+ ['create_report', 'report', 0, 'Generate a detailed report (typeset using latex) showing account activity, spending by category, and projections.', [], [:a, :b, :t]],
50
+
51
+ ]
52
+
53
+
54
+
55
+ COMMAND_LINE_FLAGS_WITH_HELP = [
56
+ ['--after', '-a', GetoptLong::REQUIRED_ARGUMENT, 'Calculate projections up till given number of days after today'],
57
+ ['--before', '-b', GetoptLong::REQUIRED_ARGUMENT, 'Start budget from given number of days before today'],
58
+ ['--today', '-t', GetoptLong::REQUIRED_ARGUMENT, "Specify today's date, i.e. change the date on which the report is generated."],
59
+ #['--formats', '-f', GetoptLong::REQUIRED_ARGUMENT, "A list of formats pertaining to the various input and output files (in the order which they appear), separated by commas. If they are all the same, only one value may be given. If a value is left empty (i.e. there are two commas in a row) then the previous value will be used. Currently supported formats are #{SUPPORTED_FORMATS.inspect}. "],
60
+
61
+ ]
62
+
63
+ LONG_COMMAND_LINE_OPTIONS = [
64
+ #["--no-short-form", "", GetoptLong::NO_ARGUMENT, %[This boolean option has no short form]],
65
+ ]
66
+
67
+ # specifying flag sets a bool to be true
68
+
69
+ CLF_BOOLS = []
70
+
71
+ CLF_INVERSE_BOOLS = [] # specifying flag sets a bool to be false
72
+
73
+ PROJECT_NAME = 'treasurer'
74
+
75
+ def self.method_missing(method, *args)
76
+ # p method, args
77
+ Treasurer.send(method, *args)
78
+ end
79
+
80
+ #def self.setup(copts)
81
+ #CommandLineFlunkyTestUtility.setup(copts)
82
+ #end
83
+
84
+ SCRIPT_FILE = __FILE__
85
+ end
86
+
87
+ class Treasurer
88
+ class << self
89
+ # This function gets called before every command
90
+ # and allows arbitrary manipulation of the command
91
+ # options (copts) hash
92
+ def setup(copts)
93
+ # None neededed
94
+ end
95
+ def verbosity
96
+ 2
97
+ end
98
+ end
99
+ SCRIPT_FOLDER = folder = File.dirname(File.expand_path(__FILE__))
100
+ end
101
+
102
+ $has_put_startup_message_for_code_runner = true
103
+ require 'coderunner'
104
+ require 'treasurer/commands.rb'
105
+ require 'treasurer/report.rb'
106
+ require 'treasurer/analysis.rb'
107
+
108
+
109
+ ######################################
110
+ # This must be at the end of the file
111
+ #
112
+ require 'command-line-flunky'
113
+ ###############################
@@ -0,0 +1,12 @@
1
+ Date,Type,Sort Code,Account Number,Description,In,Out,Balance,
2
+ 03/09/2010,CPT,000000,99999999,LTSB CARFAX OXFORDCD 03SEP10,,20.00,156.33
3
+ 03/09/2010,DD,000000,99999999,VODAFONE LIMITED,,28.61,176.33
4
+ 03/09/2010,DEB,000000,99999999,ENDSLEIGH INSURANCE,,24.63,204.94
5
+ 02/09/2010,DEB,000000,99999999,MIDCOUNTIES CO-OP,,32.65,229.57
6
+ 01/09/2010,FPO,000000,99999999,MISS C ADAMS,,150.00,262.22
7
+ 01/09/2010,DD,000000,99999999,PAYPAL PAYMENT,,5.97,412.22
8
+ 01/09/2010,DEB,000000,99999999,S390-BLACKWELLCD ,,15.00,418.19
9
+ 25/08/2010,DEB,000000,99999999,MIDCOUNTIES CO-OP,,52.65,229.57
10
+ 15/08/2010,DEB,000000,99999999,MIDCOUNTIES CO-OP,,12.65,229.57
11
+ 07/08/2010,DEB,000000,99999999,MIDCOUNTIES CO-OP,,32.65,229.57
12
+ 01/08/2010,DEB,000000,99999999,MISS C ADAMS,,150.00,418.19
@@ -0,0 +1,2 @@
1
+ Date,Type,Sort Code,Account Number,Description,In,Out,Balance,
2
+ 04/09/2010,DEB,000000,99999999,MARKS & SPENCER,,35.00,121.33
@@ -0,0 +1,2 @@
1
+ Date,Type,Sort Code,Account Number,Description,In,Out,Balance,
2
+ 06/09/2010,CPT,222222,88888888,LNK BIRMINGHAM,,40.00,820.75
@@ -0,0 +1,9 @@
1
+ Date,Type,Sort Code,Account Number,Description,In,Out,Balance,
2
+ 05/09/2010,CPT,222222,88888888,LNK WINDERMERE,,30.00,860.75
3
+ 05/09/2010,FPO,222222,88888888,M H NORRIES,560.00,,890.75
4
+ 05/09/2010,DEB,222222,88888888,SAINSBURY'S SMKT,,6.05,1450.75
5
+ 05/09/2010,DEB,222222,88888888,AL-ANDALUS,,50.00,1456.80
6
+ 05/09/2010,DEB,222222,88888888,ANGELS,,11.50,1506.80
7
+ 01/09/2010,DEB,222222,88888888,THE MAGGIE ARMS,,48.00,1518.30
8
+ 01/09/2010,DEB,222222,88888888,BARLEY MOW,,6.60,1566.30
9
+ 30/08/2010,DD,222222,88888888,PAYPAL PAYMENT,,11.91,1572.90
@@ -1,7 +1,20 @@
1
1
  require 'helper'
2
2
 
3
3
  class TestTreasurer < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
6
- end
4
+ def testfolder
5
+ 'test/myaccount'
6
+ end
7
+ def test_setup
8
+ FileUtils.rm_r(testfolder) if FileTest.exist? testfolder
9
+ Treasurer.init_root_folder('test/myaccount', {})
10
+ Dir.chdir('test/myaccount') do
11
+ Treasurer.add_file('../bankaccountstatement.csv', 'FirstBank', {})
12
+ Treasurer.status
13
+ Treasurer.add_file('../otheraccountstatement.csv', 'SecondBank', {})
14
+ Treasurer.add_folder('../multiple')
15
+ Treasurer.status h: :component
16
+ Treasurer.report t: Date.parse('2010-09-07'), b: 40, a: 35
17
+ end
18
+ FileUtils.rm_r(testfolder) if FileTest.exist? testfolder
19
+ end
7
20
  end
data/treasurer.gemspec CHANGED
@@ -2,18 +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.0.0 ruby lib
5
+ # stub: treasurer 0.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "treasurer"
9
- s.version = "0.0.0"
9
+ s.version = "0.1.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
13
  s.authors = ["Edmund Highcock"]
14
- s.date = "2014-05-26"
14
+ s.date = "2014-05-30"
15
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
16
  s.email = "edmundhighcock@users.sourceforge.net"
17
+ s.executables = ["treasurer"]
17
18
  s.extra_rdoc_files = [
18
19
  "LICENSE.txt",
19
20
  "README.rdoc"
@@ -25,8 +26,17 @@ Gem::Specification.new do |s|
25
26
  "README.rdoc",
26
27
  "Rakefile",
27
28
  "VERSION",
29
+ "bin/treasurer",
28
30
  "lib/treasurer.rb",
31
+ "lib/treasurer/analysis.rb",
32
+ "lib/treasurer/commands.rb",
33
+ "lib/treasurer/local_customisations.rb",
34
+ "lib/treasurer/report.rb",
35
+ "test/bankaccountstatement.csv",
29
36
  "test/helper.rb",
37
+ "test/multiple/FirstBank.csv",
38
+ "test/multiple/SecondBank.csv",
39
+ "test/otheraccountstatement.csv",
30
40
  "test/test_treasurer.rb",
31
41
  "treasurer.gemspec"
32
42
  ]
@@ -39,12 +49,18 @@ Gem::Specification.new do |s|
39
49
  s.specification_version = 4
40
50
 
41
51
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
52
+ s.add_runtime_dependency(%q<coderunner>, [">= 0.14.16"])
53
+ s.add_runtime_dependency(%q<budgetcrmod>, [">= 0.0.0"])
54
+ s.add_runtime_dependency(%q<command-line-flunky>, [">= 1.0.0"])
42
55
  s.add_development_dependency(%q<shoulda>, [">= 0"])
43
56
  s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
44
57
  s.add_development_dependency(%q<bundler>, ["~> 1.0"])
45
58
  s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
46
59
  s.add_development_dependency(%q<simplecov>, [">= 0"])
47
60
  else
61
+ s.add_dependency(%q<coderunner>, [">= 0.14.16"])
62
+ s.add_dependency(%q<budgetcrmod>, [">= 0.0.0"])
63
+ s.add_dependency(%q<command-line-flunky>, [">= 1.0.0"])
48
64
  s.add_dependency(%q<shoulda>, [">= 0"])
49
65
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
50
66
  s.add_dependency(%q<bundler>, ["~> 1.0"])
@@ -52,6 +68,9 @@ Gem::Specification.new do |s|
52
68
  s.add_dependency(%q<simplecov>, [">= 0"])
53
69
  end
54
70
  else
71
+ s.add_dependency(%q<coderunner>, [">= 0.14.16"])
72
+ s.add_dependency(%q<budgetcrmod>, [">= 0.0.0"])
73
+ s.add_dependency(%q<command-line-flunky>, [">= 1.0.0"])
55
74
  s.add_dependency(%q<shoulda>, [">= 0"])
56
75
  s.add_dependency(%q<rdoc>, ["~> 3.12"])
57
76
  s.add_dependency(%q<bundler>, ["~> 1.0"])
metadata CHANGED
@@ -1,15 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: treasurer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.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: 2014-05-26 00:00:00.000000000 Z
11
+ date: 2014-05-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: coderunner
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.14.16
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.14.16
27
+ - !ruby/object:Gem::Dependency
28
+ name: budgetcrmod
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: command-line-flunky
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 1.0.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.0
13
55
  - !ruby/object:Gem::Dependency
14
56
  name: shoulda
15
57
  requirement: !ruby/object:Gem::Requirement
@@ -83,7 +125,8 @@ dependencies:
83
125
  description: A simple command line tool for managing accounts and finances. Easily
84
126
  import internet banking spreadsheets and generate sophisticated reports and projections.
85
127
  email: edmundhighcock@users.sourceforge.net
86
- executables: []
128
+ executables:
129
+ - treasurer
87
130
  extensions: []
88
131
  extra_rdoc_files:
89
132
  - LICENSE.txt
@@ -95,8 +138,17 @@ files:
95
138
  - README.rdoc
96
139
  - Rakefile
97
140
  - VERSION
141
+ - bin/treasurer
98
142
  - lib/treasurer.rb
143
+ - lib/treasurer/analysis.rb
144
+ - lib/treasurer/commands.rb
145
+ - lib/treasurer/local_customisations.rb
146
+ - lib/treasurer/report.rb
147
+ - test/bankaccountstatement.csv
99
148
  - test/helper.rb
149
+ - test/multiple/FirstBank.csv
150
+ - test/multiple/SecondBank.csv
151
+ - test/otheraccountstatement.csv
100
152
  - test/test_treasurer.rb
101
153
  - treasurer.gemspec
102
154
  homepage: http://github.com/edmundhighcock/treasurer