<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <title>Ruby Invoicing Framework</title>
    <link href="http://feeds2.feedburner.com/invoicing" rel="self"/>
    <link href="http://ept.github.com/invoicing/"/>
    <updated>2009-07-22T09:55:51-07:00</updated>
    <id>http://ept.github.com/invoicing/</id>
    <author>
        <name>Martin Kleppmann</name>
        <email>martin@eptcomputing.com</email>
    </author>

    
        <entry>
            <title>Ruby Invoicing Gem version 0.2 — Now with generator</title>
            <link href="http://ept.github.com/invoicing/2009/04/21/invoicing-0-2-generator.html"/>
            <updated>2009-04-21T00:00:00-07:00</updated>
            <id>http://ept.github.com/invoicing/2009/04/21/invoicing-0-2-generator</id>
            <content type="html">&lt;p&gt;&lt;strong&gt;Updated 2009-04-26: added video of the talk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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 &lt;a href='http://lrug.org/meetings/2009/03/23/april-2009-meeting/'&gt;London Ruby User Group&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The format of my talk was an attempt to replicate a screencast, but given completely live. The result was pretty hilarious, and &lt;a href='http://skillsmatter.com/'&gt;Skills Matter&lt;/a&gt;, who host the LRUG events, kindly &lt;a href='http://skillsmatter.com/podcast/ajax-ria/invoicing-gem'&gt;recorded the talk and put it online&lt;/a&gt;:&lt;/p&gt;
&lt;object height='512px' width='550px'&gt;
&lt;param name='allowfullscreen' value='true' /&gt;
&lt;param name='allowscriptaccess' value='always' /&gt;
&lt;param name='movie' value='http://vimeo.com/moogaloop.swf?clip_id=4279902&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1' /&gt;
&lt;embed src='http://vimeo.com/moogaloop.swf?clip_id=4279902&amp;amp;server=vimeo.com&amp;amp;show_title=1&amp;amp;show_byline=1&amp;amp;show_portrait=0&amp;amp;color=00ADEF&amp;amp;fullscreen=1' type='application/x-shockwave-flash' allowfullscreen='true' allowscriptaccess='always' height='512px' width='550px'&gt;
&lt;/embed&gt;
&lt;/object&gt;
&lt;p&gt;Since I didn&amp;#8217;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.&lt;/p&gt;

&lt;h2 id='about_the_invoicing_gem'&gt;About the invoicing gem&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Commercial app: start with paypal; gets complicated soon &amp;#8211; VAT, resellers/affiliates, &amp;#8230;&lt;/li&gt;

&lt;li&gt;You don&amp;#8217;t parse HTTP headers yourself; you shouldn&amp;#8217;t need to juggle credits+debits yourself&lt;/li&gt;

&lt;li&gt;Developer friendly AND accountant-friendly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;v0.1: get basics right (core ledger, multi-currency, time-dependent values)&lt;/li&gt;

&lt;li&gt;v0.2: &lt;code&gt;script/generate invoicing_ledger&lt;/code&gt; &amp;#8211; render ledger/statement/invoice&lt;/li&gt;

&lt;li&gt;v0.3: &lt;code&gt;script/generate invoicing_taxable&lt;/code&gt; &amp;#8211; support European VAT out of the box&lt;/li&gt;

&lt;li&gt;v0.4: higher-level tools (subscriptions, price plans, affiliate programmes)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id='cracktastic_preparation'&gt;Cracktastic preparation&lt;/h2&gt;

&lt;p&gt;&lt;a href='http://github.com/ept/cracktastic'&gt;Cracktastic&lt;/a&gt; is a simple and contrived example Rails app into which we want to integrate the invoicing gem. Check out the head of the &lt;code&gt;master&lt;/code&gt; branch. Start with a clean database and run &lt;code&gt;rake db:migrate&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id='set_up_ledger'&gt;Set up ledger&lt;/h2&gt;

