Class: OutgoingPayment
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- OutgoingPayment
- Includes:
- Models::Auditable
- Defined in:
- app/models/outgoing_payment.rb
Overview
== Schema Information
Table name: outgoing_payments
Database name: primary
id :integer not null, primary key
amount :decimal(10, 2)
approved_at :datetime
category :string(255)
check_state :string(255)
currency :string(255)
exchange_rate :float
payment_date :date
print_reminder_sent :boolean default(FALSE)
reference_number :string(255) not null
remark :text
reversal_date :date
review_request_sent :boolean default(FALSE)
state :string(255)
created_at :datetime
updated_at :datetime
approved_by_id :integer
bank_account_id :integer
company_id :integer
creator_id :integer
job_id :string(255)
mailing_address_id :integer
supplier_id :integer
updater_id :integer
Indexes
category_state (category,state)
index_outgoing_payments_on_amount (amount)
index_outgoing_payments_on_bank_account_id (bank_account_id)
index_outgoing_payments_on_check_state (check_state)
index_outgoing_payments_on_company_id (company_id)
index_outgoing_payments_on_currency (currency)
index_outgoing_payments_on_job_id (job_id)
index_outgoing_payments_on_mailing_address_id (mailing_address_id)
index_outgoing_payments_on_payment_date (payment_date)
index_outgoing_payments_on_reference_number (reference_number)
index_outgoing_payments_on_state (state)
index_outgoing_payments_on_supplier_id (supplier_id)
Constant Summary collapse
- CATEGORIES =
Categories.
%w[ach cash check debit_card e_billpay non_cash paypal wire_transfer].freeze
- DEFAULT_BANK_ACCOUNTS =
default bank account per country and payment method, using => instead of : to force it to use strings for the keys
{ 1 => { 'ach' => 1, 'check' => 1, 'debit_card' => 1, 'e_billpay' => 1, 'paypal' => 7, 'wire_transfer' => 1 }, 2 => { 'ach' => 2, 'check' => 2, 'debit_card' => 2, 'e_billpay' => 2, 'paypal' => 8, 'wire_transfer' => 2 }, 3 => {}, 4 => {} }.freeze
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
- #amount ⇒ Object readonly
- #bank_account_id ⇒ Object readonly
- #category ⇒ Object readonly
- #company_id ⇒ Object readonly
-
#credit_memo_id ⇒ Object
Returns the value of attribute credit_memo_id.
- #currency ⇒ Object readonly
- #payment_date ⇒ Object readonly
- #supplier_id ⇒ Object readonly
Belongs to collapse
- #address ⇒ Address
- #approved_by ⇒ Employee
- #bank_account ⇒ BankAccount
- #company ⇒ Company
- #supplier ⇒ Party
Methods included from Models::Auditable
Has many collapse
- #checks ⇒ ActiveRecord::Relation<Check>
- #ledger_entries ⇒ ActiveRecord::Relation<LedgerEntry>
- #ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
- #outgoing_payment_items ⇒ ActiveRecord::Relation<OutgoingPaymentItem>
Class Method Summary collapse
-
.applied ⇒ ActiveRecord::Relation<OutgoingPayment>
A relation of OutgoingPayments that are applied.
-
.billing_address(payee, outgoing_payment_items) ⇒ Address?
Resolve the cheque mailing address: SPIFF enrollment override first (commission programs may direct payouts elsewhere), then the payee's default billing address.
-
.check_states_for_select ⇒ Array<Array(String, Symbol)>
Hard-coded label/value pairs of each
check_state. -
.get_next_reference_number ⇒ String
Pull the next number from
payment_reference_numbers_seqfor thereference_numbercolumn. -
.payment_count(company_id = nil, bank_account_id = nil, where_conditions = nil, where_not_conditions = nil) ⇒ Integer
Count payments, optionally narrowed by company/bank account and arbitrary where/where-not predicates.
-
.print_all_checks(payment_ids, current_user) ⇒ String
Bulk-print a batch of payments and combine each cheque PDF into one printable file.
-
.send_checks_pending_review_notification ⇒ Object
Mailer hook: alert AP about cheques sitting in
pending_review. -
.send_checks_ready_to_print_notification ⇒ Object
Mailer hook: notify AP that approved cheques are queued and ready to print.
-
.send_daily_unprinted_checks_digest ⇒ Object
Daily digest of cheques in
queuedorgeneratedthat have no background-job in flight (so a stalled batch doesn't get double counted). -
.states_for_select ⇒ Array<Array(String, Symbol)>
[label, value]pairs of every payment workflow state for select inputs.
Instance Method Summary collapse
-
#background_job ⇒ BackgroundJobStatus?
In-flight check generation job, if any.
-
#build_outgoing_payment_items(voucher_items = {}, credit_memos = {}, receipts = {}) ⇒ Object
Replace this payment's line items with one row per voucher/credit memo/receipt the operator chose to settle.
-
#can_print_check? ⇒ Boolean
Whether AP is allowed to (re)print this physical cheque now.
-
#currency_symbol ⇒ String
Currency symbol for display, defaulting to USD when no currency is set yet and falling back to
$for unrecognized codes. - #editing_locked? ⇒ Boolean
-
#generate_check ⇒ Object
Build one Check per distinct payee on the payment, allocate a new cheque number from the BankAccount sequence, and render the PDF.
-
#generate_check_pdf(check) ⇒ Upload
Render the cheque PDF via Pdf::Document::Check and attach it as the cheque's
category: 'check'upload, replacing any prior PDF (e.g. on regeneration after a corrected mailing address). - #has_active_background_job? ⇒ Boolean
-
#payee ⇒ Customer, ...
The payee on the first OutgoingPaymentItem.
- #payment_items_applied? ⇒ Boolean
-
#print_check(current_user, regenerate_pdf: false) ⇒ Upload, String
Print (or reprint) every cheque on this payment, regenerating PDFs if requested, recording the print event on each Check, advancing the
check_statemachine, and combining multi-cheque PDFs into one printable file. -
#reverse(date) ⇒ Object
Reverse the payment under a given GL date.
-
#supplier_name ⇒ String?
Full name of the vendor Party being paid.
- #supplier_type ⇒ Object
-
#to_s ⇒ String
"OutgoingPayment #PVnnn".
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 Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#amount ⇒ Object (readonly)
88 |
# File 'app/models/outgoing_payment.rb', line 88 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#bank_account_id ⇒ Object (readonly)
88 |
# File 'app/models/outgoing_payment.rb', line 88 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#category ⇒ Object (readonly)
88 |
# File 'app/models/outgoing_payment.rb', line 88 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#company_id ⇒ Object (readonly)
88 |
# File 'app/models/outgoing_payment.rb', line 88 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#credit_memo_id ⇒ Object
Returns the value of attribute credit_memo_id.
113 114 115 |
# File 'app/models/outgoing_payment.rb', line 113 def credit_memo_id @credit_memo_id end |
#currency ⇒ Object (readonly)
88 |
# File 'app/models/outgoing_payment.rb', line 88 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#payment_date ⇒ Object (readonly)
88 |
# File 'app/models/outgoing_payment.rb', line 88 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#supplier_id ⇒ Object (readonly)
88 |
# File 'app/models/outgoing_payment.rb', line 88 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
Class Method Details
.applied ⇒ ActiveRecord::Relation<OutgoingPayment>
A relation of OutgoingPayments that are applied. Active Record Scope
111 |
# File 'app/models/outgoing_payment.rb', line 111 scope :applied, -> { where(state: 'applied') } |
.billing_address(payee, outgoing_payment_items) ⇒ Address?
Resolve the cheque mailing address: SPIFF enrollment override
first (commission programs may direct payouts elsewhere), then the
payee's default billing address.
361 362 363 364 365 366 367 |
# File 'app/models/outgoing_payment.rb', line 361 def self.billing_address(payee, outgoing_payment_items) billing_address = outgoing_payment_items&.first&.spiff_enrollment&.mailing_address billing_address ||= payee&.billing_address billing_address end |
.check_states_for_select ⇒ Array<Array(String, Symbol)>
Hard-coded label/value pairs of each check_state. Useful in filter
dropdowns even though the state machine knows the same thing.
231 232 233 |
# File 'app/models/outgoing_payment.rb', line 231 def self.check_states_for_select [['No Check', :no_check], ['Queued', :queued], ['Printed', :printed], ['Reprinted', :reprinted], ['Generated', :generated]] end |
.get_next_reference_number ⇒ String
Pull the next number from payment_reference_numbers_seq for the
reference_number column.
446 447 448 449 |
# File 'app/models/outgoing_payment.rb', line 446 def self.get_next_reference_number seq = OutgoingPayment.find_by_sql("SELECT nextval('payment_reference_numbers_seq') AS reference_number") seq[0].reference_number.to_s end |
.payment_count(company_id = nil, bank_account_id = nil, where_conditions = nil, where_not_conditions = nil) ⇒ Integer
Count payments, optionally narrowed by company/bank account and
arbitrary where/where-not predicates. Used by AP dashboard counters.
243 244 245 246 247 248 249 250 |
# File 'app/models/outgoing_payment.rb', line 243 def self.payment_count(company_id = nil, bank_account_id = nil, where_conditions = nil, where_not_conditions = nil) p = OutgoingPayment.order(:id) p = p.where(company_id:) unless company_id.nil? p = p.where(bank_account_id:) unless bank_account_id.nil? p = p.where(where_conditions) unless where_conditions.nil? p = p.where.not(where_not_conditions) unless where_not_conditions.nil? p.count end |
.print_all_checks(payment_ids, current_user) ⇒ String
Bulk-print a batch of payments and combine each cheque PDF into one
printable file. Used by the AP "print all checks" action.
413 414 415 416 417 418 419 420 421 422 |
# File 'app/models/outgoing_payment.rb', line 413 def self.print_all_checks(payment_ids, current_user) OutgoingPayment.transaction do pdfs = [] payment_ids.each do |payment_id| payment = OutgoingPayment.find(payment_id) pdfs << payment.print_check(current_user) end return PdfTools.combine(pdfs, output_file_path: Upload.temp_location("combined_check_printout_#{Time.current.to_fs(:no_spaces)}.pdf")) end end |
.send_checks_pending_review_notification ⇒ Object
Mailer hook: alert AP about cheques sitting in pending_review. Only
fires once per payment via the review_request_sent flag.
189 190 191 192 193 194 195 196 |
# File 'app/models/outgoing_payment.rb', line 189 def self.send_checks_pending_review_notification payments = where(category: 'check', state: 'applied', check_state: 'pending_review', review_request_sent: false) if payments.any? FinancialsMailer.checks_pending_review_notification(payments).deliver payments.each { |payment| payment.update(review_request_sent: true) } end true end |
.send_checks_ready_to_print_notification ⇒ Object
Mailer hook: notify AP that approved cheques are queued and ready to
print. Single-shot via print_reminder_sent.
200 201 202 203 204 205 206 207 |
# File 'app/models/outgoing_payment.rb', line 200 def self.send_checks_ready_to_print_notification payments = where(category: 'check', state: 'applied', check_state: 'queued', print_reminder_sent: false) if payments.any? FinancialsMailer.checks_ready_to_print_notification(payments).deliver payments.each { |payment| payment.update(print_reminder_sent: true) } end true end |
.send_daily_unprinted_checks_digest ⇒ Object
Daily digest of cheques in queued or generated that have no
background-job in flight (so a stalled batch doesn't get double
counted).
212 213 214 215 216 217 |
# File 'app/models/outgoing_payment.rb', line 212 def self.send_daily_unprinted_checks_digest payments = applied.where(category: 'check', check_state: %w[queued generated], job_id: nil) .includes(:company, :supplier, :outgoing_payment_items) FinancialsMailer.daily_unprinted_checks_digest(payments.to_a).deliver if payments.any? true end |
.states_for_select ⇒ Array<Array(String, Symbol)>
[label, value] pairs of every payment workflow state for select
inputs.
223 224 225 |
# File 'app/models/outgoing_payment.rb', line 223 def self.states_for_select OutgoingPayment.state_machines[:state].states.map { |s| [s.human_name.titleize, s.name] }.sort end |
Instance Method Details
#address ⇒ Address
80 |
# File 'app/models/outgoing_payment.rb', line 80 belongs_to :address, foreign_key: :mailing_address_id, primary_key: :id, optional: true |
#approved_by ⇒ Employee
79 |
# File 'app/models/outgoing_payment.rb', line 79 belongs_to :approved_by, class_name: 'Employee', optional: true |
#background_job ⇒ BackgroundJobStatus?
Returns in-flight check generation job, if any.
253 254 255 256 257 |
# File 'app/models/outgoing_payment.rb', line 253 def background_job return unless job_id BackgroundJobStatus.find(job_id) end |
#bank_account ⇒ BankAccount
78 |
# File 'app/models/outgoing_payment.rb', line 78 belongs_to :bank_account, optional: true |
#build_outgoing_payment_items(voucher_items = {}, credit_memos = {}, receipts = {}) ⇒ Object
Replace this payment's line items with one row per voucher/credit
memo/receipt the operator chose to settle. Each value hash must
carry an 'amount_to_pay' string.
306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'app/models/outgoing_payment.rb', line 306 def build_outgoing_payment_items(voucher_items = {}, credit_memos = {}, receipts = {}) outgoing_payment_items.destroy_all voucher_items.each do |voucher_item_id, attrs| outgoing_payment_items.build(voucher_item_id:, amount: attrs['amount_to_pay']) end credit_memos.each do |credit_memo_id, attrs| outgoing_payment_items.build(credit_memo_id:, amount: attrs['amount_to_pay']) end receipts.each do |receipt_id, attrs| outgoing_payment_items.build(receipt_id:, amount: attrs['amount_to_pay']) end end |
#can_print_check? ⇒ Boolean
Whether AP is allowed to (re)print this physical cheque now.
183 184 185 |
# File 'app/models/outgoing_payment.rb', line 183 def can_print_check? category == 'check' and !voided? and !pending_review? end |
#checks ⇒ ActiveRecord::Relation<Check>
84 |
# File 'app/models/outgoing_payment.rb', line 84 has_many :checks |
#company ⇒ Company
76 |
# File 'app/models/outgoing_payment.rb', line 76 belongs_to :company, optional: true |
#currency_symbol ⇒ String
Currency symbol for display, defaulting to USD when no currency is
set yet and falling back to $ for unrecognized codes.
288 289 290 291 292 |
# File 'app/models/outgoing_payment.rb', line 288 def currency_symbol Money::Currency.new(currency || 'USD').symbol rescue Money::Currency::UnknownCurrency '$' end |
#editing_locked? ⇒ Boolean
280 281 282 |
# File 'app/models/outgoing_payment.rb', line 280 def editing_locked? !draft? end |
#generate_check ⇒ Object
Build one Check per distinct payee on the payment, allocate a new
cheque number from the BankAccount sequence, and render the PDF.
Wrapped in a transaction so a numbering or PDF failure rolls back.
322 323 324 325 326 327 328 329 330 331 332 |
# File 'app/models/outgoing_payment.rb', line 322 def generate_check OutgoingPayment.transaction do outgoing_payment_items.group_by(&:payee).each do |payee, outgoing_payment_items| check_number = bank_account.get_next_check_number address = self.address.nil? ? OutgoingPayment.billing_address(payee, outgoing_payment_items).try(:full_address, false, ', ') : self.address.try(:full_address, false, ', ') check = Check.create!(outgoing_payment: self, payee: payee.full_name, check_number:, bank_account:, address:, date: payment_date, amount: outgoing_payment_items.sum(&:amount)) generate_check_pdf(check) end trigger_check_generated end end |
#generate_check_pdf(check) ⇒ Upload
Render the cheque PDF via Pdf::Document::Check and attach it as
the cheque's category: 'check' upload, replacing any prior PDF
(e.g. on regeneration after a corrected mailing address).
340 341 342 343 344 345 346 347 348 349 350 351 352 |
# File 'app/models/outgoing_payment.rb', line 340 def generate_check_pdf(check) result = Pdf::Document::Check.new(check:, outgoing_payment: self).call path = Upload.temp_location(check.file_name) File.open(path, 'wb') do |file| file.write result.pdf file.flush file.fsync end check.uploads.destroy_by(category: 'check') upload = Upload.uploadify(path, 'check', check, check.file_name) check.uploads.reload upload end |
#has_active_background_job? ⇒ Boolean
268 269 270 |
# File 'app/models/outgoing_payment.rb', line 268 def has_active_background_job? background_job&.active? end |
#ledger_entries ⇒ ActiveRecord::Relation<LedgerEntry>
83 |
# File 'app/models/outgoing_payment.rb', line 83 has_many :ledger_entries, through: :ledger_transactions |
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
82 |
# File 'app/models/outgoing_payment.rb', line 82 has_many :ledger_transactions |
#outgoing_payment_items ⇒ ActiveRecord::Relation<OutgoingPaymentItem>
81 |
# File 'app/models/outgoing_payment.rb', line 81 has_many :outgoing_payment_items, inverse_of: :outgoing_payment, dependent: :destroy |
#payee ⇒ Customer, ...
The payee on the first OutgoingPaymentItem. Cheques are restricted
to a single payee by #only_for_one_payee, so this also represents
the cheque payee.
264 265 266 |
# File 'app/models/outgoing_payment.rb', line 264 def payee outgoing_payment_items.empty? ? nil : outgoing_payment_items.first.payee end |
#payment_items_applied? ⇒ Boolean
276 277 278 |
# File 'app/models/outgoing_payment.rb', line 276 def payment_items_applied? outgoing_payment_items.sum(:amount) == amount end |
#print_check(current_user, regenerate_pdf: false) ⇒ Upload, String
Print (or reprint) every cheque on this payment, regenerating PDFs
if requested, recording the print event on each Check, advancing
the check_state machine, and combining multi-cheque PDFs into one
printable file.
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 |
# File 'app/models/outgoing_payment.rb', line 377 def print_check(current_user, regenerate_pdf: false) OutgoingPayment.transaction do # generate the checks if we don't already have them if checks.empty? generate_check elsif regenerate_pdf checks.each do |check| Rails.logger.info "Regenerating PDF for outgoing_payment id #{id}, check id #{check.id}" generate_check_pdf(check) end end printed_checks = checks.reload # forcing a reload here, in case new checks were generated pdfs = printed_checks.map { |c| c.uploads.first } check_numbers = printed_checks.map(&:check_number) printed_checks.each { |c| c.record_print(current_user) } trigger_check_printed if printed_checks.any? # If more than one check generated, combine them if pdfs.length > 1 # Combine check_temp_path = Upload.temp_location("check_#{check_numbers.join('_')}.pdf") PdfTools.combine(pdfs, output_file_path: check_temp_path) else pdfs.first end end end |
#reverse(date) ⇒ Object
Reverse the payment under a given GL date. Voids the payment, which
cascades through the state machine to also reverse the linked
ledger transaction and unlock the underlying voucher items.
430 431 432 433 434 435 436 437 438 |
# File 'app/models/outgoing_payment.rb', line 430 def reverse(date) raise 'Reversal date required' if date.nil? OutgoingPayment.transaction do self.reversal_date = date self.state_event = 'void' save! end end |
#supplier ⇒ Party
77 |
# File 'app/models/outgoing_payment.rb', line 77 belongs_to :supplier, class_name: 'Party', inverse_of: :outgoing_payments, optional: true |
#supplier_name ⇒ String?
Returns full name of the vendor Party being paid.
295 296 297 |
# File 'app/models/outgoing_payment.rb', line 295 def supplier_name supplier.try(:full_name) end |
#supplier_type ⇒ Object
272 273 274 |
# File 'app/models/outgoing_payment.rb', line 272 def supplier_type supplier.try(:class).try(:to_s) end |
#to_s ⇒ String
Returns "OutgoingPayment #PVnnn".
176 177 178 |
# File 'app/models/outgoing_payment.rb', line 176 def to_s "OutgoingPayment ##{reference_number}" end |