moser/foxtrot_mike

View on GitHub
app/models/accounting_session.rb

Summary

Maintainability
B
4 hrs
Test Coverage
class AccountingSession < ActiveRecord::Base
  has_many :accounting_entries
  has_many :flights, :order => "departure_date ASC, departure_date ASC"
  has_many :first_debit_financial_accounts, class_name: 'FinancialAccount', foreign_key: 'first_debit_accounting_session_id'

  belongs_to :credit_financial_account, :class_name => "FinancialAccount"

  serialize :exclusions, Array

  validates_presence_of :name, :voucher_number, :accounting_date
  validates_presence_of :start_date, :end_date, :if => lambda { |s| !s.without_flights? }
  validates_presence_of :credit_financial_account, :if => lambda { |s| s.bank_debit? }
  validate do |a|
    errors.add(:end_date, AccountingSession.l(:must_not_be_in_the_future)) if !(a.without_flights? || a.bank_debit?) && a.end_date && a.end_date > DateTime.now.to_date
  end

  before_save :remove_dates_if_without_flights
  before_destroy :destroy_manual_accounting_entries

  default_scope order('accounting_date DESC')

  attr_reader :problems
  def soft_validate
    @problems = {}
    unless without_flights?
      unaccounted_flights = Flight.where(Flight.arel_table[:departure_date].lt(start_date)).where(:accounting_session_id => nil)
      count = unaccounted_flights.count
      oldest = unaccounted_flights.pluck(:departure_date).min
      if count > 0
        @problems[:unaccounted_flights_before_start] = { :count => count, :oldest => I18n.l(oldest), :ids => unaccounted_flights.limit(10).pluck(:id).join(" ") }
      end
    end
    @problems[:financial_account_missing_number] = {} if concerned_financial_accounts.find { |a| !a.number? }
    @problems.empty?
  end

  def exclusions
    read_attribute(:exclusions) || []
  end

  def add_excluded_account(financial_account)
    self.exclusions << financial_account
    self.save
  end

  def remove_excluded_account(financial_account)
    self.exclusions.delete(financial_account)
    self.save
  end

  def manual_accounting_entries
    accounting_entries_without_default.where(:manual => true)
  end

  #TODO make all other flights methods unaccessible
  def flights_with_default
    unless without_flights?
      if finished?
        flights_without_default
      else
        Flight.include_all.where(AbstractFlight.arel_table[:departure_date].gteq(start_date)).
                              where(AbstractFlight.arel_table[:departure_date].lteq(end_date)).
                              where(:accounting_session_id => nil).
                              order('departure_date ASC').all
      end
    else
      []
    end
  end
  alias_method_chain :flights, :default

  def accounting_entries_with_default
    if finished?
      accounting_entries_without_default
    else
      unless bank_debit?
        flights.map { |f| f.calculate_cost_if_necessary; f.accounting_entries }.flatten + manual_accounting_entries
      else
        negative_financial_accounts.select do |financial_account|
          !self.exclusions.include?(financial_account)
        end.map do |financial_account|
          AccountingEntry.new(:from => credit_financial_account, 
                              :to => financial_account, 
                              :value => [-financial_account.accounted_balance, financial_account.max_debit_value].min, 
                              :accounting_session => self, 
                              :text => name)
        end
      end
    end
  end
  alias_method_chain :accounting_entries, :default

  def negative_financial_accounts 
    if debit_type == 'dta'
      FinancialAccount.where("bank_account_number != ''").where(:advance_payment => false, :member_account => true).select { |f| f.accounted_balance < 0 }
    else
      FinancialAccount.where("iban != ''").where(:advance_payment => false, :member_account => true).select { |f| f.accounted_balance < 0 }
    end
  end

  def initialize(*args)
    super(*args)
    if new_record? && start_date.nil? && end_date.nil?
      self.start_date = AccountingSession.latest_session_end + 1.day
      self.end_date = start_date.end_of_month
    end
  end

  def start_date=(d)
    d = d.to_date if d.respond_to?(:to_date)
    write_attribute(:start_date, d)
  end

  def end_date=(d)
    d = d.to_date if d.respond_to?(:to_date)
    write_attribute(:end_date, d)
  end

  def dta?
    debit_type == 'dta'
  end

  def sepa?
    debit_type == 'sepa'
  end

  def any_first?
    !first_debit_financial_accounts.empty?
  end

  def any_recurring?
    accounting_entries.any? { |ae| ae.to.first_debit_accounting_session != self }
  end

  def finished?
    !finished_at.nil?
  end
  alias :finished :finished?

  def finished=(b)
    unless !b || finished?
      flights.each do |f|
        f.update_attribute :accounting_session, self
        f.accounting_entries.each do |e|
          e.update_attribute :accounting_session, self
        end
      end
      if bank_debit?
        accounting_entries.each do |e|
          if self.sepa? && e.to.first_debit_accounting_session.nil?
            e.to.update_attribute :first_debit_accounting_session_id, self.id
          end
          e.save
        end
      end
      update_attribute :finished_at, DateTime.now
    end
  end

  def aggregated_entries
    ae = accounting_entries.select { |e| !e.manual? }.group_by { |e| e.from }.map do |from, entries|
      entries.group_by { |e| e.to }.map do |to, entries|
        AggregatedEntry.new(from, to, entries)
      end
    end
    ae.flatten + manual_accounting_entries
  end

  def concerned_financial_accounts
    accounting_entries.map { |e| [e.to, e.from] }.flatten.uniq
  end


  def bookings_csv
    CSV.generate do |csv|
      csv << %w(booking_date voucher_number from_account_number
      from_account to_account_number to_account value_f text)
      
      aggregated_entries.each do |entry|
        csv << [ accounting_date.to_s,
                 voucher_number,
                 entry.from_account_number,
                 entry.from_account_name,
                 entry.to_account_number,
                 entry.to_account_name,
                 entry.value_f,
                 "#{ name } #{ entry.manual? ? entry.text : "" }" ]
      end
    end
  end


  def filename
    "#{ finished_at.to_date }-#{ AccountingSession.l(:entries) }-#{ voucher_number }-#{ name.gsub(" ", "-") }.csv"
  end

#  def self.booking_now
#    DateTime.now
#  end

  # The end date of the latest accounting session.
  # If there are no accounting sessions, the day before the first flight.
  # If there are no flights, it is yesterday.
  # This date is used to determine if any cost related models
  # may be changed. (E.g. a cost rule that was valid at this date must
  # not be changed.)
  def self.latest_finished_session_end
    AccountingSession.where(AccountingSession.arel_table[:finished_at].not_eq(nil)).select(:end_date).maximum(:end_date) || (AbstractFlight.oldest_departure - 1.year).to_date
  end

  def self.latest_session_end
    AccountingSession.select(:end_date).maximum(:end_date) || (AbstractFlight.oldest_departure - 1.day).to_date
  end

  def self.latest_finished_bank_debit_session_end
    AccountingSession.where(AccountingSession.arel_table[:finished_at].not_eq(nil)).where(:bank_debit => true).select(:end_date).maximum(:end_date) || (AbstractFlight.oldest_departure - 1.year).to_date
  end

private
  def remove_dates_if_without_flights
    self.without_flights = true if bank_debit?
    if without_flights?
      self.start_date = self.end_date = nil
    end
  end

  def destroy_manual_accounting_entries
    self.manual_accounting_entries.destroy_all
  end
end