Updated 2009-04-26: added video of the talk
Yesterday was a busy day: in the morning I released the new version 0.2 of the invoicing gem, and in the evening I presented a live demo of the latest version at the London Ruby User Group.
The format of my talk was an attempt to replicate a screencast, but given completely live. The result was pretty hilarious, and Skills Matter, who host the LRUG events, kindly recorded the talk and put it online:
Since I didn’t have any slides for the talk I will simply share the notes which I wrote for myself in preparation. You might be able to use them to follow along with what I was doing.
Roadmap:
script/generate invoicing_ledger
– render ledger/statement/invoicescript/generate invoicing_taxable
– support European VAT out of the boxCracktastic is a simple and contrived example Rails app into which we want to integrate the invoicing gem. Check out the head of the master
branch. Start with a clean database and run rake db:migrate
.
Invoke generator:
script/generate invoicing_ledger billing --currency=GBP
rake db:migrate
Manually edit app/models/billing/ledger_item.rb
:
belongs_to :sender, :class_name => 'Company'
belongs_to :recipient, :class_name => 'Company'
def sender_details
sender.attributes.symbolize_keys
end
def recipient_details
recipient.attributes.symbolize_keys
end
Add new model classes:
module Billing
class SubscriptionInvoice < Invoice
def initialize(*args)
super
self.status ||= 'open'
self.period_start ||= Time.now.utc.at_beginning_of_month
self.period_end ||= period_start.next_month.at_beginning_of_month
self.issue_date ||= period_end
self.due_date ||= period_end + 14.days
self.description ||= "Cracktastic subscription for " +
period_start.strftime('%B %Y')
end
end
end
module Billing
class MonthlySubscriptionCharge < LineItem
before_validation :calculate_tax
def initialize(*args)
super
self.tax_point ||= Time.now.utc
self.description ||= "Monthly subscription, standard package"
end
def calculate_tax
self.tax_amount = 0.15*net_amount
end
end
end
restful_authentication
Edit generated billing_controller.rb
:
before_filter :login_required
def index
redirect_to ledger_url(current_user.company)
end
def ledger
raise ActiveRecord::RecordNotFound unless current_user.company_id.to_s == params[:id]
#...
end
def statement
raise ActiveRecord::RecordNotFound unless current_user.company_id.to_s == params[:id]
#...
end
def document
@ledger_item = Billing::LedgerItem.sent_or_received_by(current_user.company_id).find(params[:id])
#...
end
Add to main menu in app/views/layouts/application.html.erb
:
<% if logged_in? -%>
<li><%= link_to 'Billing', ledger_path(current_user.company) %></li>
<% end -%>
Open up script/console
:
ourselves = Company.find(1)
customer = Company.find(2)
# Closed invoice for last month
inv = Billing::SubscriptionInvoice.new(
:period_start => Time.now.utc.last_month.at_beginning_of_month)
inv.sender = ourselves
inv.recipient = customer
inv.line_items << Billing::MonthlySubscriptionCharge.new(:net_amount => 99.95)
inv.save!
inv.status = 'closed'
inv.save!
# Payment for last month
pay = Billing::Payment.new :total_amount => 114.94, :status => 'cleared',
:description => 'Credit card payment', :issue_date => 3.days.ago.utc
pay.sender = ourselves
pay.recipient = customer
pay.save!
# Open invoice for current month
inv = Billing::SubscriptionInvoice.new
inv.sender = ourselves
inv.recipient = customer
inv.line_items << Billing::MonthlySubscriptionCharge.new(:net_amount => 99.95)
inv.save!
Then reload the mongrel and open /billing
in the app to see the results.
ourselves = Company.find(1)
customer = Company.find(2)
note = Billing::CreditNote.new(
:period_start => Time.now.utc.last_month.at_beginning_of_month,
:period_end => Time.now.utc.at_beginning_of_month,
:issue_date => Time.now.utc,
:description => 'Refund', :status => 'closed')
note.sender = ourselves
note.recipient = customer
note.line_items << Billing::MonthlySubscriptionCharge.new(
:net_amount => -50, :description => 'Special half-price offer')
note.save!
Note the negative amount on the line item.
In script/console
:
inv = Billing::Invoice.new :currency => 'USD'
line = Billing::LineItem.new :net_amount => 12.34
inv.line_items << line
inv.save!
line.net_amount.to_s
# => "12.34"
line.net_amount_formatted
# => "$12.34"
line.tax_amount = 0.15 * line.net_amount
# => 1.851
line.tax_amount.to_s
# => "1.85" <==== automatically rounded to 0.01 precision
line.tax_amount_formatted
# => "$1.85"
inv.currency = 'JPY' # Smallest currency unit in Japanese yen is 1
line.net_amount_formatted :symbol => 'JPY'
# => "12 JPY" <==== automatically rounded to integer
line.tax_amount_formatted :symbol => 'JPY'
# => "2 JPY"
You can also leave away the :symbol => 'JPY'
bit, in which case it will be rendered as “¥12” and “¥2” respectively.
In script/console
:
ourselves = Company.find(1)
customer = Company.find(2)
inv = Billing::SubscriptionInvoice.new(
:currency => 'JPY', :status => 'closed', :description => 'Commission charges')
inv.sender = customer
inv.recipient = ourselves
inv.line_items << Billing::MonthlySubscriptionCharge.new(
:net_amount => 23, :description => 'Referral commission')
inv.save!
See the results in a web browser. Also demonstrates customer
charging ourselves
, i.e. acting as supplier to us.
Append .xml
to the URL of an invoice document.
NOTE: This is a feature from the upcoming 0.3 release of the gem. It will not work with the version currently released to Rubyforge.
Run the generator:
script/generate invoicing_taxable UK
rake db:migrate
In file app/models/billing/line_item.rb
, add:
belongs_to :tax_rate
acts_as_taxable :net_amount, :tax_logic => Invoicing::Countries::UK::VAT.new
def initialize(*args)
super
self.tax_point ||= Time.now.utc
self.tax_rate ||= TaxRate.default_record_at(tax_point)
end
From app/models/billing/monthly_subscription_charge.rb
, remove the calculate_tax
stuff.
In script/console
:
ourselves = Company.find(1)
customer = Company.find(2)
inv = Billing::SubscriptionInvoice.new :period_start => '2008-11-01', :status => 'closed'
inv.sender = ourselves
inv.recipient = customer
nov = Billing::LineItem.new(:description => 'random 1',
:net_amount => 10, :tax_point => '2008-11-30')
dec = Billing::LineItem.new(:description => 'random 2',
:net_amount => 10, :tax_point => '2008-12-01')
inv.line_items << nov
inv.line_items << dec
inv.save!
nov.amount_formatted
# => "10.00"
nov.amount_taxed_formatted
# => "11.75"
nov.amount_with_tax_info
# => "11.75 (inc. VAT)"
nov.amount_with_tax_details
# => "11.75 (including VAT at 17.5%)"
dec.amount_formatted
# => "10.00"
dec.amount_taxed_formatted
# => "11.50"
dec.amount_with_tax_info
# => "11.50 (inc. VAT)"
dec.amount_with_tax_details
# => "11.50 (including VAT at 15%)"
Open /thankyou
in web browser to show my contact details on the projector.
If you’re thinking of using the invoicing gem, or have any questions, or want to submit patches, or anything… let me know!
Please help me spread the word, tag your tweets with #invgem
, subscribe to the invoicing gem feed, give the gem a try, browse the docs and the source, and let me know what you think!