Created
January 8, 2010 18:46
-
-
Save lukeredpath/272275 to your computer and use it in GitHub Desktop.
Generate a HTML expense report using the FreeAgent API
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
# this goes in ~/.freeagent | |
ENV['FA_COMPANY'] = 'mycompany' | |
ENV['FA_USERNAME'] = 'myloginemail' | |
ENV['FA_PASSWORD'] = 'mypassword' |
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
<html> | |
<head> | |
<title>Expense Report for Luke Redpath (<%= label %>)</title> | |
<style> | |
<!-- | |
body { | |
margin: 0; padding: 50px; | |
font-family: Helvetica, Arial; | |
} | |
h1 { | |
font-weight: normal; | |
padding-top: 20px; | |
margin-bottom: 50px; | |
} | |
#logo { | |
position: absolute; | |
top: 50px; right: 50px; | |
} | |
table { | |
width: 100%; | |
border-collapse: collapse; | |
border-bottom: 2px solid #555; | |
margin-bottom: 30px; | |
} | |
th, td { | |
padding: 8px 3px; | |
} | |
th { | |
text-align: left; | |
border-bottom: 2px solid #555; | |
} | |
td { | |
border-bottom: 1px solid #ccc; | |
} | |
.numeric { | |
text-align: right; | |
} | |
tfoot td, tfoot th { | |
border-top: 2px solid #555; | |
background: #eee; | |
} | |
tfoot th { | |
text-align: right; | |
} | |
--> | |
</style> | |
</head> | |
<body> | |
<h1>Expense Report for Luke Redpath, <%= label %></h1> | |
<div id="logo"> | |
<img src="/Users/luke/Documents/Business/Branding/logo-small.png" width="200" height="52" alt="Logo Small"> | |
</div> | |
<table> | |
<thead> | |
<tr> | |
<th>Date</th> | |
<th>Description</th> | |
<th>Category</th> | |
<th>Appears on P11D?</th> | |
<th class="numeric">Total (GBP)</th> | |
</tr> | |
</thead> | |
<tbody> | |
<% expenses.sort_by(&:dated_on).each do |expense| %> | |
<tr> | |
<td><%= expense.dated_on.to_date.to_formatted_s(:short) %></td> | |
<td><%= expense.description %></td> | |
<td><%= expense.expense_type %></td> | |
<td></td> | |
<td class="numeric"><%= "%.2f" % -expense.gross_value %></td> | |
</tr> | |
<% end %> | |
</tbody> | |
<tfoot> | |
<tr> | |
<th colspan="4">Total</th> | |
<td class="numeric"><%= "%.2f" % -expenses.sum(&:gross_value) %></td> | |
</tr> | |
</tfoot> | |
</table> | |
<p><em>Generated at <%= Time.now %></em></p> | |
</body> | |
</html> |
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
#!/usr/bin/env ruby | |
require File.join(File.dirname(__FILE__), *%w[freeagent freeagent]) | |
require 'ostruct' | |
require 'erb' | |
require 'tempfile' | |
MY_USER_ID = XXXX | |
freeagent = FreeAgent::Company.new(ENV['FA_COMPANY'], ENV['FA_USERNAME'], ENV["FA_PASSWORD"]) | |
class ExpensesReport | |
def initialize(expenses, label) | |
@expenses = expenses | |
@label = label | |
end | |
def to_s | |
output = @label.upcase + "\n" + separator + "\n" | |
grouped_expenses.map do |category, expenses| | |
output << "#{category.ljust(60)}#{(-expenses.sum(&:gross_value)).to_s.rjust(10)}\n" | |
end | |
output << separator + "\n" | |
output | |
end | |
def to_html | |
vars = OpenStruct.new(:expenses => @expenses, :label => @label) | |
ERB.new(File.read(template_path)).result(vars.send(:binding)) | |
end | |
private | |
def separator | |
70.times.inject('') { |buff, x| buff + '-' } | |
end | |
def grouped_expenses | |
@grouped_expenses ||= @expenses.group_by(&:expense_type) | |
end | |
def template_path | |
File.join(File.dirname(__FILE__), *%w[freeagent templates expense_report.erb.html]) | |
end | |
end | |
report = case ARGV[0] | |
when 'last' | |
ExpensesReport.new(freeagent.expenses(MY_USER_ID, | |
:from => Date.today.last_month.beginning_of_month, | |
:to => Date.today.last_month.end_of_month | |
).find_all, "#{Date::MONTHNAMES[Date.today.last_month.month]} #{Date.today.last_month.year}") | |
else | |
ExpensesReport.new(freeagent.expenses(MY_USER_ID, | |
:from => Date.today.beginning_of_month, | |
:to => Date.today.end_of_month | |
).find_all, "#{Date::MONTHNAMES[Date.today.month]} #{Date.today.year}") | |
end | |
temp_path = "/tmp/expense_report_#{Time.now.to_i}.html" | |
File.open(temp_path, 'w+') do |io| | |
io.write(report.to_html) | |
end | |
system("open -a Safari #{temp_path}") | |
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
require 'rubygems' | |
require 'restclient' | |
require 'crack' | |
require 'mash' | |
require 'active_support/all' | |
RestClient::Resource.class_eval do | |
def root | |
self.class.new(URI.parse(url).merge('/').to_s, options) | |
end | |
end | |
module FreeAgent | |
class Company | |
def initialize(domain, username, password) | |
@resource = RestClient::Resource.new( | |
"https://#{domain}.freeagentcentral.com", | |
:user => username, :password => password | |
) | |
end | |
def invoices | |
@invoices ||= Collection.new(@resource['invoices'], :entity => :invoice) | |
end | |
def contacts | |
@contacts ||= Collection.new(@resource['contacts'], :entity => :contact) | |
end | |
def expenses(user_id, options={}) | |
options.assert_valid_keys(:view, :from, :to) | |
options.reverse_merge!(:view => 'recent') | |
if options[:from] && options[:to] | |
options[:view] = "#{options[:from].strftime('%Y-%m-%d')}_#{options[:to].strftime('%Y-%m-%d')}" | |
end | |
Collection.new(@resource["users/#{user_id}/expenses?view=#{options[:view]}"], :entity => :expense) | |
end | |
end | |
class Collection | |
def initialize(resource, options={}) | |
@resource = resource | |
@entity = options.delete(:entity) | |
end | |
def url | |
@resource.url | |
end | |
def find(id) | |
entity_for_id(id).reload | |
end | |
def find_all | |
case (response = @resource.get).code | |
when 200 | |
if entities = Crack::XML.parse(response.body)[@entity.to_s.pluralize] | |
entities.map do |attributes| | |
entity_for_id(attributes['id'], attributes) | |
end | |
else | |
[] | |
end | |
end | |
end | |
def create(attributes) | |
payload = attributes.to_xml(:root => @entity.to_s ) | |
case (response = @resource.post(payload, | |
:content_type => 'application/xml', :accept => 'application/xml')).code | |
when 201 | |
resource_path = URI.parse(response.headers[:location]).path | |
Entity.from(@resource.root[resource_path], @entity) | |
end | |
end | |
def update(id, attributes) | |
entity_for_id(id).update(attributes, headers) | |
end | |
def destroy(id) | |
entity_for_id(id).destroy | |
end | |
private | |
def entity_for_id(id, attributes={}) | |
Entity.from(@resource[id], @entity, attributes) | |
end | |
end | |
class Entity | |
attr_reader :attributes | |
def initialize(resource, entity, attributes = {}) | |
@resource, @entity = resource, entity | |
@attributes = attributes.to_mash | |
end | |
def self.from(resource, entity, attributes = {}) | |
klass = begin | |
FreeAgent.const_get(entity.to_s.classify) | |
rescue NameError | |
self | |
end | |
klass.new(resource, entity, attributes) | |
end | |
def url | |
@resource.url | |
end | |
def get(options) | |
@resource.get(options) | |
end | |
def collection(path, entity) | |
Collection.new(@resource[path], :entity => entity) | |
end | |
def reload | |
returning(self) do | |
@attributes = Crack::XML.parse(@resource.get)[@entity.to_s].to_mash | |
end | |
end | |
def update(attributes = {}) | |
@resource.put(attributes.to_xml(:root => @entity.to_s.downcase), | |
:content_type =>'application/xml', :accept => 'application/xml') | |
end | |
def destroy | |
@resource.delete | |
end | |
def id | |
@attributes["id"] | |
end | |
private | |
def method_missing(*args) | |
@attributes.send(*args) | |
end | |
end | |
class Invoice < Entity | |
def mark_as_sent! | |
@resource["mark_as_sent"].put("", | |
:content_type =>'application/xml', :accept => 'application/xml') | |
end | |
end | |
end |
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
#!/usr/bin/env ruby | |
# all of my FreeAgent scripts use ENV vars to grab the login credentials | |
# I wrap things up in a small bin wrapper script like this which loads in | |
# ~/.freeagent to set the ENV vars followed by the script itself. | |
load('~/.freeagent') | |
load('~/Code/mine/utilities/expenses_report.rb') |
I assume the column for P11D is information you input manually?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I made a few minor changes, but works now, thank you :)