Skip to content

Instantly share code, notes, and snippets.

@markburns
Last active March 11, 2018 19:44
Show Gist options
  • Save markburns/d4a0368e6400bdc852d0 to your computer and use it in GitHub Desktop.
Save markburns/d4a0368e6400bdc852d0 to your computer and use it in GitHub Desktop.
MetroBank UK to FreeAgent CSV format converter
require "csv"
class MetroBankCsvConverter
def initialize(lines)
lines =lines.split("\n")
lines.shift(3)
lines = lines.join("\n")
@raw_csv = CSV.parse(lines, headers: headers)
end
def headers
["date","type","reference","currency","money_out","money_in","balance"]
end
def to_freeagent_csv(start_date=nil, end_date=nil)
CSV.generate do |csv|
transactions.each do |t|
if t.within(start_date, end_date)
csv << [t.date_string, t.amount, t.long_description]
end
end
end
end
def columns
@raw_csv.headers
end
def transaction_count
transactions.count
end
def transactions
transactions = []
current_transaction = nil
@raw_csv.each do |row|
if row["date"].length > 0
current_transaction = Transaction.new(row)
transactions << current_transaction
else
current_transaction << Detail.new(row)
end
end
transactions
end
module CsvRowWrapper
def initialize(row)
@row = row
end
def method_missing(meth, *args, &block)
if @row.headers.include?(meth.to_s)
@row[meth.to_s]
else
super
end
end
end
class Transaction
include CsvRowWrapper
extend Forwardable
def_delegator :@details, :<<
def initialize(row)
super row
@details = Details.new
end
def within(start_date, end_date)
return true if start_date.nil? && end_date.nil?
start_date, end_date = Date.parse(start_date), Date.parse(end_date)
outside_range = false
if start_date && start_date > date
outside_range = true
end
if end_date && end_date < date
outside_range = true
end
!outside_range
end
def long_description
[type, reference, details].join(" | ")
end
def details
@details.to_s
end
def date
Date.parse(@row["date"])
end
def date_string
date.strftime("%d/%m/%Y")
end
def amount
money_in - money_out
end
def money_in
to_money("money_in")
end
def money_out
to_money("money_out")
end
def balance
to_money("balance")
end
private
def to_money(column)
@row[column].gsub(/\,/,"").to_f
end
end
class Details < Array
def to_s
map(&:to_s).join(" | ")
end
end
class Detail
include CsvRowWrapper
def to_s
reference
end
end
end
require "byebug"
require "./converter"
describe MetroBankCsvConverter do
let(:converter) { MetroBankCsvConverter.new lines }
let(:lines) { <<-EOF.gsub(/^\s+/, "")
"Transaction Search Results"
"","","","","",""
"Date","Transaction Type","Reference","Currency","Money Out","Money In","Balance"
"03 DEC 2014","Outward Faster Payment","1234567","GBP","1,000,000.00","0.00","121,472,236.98"
"","","Something something","","","",""
"","","More silliness","","","",""
"","","Hopefully we handle a comma, even when it's inside a string","","","",""
"01 DEC 2014","Card Purchase","ABCDEFG","GBP","134.34","0.00","122,472,236.98"
"","","01/12/14 01:17:02","","","",""
"","","LINODE.COM","","","",""
"","","855-4546633","","","",""
"","","USD 86.84@1.54690142","","","",""
EOF
}
it do
expect(converter.columns).to eq ["date","type","reference","currency","money_out","money_in","balance"]
expect(converter.transaction_count).to eq 2
expect(converter.transactions[0].date_string).to eq "03/12/2014"
end
it do
transaction = converter.transactions[1]
expect(transaction.date).to eq Date.parse("2014/12/01")
expect(transaction.date_string).to eq "01/12/2014"
expect(transaction.type).to eq "Card Purchase"
expect(transaction.reference).to eq "ABCDEFG"
expect(transaction.currency).to eq "GBP"
expect(transaction.amount).to eq -134.34
expect(transaction.money_out).to eq 134.34
expect(transaction.money_in).to eq 0.00
expect(transaction.balance).to eq 122_472_236.98
expect(transaction.details).to eq [
"01/12/14 01:17:02",
"LINODE.COM",
"855-4546633",
"USD 86.84@1.54690142"
].join(" | ")
expect(converter.transactions[0].details).to eq "Something something | More silliness | Hopefully we handle a comma, even when it's inside a string"
end
it "Transaction#long_description" do
long_description = converter.transactions[0].long_description
expect(long_description).to eq "Outward Faster Payment | 1234567 | Something something | More silliness | Hopefully we handle a comma, even when it's inside a string"
end
describe "#to_freeagent_csv" do
it "returns all transactions with no args" do
csv = converter.to_freeagent_csv
expect(csv).to eq <<-CSV.gsub(/^\s+/,"")
03/12/2014,-1000000.0,"Outward Faster Payment | 1234567 | Something something | More silliness | Hopefully we handle a comma, even when it's inside a string"
01/12/2014,-134.34,Card Purchase | ABCDEFG | 01/12/14 01:17:02 | LINODE.COM | 855-4546633 | USD 86.84@1.54690142
CSV
end
it "accepts an inclusive date range" do
csv = converter.to_freeagent_csv("2014/12/01", "2014/12/01")
expect(csv).to eq <<-CSV.gsub(/^\s+/,"")
01/12/2014,-134.34,Card Purchase | ABCDEFG | 01/12/14 01:17:02 | LINODE.COM | 855-4546633 | USD 86.84@1.54690142
CSV
csv = converter.to_freeagent_csv("2014/12/03", "2014/12/03")
expect(csv).to eq <<-CSV.gsub(/^\s+/,"")
03/12/2014,-1000000.0,"Outward Faster Payment | 1234567 | Something something | More silliness | Hopefully we handle a comma, even when it's inside a string"
CSV
csv = converter.to_freeagent_csv("2014/12/01", "2014/12/03")
expect(csv).to eq <<-CSV.gsub(/^\s+/,"")
03/12/2014,-1000000.0,"Outward Faster Payment | 1234567 | Something something | More silliness | Hopefully we handle a comma, even when it's inside a string"
01/12/2014,-134.34,Card Purchase | ABCDEFG | 01/12/14 01:17:02 | LINODE.COM | 855-4546633 | USD 86.84@1.54690142
CSV
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment