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.
rubocop:disable Metrics/ModuleLength -- AR/financial surface intentionally grouped
Class Method Summary collapse
-
.duplicate_addresses ⇒ Object
:reek:UtilityFunction.
-
.duplicate_contact_points ⇒ Object
:reek:UtilityFunction.
-
.statement_of_account(party_id) ⇒ Object
:reek:UtilityFunction.
Instance Method Summary collapse
- #account_on_hold? ⇒ Boolean
- #all_rmas ⇒ Object
- #all_terms ⇒ Object
- #apc_available? ⇒ Boolean
- #approve_credit_request(amount, order = nil) ⇒ Object
-
#ar_listings(show_open_only: false) ⇒ Object
:reek:BooleanParameter :reek:ControlParameter.
-
#auto_increase_available_credit(amount, order = nil) ⇒ Object
rubocop:disable Naming/PredicateMethod -- imperative API: performs side effects and notifications.
- #available_store_credit ⇒ Object
- #bad_debt ⇒ Object
- #billing_entity ⇒ Object
- #calculated_purchase_history ⇒ Object
- #combined_terms ⇒ Object
- #credit_limit_verification(amount, quote = nil) ⇒ Object
- #determine_receipt_billing_entity ⇒ Object
- #locked_credit_amount ⇒ Object
- #open_amount_greater_than_a_month ⇒ Object
- #open_orders_total(exclude_order = nil) ⇒ Object
- #outstanding_amount ⇒ Object
- #outstanding_amount_numeric ⇒ Object
- #outstanding_ar_listings ⇒ Object
- #outstanding_balance ⇒ Object
- #overdue_balance ⇒ Object
- #payment_options ⇒ Object
- #pre_authorized_amount_numeric ⇒ Object
- #pre_authorized_payments ⇒ Object
- #preferred_payment_method(limit: 10) ⇒ Object
- #purchase_history ⇒ Object
- #receipts_list ⇒ Object
-
#request_credit(amount, ignore_annual_revenue: false) ⇒ Object
rubocop:enable Naming/PredicateMethod.
- #sync_credit_limit ⇒ Object
- #terms? ⇒ Boolean
- #terms_credit_limit ⇒ Object
- #terms_in_days ⇒ Object
Class Method Details
.duplicate_addresses ⇒ Object
:reek:UtilityFunction
221 222 223 |
# File 'app/models/concerns/customer_financials.rb', line 221 def duplicate_addresses # :reek:UtilityFunction Maintenance::DuplicateCustomerAddresses.call end |
.duplicate_contact_points ⇒ Object
:reek:UtilityFunction
225 226 227 |
# File 'app/models/concerns/customer_financials.rb', line 225 def duplicate_contact_points # :reek:UtilityFunction Maintenance::DuplicateCustomerContactPoints.call end |
.statement_of_account(party_id) ⇒ Object
:reek:UtilityFunction
217 218 219 |
# File 'app/models/concerns/customer_financials.rb', line 217 def statement_of_account(party_id) # :reek:UtilityFunction Query::CustomerStatementOfAccount.call(party_id) end |
Instance Method Details
#account_on_hold? ⇒ Boolean
206 207 208 |
# File 'app/models/concerns/customer_financials.rb', line 206 def account_on_hold? on_hold end |
#all_rmas ⇒ Object
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_terms ⇒ Object
198 199 200 201 202 203 204 |
# File 'app/models/concerns/customer_financials.rb', line 198 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
191 192 193 194 195 196 |
# File 'app/models/concerns/customer_financials.rb', line 191 def apc_available? bg = .corporate_account bg. && 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! InternalMailer.credit_request_approved_notification(self, amount, order).deliver_later end |
#ar_listings(show_open_only: false) ⇒ Object
:reek:BooleanParameter :reek:ControlParameter
85 86 87 88 89 |
# File 'app/models/concerns/customer_financials.rb', line 85 def ar_listings(show_open_only: false) # :reek:BooleanParameter :reek:ControlParameter 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
rubocop:disable Naming/PredicateMethod -- imperative API: performs side effects and notifications
41 42 43 |
# File 'app/models/concerns/customer_financials.rb', line 41 def auto_increase_available_credit(amount, order = nil) Credit::AutoIncrease.call(self, amount, order:) end |
#available_store_credit ⇒ Object
129 130 131 132 133 134 135 136 |
# File 'app/models/concerns/customer_financials.rb', line 129 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_debt ⇒ Object
32 33 34 |
# File 'app/models/concerns/customer_financials.rb', line 32 def bad_debt ReceiptDetail.joins(:receipt).where(receipts: { customer_id: billing_entity.id }).sum(:write_off) end |
#billing_entity ⇒ Object
150 151 152 153 154 155 156 157 158 |
# File 'app/models/concerns/customer_financials.rb', line 150 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_history ⇒ Object
24 25 26 |
# File 'app/models/concerns/customer_financials.rb', line 24 def calculated_purchase_history purchase_history - outstanding_balance end |
#combined_terms ⇒ Object
166 167 168 169 170 171 172 |
# File 'app/models/concerns/customer_financials.rb', line 166 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 InternalMailer.credit_limit_verification_denied_notification(self, amount, rc[:fail_reasons], quote).deliver_later end |
#determine_receipt_billing_entity ⇒ Object
160 161 162 163 164 |
# File 'app/models/concerns/customer_financials.rb', line 160 def determine_receipt_billing_entity return self if billing_entity == self billing_entity.company == company ? billing_entity : self end |
#locked_credit_amount ⇒ Object
69 70 71 |
# File 'app/models/concerns/customer_financials.rb', line 69 def locked_credit_amount outstanding_amount_numeric + end |
#open_amount_greater_than_a_month ⇒ Object
36 37 38 |
# File 'app/models/concerns/customer_financials.rb', line 36 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
14 15 16 17 18 |
# File 'app/models/concerns/customer_financials.rb', line 14 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.where.not(id: exclude_order.id) if exclude_order res.sum(:total) end |
#outstanding_amount ⇒ Object
101 102 103 |
# File 'app/models/concerns/customer_financials.rb', line 101 def outstanding_amount ActionController::Base.helpers.number_to_currency(outstanding_amount_numeric) end |
#outstanding_amount_numeric ⇒ Object
97 98 99 |
# File 'app/models/concerns/customer_financials.rb', line 97 def outstanding_amount_numeric outstanding_ar_listings.sum(:open_amount_calc) end |
#outstanding_ar_listings ⇒ Object
91 92 93 94 95 |
# File 'app/models/concerns/customer_financials.rb', line 91 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_balance ⇒ Object
20 21 22 |
# File 'app/models/concerns/customer_financials.rb', line 20 def outstanding_balance Invoice.where(billing_customer_id: billing_entity.id).unpaid.sum(:total) end |
#overdue_balance ⇒ Object
28 29 30 |
# File 'app/models/concerns/customer_financials.rb', line 28 def overdue_balance Invoice.where(billing_customer_id: billing_entity.id).overdue.sum(:total) end |
#payment_options ⇒ Object
210 211 212 213 214 |
# File 'app/models/concerns/customer_financials.rb', line 210 def = ['Credit Card'] << terms if terms != Customer::TERM_DUE end |
#pre_authorized_amount_numeric ⇒ Object
112 113 114 |
# File 'app/models/concerns/customer_financials.rb', line 112 def .sum(:amount) end |
#pre_authorized_payments ⇒ Object
105 106 107 108 109 110 |
# File 'app/models/concerns/customer_financials.rb', line 105 def Payment.joins(:order).purchase_orders. .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
138 139 140 141 142 143 144 145 146 147 148 |
# File 'app/models/concerns/customer_financials.rb', line 138 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_history ⇒ Object
10 11 12 |
# File 'app/models/concerns/customer_financials.rb', line 10 def purchase_history Invoice.where(billing_customer_id: billing_entity.id).sum(:total) end |
#receipts_list ⇒ Object
116 117 118 119 |
# File 'app/models/concerns/customer_financials.rb', line 116 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
rubocop:enable Naming/PredicateMethod
46 47 48 |
# File 'app/models/concerns/customer_financials.rb', line 46 def request_credit(amount, ignore_annual_revenue: false) # :reek:BooleanParameter Credit::EligibilityEvaluator.call(self, amount, ignore_annual_revenue:) end |
#sync_credit_limit ⇒ Object
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
187 188 189 |
# File 'app/models/concerns/customer_financials.rb', line 187 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_limit ⇒ Object
121 122 123 124 125 126 127 |
# File 'app/models/concerns/customer_financials.rb', line 121 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_days ⇒ Object
174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'app/models/concerns/customer_financials.rb', line 174 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 |