&lt;p&gt;Invoke generator:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;script/generate invoicing_ledger billing --currency=GBP
rake db:migrate&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Manually edit &lt;code&gt;app/models/billing/ledger_item.rb&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;belongs_to&lt;/span&gt; &lt;span class='ss'&gt;:sender&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:class_name&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;Company&amp;#39;&lt;/span&gt;
&lt;span class='n'&gt;belongs_to&lt;/span&gt; &lt;span class='ss'&gt;:recipient&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:class_name&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;Company&amp;#39;&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;sender_details&lt;/span&gt;
  &lt;span class='n'&gt;sender&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;symbolize_keys&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;recipient_details&lt;/span&gt;
  &lt;span class='n'&gt;recipient&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;attributes&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;symbolize_keys&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='create_custom_invoice_type'&gt;Create custom invoice type&lt;/h2&gt;

&lt;p&gt;Add new model classes:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='k'&gt;module&lt;/span&gt; &lt;span class='nn'&gt;Billing&lt;/span&gt;
  &lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;SubscriptionInvoice&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&lt;/span&gt; &lt;span class='no'&gt;Invoice&lt;/span&gt;
    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;initialize&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;args&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='k'&gt;super&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;status&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;open&amp;#39;&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;period_start&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;utc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;at_beginning_of_month&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;period_end&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='n'&gt;period_start&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;next_month&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;at_beginning_of_month&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;issue_date&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='n'&gt;period_end&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;due_date&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='n'&gt;period_end&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt; &lt;span class='mi'&gt;14&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;days&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;description&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Cracktastic subscription for &amp;quot;&lt;/span&gt; &lt;span class='o'&gt;+&lt;/span&gt;
        &lt;span class='n'&gt;period_start&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;strftime&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='s1'&gt;&amp;#39;%B %Y&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='k'&gt;module&lt;/span&gt; &lt;span class='nn'&gt;Billing&lt;/span&gt;
  &lt;span class='k'&gt;class&lt;/span&gt; &lt;span class='nc'&gt;MonthlySubscriptionCharge&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&lt;/span&gt; &lt;span class='no'&gt;LineItem&lt;/span&gt;
    &lt;span class='n'&gt;before_validation&lt;/span&gt; &lt;span class='ss'&gt;:calculate_tax&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;initialize&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;args&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
      &lt;span class='k'&gt;super&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;tax_point&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;utc&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;description&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='s2'&gt;&amp;quot;Monthly subscription, standard package&amp;quot;&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;

    &lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;calculate_tax&lt;/span&gt;
      &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;tax_amount&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='mi'&gt;15&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;net_amount&lt;/span&gt;
    &lt;span class='k'&gt;end&lt;/span&gt;
  &lt;span class='k'&gt;end&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='integrate_with_'&gt;Integrate with &lt;code&gt;restful_authentication&lt;/code&gt;&lt;/h2&gt;

&lt;p&gt;Edit generated &lt;code&gt;billing_controller.rb&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;before_filter&lt;/span&gt; &lt;span class='ss'&gt;:login_required&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;index&lt;/span&gt;
  &lt;span class='n'&gt;redirect_to&lt;/span&gt; &lt;span class='n'&gt;ledger_url&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;current_user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;company&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
    
&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;ledger&lt;/span&gt;
  &lt;span class='k'&gt;raise&lt;/span&gt; &lt;span class='no'&gt;ActiveRecord&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;RecordNotFound&lt;/span&gt; &lt;span class='k'&gt;unless&lt;/span&gt; &lt;span class='n'&gt;current_user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;company_id&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='n'&gt;params&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
  &lt;span class='c1'&gt;#...&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;statement&lt;/span&gt;
  &lt;span class='k'&gt;raise&lt;/span&gt; &lt;span class='no'&gt;ActiveRecord&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;RecordNotFound&lt;/span&gt; &lt;span class='k'&gt;unless&lt;/span&gt; &lt;span class='n'&gt;current_user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;company_id&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt; &lt;span class='o'&gt;==&lt;/span&gt; &lt;span class='n'&gt;params&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;
  &lt;span class='c1'&gt;#...&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;document&lt;/span&gt;
  &lt;span class='vi'&gt;@ledger_item&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;LedgerItem&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sent_or_received_by&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;current_user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;company_id&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;params&lt;/span&gt;&lt;span class='o'&gt;[&lt;/span&gt;&lt;span class='ss'&gt;:id&lt;/span&gt;&lt;span class='o'&gt;]&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='c1'&gt;#...&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Add to main menu in &lt;code&gt;app/views/layouts/application.html.erb&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='cp'&gt;&amp;lt;%&lt;/span&gt; &lt;span class='k'&gt;if&lt;/span&gt; &lt;span class='n'&gt;logged_in?&lt;/span&gt; &lt;span class='cp'&gt;-%&amp;gt;&lt;/span&gt;&lt;span class='x' /&gt;
