Class: Voucher

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/voucher.rb

Overview

== Schema Information

Table name: vouchers
Database name: primary

id :integer not null, primary key
category :string(255)
currency :string(255)
exchange_rate :float
gl_date :date
invoice_date :date
invoice_number :string(255)
payment_terms :string(255)
reference_number :string(255) not null
reversal_date :date
state :string(255)
created_at :datetime
updated_at :datetime
business_unit_id :integer
company_id :integer
creator_id :integer
order_id :integer
supplier_id :integer
updater_id :integer

Indexes

index_vouchers_on_business_unit_id (business_unit_id)
index_vouchers_on_category (category)
index_vouchers_on_company_id (company_id)
index_vouchers_on_currency (currency)
index_vouchers_on_gl_date (gl_date)
index_vouchers_on_invoice_date (invoice_date)
index_vouchers_on_invoice_number (invoice_number)
index_vouchers_on_order_id (order_id)
index_vouchers_on_payment_terms (payment_terms)
index_vouchers_on_reference_number (reference_number)
index_vouchers_on_state (state)
supplier_id_invoice_number (supplier_id,invoice_number)

Constant Summary collapse

CATEGORIES =
%w[voucher debit_memo commission].freeze
REFERENCE_NUMBER_PATTERN =
/^PV(\d+)$/i

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#business_unit_idObject (readonly)



58
# File 'app/models/voucher.rb', line 58

validates :category, :company_id, :supplier_id, :invoice_date, :gl_date, :currency, :business_unit_id, :payment_terms, presence: true

#categoryObject (readonly)



58
# File 'app/models/voucher.rb', line 58

validates :category, :company_id, :supplier_id, :invoice_date, :gl_date, :currency, :business_unit_id, :payment_terms, presence: true

#company_idObject (readonly)



58
# File 'app/models/voucher.rb', line 58

validates :category, :company_id, :supplier_id, :invoice_date, :gl_date, :currency, :business_unit_id, :payment_terms, presence: true

#currencyObject (readonly)



58
# File 'app/models/voucher.rb', line 58

validates :category, :company_id, :supplier_id, :invoice_date, :gl_date, :currency, :business_unit_id, :payment_terms, presence: true

#gl_dateObject (readonly)



58
# File 'app/models/voucher.rb', line 58

validates :category, :company_id, :supplier_id, :invoice_date, :gl_date, :currency, :business_unit_id, :payment_terms, presence: true

#invoice_dateObject (readonly)



58
# File 'app/models/voucher.rb', line 58

validates :category, :company_id, :supplier_id, :invoice_date, :gl_date, :currency, :business_unit_id, :payment_terms, presence: true

#invoice_numberObject (readonly)



65
# File 'app/models/voucher.rb', line 65

validates :invoice_number, uniqueness: { scope: :supplier_id, message: 'is already in use on another voucher for this supplier', unless: :voided? }

#order_idObject (readonly)



59
# File 'app/models/voucher.rb', line 59

validates :order_id, presence: { if: proc { |v| v.category == 'commission' } }

#order_refObject

Returns the value of attribute order_ref.



86
87
88
# File 'app/models/voucher.rb', line 86

def order_ref
  @order_ref
end

#payment_termsObject (readonly)



58
# File 'app/models/voucher.rb', line 58

validates :category, :company_id, :supplier_id, :invoice_date, :gl_date, :currency, :business_unit_id, :payment_terms, presence: true

#supplier_idObject (readonly)



58
# File 'app/models/voucher.rb', line 58

validates :category, :company_id, :supplier_id, :invoice_date, :gl_date, :currency, :business_unit_id, :payment_terms, presence: true

#supplier_typeObject

Returns the value of attribute supplier_type.



86
87
88
# File 'app/models/voucher.rb', line 86

def supplier_type
  @supplier_type
end

Class Method Details

.activeActiveRecord::Relation<Voucher>

A relation of Vouchers that are active. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Voucher>)

See Also:



84
# File 'app/models/voucher.rb', line 84

scope :active, -> { where.not(state: 'voided') }

.build_gl_entries_from_csv(file_path) ⇒ Object



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# File 'app/models/voucher.rb', line 337

