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"].



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
# File 'lib/invoicing/ledger_item.rb', line 684

def (self_id, options={})
  info = ledger_item_class_info
  ext = Invoicing::ConnectionAdapterExt

  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}

  # rails 3 idiocricies. in case of STI, type of base class is nil. Need special handling
  debit_when_sent      = merge_conditions(inheritance_condition(debit_classes),  info.method(:sender_id)    => self_id)
  debit_when_received  = merge_conditions(inheritance_condition(credit_classes), info.method(:recipient_id) => self_id)
  credit_when_sent     = merge_conditions(inheritance_condition(credit_classes), info.method(:sender_id)    => self_id)
  credit_when_received = merge_conditions(inheritance_condition(debit_classes),  info.method(:recipient_id) => self_id)

  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 = merge_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 ")

  sql = sql.where(filter_conditions)
  sql = sql.group("other_id, currency")

  # add order, limit, and lock from outside
  rows = connection.execute(sql.to_sql).to_a

  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].to_s)
      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"].



635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
# File 'lib/invoicing/ledger_item.rb', line 635

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]}
    where(conditions).(self_id, options)[other_id] || {}
  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.



582
583
584
585
586
587
588
589
# File 'lib/invoicing/ledger_item.rb', line 582

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

#inheritance_condition(classes) ⇒ Object



787
788
789
790
791
792
793
794
795
796
# File 'lib/invoicing/ledger_item.rb', line 787

def inheritance_condition(classes)
  segments = []
  segments << sanitize_sql(inheritance_column => classes)

  if classes.include?(self.to_s) && self.new.send(inheritance_column).nil?
    segments << sanitize_sql(type: nil)
  end

  "(#{segments.join(') OR (')})" unless segments.empty?
end

#is_credit_noteObject

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



597
598
599
# File 'lib/invoicing/ledger_item.rb', line 597

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.



592
593
594
# File 'lib/invoicing/ledger_item.rb', line 592

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.



602
603
604
# File 'lib/invoicing/ledger_item.rb', line 602

def is_payment
  ledger_item_class_info.subtype == :payment
end

#merge_conditions(*conditions) ⇒ Object



798
799
800
801
802
803
804
805
806
807
808
809
# File 'lib/invoicing/ledger_item.rb', line 798

def merge_conditions(*conditions)
  segments = []

  conditions.each do |condition|
    unless condition.blank?
      sql = sanitize_sql(condition)
      segments << sql unless sql.blank?
    end
  end

  "(#{segments.join(') AND (')})" unless segments.empty?
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"}


748
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
# File 'lib/invoicing/ledger_item.rb', line 748

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