class CodeRunner::Budget
Constants
- DOUBLE_STRING
Attributes
Public Class Methods
# File lib/budgetcrmod/budget.rb, line 308 def self.kit_time_format_x(kit) kit.gp.timefmt = %["%s"] kit.data.each{|dk| dk.gp.using = "1:2"} kit.gp.xdata = "time" kit.gp.format = %[x "%d %B %Y"] kit.gp.format = [%[x "%b %Y"]] kit.gp.mxtics = "30" kit.gp.xtics = "rotate by 340 offset 0,-0.5 #{24*3600*14}" kit.gp.xtics = "rotate by 340 offset 0,-0.5 2629746" end
# File lib/budgetcrmod/budget.rb, line 306 def self.predictable_component_ids(runner) end
Public Instance Methods
# File lib/budgetcrmod/budget.rb, line 56 def credit case @account_type when :Asset, :Expense withdrawal else deposit end end
def external_account
(budget.to_s + '_' + sub_account.to_s).to_sym end
# File lib/budgetcrmod/budget.rb, line 160 def csv_data_fields case @first_line_string when /Date,Type,Sort Code,Account Number,Description,In,Out,Balance/ # Old Lloyds Bank Format [:date, :type, :sc, :ac, :description, :deposit, :withdrawal, :balance] when /Transaction Date,Transaction Type,Sort Code,Account Number,Transaction Description,Debit Amount,Credit Amount,Balance/ # 2013 Lloyds Bank Format, NB they are using debit and credit as if the account is an equity account (when of course a bank account is really an asset) [:date, :type, :sc, :ac, :description, :withdrawal, :deposit, :balance] when /Date,Date entered,Reference,Description,Amount/ # Lloyds Credit Card statement [:date, :dummy, :dummy, :description, :withdrawal] when /date,description,type,user,expensetype,withdrawal,withdrawal/ [:date,:description,:type,:dummy,:dummy, :withdrawal, :withdrawal] when /Datum,Transaktion,Kategori,Belopp,Saldo/ # Nordea.se privat, Belopp is positive when the asset increases [:date,:description,:dummy,:deposit,:balance] when /Bokföringsdag;Belopp;Avsändare;Mottagare;Namn;Rubrik;Saldo;Valuta/ # Nordea new [:date,:deposit,:dummy,:dummy,:dummy,:description,:balance,:dummy] when /Bokföringsdatum,Transaktionsreferens,Mottagare,Belopp,Valuta/ # Forex.se privat, Belopp is positive when the asset increases [:date,:description,:dummy,:deposit,:dummy] when /Datum,Text,Belopp/ #Ecster Credit Card [:date,:description,:deposit] when /Effective Date,Entered Date,Transaction Description,Amount,Balance/ #QudosBank new format the first field is blank [:dummy,:date,:description,:deposit,:balance] when /Effective Date,Entered Date,Transaction Description,Amount/ #QudosBank, the first field is blank [:dummy,:date,:description,:deposit] when /Date,Amount,Currency,Description,"Payment Reference","Running Balance","Exchange Rate","Payer Name","Payee Name","Payee Account Number",Merchant/ #TransferWise [:date,:deposit,:dummy,:description,:dummy,:balance,:dummy,:dummy,:dummy,:dummy,:dummy] when /"TransferWise ID",Date,Amount,Currency,Description,"Payment Reference","Running Balance","Exchange From","Exchange To","Exchange Rate","Payer Name","Payee Name","Payee Account Number",Merchant,"Total fees"/ #TransferWise New [:dummy,:date,:deposit,:dummy,:description,:description2,:balance,:dummy,:dummy,:dummy,:dummy,:dummy,:dummy,:dummy,:dummy] else raise "unknown data format for #@id: #@first_line_string, #{@data.slice(0,[4,@data.size].min)}" end end
# File lib/budgetcrmod/budget.rb, line 125 def date_sorted_component @date_sorted_component ||= component_runs.sort_by{|r| r.id} end
# File lib/budgetcrmod/budget.rb, line 282 def days_ago(today = Date.today) - ((date.to_datetime.to_time.to_i - today.to_datetime.to_time.to_i) / 24 / 3600).to_i end
# File lib/budgetcrmod/budget.rb, line 48 def debit case @account_type when :Asset, :Expense deposit else withdrawal end end
# File lib/budgetcrmod/budget.rb, line 288 def ds description end
# File lib/budgetcrmod/budget.rb, line 128 def end_date date_sorted_component[-1].date rescue nil end
All transactions occur between two accounts. One of those accounts is always physical (e.g. bank account, loan, credit card) and the other can be either physical or a virtual account of the users choice, e.g. Food or Petrol or Energy, or maybe WeeklyBudget and LongTermBudget etc.
# File lib/budgetcrmod/account_choices.rb, line 7 def external_account return @external_account if @external_account ext_account = false unless @runner raise "No runner for " + data_line end Dir.chdir(@runner.root_folder) do chosen = false Hash.phoenix('account_choices.rb') do |choices_hash| if choices_hash[signature] chosen = choices_hash[signature][:external_account] elsif choices_hash[data_line] #choices_hash[data_line][:external_account] = #choices_hash[data_line][:external_account].to_sym #fixes earlier bug #choices_hash[data_line][:sub_account] = #choices_hash[data_line][:sub_account].to_sym #fixes earlier bug chosen = choices_hash[data_line][:external_account] choices_hash[signature] = choices_hash[data_line] choices_hash.delete(data_line) elsif choices_hash[old_data_line] chosen = choices_hash[old_data_line][:external_account] choices_hash[signature] = choices_hash[old_data_line] end end return @external_account = chosen if chosen chosen = false sym = nil while not chosen Hash.phoenix('external_accounts.rb') do |account_hash| #account_hash.each{|k,v| v[:name] = v[:name].to_sym} #Fixes an earlier bug #choices = account_arr.size.times.map{|i| [i,account_arr[i][:name]]} choices = account_hash.map{|k,v| [v[:sym], k]}.to_h puts Terminal.default_colour puts puts "-" * data_line.size puts signature.inspect puts "-" * data_line.size puts puts "Account: " + account puts puts choices.inspect puts puts "Please choose from the above external accounts for this transaction." puts "If you wish to add a new account type 0. To quit type q" puts "To start again for this transaction, type z" while not chosen require 'io/console' choice = STDIN.getch if choice == "q" throw :quit_data_entry elsif choice == "0" puts "Please type the name of the new account" name = STDIN.gets.chomp.to_sym puts "Please enter a symbol to represent this account (e.g. digit, letter, punctuation)." sym = false until sym sym = STDIN.getch if choices.keys.include? sym puts "This symbol is taken" sym = false end end account_hash[name] = {name: name, sym: sym, sub_accounts: {}} #choices_hash[@id] = name chosen = name elsif choice == "z" chosen = false break elsif not choices.keys.include? choice puts "Error: this symbol does not correspond to an account" else chosen = choices[choice] #account_hash[choice][:name] end end #Hash.phoenix('account_choices.rb') do |choices_hash| #choices_hash[data_line] = {external_account: chosen} #end end ext_account = chosen next if not chosen chosen = false Hash.phoenix('external_accounts.rb') do |account_hash| #choices = account_arr.size.times.map{|i| [i,account_arr[i][:name]]} sub_accounts = account_hash[ext_account][:sub_accounts] sub_accounts.each{|k,v| v[:name] = v[:name].to_sym} #Fixes an earlier bug choices = sub_accounts.map{|k,v| [v[:sym], k]}.to_h puts "-" * data_line.size puts puts choices.inspect puts puts "Please choose from the above sub-accounts for this transaction." puts "If you wish to add a new sub account, type 0. To quit, type q" puts "To start again for this transaction, type z" while not chosen require 'io/console' choice = STDIN.getch if choice == "q" throw :quit_data_entry elsif choice == "0" puts "Please type the name of the new sub account" name = STDIN.gets.chomp.to_sym puts "Please enter a symbol to represent this account (e.g. digit, letter, punctuation)." sym = false until sym sym = STDIN.getch if choices.keys.include? sym puts "This symbol is taken" sym = false end end sub_accounts[name] = {name: name, sym: sym} #choices_hash[@id] = name chosen = name elsif choice == "z" chosen = false break elsif not choices.keys.include? choice puts "Error: this symbol does not correspond to a sub-account" else chosen = choices[choice] #sub_accounts[choice][:name] end end end next if not chosen Hash.phoenix('account_choices.rb') do |choices_hash| choices_hash[signature] = {external_account: ext_account, sub_account: chosen} end end #while not chosen end ext_account end
# File lib/budgetcrmod/budget.rb, line 134 def final_balance date_sorted_component[-1].balance rescue nil end
# File lib/budgetcrmod/budget.rb, line 206 def generate_component_runs #puts Kernel.caller #p ['generate_component_runs', @component_runs.class, (@component_runs.size rescue nil), @runner.component_run_list.size, @directory] return if @component_runs and @component_runs.size > 0 @runner.cache[:data] ||= [] #reslts = rcp.component_results #if reversed? #reslts[5] = :withdrawal #reslts[6] = :deposit #end @data.each do |dataset| #next if @runner.cache[:data].include? dataset and Date.parse(dataset[0]) > Date.parse("1/1/2013") #next if @runner.component_run_list.map{|k,v| v.instance_variable_get(:@dataset)}.include? dataset # and Date.parse(dataset[0]) > Date.parse("1/1/2013") next if @first_line_string =~ /^Datum/ and dataset[1] =~ /Reservation/ next if @first_line_string =~ /Bokf/ and dataset[0] =~ /Invalid/ h = {} h[:withdrawal] = 0.0 h[:deposit] = 0.0 h[:account] = @account reslts = csv_data_fields reslts.each_with_index do |res,index| value = dataset[index] #ep value value = Date.parse value if res == :date if [:deposit, :withdrawal, :balance].include? res case @first_line_string when /^Datum/i, /Bokföringsdag;Belopp;/ # we are dealing with European numbers value = value.gsub(/[." ]/, '') value = value.sub(/[,]/, '.') else value = value.gsub(/[",]/, '') end next unless value =~ /\d/ value = value.to_f end #component.set(res, value) h[res] = value end h[:data_line] = reslts.map{|r| h[r].to_s}.join(',') if h[:description2] and h[:description] h[:description] += ":" + h[:description2] end h[:given_withdrawal] = h[:withdrawal] h[:given_deposit] = h[:deposit] if h[:deposit] < 0.0 and h[:withdrawal] == 0.0 h[:withdrawal] = -h[:deposit] h[:deposit] = 0.0 end if h[:withdrawal] < 0.0 and h[:deposit] == 0.0 h[:deposit] = -h[:withdrawal] h[:withdrawal] = 0.0 end next if @runner.component_run_list.find{|k,v| v.signature == rcp.signature_fields.map{|res| h[res]} } # and Date.parse(dataset[0]) > Date.parse("1/1/2013") component = create_component #component.set_zeroes h.each{|k,v| component.set(k,v)} ep 'Generating Component', @component_runs.size component.set(:dataset, dataset) component.date_i = component.date.to_datetime.to_time.to_i #if component.description2 and component.description #component.description += ":" + component.description2 #end #if component.deposit < 0.0 and component.withdrawal == 0.0 #component.withdrawal = -component.deposit #component.deposit = 0.0 #end #@runner.cache[:data].push dataset #component.external_account; component.sub_account # Triggers interactive account choices #component.account = @account end end
# File lib/budgetcrmod/budget.rb, line 45 def generate_input_file FileUtils.cp @data_file.sub(/~/, ENV['HOME']), @directory + '/data.cvs' end
def withdrawn @withdrawn||0.0 end
# File lib/budgetcrmod/budget.rb, line 195 def has_balance? @has_balance ||= csv_data_fields.include? :balance end
# File lib/budgetcrmod/budget.rb, line 285 def idate date.to_datetime.to_time.to_i end
# File lib/budgetcrmod/budget.rb, line 296 def old_data_line # For backwards compatibility issues when csv formats change case @first_line_string when /Effective Date,Entered Date,Transaction Description,Amount,Balance/ #QudosBank new format the first field is blank data_line.sub(/,[^,]*?$/, '') else nil end end
# File lib/budgetcrmod/budget.rb, line 139 def parameter_string "" end
# File lib/budgetcrmod/budget.rb, line 137 def parameter_transition(run) end
def reversed? case account_type(@account) when :Asset @first_line =~ /Debit.*Credit/ end end
# File lib/budgetcrmod/budget.rb, line 117 def print_out_line if @is_component sprintf("%4d. %10s %10s %3s %-40s %8s %8s %8s %8s %8s", id, account, *rcp.component_results.find_all{|r| r!=:ac and r!=:sc}.map{|res| send(res).to_s.gsub(/\s+/, ' ')}, external_account, sub_account) else #pr = component_runs.sort_by{|r| r.id} "#{sprintf("%3d", @id)}. #{sprintf("%-20s", @account)} Start: #{start_date} End: #{end_date} Final Balance: #{final_balance} " end end
# File lib/budgetcrmod/budget.rb, line 65 def process_directory_code_specific @status=:Complete data = File.read('data.cvs') #p ['encoding', data.encoding] #data.encode(Encoding::UTF_8) tries = 1 begin data = data.split(/\n\r|\r\n|\n|\r/) rescue #require 'ensure/encoding.rb' if tries > 0 if tries==1 #data.force_encoding('iso-8859-1') data = File.read('data.cvs', encoding: "ISO-8859-1") data = data.encode('utf-8') end tries-=1 retry end end if data[0] =~ /^\d{2} \w{3} \d\d,/ # BarclayCard format data.unshift 'date,description,type,user,expensetype,withdrawal,withdrawal' elsif data[0] == "" data.shift end @first_line_string = data[0].dup separator = case @first_line_string when /Bokf.*ringsdag;Belopp;/ ";" else "," end puts "Separator is: " + separator data = data.map do |line| matches = line.scan(Regexp.new("((?:#{DOUBLE_STRING}|[^#{separator}])*)(?:#{separator}|$)")) pp matches matches.flatten end #pp data @data = data @first_line = @data.shift.join(',') generate_component_runs end
# File lib/budgetcrmod/budget.rb, line 202 def set_zeroes @withdrawal||=0.0 @deposit||=0.0 end
# File lib/budgetcrmod/budget.rb, line 291 def signature #[date,description,deposit,withdrawal,account] rcp.signature_fields.map{|res| send(res)} end
# File lib/budgetcrmod/budget.rb, line 131 def start_date date_sorted_component[0].date rescue nil end
All transactions have a subaccount. For transactions between physical accounts this will almost always be just 'Transfer' or similar, but virtual accounts will have more meaningful sub_accounts, e.g. Food might have Groceries, EatingOut or WeeklyBudget might have Transport, Food, Clothes… depending on the users preferred way of organising things. The sub_accounts are primarily a labelling exercise, but done well can be very helpful in showing a breakdown of expenditure.
# File lib/budgetcrmod/account_choices.rb, line 147 def sub_account return @sub_account if @sub_account puts "SIGNATURE ", signature.inspect Dir.chdir(@runner.root_folder) do external_account until ( choices = nil Hash.phoenix('account_choices.rb'){|choices_hash| choices = choices_hash[signature]} #p [choices, data_line] choices ) and choices[:sub_account] @sub_account = choices[:sub_account] end end