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 Models::Schedulable
Models::Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#amount ⇒ Object
readonly
Validates presence of the core payment fields.
-
#bank_account_id ⇒ Object
readonly
Validates presence of the core payment fields.
-
#category ⇒ Object
readonly
Validates presence of the core payment fields.
-
#company_id ⇒ Object
readonly
Validates presence of the core payment fields.
-
#credit_memo_id ⇒ Object
Returns the value of attribute credit_memo_id.
-
#currency ⇒ Object
readonly
Validates presence of the core payment fields.
-
#payment_date ⇒ Object
readonly
Validates presence of the core payment fields.
-
#supplier_id ⇒ Object
readonly
Validates presence of the core payment fields.
Belongs to collapse
-
#address ⇒ Address
Address a check is mailed to.
-
#approved_by ⇒ Employee
Employee who approved the payment.
-
#bank_account ⇒ BankAccount
Bank account the payment is drawn from.
-
#company ⇒ Company
Company making the payment.
-
#supplier ⇒ Party
Supplier (party) being paid.
Methods included from Models::Auditable
Has many collapse
-
#checks ⇒ ActiveRecord::Relation<Check>
Printed checks for this payment.
-
#ledger_entries ⇒ ActiveRecord::Relation<LedgerEntry>
GL entries posted for this payment.
-
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
GL transactions posted for this payment.
-
#outgoing_payment_items ⇒ ActiveRecord::Relation<OutgoingPaymentItem>
Individual voucher/payment applications making up this payment.
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
Whether the payment is locked from editing (no longer a draft).
-
#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?
Whether the payment has a background job currently running.
-
#payee ⇒ Customer, ...
The payee on the first OutgoingPaymentItem.
-
#payment_items_applied? ⇒ Boolean
Whether the payment items sum exactly to the payment amount.
-
#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 ⇒ String?
Class name of the supplier party (e.g.
'Supplier'). -
#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 Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#amount ⇒ Object (readonly)
Validates presence of the core payment fields.
Validations:
- Presence
- Numericality
- Numericality ({ greater_than: 0.0, if: proc { |p| p.category == 'check' } })
98 |
# File 'app/models/outgoing_payment.rb', line 98 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#bank_account_id ⇒ Object (readonly)
Validates presence of the core payment fields.
Validations:
98 |
# File 'app/models/outgoing_payment.rb', line 98 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#category ⇒ Object (readonly)
Validates presence of the core payment fields.
Validations:
98 |
# File 'app/models/outgoing_payment.rb', line 98 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#company_id ⇒ Object (readonly)
Validates presence of the core payment fields.
Validations:
98 |
# File 'app/models/outgoing_payment.rb', line 98 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.
123 124 125 |
# File 'app/models/outgoing_payment.rb', line 123 def credit_memo_id @credit_memo_id end |
#currency ⇒ Object (readonly)
Validates presence of the core payment fields.
Validations:
98 |
# File 'app/models/outgoing_payment.rb', line 98 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#payment_date ⇒ Object (readonly)
Validates presence of the core payment fields.
Validations:
98 |
# File 'app/models/outgoing_payment.rb', line 98 validates :company_id, :supplier_id, :bank_account_id, :category, :amount, :payment_date, :currency, presence: true |
#supplier_id ⇒ Object (readonly)
Validates presence of the core payment fields.
Validations:
98 |
# File 'app/models/outgoing_payment.rb', line 98 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
121 |
# File 'app/models/outgoing_payment.rb', line 121 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.
379 380 381 382 383 384 385 |
# File 'app/models/outgoing_payment.rb', line 379 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.
241 242 243 |
# File 'app/models/outgoing_payment.rb', line 241 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.
464 465 466 467 |
# File 'app/models/outgoing_payment.rb', line 464 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.
253 254 255 256 257 258 259 260 |
# File 'app/models/outgoing_payment.rb', line 253 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.
431 432 433 434 435 436 437 438 439 440 |
# File 'app/models/outgoing_payment.rb', line 431 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.
199 200 201 202 203 204 205 206 |
# File 'app/models/outgoing_payment.rb', line 199 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.
210 211 212 213 214 215 216 217 |
# File 'app/models/outgoing_payment.rb', line 210 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).
222 223 224 225 226 227 |
# File 'app/models/outgoing_payment.rb', line 222 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.
233 234 235 |
# File 'app/models/outgoing_payment.rb', line 233 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
Address a check is mailed to.
85 |
# File 'app/models/outgoing_payment.rb', line 85 belongs_to :address, foreign_key: :mailing_address_id, primary_key: :id, optional: true |
#approved_by ⇒ Employee
Employee who approved the payment.
83 |
# File 'app/models/outgoing_payment.rb', line 83 belongs_to :approved_by, class_name: 'Employee', optional: true |
#background_job ⇒ BackgroundJobStatus?
Returns in-flight check generation job, if any.
263 264 265 266 267 |
# File 'app/models/outgoing_payment.rb', line 263 def background_job return unless job_id BackgroundJobStatus.find(job_id) end |
#bank_account ⇒ BankAccount
Bank account the payment is drawn from.
81 |
# File 'app/models/outgoing_payment.rb', line 81 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.
324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'app/models/outgoing_payment.rb', line 324 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.
193 194 195 |
# File 'app/models/outgoing_payment.rb', line 193 def can_print_check? category == 'check' and !voided? and !pending_review? end |
#checks ⇒ ActiveRecord::Relation<Check>
Printed checks for this payment.
93 |
# File 'app/models/outgoing_payment.rb', line 93 has_many :checks |
#company ⇒ Company
Company making the payment.
77 |
# File 'app/models/outgoing_payment.rb', line 77 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.
306 307 308 309 310 |
# File 'app/models/outgoing_payment.rb', line 306 def currency_symbol Money::Currency.new(currency || 'USD').symbol rescue Money::Currency::UnknownCurrency '$' end |
#editing_locked? ⇒ Boolean
Whether the payment is locked from editing (no longer a draft).
298 299 300 |
# File 'app/models/outgoing_payment.rb', line 298 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.
340 341 342 343 344 345 346 347 348 349 350 |
# File 'app/models/outgoing_payment.rb', line 340 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).
358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'app/models/outgoing_payment.rb', line 358 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?
Whether the payment has a background job currently running.
280 281 282 |
# File 'app/models/outgoing_payment.rb', line 280 def has_active_background_job? background_job&.active? end |
#ledger_entries ⇒ ActiveRecord::Relation<LedgerEntry>
GL entries posted for this payment.
91 |
# File 'app/models/outgoing_payment.rb', line 91 has_many :ledger_entries, through: :ledger_transactions |
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
GL transactions posted for this payment.
89 |
# File 'app/models/outgoing_payment.rb', line 89 has_many :ledger_transactions |
#outgoing_payment_items ⇒ ActiveRecord::Relation<OutgoingPaymentItem>
Individual voucher/payment applications making up this payment.
87 |
# File 'app/models/outgoing_payment.rb', line 87 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.
274 275 276 |
# File 'app/models/outgoing_payment.rb', line 274 def payee outgoing_payment_items.empty? ? nil : outgoing_payment_items.first.payee end |
#payment_items_applied? ⇒ Boolean
Whether the payment items sum exactly to the payment amount.
292 293 294 |
# File 'app/models/outgoing_payment.rb', line 292 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.
395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'app/models/outgoing_payment.rb', line 395 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.
448 449 450 451 452 453 454 455 456 |
# File 'app/models/outgoing_payment.rb', line 448 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
Supplier (party) being paid.
79 |
# File 'app/models/outgoing_payment.rb', line 79 belongs_to :supplier, class_name: 'Party', inverse_of: :outgoing_payments, optional: true |
#supplier_name ⇒ String?
Returns full name of the vendor Party being paid.
313 314 315 |
# File 'app/models/outgoing_payment.rb', line 313 def supplier_name supplier.try(:full_name) end |
#supplier_type ⇒ String?
Class name of the supplier party (e.g. 'Supplier').
286 287 288 |
# File 'app/models/outgoing_payment.rb', line 286 def supplier_type supplier.try(:class).try(:to_s) end |
#to_s ⇒ String
Returns "OutgoingPayment #PVnnn".
186 187 188 |
# File 'app/models/outgoing_payment.rb', line 186 def to_s "OutgoingPayment ##{reference_number}" end |