class CodeRunner::Budget

Constants

DOUBLE_STRING

Attributes

dummy[RW]

Public Class Methods

kit_time_format_x(kit) click to toggle source
# 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
predictable_component_ids(runner) click to toggle source
# File lib/budgetcrmod/budget.rb, line 306
def self.predictable_component_ids(runner)
end

Public Instance Methods

credit() click to toggle source
# File lib/budgetcrmod/budget.rb, line 56
def credit
  case @account_type
  when :Asset, :Expense
    withdrawal
  else
    deposit
  end
end
csv_data_fields() click to toggle source

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
date_sorted_component() click to toggle source
# File lib/budgetcrmod/budget.rb, line 125
def date_sorted_component
  @date_sorted_component ||= component_runs.sort_by{|r| r.id}
end
days_ago(today = Date.today) click to toggle source
# 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
debit() click to toggle source
# File lib/budgetcrmod/budget.rb, line 48
def debit
  case @account_type
  when :Asset, :Expense
    deposit
  else
    withdrawal
  end
end
ds() click to toggle source
# File lib/budgetcrmod/budget.rb, line 288
def ds
  description
end
end_date() click to toggle source
# File lib/budgetcrmod/budget.rb, line 128
def end_date
  date_sorted_component[-1].date rescue nil
end
external_account() click to toggle source

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
final_balance() click to toggle source
# File lib/budgetcrmod/budget.rb, line 134
def final_balance
  date_sorted_component[-1].balance rescue nil
end
generate_component_runs() click to toggle source
# 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
generate_input_file() click to toggle source
# File lib/budgetcrmod/budget.rb, line 45
def generate_input_file
  FileUtils.cp @data_file.sub(/~/, ENV['HOME']), @directory + '/data.cvs'
end
has_balance?() click to toggle source

def withdrawn @withdrawn||0.0 end

# File lib/budgetcrmod/budget.rb, line 195
def has_balance? 
  @has_balance ||= csv_data_fields.include? :balance
end
idate() click to toggle source
# File lib/budgetcrmod/budget.rb, line 285
def idate
  date.to_datetime.to_time.to_i
end
old_data_line() click to toggle source
# 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
parameter_string() click to toggle source
# File lib/budgetcrmod/budget.rb, line 139
def parameter_string
  ""
end
parameter_transition(run) click to toggle source
# File lib/budgetcrmod/budget.rb, line 137
def parameter_transition(run)
end
print_out_line() click to toggle source

def reversed? case account_type(@account) when :Asset @first_line =~ /Debit.*Credit/ end end

process_directory_code_specific() click to toggle source
# 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
set_zeroes() click to toggle source
# File lib/budgetcrmod/budget.rb, line 202
def set_zeroes
  @withdrawal||=0.0
  @deposit||=0.0
end
signature() click to toggle source
# File lib/budgetcrmod/budget.rb, line 291
def signature
  #[date,description,deposit,withdrawal,account]
  rcp.signature_fields.map{|res| send(res)}
end
start_date() click to toggle source
# File lib/budgetcrmod/budget.rb, line 131
def start_date
  date_sorted_component[0].date rescue nil
end
sub_account() click to toggle source

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