&lt;span class='x'&gt;  &amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class='cp'&gt;&amp;lt;%=&lt;/span&gt; &lt;span class='n'&gt;link_to&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;Billing&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='n'&gt;ledger_path&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;current_user&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;company&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt; &lt;span class='cp'&gt;%&amp;gt;&lt;/span&gt;&lt;span class='x'&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
&lt;span class='cp'&gt;&amp;lt;%&lt;/span&gt; &lt;span class='k'&gt;end&lt;/span&gt; &lt;span class='cp'&gt;-%&amp;gt;&lt;/span&gt;&lt;span class='x' /&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='create_example_records'&gt;Create example records&lt;/h2&gt;

&lt;p&gt;Open up &lt;code&gt;script/console&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;ourselves&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Company&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;customer&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Company&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='c1'&gt;# Closed invoice for last month&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;SubscriptionInvoice&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
  &lt;span class='ss'&gt;:period_start&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;utc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;last_month&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;at_beginning_of_month&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sender&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ourselves&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;recipient&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;customer&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;line_items&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;MonthlySubscriptionCharge&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:net_amount&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;99&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='mi'&gt;95&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save!&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;status&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;closed&amp;#39;&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save!&lt;/span&gt;

&lt;span class='c1'&gt;# Payment for last month&lt;/span&gt;
&lt;span class='n'&gt;pay&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Payment&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt; &lt;span class='ss'&gt;:total_amount&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;114&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='mi'&gt;94&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:status&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;cleared&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; 
  &lt;span class='ss'&gt;:description&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;Credit card payment&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:issue_date&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;3&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;days&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;ago&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;utc&lt;/span&gt;
&lt;span class='n'&gt;pay&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sender&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ourselves&lt;/span&gt;
&lt;span class='n'&gt;pay&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;recipient&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;customer&lt;/span&gt;
&lt;span class='n'&gt;pay&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save!&lt;/span&gt;

&lt;span class='c1'&gt;# Open invoice for current month&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;SubscriptionInvoice&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sender&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ourselves&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;recipient&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;customer&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;line_items&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;MonthlySubscriptionCharge&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:net_amount&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;99&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='mi'&gt;95&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save!&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Then reload the mongrel and open &lt;code&gt;/billing&lt;/code&gt; in the app to see the results.&lt;/p&gt;

&lt;h2 id='credit_notes'&gt;Credit notes&lt;/h2&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;ourselves&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Company&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;customer&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Company&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='n'&gt;note&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;CreditNote&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
  &lt;span class='ss'&gt;:period_start&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;utc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;last_month&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;at_beginning_of_month&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='ss'&gt;:period_end&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;utc&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;at_beginning_of_month&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='ss'&gt;:issue_date&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;utc&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='ss'&gt;:description&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;Refund&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:status&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;closed&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;note&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sender&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ourselves&lt;/span&gt;
&lt;span class='n'&gt;note&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;recipient&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;customer&lt;/span&gt;
&lt;span class='n'&gt;note&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;line_items&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;MonthlySubscriptionCharge&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
  &lt;span class='ss'&gt;:net_amount&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='o'&gt;-&lt;/span&gt;&lt;span class='mi'&gt;50&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:description&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;Special half-price offer&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;note&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save!&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Note the negative amount on the line item.&lt;/p&gt;

&lt;h2 id='currencyvalue_magic'&gt;CurrencyValue magic&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;script/console&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;inv&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Invoice&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt; &lt;span class='ss'&gt;:currency&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;USD&amp;#39;&lt;/span&gt;
&lt;span class='n'&gt;line&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;LineItem&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt; &lt;span class='ss'&gt;:net_amount&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;12&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='mi'&gt;34&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;line_items&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='n'&gt;line&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save!&lt;/span&gt;

&lt;span class='n'&gt;line&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;net_amount&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;12.34&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;line&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;net_amount_formatted&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;$12.34&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;line&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;tax_amount&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='mi'&gt;0&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='mi'&gt;15&lt;/span&gt; &lt;span class='o'&gt;*&lt;/span&gt; &lt;span class='n'&gt;line&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;net_amount&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; 1.851&lt;/span&gt;
&lt;span class='n'&gt;line&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;tax_amount&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;to_s&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;1.85&amp;quot;         &amp;lt;==== automatically rounded to 0.01 precision&lt;/span&gt;
&lt;span class='n'&gt;line&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;tax_amount_formatted&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;$1.85&amp;quot;&lt;/span&gt;

&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;currency&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;JPY&amp;#39;&lt;/span&gt; &lt;span class='c1'&gt;# Smallest currency unit in Japanese yen is 1&lt;/span&gt;
&lt;span class='n'&gt;line&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;net_amount_formatted&lt;/span&gt; &lt;span class='ss'&gt;:symbol&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;JPY&amp;#39;&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;12 JPY&amp;quot;       &amp;lt;==== automatically rounded to integer&lt;/span&gt;
&lt;span class='n'&gt;line&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;tax_amount_formatted&lt;/span&gt; &lt;span class='ss'&gt;:symbol&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;JPY&amp;#39;&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;2 JPY&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;You can also leave away the &lt;code&gt;:symbol =&amp;gt; &amp;#39;JPY&amp;#39;&lt;/code&gt; bit, in which case it will be rendered as &amp;#8220;¥12&amp;#8221; and &amp;#8220;¥2&amp;#8221; respectively.&lt;/p&gt;

&lt;h2 id='multicurrency_support'&gt;Multi-currency support&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;script/console&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;ourselves&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Company&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;customer&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Company&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='n'&gt;inv&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;SubscriptionInvoice&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
  &lt;span class='ss'&gt;:currency&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;JPY&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:status&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;closed&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:description&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;Commission charges&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sender&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;customer&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;recipient&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ourselves&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;line_items&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;MonthlySubscriptionCharge&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;
  &lt;span class='ss'&gt;:net_amount&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;23&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:description&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;Referral commission&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save!&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;See the results in a web browser. Also demonstrates &lt;code&gt;customer&lt;/code&gt; charging &lt;code&gt;ourselves&lt;/code&gt;, i.e. acting as supplier to us.&lt;/p&gt;

&lt;h2 id='ubl_support'&gt;UBL support&lt;/h2&gt;

&lt;p&gt;Append &lt;code&gt;.xml&lt;/code&gt; to the URL of an invoice document.&lt;/p&gt;

&lt;h2 id='automatic_tax_calculation'&gt;Automatic tax calculation&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;NOTE:&lt;/em&gt; This is a feature from the upcoming 0.3 release of the gem. It will not work with the version currently released to Rubyforge.&lt;/p&gt;

&lt;p&gt;Run the generator:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;script/generate invoicing_taxable UK
rake db:migrate&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In file &lt;code&gt;app/models/billing/line_item.rb&lt;/code&gt;, add:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;belongs_to&lt;/span&gt; &lt;span class='ss'&gt;:tax_rate&lt;/span&gt;
&lt;span class='n'&gt;acts_as_taxable&lt;/span&gt; &lt;span class='ss'&gt;:net_amount&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:tax_logic&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='no'&gt;Invoicing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;Countries&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;UK&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;VAT&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;

&lt;span class='k'&gt;def&lt;/span&gt; &lt;span class='nf'&gt;initialize&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='o'&gt;*&lt;/span&gt;&lt;span class='n'&gt;args&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
  &lt;span class='k'&gt;super&lt;/span&gt;
  &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;tax_point&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='no'&gt;Time&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;now&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;utc&lt;/span&gt;      
  &lt;span class='nb'&gt;self&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;tax_rate&lt;/span&gt; &lt;span class='o'&gt;||=&lt;/span&gt; &lt;span class='no'&gt;TaxRate&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;default_record_at&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='n'&gt;tax_point&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='k'&gt;end&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;From &lt;code&gt;app/models/billing/monthly_subscription_charge.rb&lt;/code&gt;, remove the &lt;code&gt;calculate_tax&lt;/code&gt; stuff.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;script/console&lt;/code&gt;:&lt;/p&gt;