def self.build_gl_entries_from_csv(file_path)
  ledger_entries = []
  CSV.foreach(file_path, headers: true) do |row|
     = LedgerCompanyAccount.joins(:company, :ledger_detail_account).where(companies: { number: row['company'] },
                                                                                 ledger_accounts: { number: row['account'] }).first
    project = LedgerDetailProject.where(project_number: row['project']).first
    ledger_entries << {
      account_id: .try(:id),
      account_ref: .try(:identifier),
      project_id: project.try(:id),
      project_ref: project.try(:project_number),
      amount: row['amount'],
      business_unit_id: BusinessUnit.where(number: row['business_unit']).first.try(:id),
      remark: row['description']
    }
  end
  ledger_entries
end

.get_next_reference_numberObject



358
359
360
361
# File 'app/models/voucher.rb', line 358

def self.get_next_reference_number
  seq = Voucher.find_by_sql("SELECT nextval('voucher_reference_numbers_seq') AS reference_number")
  seq[0].reference_number.to_s
end

.states_for_selectObject



134
135
136
# File 'app/models/voucher.rb', line 134

def self.states_for_select
  Voucher.state_machines[:state].states.map { |s| [s.human_name.titleize, s.name] }.sort
end

.voucher_count(company_id = nil, where_conditions = nil, where_not_conditions = nil) ⇒ Object



126
127
128
129
130
131
132
# File 'app/models/voucher.rb', line 126

def self.voucher_count(company_id = nil, where_conditions = nil, where_not_conditions = nil)
  v = Voucher.order('id')
  v = v.where(company_id: company_id) unless company_id.nil?
  v = v.where(where_conditions) unless where_conditions.nil?
  v = v.where.not(where_not_conditions) unless where_not_conditions.nil?
  v.count
end

.with_lazy_loadsActiveRecord::Relation<Voucher>

A relation of Vouchers that are with lazy loads. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Voucher>)

See Also:



80
81
82
83
# File 'app/models/voucher.rb', line 80

scope :with_lazy_loads, -> {
  includes({ voucher_items: :tax_rate }, :business_unit, :company, :supplier,
           { ledger_transactions: [:company, { ledger_entries: { ledger_company_account: %i[company ledger_detail_account] } }] })
}

Instance Method Details

#all_items_fully_paid?Boolean

Returns:

  • (Boolean)


170
171
172
173
# File 'app/models/voucher.rb', line 170

def all_items_fully_paid?
  # force reload of the voucher items as sometimes it's referring to already loaded stale objects
  voucher_items.reload.all?(&:fully_paid?)
end

#all_payments_voided?Boolean

Returns:

  • (Boolean)


175
176
177
# File 'app/models/voucher.rb', line 175

def all_payments_voided?
  outgoing_payment_items.reload.all?(&:voided?)
end

#amount_to_distributeObject



195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/models/voucher.rb', line 195

def amount_to_distribute
  amount = BigDecimal(0)
  voucher_items.each do |vi|
    amount += case vi.tax_type
              when 'V'
                (vi.gross_amount - vi.tax_amount)
              when 'U'
                (vi.gross_amount + vi.tax_amount)
              else
                vi.gross_amount
              end
  end
  amount
end

#balanceObject



156
157
158
159
160
# File 'app/models/voucher.rb', line 156

def balance
  return 0 if voided?

  total - voucher_items.all.to_a.sum { |vi| vi.outgoing_payment_items.applied.sum(:amount) }
end

#business_unitBusinessUnit



50
# File 'app/models/voucher.rb', line 50

belongs_to :business_unit, optional: true

#can_be_voided?Boolean

Returns:

  • (Boolean)


179
180
181
# File 'app/models/voucher.rb', line 179

def can_be_voided?
  unpaid? or paid?
end

#companyCompany

Returns:

See Also:



48
# File 'app/models/voucher.rb', line 48

belongs_to :company, optional: true

#currency_symbolObject



333
334
335
# File 'app/models/voucher.rb', line 333

def currency_symbol
  Money::Currency.new(currency).symbol
end

#distribute_to_gl(_temporary_account_id = nil, ledger_entries = []) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'app/models/voucher.rb', line 210

