Module: Invoicing::LedgerItem

Extended by:
ActiveSupport::Concern
Defined in:
lib/invoicing/ledger_item.rb,
lib/invoicing/ledger_item/render_ubl.rb,
lib/invoicing/ledger_item/render_html.rb,
lib/invoicing/ledger_item/pdf_generator.rb

Overview

Ledger item objects

This module implements a simple ledger, i.e. the record of all of the business transactions which are handled by your application. Each transaction is recorded as one LedgerItem object, each of which may have one of the following three types:

Invoice

When you send an invoice to someone (= a customer), this is a record of the fact that you have sold them something (a product, a service etc.), and how much you expect to be paid for it. An invoice can consist of a list of individual charges, but it is considered as one document for legal purposes. You can also create invoices from someone else to yourself, if you owe someone else money – for example, if you need to pay commissions to a reseller of your application.

CreditNote

This is basically a invoice for a negative amount; you should use it if you have previously sent a customer an invoice with an amount which was too great (i.e. you have overcharged them). The numeric values stored in the database for a credit note are negative, to make it easier to calculate account summaries, but they may be formatted as positive values when presented to users if that is customary in your country. For example, if you send a customer an invoice with a total_amount of $20 and a credit note with a total_amount of -$10, that means that overall you're asking them to pay $10.

Payment

This is a record of the fact that a payment has been made. It's a simple object, in effect just saying that party A paid amount X to party B on date Y. This module does not implement any particular payment mechanism such as credit card handling, although it could be implemented on top of a Payment object.

Important principles

