Module: Invoicing::LedgerItem::ClassMethods

Defined in:
lib/invoicing/ledger_item.rb

Instance Method Summary collapse

Instance Method Details

#account_summaries(self_id, options = {}) ⇒ Object

Returns a summary account status for all customers or suppliers with which a particular party has dealings. Takes into account all closed invoices/credit notes and all cleared payments which have self_id as their sender_id or recipient_id. Returns a hash whose keys are the other party of each account (i.e. the value of sender_id or recipient_id which is not self_id, as an integer), and whose values are again hashes, of the same form as returned by account_summary (summary objects as documented on account_summary):

LedgerItem.(1)
  # => { 2 => { :USD => summary, :EUR => summary },
  #      3 => { :EUR => summary } }

If you want to further restrict the ledger items taken into account in this calculation (e.g. include only data from a particular quarter) you can call this method within an ActiveRecord scope:

q3_2008 = ['issue_date >= ? AND issue_date < ?', DateTime.parse('2008-07-01'), DateTime.parse('2008-10-01')]
LedgerItem.scoped(:conditions => q3_2008).(1)

Also accepts options:

:with_status

List of ledger item status strings; only ledger items whose status is one of these will be taken into account. Default: ["closed", "cleared"].


679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
# File 'lib/invoicing/ledger_item.rb', line 679

def (self_id, options={})
  info = ledger_item_class_info
  ext = Invoicing::ConnectionAdapterExt
  scope = scope(:find)
  
  debit_classes  = select_matching_subclasses(:debit_when_sent_by_self, true,  self.table_name, self.inheritance_column).map{|c| c.name}
  credit_classes = select_matching_subclasses(:debit_when_sent_by_self, false, self.table_name, self.inheritance_column).map{|c| c.name}
  debit_when_sent      = merge_conditions({info.method(:sender_id)    => self_id, info.method(:type) => debit_classes})
  debit_when_received  = merge_conditions({info.method(:recipient_id) => self_id, info.method(:type) => credit_classes})
  credit_when_sent     = merge_conditions({info.method(:sender_id)    => self_id, info.method(:type) => credit_classes})
  credit_when_received = merge_conditions({info.method(:recipient_id) => self_id, info.method(:type) => debit_classes})

  cols = {}
  [:total_amount, :sender_id, :recipient_id, :status, :currency].each do |col|
    cols[col] = connection.quote_column_name(info.method(col))
  end
  
  sender_is_self    = merge_conditions({info.method(:sender_id)    => self_id})
  recipient_is_self = merge_conditions({info.method(:recipient_id) => self_id})
  other_id_column = ext.conditional_function(sender_is_self, cols[:recipient_id], cols[:sender_id])
  accept_status = sanitize_sql_hash_for_conditions(info.method(:status) => (options[:with_status] || %w(closed cleared)))
  filter_conditions = "#{accept_status} AND (#{sender_is_self} OR #{recipient_is_self})"

  sql = "SELECT #{other_id_column} AS other_id, #{cols[:currency]} AS currency, " + 
    "SUM(#{ext.conditional_function(debit_when_sent,      cols[:total_amount], 0)}) AS sales, " +
    "SUM(#{ext.conditional_function(debit_when_received,  cols[:total_amount], 0)}) AS purchase_payments, " +
    "SUM(#{ext.conditional_function(credit_when_sent,     cols[:total_amount], 0)}) AS sale_receipts, " +
    "SUM(#{ext.conditional_function(credit_when_received, cols[:total_amount], 0)}) AS purchases " +
    "FROM #{(scope && scope[:from]) || quoted_table_name} "
  
  # Structure borrowed from ActiveRecord::Base.construct_finder_sql
  add_joins!(sql, nil, scope)
  add_conditions!(sql, filter_conditions, scope)
  
  sql << " GROUP BY other_id, currency"

  add_order!(sql, nil, scope)
  add_limit!(sql, {}, scope)
  add_lock!(sql, {}, scope)
  
  rows = connection.select_all(sql)

  results = {}
  rows.each do |row|
    row.symbolize_keys!
    other_id = row[:other_id].to_i
    currency = row[:currency].to_sym
    summary = {:balance => BigDecimal('0'), :currency => currency}
    
    {:sales => 1, :purchases => -1, :sale_receipts => -1, :purchase_payments => 1}.each_pair do |field, factor|
      summary[field] = BigDecimal(row[field])
      summary[:balance] += BigDecimal(factor.to_s) * summary[field]
    end
    
    results[other_id] ||= {}
    results[other_id][currency] = AccountSummary.new summary
  end
  
  results
end

#account_summary(self_id, other_id = nil, options = {}) ⇒ Object

Returns a summary of the customer or supplier account between two parties identified by self_id (the party from whose perspective the account is seen, 'you') and other_id ('them', your supplier/customer). The return value is a hash with ISO 4217 currency codes as keys (as symbols), and summary objects as values. An account using only one currency will have only one entry in the hash, but more complex accounts may have several.

The summary object has the following methods:

currency          => symbol           # Same as the key of this hash entry
sales             => BigDecimal(...)  # Sum of sales (invoices sent by self_id)
purchases         => BigDecimal(...)  # Sum of purchases (invoices received by self_id)
sale_receipts     => BigDecimal(...)  # Sum of payments received from customer
purchase_payments => BigDecimal(...)  # Sum of payments made to supplier
balance           => BigDecimal(...)  # sales - purchases - sale_receipts + purchase_payments

The :balance fields indicate any outstanding money owed on the account: the value is positive if they owe you money, and negative if you owe them money.

In addition, acts_as_currency_value is set on the numeric fields, so you can use its convenience methods such as summary.sales_formatted.

If other_id is nil, this method aggregates the accounts of self_id with all other parties.

Also accepts options:

:with_status

List of ledger item status strings; only ledger items whose status is one of these will be taken into account. Default: ["closed", "cleared"].


627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
# File 'lib/invoicing/ledger_item.rb', line 627

def (self_id, other_id=nil, options={})
  info = ledger_item_class_info
  self_id = self_id.to_i
  other_id = [nil, ''].include?(other_id) ? nil : other_id.to_i
  
  if other_id.nil?
    result = {}
    # Sum over all others, grouped by currency
    (self_id, options).each_pair do |other_id, hash|
      hash.each_pair do |currency, summary|
        if result[currency]
          result[currency] += summary
        else
          result[currency] = summary
        end
      end
    end
    result
    
  else
    conditions = {info.method(:sender_id)    => [self_id, other_id],
                  info.method(:recipient_id) => [self_id, other_id]}
    with_scope(:find => {:conditions => conditions}) do
      (self_id, options)[other_id] || {}
    end
  end
end

#debit_when_sent_by_selfObject

Returns true if this type of ledger item should be recorded as a debit when the party viewing the account is the sender of the document, and recorded as a credit when the party viewing the account is the recipient. Returns false if those roles are reversed. This method implements default behaviour for invoices, credit notes and payments (see Invoicing::LedgerItem#debit?); if you define custom ledger item subtypes (other than invoice, credit_note and payment), you should override this method accordingly in those subclasses.


574
575
576
577
578
579
580
581
# File 'lib/invoicing/ledger_item.rb', line 574

def debit_when_sent_by_self
  case ledger_item_class_info.subtype
    when :invoice     then true
    when :credit_note then true
    when :payment     then false
    else nil
  end
end

#is_credit_noteObject

Returns true if this type of ledger item is a credit_note subtype, and false otherwise.


589
590
591
# File 'lib/invoicing/ledger_item.rb', line 589

def is_credit_note
  ledger_item_class_info.subtype == :credit_note
end

#is_invoiceObject

Returns true if this type of ledger item is a invoice subtype, and false otherwise.


584
585
586
# File 'lib/invoicing/ledger_item.rb', line 584

def is_invoice
  ledger_item_class_info.subtype == :invoice
end

#is_paymentObject

Returns true if this type of ledger item is a payment subtype, and false otherwise.


594
595
596
# File 'lib/invoicing/ledger_item.rb', line 594

def is_payment
  ledger_item_class_info.subtype == :payment
end

#sender_recipient_name_map(*sender_recipient_ids) ⇒ Object

Takes an array of IDs like those used in sender_id and recipient_id, and returns a hash which maps each of these IDs (typecast to integer) to the :name field of the hash returned by sender_details or recipient_details for that ID. This is useful as it allows LedgerItem to use human-readable names for people or organisations in its output, without depending on a particular implementation of the model objects used to store those entities.

LedgerItem.sender_recipient_name_map [2, 4]
=> {2 => "Fast Flowers Ltd.", 4 => "Speedy Motors"}

749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
# File 'lib/invoicing/ledger_item.rb', line 749

def sender_recipient_name_map(*sender_recipient_ids)
  sender_recipient_ids = sender_recipient_ids.flatten.map &:to_i
  sender_recipient_to_ledger_item_ids = {}
  result_map = {}
  info = ledger_item_class_info
  
  # Find the most recent occurrence of each ID, first in the sender_id column, then in recipient_id
  [:sender_id, :recipient_id].each do |column|
    column = info.method(column)
    quoted_column = connection.quote_column_name(column)
    sql = "SELECT MAX(#{primary_key}) AS id, #{quoted_column} AS ref FROM #{quoted_table_name} WHERE "
    sql << merge_conditions({column => sender_recipient_ids})
    sql << " GROUP BY #{quoted_column}"
    
    ActiveRecord::Base.connection.select_all(sql).each do |row|
      sender_recipient_to_ledger_item_ids[row['ref'].to_i] = row['id'].to_i
    end
    
    sender_recipient_ids -= sender_recipient_to_ledger_item_ids.keys
  end
  
  # Load all the ledger items needed to get one representative of each name
  find(sender_recipient_to_ledger_item_ids.values.uniq).each do |ledger_item|
    sender_id = info.get(ledger_item, :sender_id)
    recipient_id = info.get(ledger_item, :recipient_id)
    
    if sender_recipient_to_ledger_item_ids.include? sender_id
      details = info.get(ledger_item, :sender_details)
      result_map[sender_id] = details[:name]
    end
    if sender_recipient_to_ledger_item_ids.include? recipient_id
      details = info.get(ledger_item, :recipient_details)
      result_map[recipient_id] = details[:name]
    end
  end
  
  result_map
end