Module: CustomerFinancials

Extended by:
ActiveSupport::Concern
Included in:
Customer
Defined in:
app/models/concerns/customer_financials.rb

Overview

Invoices, receipts, credit, AR, terms, store credit, payment methods.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.duplicate_addressesObject

:reek:UtilityFunction



224
225
226
# File 'app/models/concerns/customer_financials.rb', line 224

def duplicate_addresses
  Maintenance::DuplicateCustomerAddresses.call
end

.duplicate_contact_pointsObject

:reek:UtilityFunction



229
230
231
# File 'app/models/concerns/customer_financials.rb', line 229

def duplicate_contact_points
  Maintenance::DuplicateCustomerContactPoints.call
end

.statement_of_account(party_id) ⇒ Object

:reek:UtilityFunction



219
220
221
# File 'app/models/concerns/customer_financials.rb', line 219

def (party_id)
  Query::CustomerStatementOfAccount.call(party_id)
end

Instance Method Details

#account_on_hold?Boolean

Returns:

  • (Boolean)


207
208
209
# File 'app/models/concerns/customer_financials.rb', line 207

def 
  on_hold
end

#all_rmasObject



64
65
66
67
# File 'app/models/concerns/customer_financials.rb', line 64

def all_rmas
  Rma.joins('left outer join orders on orders.id = rmas.original_order_id')
     .where('rmas.customer_id = :customer_id or orders.customer_id = :customer_id', customer_id: id)
end

#all_termsObject



199
200
201
202
203
204
205
# File 'app/models/concerns/customer_financials.rb', line 199

def all_terms
  if billing_address.present? && billing_address.party && (billing_address.party != self)
    billing_address.party.all_terms
  else
    build_local_all_terms
  end
end

#apc_available?Boolean

Returns:

  • (Boolean)


192
193
194
195
196
197
# File 'app/models/concerns/customer_financials.rb', line 192

def apc_available?
  bg = buying_group.
  bg.authorization_code_required && bg.terms?
rescue StandardError
  false
end

#approve_credit_request(amount, order = nil) ⇒ Object



50
51
52
53
54
55
# File 'app/models/concerns/customer_financials.rb', line 50

def approve_credit_request(amount, order = nil)
  self.available_credit += amount
  self.on_hold = false if on_hold?
  save!
  FinancialsMailer.credit_request_approved_notification(self, amount, order).deliver_later
end

#ar_listings(show_open_only: false) ⇒ Object

:reek:BooleanParameter :reek:ControlParameter



86
87
88
89
90
# File 'app/models/concerns/customer_financials.rb', line 86

def ar_listings(show_open_only: false)
  list = ViewArListing.where('billing_customer_id = ? or customer_id = ?', id, id)
  list = list.where(open_amount_is_zero: false) if show_open_only.present?
  list.order(Arel.sql('open_amount_is_zero,coalesce(due_date, document_date) DESC,line_type DESC,reference_number DESC'))
end

#auto_increase_available_credit(amount, order = nil) ⇒ Object

-- imperative API: performs side effects and notifications



40
41
42
# File 'app/models/concerns/customer_financials.rb', line 40

def auto_increase_available_credit(amount, order = nil)
  Credit::AutoIncrease.call(self, amount, order:)
end

#available_store_creditObject



130
131
132
133
134
135
136
137
# File 'app/models/concerns/customer_financials.rb', line 130

def available_store_credit
  ar_scope = ViewArListing.where('billing_customer_id = ? or customer_id = ?', id, id)
  totals = ar_scope.where(line_type: %w[CM INV]).pick(
    Arel.sql("COALESCE(SUM(CASE WHEN line_type = 'CM' THEN open_amount_calc * -1 ELSE 0 END), 0)"),
    Arel.sql("COALESCE(SUM(CASE WHEN line_type = 'INV' THEN open_amount_calc ELSE 0 END), 0)")
  )
  (totals[0] - totals[1]) - store_credit_used_on_open_orders
end

#bad_debtObject



