Last active
December 14, 2015 23:39
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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