&lt;div class='highlight'&gt;&lt;pre&gt;&lt;span class='n'&gt;ourselves&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Company&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;1&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;customer&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Company&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;find&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='mi'&gt;2&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;

&lt;span class='n'&gt;inv&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;SubscriptionInvoice&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt; &lt;span class='ss'&gt;:period_start&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;2008-11-01&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:status&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;closed&amp;#39;&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;sender&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;ourselves&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;recipient&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='n'&gt;customer&lt;/span&gt;
&lt;span class='n'&gt;nov&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;LineItem&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:description&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;random 1&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='ss'&gt;:net_amount&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:tax_point&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;2008-11-30&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;dec&lt;/span&gt; &lt;span class='o'&gt;=&lt;/span&gt; &lt;span class='no'&gt;Billing&lt;/span&gt;&lt;span class='o'&gt;::&lt;/span&gt;&lt;span class='no'&gt;LineItem&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;new&lt;/span&gt;&lt;span class='p'&gt;(&lt;/span&gt;&lt;span class='ss'&gt;:description&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;random 2&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt;
  &lt;span class='ss'&gt;:net_amount&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='mi'&gt;10&lt;/span&gt;&lt;span class='p'&gt;,&lt;/span&gt; &lt;span class='ss'&gt;:tax_point&lt;/span&gt; &lt;span class='o'&gt;=&amp;gt;&lt;/span&gt; &lt;span class='s1'&gt;&amp;#39;2008-12-01&amp;#39;&lt;/span&gt;&lt;span class='p'&gt;)&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;line_items&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='n'&gt;nov&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;line_items&lt;/span&gt; &lt;span class='o'&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class='n'&gt;dec&lt;/span&gt;
&lt;span class='n'&gt;inv&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;save!&lt;/span&gt;

&lt;span class='n'&gt;nov&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;amount_formatted&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;10.00&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;nov&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;amount_taxed_formatted&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;11.75&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;nov&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;amount_with_tax_info&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;11.75 (inc. VAT)&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;nov&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;amount_with_tax_details&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;11.75 (including VAT at 17.5%)&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;dec&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;amount_formatted&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;10.00&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;dec&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;amount_taxed_formatted&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;11.50&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;dec&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;amount_with_tax_info&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;11.50 (inc. VAT)&amp;quot;&lt;/span&gt;
&lt;span class='n'&gt;dec&lt;/span&gt;&lt;span class='o'&gt;.&lt;/span&gt;&lt;span class='n'&gt;amount_with_tax_details&lt;/span&gt;
&lt;span class='c1'&gt;# =&amp;gt; &amp;quot;11.50 (including VAT at 15%)&amp;quot;&lt;/span&gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id='and_thats_it'&gt;And that&amp;#8217;s it&lt;/h2&gt;

&lt;p&gt;Open &lt;code&gt;/thankyou&lt;/code&gt; in web browser to show my contact details on the projector.&lt;/p&gt;

&lt;p&gt;If you&amp;#8217;re thinking of using the invoicing gem, or have any questions, or want to submit patches, or anything&amp;#8230; &lt;a href='http://www.yes-no-cancel.co.uk/contact/'&gt;let me know&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Please help me spread the word, tag your tweets with &lt;code&gt;#invgem&lt;/code&gt;, &lt;a href='http://feeds2.feedburner.com/invoicing'&gt;subscribe to the invoicing gem feed&lt;/a&gt;, &lt;a href='http://ept.github.com/invoicing/getting_started.html'&gt;give the gem a try&lt;/a&gt;, browse &lt;a href='http://invoicing.rubyforge.org/doc/'&gt;the docs&lt;/a&gt; and &lt;a href='http://github.com/ept/invoicing/'&gt;the source&lt;/a&gt;, and &lt;a href='http://www.yes-no-cancel.co.uk/contact/'&gt;let me know what you think&lt;/a&gt;!&lt;/p&gt;
&lt;div class='footer'&gt;
Martin Kleppmann is a web developer and entrepreneur based in Cambridge, UK.
He has a blog at &lt;a href='http://www.yes-no-cancel.co.uk/'&gt;Yes/No/Cancel&lt;/a&gt;,
is known as &lt;a href='http://twitter.com/martinkl'&gt;@martinkl&lt;/a&gt; on Twitter, and his
company is called &lt;a href='http://www.eptcomputing.com/'&gt;Ept Computing&lt;/a&gt;.
&lt;/div&gt;</content>
        </entry>
    
        <entry>
            <title>Ruby Invoicing Gem version 0.1 released</title>
            <link href="http://ept.github.com/invoicing/2009/02/12/ruby-invoicing-gem-version-0-1-released.html"/>
            <updated>2009-02-12T00:00:00-08:00</updated>
            <id>http://ept.github.com/invoicing/2009/02/12/ruby-invoicing-gem-version-0-1-released</id>
            <content type="html">&lt;p&gt;Here it is, the first public release of the Ruby Invoicing Framework gem! In case you don&amp;#8217;t know what it is, &lt;a href='http://ept.github.com/invoicing'&gt;check the website&lt;/a&gt; &amp;#8211; in a nutshell, it is a bunch of tools and a structure which can be used inside commercial (web) applications to handle billing of customers, tax calculations, and other financial matters.&lt;/p&gt;

&lt;p&gt;The core of the invoicing gem was born in mid-2008 while I was working on the Rails app &lt;a href='http://www.bidforwine.co.uk/'&gt;Bid for Wine&lt;/a&gt;, an eBay-style auction site specifically for wine, alongside Patrick Dietrich, Conrad Irwin and Michael Arnold. Our client required a very flexible invoicing system for the site: not only should the site be able to invoice sellers for listing fees and commissions, but sellers should also be able to send invoices to their buyers, directly via the site. Some sellers are VAT registered while others are not, which adds another dimension. What&amp;#8217;s more, due to UK alcohol trade licensing legislation, some transactions must be handled with Bid for Wine as intermediary &amp;#8211; i.e. the seller invoices Bid for Wine, and Bid for Wine invoices the buyer. Add in-bond wine to the mix (wine on which duty has not yet been paid &amp;#8211; it is stored in a special warehouse and the taxman gets his money when you take it out of the warehouse), and you end up with a massively confusing array of different transactions.&lt;/p&gt;

&lt;p&gt;Natural, therefore, that we would build a system which was flexible and generalised enough to cope with a wide variety of different transaction types.&lt;/p&gt;

&lt;p&gt;Since &lt;a href='http://www.yes-no-cancel.co.uk/2008/11/01/bid-for-wine-is-up-and-running/'&gt;Bid for Wine launched in November 2008&lt;/a&gt;, I have found myself wanting to use such a framework for financial transactions in other applications too. I realised that even if you have a simple web app with, say, a monthly subscription model, you will inevitably end up adding complexity over time: for example, if you want to partner with resellers (and pay them a commission, or invoice them at a preferential rate), if you want to trade in other currencies, and of course you may have to implement whatever new tax regulations the political forces may dream up.&lt;/p&gt;

&lt;p&gt;I knew &lt;em&gt;nothing&lt;/em&gt; about bookkeeping and accountancy until a year or so ago, when I started doing the accounts for &lt;a href='http://www.eptcomputing.com/'&gt;my company&lt;/a&gt;. It was a fairly painful process, because I am very much a developer and a computer scientist, and the accountants&amp;#8217; way of thinking first made no sense to me whatsoever. The principles of &lt;a href='http://en.wikipedia.org/wiki/Accounting_methods#Accrual_basis'&gt;accrual accounting&lt;/a&gt; and &lt;a href='http://en.wikipedia.org/wiki/Double-entry_bookkeeping_system'&gt;double-entry bookkeeping&lt;/a&gt; are perfectly sensible and sound, but the terminology and practical implementation I found just plain confusing.&lt;/p&gt;

&lt;p&gt;After learning the basics of accounting, I still find it dead boring. And that is really the reason why I took the Bid for Wine code in January 2009 and started extracting a general-purpose finance handling framework out of it: I wanted to find a neat representation of the data, one which made both me as a developer happy and also satisfied the accountants; and I wanted to package it up so that I wouldn&amp;#8217;t have to think about the financial stuff any more than necessary: I wanted to make it go away, not by ignoring it, but by automating it as much as possible, and by explaining it in a way which doesn&amp;#8217;t require you to know any accounting jargon.&lt;/p&gt;

&lt;p&gt;What I have now released, the &lt;em&gt;Invoicing gem version 0.1.0&lt;/em&gt;, is the combination of all the experience we gathered while developing Bid for Wine with several weeks of my full-time work. Despite the young version number, this is already a pretty solid and stable framework. I waited with the first release until I felt that the core API was well enough thought out that I could minimize the number of backwards-incompatible changes in the upcoming minor versions up to 1.0.&lt;/p&gt;

&lt;p&gt;And even this very first public release is thoroughly documented and tested. In fact, about half of the 3,300 lines of library code are actually documentation, and the code has 100% &lt;a href='http://eigenclass.org/hiki/rcov'&gt;rcov&lt;/a&gt; coverage through unit tests. Databases currently supported are MySQL and PostgreSQL, and the gem depends on ActiveRecord 2.1 or higher.&lt;/p&gt;

&lt;p&gt;The &lt;a href='http://ept.github.com/invoicing/overview.html'&gt;feature list&lt;/a&gt; includes a lot of things which you know you ought to have, but you probably wouldn&amp;#8217;t bother implementing yourself. For example, when we first developed Bid for Wine, the VAT rate was set in a constant; however, within a month of launching, the UK government decided to change the VAT rate (with one week&amp;#8217;s notice!), and I had &lt;a href='http://search.twitter.com/search?q=vat+from%3Amartinkl'&gt;a bit of a panic&lt;/a&gt; to implement a tax rate change feature in our application. (It&amp;#8217;s not as simple as changing the value of the constant, because the VAT applied to the price of an auction is the rate applicable at a point in time in the future, namely the time when the auction ends &amp;#8211; so you can have different items taxed at different rates on the site simultaneously.)&lt;/p&gt;

&lt;p&gt;Needless to say, the invoicing gem includes a feature for handling tax rate changes neatly out of the box. And that was just an example. See the &lt;a href='http://ept.github.com/invoicing/overview.html'&gt;invoicing gem overview page&lt;/a&gt; for a list of features.&lt;/p&gt;

&lt;p&gt;Yesterday I met up with &lt;a href='http://www.fluffy.co.uk/'&gt;Ben Summers&lt;/a&gt;, who is using the invoicing gem in his soon-to-be-launched online information management system &lt;a href='http://www.oneis.co.uk/'&gt;OneIS&lt;/a&gt;. He is delighted at how much time it is saving him, and at the future possibilities which the invoicing gem already offers now.&lt;/p&gt;

&lt;p&gt;And I have plenty of plans for the future. But more on that another day.&lt;/p&gt;

&lt;p&gt;Please help me spread the word, tag your tweets with &lt;code&gt;#invgem&lt;/code&gt;, &lt;a href='http://feeds2.feedburner.com/invoicing'&gt;subscribe to the invoicing gem feed&lt;/a&gt;, &lt;a href='http://ept.github.com/invoicing/getting_started.html'&gt;give the gem a try&lt;/a&gt;, browse &lt;a href='http://invoicing.rubyforge.org/doc/'&gt;the docs&lt;/a&gt; and &lt;a href='http://github.com/ept/invoicing/'&gt;the source&lt;/a&gt;, and &lt;a href='http://www.yes-no-cancel.co.uk/contact/'&gt;let me know what you think&lt;/a&gt;!&lt;/p&gt;
&lt;div class='footer'&gt;
Martin Kleppmann is a web developer and entrepreneur based in Cambridge, UK.
He has a blog at &lt;a href='http://www.yes-no-cancel.co.uk/'&gt;Yes/No/Cancel&lt;/a&gt;,
is known as &lt;a href='http://twitter.com/martinkl'&gt;@martinkl&lt;/a&gt; on Twitter, and his
company is called &lt;a href='http://www.eptcomputing.com/'&gt;Ept Computing&lt;/a&gt;.
&lt;/div&gt;</content>
        </entry>
    
</feed>