31
32
33
# File 'app/models/concerns/customer_financials.rb', line 31

def bad_debt
  ReceiptDetail.joins(:receipt).where(receipts: { customer_id: billing_entity.id }).sum(:write_off)
end

#billing_entityObject



151
152
153
154
155
156
157
158
159
# File 'app/models/concerns/customer_financials.rb', line 151

def billing_entity
  return self if billing_address.nil? || (billing_address.party_id == id) || billing_address.party_id.nil?

  (begin
    billing_address.party
  rescue StandardError
    nil
  end) || self
end

#calculated_purchase_historyObject



23
24
25
# File 'app/models/concerns/customer_financials.rb', line 23

def calculated_purchase_history
  purchase_history - outstanding_balance
end

#combined_termsObject



167
168
169
170
171
172
173
# File 'app/models/concerns/customer_financials.rb', line 167

def combined_terms
  if early_payment_discount && early_payment_timescale
    "#{terms} - #{early_payment_discount}%/#{early_payment_timescale}"
  else
    terms
  end
end

#credit_limit_verification(amount, quote = nil) ⇒ Object



57
58
59
60
61
62
# File 'app/models/concerns/customer_financials.rb', line 57

def credit_limit_verification(amount, quote = nil)
  rc = request_credit(amount, ignore_annual_revenue: false)
  return unless rc[:approved] == false

  FinancialsMailer.credit_limit_verification_denied_notification(self, amount, rc[:fail_reasons], quote).deliver_later
end

#determine_receipt_billing_entityObject



161
162
163
164
165
# File 'app/models/concerns/customer_financials.rb', line 161

def determine_receipt_billing_entity
  return self if billing_entity == self

  billing_entity.company == company ? billing_entity : self
end

#locked_credit_amountObject



69
70
71
# File 'app/models/concerns/customer_financials.rb', line 69

def locked_credit_amount
  outstanding_amount_numeric + pre_authorized_amount_numeric
end

#open_amount_greater_than_a_monthObject



35
36
37
# File 'app/models/concerns/customer_financials.rb', line 35

def open_amount_greater_than_a_month
  outstanding_ar_listings.where('(now()::date - due_date::date) > 30').sum(:open_amount_calc)
end

#open_orders_total(exclude_order = nil) ⇒ Object



13
14
15
16
17
# File 'app/models/concerns/customer_financials.rb', line 13

def open_orders_total(exclude_order = nil)
  res = Order.non_credit.where(customer_id: billing_entity.self_and_children_ids).where.not(state: %w[cart invoiced cancelled fraudulent])
  res = res.excluding(exclude_order) if exclude_order
  res.sum(:total)
end

#outstanding_amountObject



102
103
104
# File 'app/models/concerns/customer_financials.rb', line 102

def outstanding_amount
  ActionController::Base.helpers.number_to_currency(outstanding_amount_numeric)
end

#outstanding_amount_numericObject



98
99
100
# File 'app/models/concerns/customer_financials.rb', line 98

def outstanding_amount_numeric
  outstanding_ar_listings.sum(:open_amount_calc)
end

#outstanding_ar_listingsObject



92
93
94
95
96
# File 'app/models/concerns/customer_financials.rb', line 92

def outstanding_ar_listings
  ViewArListing.where('billing_customer_id = :customer_id or customer_id = :customer_id', customer_id: id)
               .where.not(open_amount_calc: 0)
               .order(:document_date)
end

#outstanding_balanceObject



19
20
21
# File 'app/models/concerns/customer_financials.rb', line 19

def outstanding_balance
  Invoice.where(billing_customer_id: billing_entity.id).unpaid.sum(:total)
end

#overdue_balanceObject



27
28
29
# File 'app/models/concerns/customer_financials.rb', line 27

def overdue_balance
  Invoice.where(billing_customer_id: billing_entity.id).overdue.sum(:total)
end

#payment_optionsObject



211
212
213
214
215
# File 'app/models/concerns/customer_financials.rb', line 211

def payment_options
  options = ['Credit Card']
  options << terms if terms != Customer::TERM_DUE
  options