def distribute_to_gl( = nil, ledger_entries = [])
  transaction do
    case category
    when 'voucher_logging'

      # trade_accounts_payable_company_account = LedgerCompanyAccount.for_company_and_account(company.id, TRADE_ACCOUNTS_PAYABLE_ACCOUNT)
      # temporary_company_account = LedgerCompanyAccount.find(temporary_account_id)
      #
      # raise "Unable to find all necessary accounts to post to" if trade_accounts_payable_company_account.nil? or temporary_company_account.nil?
      #
      # ap_amount = -total
      # temp_amount = BigDecimal("0")
      # tax_accounts = {}
      #
      # voucher_items.each do |vi|
      #   case vi.tax_type
      #   when "N/A"
      #     temp_amount += vi.gross_amount
      #   when "S"
      #     temp_amount += vi.gross_amount
      #   when "U"
      #     tax_account = LedgerCompanyAccount.where(:company_id => company.id, :ledger_detail_account_id => vi.tax_rate.use_tax_account_id).first
      #     raise "Cannot find use tax account for tax rate id: #{vi.tax_rate_id}" if tax_account.nil?
      #     tax_accounts[tax_account.id] = tax_accounts[tax_account.id].nil? ? -vi.tax_amount : (tax_accounts[tax_account.id] += -vi.tax_amount)
      #     temp_amount += (vi.gross_amount + vi.tax_amount)
      #   when "V"
      #     tax_account = LedgerCompanyAccount.where(:company_id => company.id, :ledger_detail_account_id => vi.tax_rate.sales_tax_credit_account_id).first
      #     raise "Cannot find output tax account for tax rate id: #{vi.tax_rate_id}" if tax_account.nil?
      #     tax_accounts[tax_account.id] = tax_accounts[tax_account.id].nil? ? vi.tax_amount : (tax_accounts[tax_account.id] += vi.tax_amount)
      #     # add the (gross_amount - tax_amount) to be distributed
      #     # for tax only lines this would be 0
      #     temp_amount += vi.gross_amount - vi.tax_amount
      #   end
      # end
      #
      # transaction = LedgerTransaction.new(:company => company, :transaction_type => "VOUCHER", :transaction_date => gl_date, :currency => currency, :voucher => self, :exchange_rate => exchange_rate)
      # transaction.ledger_entries << LedgerEntry.new(:ledger_company_account => trade_accounts_payable_company_account, :currency => currency, :amount => ap_amount)
      # transaction.ledger_entries << LedgerEntry.new(:ledger_company_account => temporary_company_account, :currency => currency, :amount => temp_amount)
      # tax_accounts.each do |account_id, amount|
      #   transaction.ledger_entries << LedgerEntry.new(:ledger_company_account_id => account_id, :currency => currency, :amount => amount)
      # end
      #
      # transaction.save!
      #
      # # need to

    when 'voucher', 'debit_memo', 'commission'
       = if supplier..present?
                                 LedgerCompanyAccount.(company.id, supplier..number)
                               else
                                 LedgerCompanyAccount.(company.id, TRADE_ACCOUNTS_PAYABLE_ACCOUNT)
                               end

      raise 'Unable to find all necessary accounts to post to' if .nil?

      accounts = {  => { amount: 0, business_unit: nil } }
      tax_accounts = {}

      voucher_items.each do |vi|
        case vi.tax_type
        when 'U'
           = LedgerCompanyAccount.where(company_id: company.id, ledger_detail_account_id: vi.tax_rate.).first
          raise "Cannot find use tax account for tax rate id: #{vi.tax_rate_id}" if .nil?

          tax_accounts[.id] = tax_accounts[.id].nil? ? -vi.tax_amount : (tax_accounts[.id] += -vi.tax_amount)
        when 'V'
           = LedgerCompanyAccount.where(company_id: company.id,
                                                   ledger_detail_account_id: vi.tax_rate.).first
          raise "Cannot find output tax account for tax rate id: #{vi.tax_rate_id}" if .nil?

          tax_accounts[.id] = tax_accounts[.id].nil? ? vi.tax_amount : (tax_accounts[.id] += vi.tax_amount)
        end
        if vi..present?
          accounts[vi.] ||= { amount: 0, business_unit: vi.business_unit }
          accounts[vi.][:amount] += -vi.gross_amount
        else
          accounts[][:amount] += -vi.gross_amount
        end
      end

      transaction = LedgerTransaction.new(company: company, transaction_type: 'VOUCHER', transaction_date: gl_date, currency: currency,
                                          voucher: self, exchange_rate: exchange_rate)
      accounts.each do |, details|
        unless details[:amount].zero?
          transaction.ledger_entries << LedgerEntry.new(ledger_company_account: , currency: currency, amount: details[:amount],
                                                        business_unit: details[:business_unit])
        end
      end
      tax_accounts.each do |, amount|
        transaction.ledger_entries << LedgerEntry.new(ledger_company_account_id: , currency: currency, amount: amount)
      end

      ledger_entries.each do |le|
        transaction.ledger_entries << LedgerEntry.new(ledger_company_account_id: le['account_id'], currency: currency,
                                                      amount: le['amount'], description: le['remark'], ledger_detail_project_id: le['project_id'], business_unit_id: le['business_unit_id'])
      end
      transaction.save!
      intercompany_posting_assign_ledger_project
      gl_completed!
    end
  end
end

#editing_locked?Boolean

Returns:

  • (Boolean)


183
184
185
# File 'app/models/voucher.rb', line 183

def editing_locked?
  !draft? or voided?
end

#intercompany_posting_assign_ledger_projectObject



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'app/models/voucher.rb', line 313

def intercompany_posting_assign_ledger_project
  itercompany_postings_entries = []
  ledger_transactions.first.ledger_entries.each do |le|
    if le.. && le...number == INTERCOMPANY_TRANSFERS_ACCOUNT
      itercompany_postings_entries << { ledger_company_account_id: le.,
company_id: le..company_id }
    end
  end
  return unless itercompany_postings_entries.length > 1

  ledger_transactions.first.ledger_entries.each do |le|
    le.update(ledger_detail_project_id: Company.find(itercompany_postings_entries.last[:company_id]).ledger_project_id) if le. == itercompany_postings_entries.first[:ledger_company_account_id]
    le.update(ledger_detail_project_id: Company.find(itercompany_postings_entries.first[:company_id]).ledger_project_id) if le. == itercompany_postings_entries.last[:ledger_company_account_id]
  end
end

#ledger_transactionsActiveRecord::Relation<LedgerTransaction>

Returns:

See Also:



54
# File 'app/models/voucher.rb', line 54

has_many :ledger_transactions

#orderOrder

Returns:

See Also:



51
# File 'app/models/voucher.rb', line 51

belongs_to :order, optional: true

#outgoing_payment_itemsActiveRecord::Relation<OutgoingPaymentItem>

Returns:

See Also:



55
# File 'app/models/voucher.rb', line 55

has_many :outgoing_payment_items, through: :voucher_items

#reverse(date) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'app/models/voucher.rb', line 138

def reverse(date)
  raise 'Reversal date required' if date.nil?

  Voucher.transaction do
    self.reversal_date = date
    save!
    payments = outgoing_payment_items.collect(&:payment).uniq
    payments.each do |p|
      p.reversal_date = reversal_date
      p.state_event = 'void'
      p.save!
    end
    reload
    self.state_event = 'void'
    save!
  end
end

#supplierParty

Returns:

See Also:



49
# File 'app/models/voucher.rb', line 49

belongs_to :supplier, class_name: 'Party', inverse_of: :vouchers, optional: true

#supplier_nameObject



187
188
189
# File 'app/models/voucher.rb', line 187

def supplier_name
  supplier.try(:full_name)
end

#to_sObject



122
123
124
# File 'app/models/voucher.rb', line 122

def to_s
  "Voucher ##{reference_number}"
end

#totalObject



329
330
331
# File 'app/models/voucher.rb', line 329

def total
  voucher_items.sum(:gross_amount)
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



56
# File 'app/models/voucher.rb', line 56

has_many :uploads, as: :resource, dependent: :destroy

#voucher_itemsActiveRecord::Relation<VoucherItem>

Returns:

See Also:



53
# File 'app/models/voucher.rb', line 53

has_many :voucher_items, inverse_of: :voucher, dependent: :destroy