Note the distinction between Invoices/Credit Notes and Payments; to keep your accounts clean, it is important that you do not muddle these up.

  • Invoices and Credit Notes are the important documents for tax purposes in most jurisdictions. They record the date on which the sale is officially made, and that date determines which tax rates apply. An invoice often also represents the transfer of ownership from the supplier to the customer; for example, if you ask your customers to send payment in advance (such as 'topping up' their account), that money still belongs to your customer until the point where they have used your service, and you have charged them for your service by sending them an invoice. You should only invoice them for what they have actually used, then the remaining balance will automatically be retained on their account.

  • Payments are just what it says on the tin – the transfer of money from one hand to another. A payment may occur before an invoice is issued (payment in advance), or after/at the same time as an invoice is issued to settle the debt (payment in arrears, giving your customers credit). You can choose whatever makes sense for your business. Payments may often be associated one-to-one with invoices, but not necessarily – an invoice may be paid in instalments, or several invoices may be lumped together to one payment. Your customer may even refuse to pay some charges, in which case there is an invoice but no payment (until at some point you either reverse it with a credit note, or write it off as bad debt, but that's beyond our scope right now).

Another very important principle is that once a piece of information has been added to the ledger, you should not modify or delete it. Particularly when you have 'sent' one of your customers or suppliers a document (which may mean simply that they have seen it on the web) you should not change it again, because they might have added that information to their own accounting system. Changing any information is guaranteed to lead to confusion. (The model objects do not restrict your editing capabilities because they might be necessary in specific circumstances, but you should be extremely careful when changing anything.)

Of course you make mistakes or change your mind, but please deal with them cleanly:

  • If you create an invoice whose value is too small, don't amend the invoice, but send them another invoice to cover the remaining amount.

  • If you create an invoice whose value is too great (for example because you want to offer one customer a special discount), don't amend the invoice, but send them a credit note to waive your claim to the difference.

  • If you create a payment, mark it as pending until it the money has actually arrived. If it never arrives, keep the record but mark it as failed in case you need to investigate it later.

The exception to the 'no modifications' rule are invoices on which you accumulate charges (e.g. over the course of a month) and then officially 'send' the invoice at the end of the period. In this gem we call such invoices open while they may still be changed. It's ok to add charges to open invoices as you go along; while it is open it is not legally an invoice, but only a statement of accumulated charges. If you display it to users, make sure that you don't call it “invoice”, to avoid confusion. Only when you set it to closed at the end of the month does the statement become an invoice for legal purposes. Once it's closed you must not add any further charges to it.

Finally, each ledger item has a sender and a recipient; typically one of the two will be you (the person/organsation who owns/operates the application):

  • For invoices, credit notes and payments between you and your customers, set the sender to be yourself and the recipient to be your customer;

  • If you use this system to record suppliers, set the sender to be your supplier and the recipient to be yourself.

(See below for details.) It is perfectly ok to have documents which are sent between your users, where you are neither sender nor recipient; this may be useful if you want to allow users to trade directly with each other.

Using invoices, credit notes and payments in your application

All invoices, credit notes and payments (collectively called 'ledger items') are stored in a single database table. We use single table inheritance to distinguish the object types. You need to create at least the following four model classes in your application:

class LedgerItem < ActiveRecord::Base
  acts_as_ledger_item
end

class Invoice < LedgerItem                      # Base class for all types of invoice
  acts_as_ledger_item :subtype => :invoice
end

class CreditNote < LedgerItem                   # Base class for all types of credit note
  acts_as_ledger_item :subtype => :credit_note
end

class Payment < LedgerItem                      # Base class for all types of payment
  acts_as_ledger_item :subtype => :payment
end

You may give the classes different names than these, and you can package them in modules if you wish, but they need to have the :subtype => ... option parameters as above.

You can create as many subclasses as you like of each of Invoice, CreditNote and Payment. This provides a convenient mechanism for encapsulating different types of functionality which you may need for different types of transactions, but still keeping the accounts in one place. You may start with only one subclass of Invoice (e.g. class MonthlyChargesInvoice < Invoice to bill users for their use of your application; but as you want to do more clever things, you can add other subclasses of Invoice as and when you need them (such as ConsultancyServicesInvoice and SalesCommissionInvoice, for example). Similarly for payments, you may have subclasses representing credit card payments, cash payments, bank transfers etc.

Please note that the Payment ledger item type does not itself implement any particular payment methods such as credit card handling; however, for third-party libraries providing credit card handling, this would be a good place to integrate.

The model classes must have a certain minimum set of columns and a few common methods, documented below (although you may rename any of them if you wish). Beyond those, you may add other methods and database columns for your application's own needs, provided they don't interfere with names used here.

Required methods/database columns

The following methods/database columns are required for LedgerItem objects (you may give them different names, but then you need to tell acts_as_ledger_item about your custom names):

type

String to store the class name, for ActiveRecord single table inheritance.

sender_id

Integer-valued foreign key, used to refer to some other model object representing the party (person, company etc.) who is the sender of the transaction.

  • In the case of an invoice or credit note, the sender_id identifies the supplier of the product or service, i.e. the person who is owed the amount specified on the invoice, also known as the creditor.

  • In the case of a payment record, the sender_id identifies the payee, i.e. the person who sends the note confirming that they received payment.

  • This field may be NULL to refer to yourself (i.e. the company/person who owns or operates this application), but you may also use non-NULL values to refer to yourself. It's just important that you consistently refer to the same party by the same value in different ledger items.

recipient_id

The counterpart to sender_id: foreign key to a model object which represents the party who is the recipient of the transaction.

  • In the case of an invoice or credit note, the recipient_id identifies the customer/buyer of the product or service, i.e. the person who owes the amount specified on the invoice, also known as the debtor.

  • In the case of a payment record, the recipient_id identifies the payer, i.e. the recipient of the payment receipt.

  • NULL may be used as in sender_id.

sender_details

A method (does not have to be a database column) which returns a hash with information about the party identified by sender_id. See the documentation of sender_details for the expected contents of the hash. Must always return valid details, even if sender_id is NULL.

recipient_details

A method (does not have to be a database column) which returns a hash with information about the party identified by recipient_id. See the documentation of sender_details for the expected contents of the hash (recipient_details uses the same format as sender_details). Must always return valid details, even if recipient_id is NULL.

identifier

A number or string used to identify this record, i.e. the invoice number, credit note number or payment receipt number as appropriate.

  • There may be legal requirements in your country concerning its format, but as long as it uniquely identifies the document within your organisation you should be safe.

  • It's possible to simply make this an alias of the primary key, but it's strongly recommended that you use a separate database column. If you ever need to generate invoices on behalf of other people (i.e. where sender_id is not you), you need to give the sender of the invoice the opportunity to enter their own identifier (because it then must be unique within the sender's organisation, not yours).

issue_date

A datetime column which indicates the date on which the document is issued, and which may also serve as the tax point (the date which determines which tax rate is applied). This should be a separate column, because it won't necessarily be the same as created_at or updated_at. There may be business reasons for choosing particular dates, but the date at which you send the invoice or receive the payment should do unless your accountant advises you otherwise.

currency

The 3-letter code which identifies the currency used in this transaction; must be one of the list of codes in ISO-4217. (Even if you only use one currency throughout your site, this is needed to format monetary amounts correctly.)

total_amount

A decimal column containing the grand total monetary sum (of the invoice or credit note), or the monetary amount paid (of the payment record), including all taxes, charges etc. For invoices and credit notes, a before_validation filter is automatically invoked, which adds up the net_amount and tax_amount values of all line items and assigns that sum to total_amount. For payment records, which do not usually have line items, you must assign the correct value to this column. See the documentation of the CurrencyValue module for notes on suitable datatypes for monetary values. acts_as_currency_value is automatically applied to this attribute.

tax_amount

If you're a small business you maybe don't need to add tax to your invoices; but if you are successful, you almost certainly will need to do so eventually. In most countries this takes the form of Value Added Tax (VAT) or Sales Tax. For invoices and credit notes, you must store the amount of tax in this table; a before_validation filter is automatically invoked, which adds up the tax_amount values of all line items and assigns that sum to total_amount. For payment records this should be zero (unless you use a cash accounting scheme, which is currently not supported). See the documentation of the CurrencyValue module for notes on suitable datatypes for monetary values. acts_as_currency_value is automatically applied to this attribute.

status

A string column used to keep track of the status of ledger items. Currently the following values are defined (but future versions may add further status values):

open

For invoices/credit notes: the document is not yet finalised, further line items may be added.

closed

For invoices/credit notes: the document has been sent to the recipient and will not be changed again.

cancelled

For invoices/credit notes: the document has been declared void and does not count towards accounts. (Use this sparingly; if you want to refund an invoice that has been sent, send a credit note.)

pending

For payments: payment is expected or has been sent, but has not yet been confirmed as received.

cleared

For payments: payment has completed successfully.

failed

For payments: payment did not succeed; this record is not counted towards accounts.

description

A method which returns a short string describing what this invoice, credit note or payment is about. Can be a database column but doesn't have to be.

line_items

You should define an association has_many :line_items, ... referring to the LineItem objects associated with this ledger item.

Optional methods/database columns

The following methods/database columns are optional, but recommended for LedgerItem objects:

period_start, period_end

Two datetime columns which define the period of time covered by an invoice or credit note. If the thing you are selling is a one-off, you can omit these columns or leave them as NULL. However, if there is any sort of duration associated with an invoice/credit note (e.g. charges incurred during a particular month, or an annual subscription, or a validity period of a license, etc.), please store that period here. It's important for accounting purposes. (For Payment objects it usually makes most sense to just leave these as NULL.)

uuid

A Universally Unique Identifier (UUID) string for this invoice, credit note or payment. It may seem unnecessary now, but may help you to keep track of your data later on as your system grows. If you have the uuid gem installed and this column is present, a UUID is automatically generated when you create a new ledger item.

due_date

The date at which the invoice or credit note is due for payment. nil on Payment records.

created_at, updated_at

The standard ActiveRecord datetime columns for recording when an object was created and last changed. The values are not directly used at the moment, but it's useful information in case you need to track down a particular transaction sometime; and ActiveRecord manages them for you anyway.

Generated methods

In return for providing LedgerItem with all the required information as documented above, you are given a number of class and instance methods which you will find useful sooner or later. In addition to those documented in this module (instance methods) and Invoicing::LedgerItem::ClassMethods (class methods), the following methods are generated dynamically:

sent_by

Named scope which takes a person/company ID and matches all ledger items whose sender_id matches that value.

received_by

Named scope which takes a person/company ID and matches all ledger items whose recipient_id matches that value.

sent_or_received_by

Union of sent_by and received_by.

in_effect

Named scope which matches all closed invoices/credit notes (not open or cancelled) and all cleared payments (not pending or failed). You probably want to use this quite often, for all reporting purposes.

open_or_pending

Named scope which matches all open invoices/credit notes and all pending payments.

due_at

Named scope which takes a DateTime argument and matches all ledger items whose due_date value is either NULL or is not after the given time. For example, you could run LedgerItem.due_at(Time.now).account_summaries once a day and process payment for all accounts whose balance is not zero.

sorted

Named scope which takes a column name as documented above (even if it has been renamed), and sorts the query by that column. If the column does not exist, silently falls back to sorting by the primary key.

exclude_empty_invoices

Named scope which excludes any invoices or credit notes which do not have any associated line items (payments without line items are included though). If you're chaining scopes it would be advantageous to put this one close to the beginning of your scope chain.

Defined Under Namespace

Modules: ActMethods, ClassMethods, RenderHTML, RenderUBL Classes: AccountSummary, ClassInfo, PdfGenerator

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_id, *args) ⇒ Object

We don't actually implement anything using method_missing at the moment, but use it to generate slightly more useful error messages in certain cases.


456
457
458
459
460
461
462
463
464
465
# File 'lib/invoicing/ledger_item.rb', line 456

def method_missing(method_id, *args)
  method_name = method_id.to_s
  if ['line_items', ledger_item_class_info.method(:line_items)].include? method_name
    raise RuntimeError, "You need to define an association like 'has_many :line_items' on #{self.class.name}. If you " +
      "have defined the association with a different name, pass the option :line_items => :your_association_name to " +
      "acts_as_ledger_item."
  else
    super
  end
end

Instance Method Details

#calculate_total_amountObject

Calculate sum of net_amount and tax_amount across all line items, and assign it to total_amount; calculate sum of tax_amount across all line items, and assign it to tax_amount. Called automatically as a before_validation callback. If the LedgerItem subtype is payment and there are no line items then the total amount is not touched.


429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/invoicing/ledger_item.rb', line 429

def calculate_total_amount
  line_items = ledger_item_class_info.get(self, :line_items)
  return if self.class.is_payment && line_items.empty?

  net_total = tax_total = BigDecimal('0')

  line_items.each do |line|
    info = line.send(:line_item_class_info)

    # Make sure ledger_item association is assigned -- the CurrencyValue
    # getters depend on it to fetch the currency
    info.set(line, :ledger_item, self)
    line.valid? # Ensure any before_validation hooks are called

    net_amount = info.get(line, :net_amount)
    tax_amount = info.get(line, :tax_amount)
    net_total += net_amount unless net_amount.nil?
    tax_total += tax_amount unless tax_amount.nil?
  end

  ledger_item_class_info.set(self, :total_amount, net_total + tax_total)
  ledger_item_class_info.set(self, :tax_amount,   tax_total)
  return net_total
end

#debit?(self_id) ⇒ Boolean

Returns a boolean which specifies whether this transaction should be recorded as a debit (true) or a credit (false) on a particular ledger. Unless you know what you are doing, you probably do not need to touch this method.

It takes an argument self_id, which should be equal to either sender_id or recipient_id of this object, and which determines from which perspective the account is viewed. The default behaviour is:

  • A sent invoice (self_id == sender_id) is a debit since it increases the recipient's liability; a sent credit note decreases the recipient's liability with a negative-valued debit; a sent payment receipt is a positive-valued credit and thus decreases the recipient's liability.

  • A received invoice (self_id == recipient_id) is a credit because it increases your own liability; a received credit note decreases your own liability with a negative-valued credit; a received payment receipt is a positive-valued debit and thus decreases your own liability.

Note that accounting practices differ with regard to credit notes: some think that a sent credit note should be recorded as a positive credit (hence the name 'credit note'); others prefer to use a negative debit. We chose the latter because it allows you to calculate the total sale volume on an account simply by adding up all the debits. If there is enough demand for the positive-credit model, we may add support for it sometime in future.

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)

554
555
556
557
558
559
560
# File 'lib/invoicing/ledger_item.rb', line 554

def debit?(self_id)
  sender_is_self = sent_by?(self_id)
  recipient_is_self = received_by?(self_id)
  raise ArgumentError, "self_id #{self_id.inspect} is neither sender nor recipient" unless sender_is_self || recipient_is_self
  raise ArgumentError, "self_id #{self_id.inspect} is both sender and recipient" if sender_is_self && recipient_is_self
  self.class.debit_when_sent_by_self ? sender_is_self : recipient_is_self
end

#initialize(*args) ⇒ Object

Overrides the default constructor of ActiveRecord::Base when acts_as_ledger_item is called. If the uuid gem is installed, this constructor creates a new UUID and assigns it to the uuid property when a new ledger item model object is created.


416
417
418
419
420
421
422
423
# File 'lib/invoicing/ledger_item.rb', line 416

def initialize(*args)
  super
  # Initialise uuid attribute if possible
  info = ledger_item_class_info
  if self.has_attribute?(info.method(:uuid)) && info.uuid_generator
    write_attribute(info.method(:uuid), info.uuid_generator.generate)
  end
end

#net_amountObject

The difference total_amount minus tax_amount.


468
469
470
471
472
# File 'lib/invoicing/ledger_item.rb', line 468

def net_amount
  total_amount = ledger_item_class_info.get(self, :total_amount)
  tax_amount   = ledger_item_class_info.get(self, :tax_amount)
  (total_amount && tax_amount) ? (total_amount - tax_amount) : nil
end

#net_amount_formattedObject

net_amount formatted in human-readable form using the ledger item's currency.


475
476
477
# File 'lib/invoicing/ledger_item.rb', line 475

def net_amount_formatted
  format_currency_value(net_amount)
end

#received_by?(user_id) ⇒ Boolean

Returns true if this document was received by the user with ID user_id. If the argument is nil (indicating yourself), this also returns true if recipient_details[:is_self].

Returns:

  • (Boolean)

530
531
532
533
# File 'lib/invoicing/ledger_item.rb', line 530

def received_by?(user_id)
  (ledger_item_class_info.get(self, :recipient_id) == user_id) ||
    !!(user_id.nil? && ledger_item_class_info.get(self, :recipient_details)[:is_self])
end

#recipient_detailsObject

You must overwrite this method in subclasses of Invoice, CreditNote and Payment so that it returns details of the party receiving the document. See recipient_id above for a detailed interpretation of sender and receiver. See sender_details for a list of fields to return in the hash.


517
518
519
# File 'lib/invoicing/ledger_item.rb', line 517

def recipient_details
  raise 'overwrite this method'
end

#sender_detailsObject

You must overwrite this method in subclasses of Invoice, CreditNote and Payment so that it returns details of the party sending the document. See sender_id above for a detailed interpretation of sender and receiver.

The methods sender_details and recipient_details are required to return hashes containing details about the sender and recipient of an invoice, credit note or payment. The reason we do this is that you probably already have your own system for handling users, customers and their personal or business details, and this framework shouldn't require you to change any of that.

The invoicing framework currently uses these details only for rendering invoices and credit notes, but in future it may serve more advanced purposes, such as determining which tax rate to apply for overseas customers.

In the hash returned by sender_details and recipient_details, the following keys are recognised – please fill in as many as possible:

:is_self

true if these details refer to yourself, i.e. the person or organsiation who owns/operates this application. false if these details refer to any other party.

:name

The name of the person or organisation whose billing address is defined below.

:contact_name

The name of a person/department within the organisation named by :name.

:address

The body of the billing address (not including city, postcode, state and country); may be a multi-line string, with lines separated by 'n' line breaks.

:city

The name of the city or town in the billing address.

:state

The state/region/province/county of the billing address as appropriate.

:postal_code

The postal code of the billing address (e.g. ZIP code in the US).

:country

The billing address country (human-readable).

:country_code

The two-letter country code of the billing address, according to ISO-3166-1.

:tax_number

The Value Added Tax registration code of this person or organisation, if they have one, preferably including the country identifier at the beginning. This is important for transactions within the European Union.


510
511
512
# File 'lib/invoicing/ledger_item.rb', line 510

def sender_details
  raise 'overwrite this method'
end

#sent_by?(user_id) ⇒ Boolean

Returns true if this document was sent by the user with ID user_id. If the argument is nil (indicating yourself), this also returns true if sender_details[:is_self].

Returns:

  • (Boolean)

523
524
525
526
# File 'lib/invoicing/ledger_item.rb', line 523

def sent_by?(user_id)
  (ledger_item_class_info.get(self, :sender_id) == user_id) ||
    !!(user_id.nil? && ledger_item_class_info.get(self, :sender_details)[:is_self])
end

#value_for_formatting(value, options = {}) ⇒ Object

Invoked internally when total_amount_formatted or tax_amount_formatted is called. Allows you to specify options like :debit => :negative, :self_id => 42 meaning that if this ledger item is a debit as regarded from the point of view of self_id then it should be displayed as a negative number. Note this only affects the output formatting, not the actual stored values.


567
568
569
570
571
# File 'lib/invoicing/ledger_item.rb', line 567

def value_for_formatting(value, options={})
  value = -value if (options[:debit]  == :negative) &&  debit?(options[:self_id])
  value = -value if (options[:credit] == :negative) && !debit?(options[:self_id])
  value
end