end

#pre_authorized_amount_numericObject



113
114
115
# File 'app/models/concerns/customer_financials.rb', line 113

def pre_authorized_amount_numeric
  pre_authorized_payments.sum(:amount)
end

#pre_authorized_paymentsObject



106
107
108
109
110
111
# File 'app/models/concerns/customer_financials.rb', line 106

def pre_authorized_payments
  Payment.joins(:order).purchase_orders.all_authorized
         .where.not(orders: { state: %w[invoiced cancelled fraudulent] })
         .where(orders: { customer_id: [customer_id, billing_entity&.id].compact.uniq })
         .order(:created_at)
end

#preferred_payment_method(limit: 10) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
# File 'app/models/concerns/customer_financials.rb', line 139

def preferred_payment_method(limit: 10)
  recent_categories = preferred_payment_categories(limit)
  return Customer::PreferredPayment.new if recent_categories.empty?

  counts = recent_categories.tally
  top_category, top_count = counts.max_by { |_k, v| v }
  return Customer::PreferredPayment.new unless top_category

  detail = preferred_payment_detail(top_category)
  Customer::PreferredPayment.new(category: top_category, count: top_count, total_orders: recent_categories.size, detail: detail)
end

#purchase_historyObject



9
10
11
# File 'app/models/concerns/customer_financials.rb', line 9

def purchase_history
  Invoice.where(billing_customer_id: billing_entity.id).sum(:total)
end

#receipts_listObject



117
118
119
120
# File 'app/models/concerns/customer_financials.rb', line 117

def receipts_list
  sort_order = "CASE state WHEN 'unapplied' THEN 1 WHEN 'partially_applied' THEN 2 WHEN 'fully_applied' THEN 3 WHEN 'voided' THEN 4 ELSE 5 END, receipt_date desc".sql_safe
  Receipt.where(customer_id: id).select('id,category,amount,reference,currency,card_type,gl_date,receipt_date,state').order(sort_order)
end

#request_credit(amount, ignore_annual_revenue: false) ⇒ Object

:reek:BooleanParameter



46
47
48
# File 'app/models/concerns/customer_financials.rb', line 46

def request_credit(amount, ignore_annual_revenue: false)
  Credit::EligibilityEvaluator.call(self, amount, ignore_annual_revenue:)
end

#sync_credit_limitObject



73
74
75
76
77
78
79
80
81
82
83
# File 'app/models/concerns/customer_financials.rb', line 73

def sync_credit_limit
  logger.info "Synchronizing credit limit for Customer #{self}"
  self.credit_limit ||= 0
  new_credit = 0
  new_credit = [credit_limit - locked_credit_amount, 0].max if !on_hold? && credit_limit.positive?
  if update(available_credit: new_credit)
    { ok: true }
  else
    { message: errors_to_s }
  end
end

#terms?Boolean

Returns:

  • (Boolean)


188
189
190
# File 'app/models/concerns/customer_financials.rb', line 188

def terms?
  [Customer::TERM_NET15, Customer::TERM_NET30, Customer::TERM_NET37, Customer::TERM_NET45, Customer::TERM_NET60, Customer::TERM_NET90].include? terms
end

#terms_credit_limitObject



122
123
124
125
126
127
128
# File 'app/models/concerns/customer_financials.rb', line 122

def terms_credit_limit
  be = billing_entity
  return 0 unless be.respond_to?(:terms?) && be.terms?
  return 0 if be.respond_to?(:on_hold) && be.on_hold

  available_credit
end

#terms_in_daysObject



175
176
177
178
179
180
181
182
183
184
185
186
# File 'app/models/concerns/customer_financials.rb', line 175

def terms_in_days
  return 0 unless terms?

  case terms
  when Customer::TERM_NET90 then 90
  when Customer::TERM_NET60 then 60
  when Customer::TERM_NET45 then 45
  when Customer::TERM_NET37 then 37
  when Customer::TERM_NET15 then 15
  else 30
  end
end