Skip to content

Instantly share code, notes, and snippets.

@cball
Last active December 14, 2015 23:39
Show Gist options
  • Save cball/5167057 to your computer and use it in GitHub Desktop.
Save cball/5167057 to your computer and use it in GitHub Desktop.
Here's a simple script to migrate Freeagent invoice data -> Freshbooks. Putting this out there in case it helps someone else.
# gem install ruby-freshbooks
# https://github.com/elucid/ruby-freshbooks
#
# * Limitations *
# - Freshbooks Invoice #'s can only be 10 characters
# - requires creating clients ahead of time (though this would be easy for someone to add)
# - client names must match exactly
#
# * Usage *
# require 'freshbooks_importer'
# freshbooks_importer = FreshbooksImporter.new
# freshbooks_importer.import_invoices
require 'ruby-freshbooks'
require 'csv'
class FreshbooksImporter
attr_accessor :connection
attr_accessor :current_invoice
attr_accessor :clients
def initialize
self.connection = FreshBooks::Client.new('your-url.freshbooks.com', FRESHBOOKS_API_KEY)
$invoices = 1
end
def import_invoices(csv='invoices.csv')
CSV.foreach(csv, headers: true) do |row|
if !row['Contact'].nil?
create_invoice row
elsif !current_invoice.nil?
append_line_item_to_current_invoice row
else
puts 'no current invoice for line item.'
end
end
# save the final invoice
save_current_invoice
end
private
def create_invoice(row)
save_current_invoice
client = freshbooks_client(row['Contact'])
build_invoice_for_client(client, row)
end
def build_invoice_for_client(client, row)
if !client.nil?
puts "building invoice for client #{client['organization']}..."
self.current_invoice = Invoice.new(connection, row)
current_invoice.freshbooks_client_id = client['client_id']
end
end
def save_current_invoice
if !current_invoice.nil?
current_invoice.save
end
end
def append_line_item_to_current_invoice(row)
line_item = LineItem.new row
current_invoice.line_items << line_item
end
def freshbooks_client(client_name)
client = freshbooks_client_list.detect { |c| c['organization'] == client_name }
if !client.nil?
client
else
puts "client #{client_name} not found!"
nil
end
end
def freshbooks_client_list
self.clients ||= fetch_freshbooks_client_list
end
def fetch_freshbooks_client_list
list = connection.client.list
list['clients']['client']
end
end
class Invoice
attr_accessor :contact, :reference, :date, :status, :net_amount, :line_items, :freshbooks_client_id, :connection
def initialize(connection, row)
super()
self.connection = connection
set_attributes(row)
self.line_items = []
end
def save
puts "ready to create with: #{attributes_for_freshbooks.inspect}"
new_invoice = connection.invoice.create(invoice: attributes_for_freshbooks)
if new_invoice && !new_invoice['id'].nil?
$invoices += 1
puts "invoice #{id} created!"
end
end
def attributes_for_freshbooks
{
client_id: freshbooks_client_id,
number: shorten_invoice_number(reference),
status: status.downcase,
date: freshbooks_date,
lines: line_items.map(&:attributes_for_freshbooks)
}
end
private
def set_attributes(row)
self.contact = row['Contact']
self.reference = row['Reference']
self.date = row['Date']
self.net_amount = row['Net Amount']
self.status = row['Status']
end
def shorten_invoice_number(invoice_number)
without_digits = invoice_number.sub(/(\d+)/,'').strip
number = $1
abbreviation = without_digits.split(/\s+|_/).map{|s| s[0]}.join
"#{abbreviation}#{number || $invoices}"
end
def freshbooks_date
# convert free agent date -> standard
Date.strptime(date, '%d %B %Y').to_s
end
end
class LineItem
attr_accessor :type, :quantity, :price, :description, :subtotal
def initialize(row)
super()
set_attributes(row)
end
def attributes_for_freshbooks
{
line: {
description: description,
unit_cost: price,
quantity: quantity,
type: freshbooks_type
}
}
end
private
def set_attributes(row)
self.type = row['Item Type']
self.quantity = row['Quantity']
self.price = row['Price']
self.description = row['Description']
self.subtotal = row['Subtotal']
end
def freshbooks_type
if type == 'Hours'
'Time'
else
'Item'
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment