spatten-bookkeeper 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/init.rb +1 -0
 - data/lib/bookkeeper.rb +8 -0
 - data/lib/bookkeeper/account.rb +95 -0
 - data/lib/bookkeeper/account/asset.rb +3 -0
 - data/lib/bookkeeper/account/expense.rb +3 -0
 - data/lib/bookkeeper/account/liability.rb +3 -0
 - data/lib/bookkeeper/account/revenue.rb +3 -0
 - data/lib/bookkeeper/asset_type.rb +3 -0
 - data/lib/bookkeeper/asset_type/cad.rb +2 -0
 - data/lib/bookkeeper/asset_type/usd.rb +2 -0
 - data/lib/bookkeeper/batch.rb +62 -0
 - data/lib/bookkeeper/journal.rb +29 -0
 - data/lib/bookkeeper/journal/bill.rb +32 -0
 - data/lib/bookkeeper/journal/deposit.rb +7 -0
 - data/lib/bookkeeper/journal/disbursement.rb +7 -0
 - data/lib/bookkeeper/journal/invoice.rb +4 -0
 - data/lib/bookkeeper/journal/transfer.rb +7 -0
 - data/lib/bookkeeper/paypal_transaction.rb +12 -0
 - data/lib/bookkeeper/paypal_transaction/masspay_subpayment.rb +17 -0
 - data/lib/bookkeeper/paypal_transaction/single.rb +14 -0
 - data/lib/bookkeeper/posting.rb +32 -0
 - data/lib/immutable-attribute-plugin/MIT-LICENSE +20 -0
 - data/lib/immutable-attribute-plugin/README +35 -0
 - data/lib/immutable-attribute-plugin/Rakefile +22 -0
 - data/lib/immutable-attribute-plugin/init.rb +4 -0
 - data/lib/immutable-attribute-plugin/install.rb +1 -0
 - data/lib/immutable-attribute-plugin/lib/ensures_immutability_of.rb +43 -0
 - data/lib/immutable-attribute-plugin/tasks/ensures_immutability_of_tasks.rake +4 -0
 - data/lib/immutable-attribute-plugin/test/fixtures/accounts.yml +6 -0
 - data/lib/immutable-attribute-plugin/test/fixtures/infos.yml +6 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/README +211 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/Rakefile +10 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/app/controllers/application.rb +7 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/app/helpers/application_helper.rb +3 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/app/models/account.rb +6 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/app/models/info.rb +3 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/boot.rb +39 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/database.yml +18 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/environment.rb +11 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/environments/development.rb +21 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/environments/mysql.rb +0 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/environments/postgresql.rb +0 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/environments/production.rb +18 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/environments/sqlite.rb +0 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/environments/sqlite3.rb +0 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/environments/test.rb +19 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/config/routes.rb +23 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/db/migrate/001_create_accounts.rb +12 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/db/migrate/002_create_infos.rb +13 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/script/console +3 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/script/runner +3 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/test/test_helper.rb +28 -0
 - data/lib/immutable-attribute-plugin/test/rails_root/vendor/plugins/phone_validation/init.rb +2 -0
 - data/lib/immutable-attribute-plugin/test/test_helper.rb +26 -0
 - data/lib/immutable-attribute-plugin/test/unit/plugin_test.rb +137 -0
 - data/lib/immutable-attribute-plugin/uninstall.rb +1 -0
 - data/rails/init.rb +1 -0
 - data/rails_generators/USAGE +3 -0
 - data/rails_generators/bookkeeper_generator.rb +17 -0
 - data/rails_generators/templates/fixtures.yml +1 -0
 - data/rails_generators/templates/migration.rb +66 -0
 - metadata +140 -0
 
    
        data/init.rb
    ADDED
    
    | 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require File.dirname(__FILE__) + "/rails/init"
         
     | 
    
        data/lib/bookkeeper.rb
    ADDED
    
    | 
         @@ -0,0 +1,8 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'find'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require File.join(File.dirname(__FILE__), 'immutable-attribute-plugin', 'init')
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            # require everything in lib/
         
     | 
| 
      
 6 
     | 
    
         
            +
            Find.find(File.join(File.dirname(__FILE__), 'bookkeeper')) do |file|
         
     | 
| 
      
 7 
     | 
    
         
            +
              require file if !File.directory?(file) && File.extname(file) == '.rb'
         
     | 
| 
      
 8 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,95 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Account < ActiveRecord::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              acts_as_tree
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              has_many  :postings
         
     | 
| 
      
 5 
     | 
    
         
            +
              belongs_to :accountable, :polymorphic => true
         
     | 
| 
      
 6 
     | 
    
         
            +
              
         
     | 
| 
      
 7 
     | 
    
         
            +
              validate :validate_account_created_through_a_subclass
         
     | 
| 
      
 8 
     | 
    
         
            +
              validate :validate_subaccount_same_type_as_parent, :if => :is_subaccount
         
     | 
| 
      
 9 
     | 
    
         
            +
              validate :validate_name_not_set, :if => :accountable_has_own_name
         
     | 
| 
      
 10 
     | 
    
         
            +
              validates_presence_of :name, :unless => :accountable_has_own_name
         
     | 
| 
      
 11 
     | 
    
         
            +
              
         
     | 
| 
      
 12 
     | 
    
         
            +
              def debit(amount, options = {})
         
     | 
| 
      
 13 
     | 
    
         
            +
                new_posting(:debit, amount, options)
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
              
         
     | 
| 
      
 16 
     | 
    
         
            +
              def credit(amount, options = {})
         
     | 
| 
      
 17 
     | 
    
         
            +
                new_posting(:credit, amount, options)
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
              
         
     | 
| 
      
 20 
     | 
    
         
            +
              def deposit(transaction)
         
     | 
| 
      
 21 
     | 
    
         
            +
                gross = transaction.notification.amount.cents.to_f / 100
         
     | 
| 
      
 22 
     | 
    
         
            +
                
         
     | 
| 
      
 23 
     | 
    
         
            +
                journal = Journal::Deposit.new(:transactable => transaction)
         
     | 
| 
      
 24 
     | 
    
         
            +
                
         
     | 
| 
      
 25 
     | 
    
         
            +
                journal.postings |= transaction.to_postings
         
     | 
| 
      
 26 
     | 
    
         
            +
                journal.postings << self.credit(gross)
         
     | 
| 
      
 27 
     | 
    
         
            +
                
         
     | 
| 
      
 28 
     | 
    
         
            +
                journal.save!
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
              
         
     | 
| 
      
 31 
     | 
    
         
            +
              def account_type
         
     | 
| 
      
 32 
     | 
    
         
            +
                self.class::ACCOUNT_TYPE
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
              
         
     | 
| 
      
 35 
     | 
    
         
            +
              def name
         
     | 
| 
      
 36 
     | 
    
         
            +
                accountable_has_own_name ? accountable.name : self[:name]
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
              
         
     | 
| 
      
 39 
     | 
    
         
            +
              def postings_with_recursion
         
     | 
| 
      
 40 
     | 
    
         
            +
                (self_and_all_children.collect { |account| account.postings_without_recursion }).flatten
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
              alias_method_chain :postings, :recursion
         
     | 
| 
      
 43 
     | 
    
         
            +
              
         
     | 
| 
      
 44 
     | 
    
         
            +
              def balance
         
     | 
| 
      
 45 
     | 
    
         
            +
                postings.inject(0) { |balance, p| 
         
     | 
| 
      
 46 
     | 
    
         
            +
                  amount_from_account_perspective = [:asset,:expense].include?(account_type) ? -p.amount : p.amount
         
     | 
| 
      
 47 
     | 
    
         
            +
                  balance + amount_from_account_perspective
         
     | 
| 
      
 48 
     | 
    
         
            +
                }
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
              
         
     | 
| 
      
 51 
     | 
    
         
            +
              def is_subaccount
         
     | 
| 
      
 52 
     | 
    
         
            +
                self != self.root
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
              
         
     | 
| 
      
 55 
     | 
    
         
            +
              def destroy
         
     | 
| 
      
 56 
     | 
    
         
            +
                raise ActiveRecord::IndestructibleRecord
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
              
         
     | 
| 
      
 59 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 60 
     | 
    
         
            +
                def accounts_payable; roots.find_by_name('Accounts Payable'); end
         
     | 
| 
      
 61 
     | 
    
         
            +
                def accounts_receivable; roots.find_by_name('Accounts Receivable'); end
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
              
         
     | 
| 
      
 64 
     | 
    
         
            +
              private
         
     | 
| 
      
 65 
     | 
    
         
            +
                def new_posting(type, amount, params)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  params.assert_valid_keys(:asset_type)
         
     | 
| 
      
 67 
     | 
    
         
            +
                  
         
     | 
| 
      
 68 
     | 
    
         
            +
                  params.merge!(:account => self)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  
         
     | 
| 
      
 70 
     | 
    
         
            +
                  case type
         
     | 
| 
      
 71 
     | 
    
         
            +
                  when :debit
         
     | 
| 
      
 72 
     | 
    
         
            +
                    params.merge!(:amount => -amount)
         
     | 
| 
      
 73 
     | 
    
         
            +
                  when :credit
         
     | 
| 
      
 74 
     | 
    
         
            +
                    params.merge!(:amount => amount)
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                  
         
     | 
| 
      
 77 
     | 
    
         
            +
                  Posting.new(params)
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
                
         
     | 
| 
      
 80 
     | 
    
         
            +
                def accountable_has_own_name
         
     | 
| 
      
 81 
     | 
    
         
            +
                  !self.accountable.name.nil? unless self.accountable.nil?
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
                
         
     | 
| 
      
 84 
     | 
    
         
            +
                def validate_account_created_through_a_subclass
         
     | 
| 
      
 85 
     | 
    
         
            +
                  errors.add_to_base 'Accounts may only be created by instantiating a subclass of Account.' if self.class == Account
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
                
         
     | 
| 
      
 88 
     | 
    
         
            +
                def validate_subaccount_same_type_as_parent
         
     | 
| 
      
 89 
     | 
    
         
            +
                  errors.add_to_base 'Subaccounts must be of the same type as their parent.' if self.class != self.parent.class
         
     | 
| 
      
 90 
     | 
    
         
            +
                end
         
     | 
| 
      
 91 
     | 
    
         
            +
                
         
     | 
| 
      
 92 
     | 
    
         
            +
                def validate_name_not_set
         
     | 
| 
      
 93 
     | 
    
         
            +
                  errors.add :name, 'should not be set on an account with an accountable that has a name.'
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,62 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Batch < ActiveRecord::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              has_many :journals
         
     | 
| 
      
 3 
     | 
    
         
            +
              
         
     | 
| 
      
 4 
     | 
    
         
            +
              def pay(payment_transaction)
         
     | 
| 
      
 5 
     | 
    
         
            +
                # Get all of the bills associated with this batch
         
     | 
| 
      
 6 
     | 
    
         
            +
                bills = journals
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                # What kind of fee have we paid as part of this payment?
         
     | 
| 
      
 9 
     | 
    
         
            +
                fee = BigDecimal(payment_transaction.notification.fee)
         
     | 
| 
      
 10 
     | 
    
         
            +
                if fee > 0
         
     | 
| 
      
 11 
     | 
    
         
            +
                  logger.info "Recording the MassPay fee for MassPay subpayment #{payment_transaction.transaction_id}"
         
     | 
| 
      
 12 
     | 
    
         
            +
                  fee_journal = Journal::Disbursement.new(:transactable => payment_transaction)
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  fee_journal.postings << payment_transaction.payer_account.credit(fee)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  fee_journal.postings << Account.paypal_fees_account.debit(fee)
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  fee_journal.save!
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
                
         
     | 
| 
      
 20 
     | 
    
         
            +
                # How much cash have we paid as part of this payment?
         
     | 
| 
      
 21 
     | 
    
         
            +
                payment_dollars = BigDecimal(payment_transaction.notification.gross)
         
     | 
| 
      
 22 
     | 
    
         
            +
                        
         
     | 
| 
      
 23 
     | 
    
         
            +
                while bills.length > 0
         
     | 
| 
      
 24 
     | 
    
         
            +
                  bill = bills.shift
         
     | 
| 
      
 25 
     | 
    
         
            +
                  amount_to_pay = bill.amount - bill.amount_paid
         
     | 
| 
      
 26 
     | 
    
         
            +
                  
         
     | 
| 
      
 27 
     | 
    
         
            +
                  logger.info "We have #{payment_dollars} left, and #{amount_to_pay} to pay for bill #{bill.id}, with #{bills.length} bills left to pay."
         
     | 
| 
      
 28 
     | 
    
         
            +
                  if(payment_dollars < amount_to_pay)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    warn_mispayment(:under, bill.id, (amount_to_pay - payment_dollars))
         
     | 
| 
      
 30 
     | 
    
         
            +
                    amount_to_pay = payment_dollars
         
     | 
| 
      
 31 
     | 
    
         
            +
                  elsif(bills.length == 0 and payment_dollars > amount_to_pay)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    warn_mispayment(:over, bill.id, (payment_dollars - amount_to_pay))
         
     | 
| 
      
 33 
     | 
    
         
            +
                    amount_to_pay = payment_dollars
         
     | 
| 
      
 34 
     | 
    
         
            +
                  end
         
     | 
| 
      
 35 
     | 
    
         
            +
                  
         
     | 
| 
      
 36 
     | 
    
         
            +
                  if(amount_to_pay > 0)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    # Record a bill payment in the ledger
         
     | 
| 
      
 38 
     | 
    
         
            +
                    logger.info "Recording a bill payment for bill #{bill.id}"
         
     | 
| 
      
 39 
     | 
    
         
            +
                  
         
     | 
| 
      
 40 
     | 
    
         
            +
                    journal = Journal::Disbursement.new(:transactable => payment_transaction)
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                    journal.postings << payment_transaction.payer_account.credit(amount_to_pay)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    journal.postings << Account.accounts_payable.debit(amount_to_pay)
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    bill.payments << journal
         
     | 
| 
      
 46 
     | 
    
         
            +
                    bill.save!
         
     | 
| 
      
 47 
     | 
    
         
            +
                    
         
     | 
| 
      
 48 
     | 
    
         
            +
                    payment_dollars -= amount_to_pay
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
              
         
     | 
| 
      
 53 
     | 
    
         
            +
              private
         
     | 
| 
      
 54 
     | 
    
         
            +
                def warn_mispayment(extreme, bill_id, amount)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  case extreme
         
     | 
| 
      
 56 
     | 
    
         
            +
                  when :over
         
     | 
| 
      
 57 
     | 
    
         
            +
                    logger.warn "About to overpay bill #{bill_id} by $#{amount}."
         
     | 
| 
      
 58 
     | 
    
         
            +
                  when :under
         
     | 
| 
      
 59 
     | 
    
         
            +
                    logger.warn "About to underpay bill #{bill_id} by $#{amount}."
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,29 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Journal < ActiveRecord::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              has_many   :postings
         
     | 
| 
      
 3 
     | 
    
         
            +
              belongs_to :transactable, :polymorphic => true
         
     | 
| 
      
 4 
     | 
    
         
            +
              belongs_to :batch
         
     | 
| 
      
 5 
     | 
    
         
            +
              
         
     | 
| 
      
 6 
     | 
    
         
            +
              ensures_immutability_of :all
         
     | 
| 
      
 7 
     | 
    
         
            +
              
         
     | 
| 
      
 8 
     | 
    
         
            +
              validates_size_of :postings, :minimum => 2
         
     | 
| 
      
 9 
     | 
    
         
            +
              validates_associated :postings
         
     | 
| 
      
 10 
     | 
    
         
            +
              validate :validate_postings_sum_to_zero
         
     | 
| 
      
 11 
     | 
    
         
            +
              
         
     | 
| 
      
 12 
     | 
    
         
            +
              def destroy
         
     | 
| 
      
 13 
     | 
    
         
            +
                raise ActiveRecord::IndestructibleRecord
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
              
         
     | 
| 
      
 16 
     | 
    
         
            +
              def transactable_type=(sType)
         
     | 
| 
      
 17 
     | 
    
         
            +
                super(sType.to_s.classify.constantize.base_class.to_s)
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
              
         
     | 
| 
      
 20 
     | 
    
         
            +
              protected    
         
     | 
| 
      
 21 
     | 
    
         
            +
                def validate_postings_sum_to_zero
         
     | 
| 
      
 22 
     | 
    
         
            +
                  errors.add :postings, 'must sum to zero.' unless postings_sum_to_zero
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
                
         
     | 
| 
      
 25 
     | 
    
         
            +
                def postings_sum_to_zero
         
     | 
| 
      
 26 
     | 
    
         
            +
                  sum = self.postings.inject(0) { |sum, posting| sum + posting.amount }
         
     | 
| 
      
 27 
     | 
    
         
            +
                  sum == 0
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Journal::Bill < Journal
         
     | 
| 
      
 2 
     | 
    
         
            +
              has_many   :payments, :as => :payable, :class_name => '::Journal'
         
     | 
| 
      
 3 
     | 
    
         
            +
              belongs_to :creditor, :polymorphic => true
         
     | 
| 
      
 4 
     | 
    
         
            +
              
         
     | 
| 
      
 5 
     | 
    
         
            +
              def amount
         
     | 
| 
      
 6 
     | 
    
         
            +
                postings.find(:first, :conditions => {:account_id => Account.accounts_payable}).amount
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
              
         
     | 
| 
      
 9 
     | 
    
         
            +
              def amount_paid
         
     | 
| 
      
 10 
     | 
    
         
            +
                payments.inject(0) { |sum, payment| sum - payment.postings.find(:first, :conditions => {:account_id => Account.accounts_payable}).amount }
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
              
         
     | 
| 
      
 13 
     | 
    
         
            +
              def paid_in_full?
         
     | 
| 
      
 14 
     | 
    
         
            +
                amount_paid >= amount
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
              
         
     | 
| 
      
 17 
     | 
    
         
            +
              def overpaid?
         
     | 
| 
      
 18 
     | 
    
         
            +
                amount_paid > amount
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
              
         
     | 
| 
      
 21 
     | 
    
         
            +
              def pay(payment_transaction)
         
     | 
| 
      
 22 
     | 
    
         
            +
                gross = payment_transaction.notification.amount.cents.to_f / 100
         
     | 
| 
      
 23 
     | 
    
         
            +
                
         
     | 
| 
      
 24 
     | 
    
         
            +
                journal = Journal::Disbursement.new(:transactable => payment_transaction)
         
     | 
| 
      
 25 
     | 
    
         
            +
                
         
     | 
| 
      
 26 
     | 
    
         
            +
                journal.postings |= payment_transaction.to_postings
         
     | 
| 
      
 27 
     | 
    
         
            +
                journal.postings << Account.accounts_payable.debit(gross)
         
     | 
| 
      
 28 
     | 
    
         
            +
                
         
     | 
| 
      
 29 
     | 
    
         
            +
                payments << journal
         
     | 
| 
      
 30 
     | 
    
         
            +
                save!
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,12 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class PaypalTransaction < ActiveRecord::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              include ActiveMerchant::Billing::Integrations
         
     | 
| 
      
 3 
     | 
    
         
            +
              
         
     | 
| 
      
 4 
     | 
    
         
            +
              serialize :notification, Paypal::Notification
         
     | 
| 
      
 5 
     | 
    
         
            +
                
         
     | 
| 
      
 6 
     | 
    
         
            +
              has_one :journal, :as => :transactable
         
     | 
| 
      
 7 
     | 
    
         
            +
              
         
     | 
| 
      
 8 
     | 
    
         
            +
              private
         
     | 
| 
      
 9 
     | 
    
         
            +
                def account
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @account ||= Account::Asset.find_by_account_number(self.notification.account)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class PaypalTransaction::MasspaySubpayment < PaypalTransaction
         
     | 
| 
      
 2 
     | 
    
         
            +
              
         
     | 
| 
      
 3 
     | 
    
         
            +
              def to_postings
         
     | 
| 
      
 4 
     | 
    
         
            +
                gross = self.notification.amount.cents.to_f / 100
         
     | 
| 
      
 5 
     | 
    
         
            +
                fee = self.notification.fee.to_f
         
     | 
| 
      
 6 
     | 
    
         
            +
                
         
     | 
| 
      
 7 
     | 
    
         
            +
                postings = []
         
     | 
| 
      
 8 
     | 
    
         
            +
                postings << payer_account.credit(gross + fee)
         
     | 
| 
      
 9 
     | 
    
         
            +
                postings << Account.paypal_fees_account.debit(fee) if(fee > 0)
         
     | 
| 
      
 10 
     | 
    
         
            +
                
         
     | 
| 
      
 11 
     | 
    
         
            +
                postings
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
              
         
     | 
| 
      
 14 
     | 
    
         
            +
              def payer_account
         
     | 
| 
      
 15 
     | 
    
         
            +
                @payer_account ||= Account::Asset.find_by_account_number(self.notification.params['payer_email'])
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class PaypalTransaction::Single < PaypalTransaction
         
     | 
| 
      
 2 
     | 
    
         
            +
              
         
     | 
| 
      
 3 
     | 
    
         
            +
              def to_postings
         
     | 
| 
      
 4 
     | 
    
         
            +
                gross = self.notification.amount.cents.to_f / 100
         
     | 
| 
      
 5 
     | 
    
         
            +
                fee = self.notification.fee.to_f
         
     | 
| 
      
 6 
     | 
    
         
            +
                
         
     | 
| 
      
 7 
     | 
    
         
            +
                postings = []
         
     | 
| 
      
 8 
     | 
    
         
            +
                postings << account.debit(gross - fee)
         
     | 
| 
      
 9 
     | 
    
         
            +
                postings << Account.paypal_fees_account.debit(fee) if(fee > 0)
         
     | 
| 
      
 10 
     | 
    
         
            +
                
         
     | 
| 
      
 11 
     | 
    
         
            +
                postings
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,32 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Posting < ActiveRecord::Base
         
     | 
| 
      
 2 
     | 
    
         
            +
              belongs_to  :account
         
     | 
| 
      
 3 
     | 
    
         
            +
              belongs_to  :journal
         
     | 
| 
      
 4 
     | 
    
         
            +
              belongs_to  :asset_type
         
     | 
| 
      
 5 
     | 
    
         
            +
              
         
     | 
| 
      
 6 
     | 
    
         
            +
              ensures_immutability_of :all
         
     | 
| 
      
 7 
     | 
    
         
            +
              
         
     | 
| 
      
 8 
     | 
    
         
            +
              before_validation_on_create :default_to_us_dollars, :unless => :asset_type
         
     | 
| 
      
 9 
     | 
    
         
            +
              
         
     | 
| 
      
 10 
     | 
    
         
            +
              validates_presence_of :account
         
     | 
| 
      
 11 
     | 
    
         
            +
              validates_presence_of :asset_type
         
     | 
| 
      
 12 
     | 
    
         
            +
              validate :validate_belongs_to_a_journal
         
     | 
| 
      
 13 
     | 
    
         
            +
              
         
     | 
| 
      
 14 
     | 
    
         
            +
              def destroy
         
     | 
| 
      
 15 
     | 
    
         
            +
                raise ActiveRecord::IndestructibleRecord
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
              
         
     | 
| 
      
 18 
     | 
    
         
            +
              private
         
     | 
| 
      
 19 
     | 
    
         
            +
                def validate_belongs_to_a_journal
         
     | 
| 
      
 20 
     | 
    
         
            +
                  parent_journal = self.journal
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  if !parent_journal
         
     | 
| 
      
 23 
     | 
    
         
            +
                    ObjectSpace.each_object(Journal) {|j| parent_journal = j if j.postings.include?(self)}
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
                  
         
     | 
| 
      
 26 
     | 
    
         
            +
                  errors.add :journal, 'must be set.' if !parent_journal      
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
                
         
     | 
| 
      
 29 
     | 
    
         
            +
                def default_to_us_dollars
         
     | 
| 
      
 30 
     | 
    
         
            +
                  self.asset_type = AssetType::USD.find(:first)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Copyright (c) 2007 Wesley Moxam - Savvica Inc.
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Permission is hereby granted, free of charge, to any person obtaining
         
     | 
| 
      
 4 
     | 
    
         
            +
            a copy of this software and associated documentation files (the
         
     | 
| 
      
 5 
     | 
    
         
            +
            "Software"), to deal in the Software without restriction, including
         
     | 
| 
      
 6 
     | 
    
         
            +
            without limitation the rights to use, copy, modify, merge, publish,
         
     | 
| 
      
 7 
     | 
    
         
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         
     | 
| 
      
 8 
     | 
    
         
            +
            permit persons to whom the Software is furnished to do so, subject to
         
     | 
| 
      
 9 
     | 
    
         
            +
            the following conditions:
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            The above copyright notice and this permission notice shall be
         
     | 
| 
      
 12 
     | 
    
         
            +
            included in all copies or substantial portions of the Software.
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         
     | 
| 
      
 15 
     | 
    
         
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         
     | 
| 
      
 16 
     | 
    
         
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         
     | 
| 
      
 17 
     | 
    
         
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         
     | 
| 
      
 18 
     | 
    
         
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         
     | 
| 
      
 19 
     | 
    
         
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         
     | 
| 
      
 20 
     | 
    
         
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            EnsuresImmutabilityOf
         
     | 
| 
      
 2 
     | 
    
         
            +
            =====================
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            There are many cases where a model attribute should not be changed once it's set. This plugin makes it dead simple.
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            Example
         
     | 
| 
      
 7 
     | 
    
         
            +
            =======
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            class Account < ActiveRecord::Base
         
     | 
| 
      
 10 
     | 
    
         
            +
              ensures_immutability_of :username, :email
         
     | 
| 
      
 11 
     | 
    
         
            +
            end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            account = Account.create(:username => 'jgreen')
         
     | 
| 
      
 14 
     | 
    
         
            +
            ...
         
     | 
| 
      
 15 
     | 
    
         
            +
            account.update(:username => 'jgreen')  # raises ActiveRecord::ImmutableAttributeError
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            Entire models can be made immutable by passing the symbol :all to ensures_immutability_of
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            class Account < ActiveRecord::Base
         
     | 
| 
      
 20 
     | 
    
         
            +
              ensures_immutability_of :all
         
     | 
| 
      
 21 
     | 
    
         
            +
            end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
            Collections can all be immutable as well (thanks to Dmitry Ratnikov on #rubyonrails)
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            class Account  < ActiveRecord::Base
         
     | 
| 
      
 26 
     | 
    
         
            +
              has_many :infos
         
     | 
| 
      
 27 
     | 
    
         
            +
              ensures_immutability_of :username, :email, :infos   # note: this must come after the has_many declaration, otherwise
         
     | 
| 
      
 28 
     | 
    
         
            +
                                                                  # AR will overwrite the setter
         
     | 
| 
      
 29 
     | 
    
         
            +
            end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            account = Account.create(:username => 'wmoxam')
         
     | 
| 
      
 32 
     | 
    
         
            +
            account.infos = Info.find(:all, :conditions => ["id < ?", 3]
         
     | 
| 
      
 33 
     | 
    
         
            +
            account.infos =  Info.find(:all, :conditions => ["id > ?", 3]  # raises ActiveRecord::ImmutableAttributeError
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            Copyright (c) 2007-2008 Wesley Moxam - Savvica Inc, released under the MIT license
         
     |