Class: Invoice
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- Invoice
- Includes:
- Models::AccountingDocumentTransmittable, Models::Auditable, Models::Itemizable, Models::LiquidMethods, Models::Notable, Models::Profitable, Models::TaxableResource, Models::TaxjarSubmittable, PgSearch::Model
- Defined in:
- app/models/invoice.rb
Overview
Invoice model representing a financial document for goods or services.
Handles billing, payments, line items, and various invoice types including
sales orders, credit memos, and consignment invoices.
Defined Under Namespace
Classes: CaptureFundsHandler, TaxjarSubmissionHandler
Constant Summary collapse
- SO =
'SO'- ST =
Sales Tax — placeholder type used for tax-only adjustment invoices.
'ST'- MI =
Miscellaneous Invoice — manually entered, not tied to an order.
'MI'- MO =
Manual Order — legacy manual invoice type, predates Order entry workflow.
'MO'- TO =
Trade Order — internal trade between companies in the JDE multi-company ledger.
'TO'- CI =
Consignment Invoice — issued when consignment stock is invoiced to the consignee.
'CI'- SS =
Service / Smart Service — invoice for installation or smart-service labour, not goods.
'SS'- INVOICE_TYPES =
All recognised JDE invoice-type codes accepted by validators and dropdowns.
[SO, ST, MI, MO, TO, CI, SS].freeze
- REFERENCE_NUMBER_PATTERN =
Regex matching the canonical
INV…reference-number format (case-insensitive). /^INV\d+$/i- LINE_ITEM_CATEGORIES =
[{ name: 'Coupon (Goods)', account_number: COUPONS_ACCOUNT }, { name: 'Coupon (Freight)', account_number: FREIGHT_COUPONS_ACCOUNT }, { name: 'Freight', account_number: FREIGHT_ACCOUNT }, { name: 'Misc', account_number: PRODUCT_SALES_ACCOUNT }, { name: 'Item', account_number: PRODUCT_SALES_ACCOUNT }, { name: 'Fee', account_number: nil }].freeze
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Constants included from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
Instance Attribute Summary collapse
-
#allow_duplicate_delivery_for_testing ⇒ Object
Returns the value of attribute allow_duplicate_delivery_for_testing.
- #billing_address_id ⇒ Object readonly
- #customer_id ⇒ Object readonly
- #delivery_id ⇒ Object readonly
-
#disable_auto_coupon ⇒ Object
Returns the value of attribute disable_auto_coupon.
-
#do_not_detect_shipping ⇒ Object
Returns the value of attribute do_not_detect_shipping.
-
#do_not_set_totals ⇒ Object
Returns the value of attribute do_not_set_totals.
- #document_date ⇒ Object readonly
- #due_date ⇒ Object readonly
-
#enter_new_address ⇒ Object
Returns the value of attribute enter_new_address.
- #gl_date ⇒ Object readonly
- #gl_offset_account_id ⇒ Object readonly
-
#gl_offset_account_ref ⇒ Object
Returns the value of attribute gl_offset_account_ref.
- #invoice_type ⇒ Object readonly
- #order_id ⇒ Object readonly
-
#original_order_ref ⇒ Object
Returns the value of attribute original_order_ref.
-
#skip_initial_state_check ⇒ Object
Flag to bypass the initial state check (use only if you have a legitimate reason to create an invoice in a non-draft state, which should be extremely rare).
-
#skip_line_item_integrity_check ⇒ Object
Flag to skip integrity check during initial creation (set by CreateInvoiceFromDelivery).
-
#skip_pdf ⇒ Object
Returns the value of attribute skip_pdf.
- #store_id ⇒ Object readonly
- #tax_date ⇒ Object readonly
- #terms ⇒ Object readonly
Attributes included from Models::Profitable
Attributes included from Models::Itemizable
#force_total_reset, #total_reset
Belongs to collapse
- #billing_address ⇒ Address
- #billing_customer ⇒ Customer
- #business_unit ⇒ BusinessUnit
- #buying_group ⇒ BuyingGroup
- #company ⇒ Company
- #customer ⇒ Customer
- #delivery ⇒ Delivery
- #gl_offset_account ⇒ LedgerCompanyAccount
- #order ⇒ Order
- #profile ⇒ Profile
- #shipping_address ⇒ Address (also: #destination_address)
- #sold_to_billing_address ⇒ Address
- #source ⇒ Source
- #store ⇒ Store
- #technical_support_rep ⇒ Employee
Methods included from Models::TaxableResource
Methods included from Models::Itemizable
#account_specialist, #local_sales_rep, #primary_sales_rep, #secondary_sales_rep
Methods included from Models::Auditable
Has many collapse
- #activities ⇒ ActiveRecord::Relation<Activity>
- #communications ⇒ ActiveRecord::Relation<Communication>
- #credit_memos ⇒ ActiveRecord::Relation<CreditMemo>
- #drop_ship_purchase_orders ⇒ ActiveRecord::Relation<DropShipPurchaseOrder>
- #edi_communication_logs ⇒ ActiveRecord::Relation<EdiCommunicationLog>
- #edi_documents ⇒ ActiveRecord::Relation<EdiDocument>
- #item_ledger_entries ⇒ ActiveRecord::Relation<ItemLedgerEntry>
- #ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
- #line_discounts ⇒ ActiveRecord::Relation<LineDiscount>
- #line_items ⇒ ActiveRecord::Relation<LineItem>
- #payments ⇒ ActiveRecord::Relation<Payment>
- #receipt_details ⇒ ActiveRecord::Relation<ReceiptDetail>
- #rmas ⇒ ActiveRecord::Relation<Rma>
- #shipping_costs ⇒ ActiveRecord::Relation<ShippingCost>
- #uploads ⇒ ActiveRecord::Relation<Upload>
Methods included from Models::Itemizable
Delegated Instance Attributes collapse
-
#balance_positive? ⇒ Object
Alias for Balance#positive?.
-
#marketplace_invoice_format ⇒ Object
Alias for Customer#marketplace_invoice_format.
Class Method Summary collapse
-
.awaiting_transmission ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are awaiting transmission.
-
.calculate_due_date(order, delivery) ⇒ Date
Computes the invoice due date as
delivery.shipped_date + billing_entity.terms_in_days. -
.calculate_terms(order) ⇒ String
Resolves the textual payment-terms string from an Order, appending
(COD)when the order is COD-funded so it prints on the PDF. -
.included_in_notifications ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are included in notifications.
-
.like_lookup ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are like lookup.
-
.lookup ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are lookup.
-
.missing_edi_810 ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are missing edi 810.
-
.overdue ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are overdue.
-
.sales_orders ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are sales orders.
-
.unpaid ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are unpaid.
Instance Method Summary collapse
- #allow_duplicate_delivery_for_testing? ⇒ Boolean
-
#amount_due ⇒ BigDecimal
Liquid-template alias for #total; kept distinct from #balance so the public pay-link template can show the original amount even after partial payments.
-
#balance ⇒ BigDecimal
Outstanding balance — invoice total minus the sum of applied ReceiptDetails (cash receipts, write-offs, applied discounts).
- #balance_is_zero? ⇒ Boolean
- #billing_entity ⇒ Party
- #build_activity ⇒ Activity
-
#calculate_all_cogs ⇒ BigDecimal
Cost-of-goods total computed in SQL across every
Item-category LineItem, ignoring tax-class filtering. -
#calculate_cogs(tax_class = %w[g svc shp])) ⇒ BigDecimal
Cost-of-goods total for this invoice restricted to the supplied tax classes — defaults to goods (
g), services (svc) and shipping (shp). - #capture_funds? ⇒ Boolean
-
#chosen_shipping_cost ⇒ BigDecimal
Numeric cost of #chosen_shipping_method, or
0.00when the carrier uses a customer-supplied shipping account number (no charge to bill). -
#chosen_shipping_method ⇒ ShippingCost?
Cheapest available ShippingCost on the parent Delivery — what the customer is actually being charged for shipping on this invoice.
-
#combined_terms ⇒ String
Terms label augmented with the early-payment offer in the standard "Net X - Y%/Z" notation (e.g.
Net 30 - 2%/10). - #copy_payments ⇒ void
- #create_receipts_for_captured_payments ⇒ void
-
#crm_link ⇒ String
CRM URL for the invoice show page.
-
#currency_symbol ⇒ String
Currency symbol (e.g.
$,€) for this invoice's #currency, used in the PDF and overpayment notification templates. -
#customer_name ⇒ String?
Display name for the Customer this invoice bills, falling back to nil for headless data (rare — most invoices have a customer).
- #disable_auto_coupon? ⇒ Boolean
-
#discount_applied ⇒ BigDecimal
Sum of
discountwritten off via ReceiptDetails, e.g. -
#discount_days_due ⇒ Integer?
Days between #gl_date and #early_payment_due_date; used by the PDF "early-payment discount" line.
- #do_not_detect_shipping? ⇒ Boolean
-
#early_payment_amount ⇒ BigDecimal
Dollar value of the early-payment discount —
early_payment_discount% of the invoice total, rounded to 2dp. -
#early_payment_due_date ⇒ Date?
Date by which the customer must pay to qualify for the early-payment discount (
shipped_date + early_payment_timescaledays). -
#early_payment_total ⇒ BigDecimal
Discounted total the customer would owe if they pay by #early_payment_due_date — total minus #early_payment_amount.
- #editing_locked? ⇒ Boolean
- #effective_store ⇒ Store?
-
#file_name(with_extension: true) ⇒ String
Filename used when attaching the invoice PDF to email or storing in S3.
-
#friendly_shipping_method(show_customer_pays_info: false) ⇒ String
Human-readable shipping-method label for the PDF and CRM — "Warehouse Pickup" for warehouse addresses, otherwise the carrier description with an optional COD-charge note.
- #fully_funded_by_rma? ⇒ Boolean
- #funded_by_cod? ⇒ Boolean
- #funded_by_rma? ⇒ Boolean
-
#generate_pdf ⇒ Upload
Renders a fresh combined invoice PDF (cover + line-item pages + any addendums) via Invoicing::CombinedPdfGenerator, uploads it to S3 under the
invoice_pdfcategory and attaches the Upload. -
#get_or_regen_pdf(logger = nil) ⇒ Upload
Returns the persisted invoice-PDF Upload, regenerating it via #generate_pdf if the upload row is missing or its file is gone from S3.
-
#name ⇒ String
Short display name for selection lists and links — just the reference number.
-
#non_service_line_items ⇒ Array<LineItem>
Goods/shipping LineItems only — services (tax_class 'svc') excluded.
-
#non_voided_receipt_details ⇒ ActiveRecord::Relation<ReceiptDetail>
All ReceiptDetails posted against this invoice excluding voided ones.
- #not_rma? ⇒ Boolean
-
#online_payment_options ⇒ Array<String>
Payment options offered on the public pay-online page.
- #order_ref ⇒ String?
-
#order_ref=(ref) ⇒ Order?
Setter pairing with #order_ref — looks up the Order by its reference number so manual-entry forms can attach an invoice to an existing order without exposing the integer primary key.
-
#po_numbers ⇒ Array<String>
Distinct customer purchase-order numbers attached to Payments on this invoice.
- #prevent_recalculate_shipping? ⇒ Boolean
- #pricing_program_discount_factor ⇒ BigDecimal
- #primary_party ⇒ Party
-
#public_pay_link ⇒ String?
Public-facing pay-online URL for self-serve payment by the customer (delegates to the parent Order's public-payment link).
- #public_pay_link_has_auth_token? ⇒ Boolean
-
#receipts_total ⇒ BigDecimal
Sum of all credit applied to this invoice — cash Receipt amount plus write-offs plus discount applied.
- #rma_awaiting_return? ⇒ Boolean
-
#rma_number ⇒ String
RMA reference for this invoice — the linked Rma's number when one exists, otherwise a synthesised "RMA # …" label from the order's
rma_reference. -
#selection_name ⇒ String
Display label for resource pickers / invoice dropdowns —
INV… <Customer Name>. -
#selection_name_for_rmas ⇒ String
Variant of #selection_name for the RMA picker that appends the parent Order's reference when present, since RMAs are scoped to an order, not just a customer.
-
#service_line_items ⇒ Array<LineItem>
Service-class LineItems only (tax_class 'svc'), e.g.
-
#set_consolidated_amount ⇒ void
Caches the consolidated-currency exchange rate for
gl_dateonto #consolidated_exchange_rate, so downstream financial reports can express the invoice in CONSOLIDATED_CURRENCY without re-querying ExchangeRate. - #show_tax_info? ⇒ Boolean
-
#tax_info ⇒ String?
Tax-identification line printed at the top of the invoice PDF — the destination country's EU VAT number when shipping into the EU, otherwise the company's tax info for Canada.
-
#terms_in_days ⇒ Object
calculate net due date in days.
-
#to_liquid ⇒ Liquid::InvoiceDrop
Liquid drop wrapper used when this invoice is rendered into transmission email/SMS templates — exposes only the safe-for-template accessors via Liquid::InvoiceDrop.
-
#to_s ⇒ String
Human-readable identifier used in audit logs and error messages.
Methods included from Models::AccountingDocumentTransmittable
#can_be_transmitted?, #fallback_notification_channel_type, #notification_channel_sort_order, #notification_channel_types, #notification_channels, #own_notification_channel_type, #post_communication_exception_hook, #post_communication_sent_hook, #primary_transmission_contact, #primary_transmission_contact_point_id, #transmission_contact_points
Methods included from Models::TaxjarSubmittable
#customer_sync_instance, #delete_from_taxjar, #evaluate_taxjar_submission, #record_already_exists_on_taxjar?, #resubmit_to_taxjar, #should_be_submitted_to_taxjar?, #should_sync_customer_with_taxjar?, #submit_to_taxjar, #sync_customer_with_taxjar, #taxjar_customer_id, #taxjar_submission_instance
Methods included from Models::Profitable
#default_sales_markup, #profit_margins_met?, #profitable_line_items, #profitable_status, #profitable_total_discounted, #profitable_total_estimated_cost, #profitable_total_estimated_line_cost, #profitable_total_profit, #profitable_total_profit_margin, #profitable_total_profit_markup, #track_profit?, #validate_min_profit_markup?
Methods included from Models::TaxableResource
#apply_tax_rate_to_line_items, #build_tax_params, #calculate_tax_for_all_lines, #copy_tax_rate, #effective_date, #get_rates_for_line, #get_tax_rate, #manual_rate_goods, #manual_rate_services, #manual_rate_shipping, #origin_address, #refresh_tax_rate, #resource_not_taxable?, #set_initial_tax_rate, #should_refresh_tax_rate?, #state_code, #state_code_sym, #taxes_grouped_by_rate, #taxes_grouped_by_type
Methods included from Models::Itemizable
#add_line_item, #additional_items, #assign_sequence, #breakdown_of_prices, #calculate_actual_insured_value, #calculate_discounts, #calculate_shipping_cost, #coupon_search, #customer_applied_coupons, #customer_can_apply_coupon?, #discounts_changed?, #discounts_grouped_by_coupon, #discounts_subtotal, #effective_discount, #effective_shipping_discount, #has_kits?, #has_kits_or_serial_numbers?, #has_serial_numbers?, #is_credit_order?, #line_items_requiring_serial_number, #line_items_with_counters, #line_total_plus_tax, #main_rep, #perform_db_total, #purge_empty_quoting_deliveries, #purge_shipping_when_no_other_lines, #remove_line_item, #require_total_reset?, #reset_discount, #set_for_recalc, #set_signature_confirmation_on_shipping_address_change, #set_totals, #shipping_conditions_changed?, #shipping_discounted, #shipping_method_changed?, #should_recalculate_shipping?, #smartinstall_data, #smartsupport_data, #subtotal_cogs, #sync_shipping_line, #total_cogs
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
Methods included from Models::Notable
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
#allow_duplicate_delivery_for_testing ⇒ Object
Returns the value of attribute allow_duplicate_delivery_for_testing.
175 176 177 |
# File 'app/models/invoice.rb', line 175 def allow_duplicate_delivery_for_testing @allow_duplicate_delivery_for_testing end |
#billing_address_id ⇒ Object (readonly)
210 |
# File 'app/models/invoice.rb', line 210 validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true |
#customer_id ⇒ Object (readonly)
210 |
# File 'app/models/invoice.rb', line 210 validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true |
#delivery_id ⇒ Object (readonly)
215 |
# File 'app/models/invoice.rb', line 215 validates :delivery_id, uniqueness: { allow_nil: true }, unless: :allow_duplicate_delivery_for_testing? |
#disable_auto_coupon ⇒ Object
Returns the value of attribute disable_auto_coupon.
175 176 177 |
# File 'app/models/invoice.rb', line 175 def disable_auto_coupon @disable_auto_coupon end |
#do_not_detect_shipping ⇒ Object
Returns the value of attribute do_not_detect_shipping.
175 176 177 |
# File 'app/models/invoice.rb', line 175 def do_not_detect_shipping @do_not_detect_shipping end |
#do_not_set_totals ⇒ Object
Returns the value of attribute do_not_set_totals.
175 176 177 |
# File 'app/models/invoice.rb', line 175 def do_not_set_totals @do_not_set_totals end |
#document_date ⇒ Object (readonly)
210 |
# File 'app/models/invoice.rb', line 210 validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true |
#due_date ⇒ Object (readonly)
210 |
# File 'app/models/invoice.rb', line 210 validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true |
#enter_new_address ⇒ Object
Returns the value of attribute enter_new_address.
175 176 177 |
# File 'app/models/invoice.rb', line 175 def enter_new_address @enter_new_address end |
#gl_date ⇒ Object (readonly)
210 |
# File 'app/models/invoice.rb', line 210 validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true |
#gl_offset_account_id ⇒ Object (readonly)
210 |
# File 'app/models/invoice.rb', line 210 validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true |
#gl_offset_account_ref ⇒ Object
Returns the value of attribute gl_offset_account_ref.
175 176 177 |
# File 'app/models/invoice.rb', line 175 def gl_offset_account_ref @gl_offset_account_ref end |
#invoice_type ⇒ Object (readonly)
210 |
# File 'app/models/invoice.rb', line 210 validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true |
#order_id ⇒ Object (readonly)
211 |
# File 'app/models/invoice.rb', line 211 validates :order_id, presence: { if: proc { |i| [MI, CI].exclude?(i.invoice_type) } } |
#original_order_ref ⇒ Object
Returns the value of attribute original_order_ref.
175 176 177 |
# File 'app/models/invoice.rb', line 175 def original_order_ref @original_order_ref end |
#skip_initial_state_check ⇒ Object
Flag to bypass the initial state check (use only if you have a legitimate reason to create
an invoice in a non-draft state, which should be extremely rare)
252 253 254 |
# File 'app/models/invoice.rb', line 252 def skip_initial_state_check @skip_initial_state_check end |
#skip_line_item_integrity_check ⇒ Object
Flag to skip integrity check during initial creation (set by CreateInvoiceFromDelivery)
248 249 250 |
# File 'app/models/invoice.rb', line 248 def skip_line_item_integrity_check @skip_line_item_integrity_check end |
#skip_pdf ⇒ Object
Returns the value of attribute skip_pdf.
175 176 177 |
# File 'app/models/invoice.rb', line 175 def skip_pdf @skip_pdf end |
#store_id ⇒ Object (readonly)
212 |
# File 'app/models/invoice.rb', line 212 validates :store_id, presence: { if: proc { |i| [MI, CI].include?(i.invoice_type) } } |
#tax_date ⇒ Object (readonly)
213 |
# File 'app/models/invoice.rb', line 213 validates :tax_date, :store_id, presence: { if: proc { |i| [MI, CI].include?(i.invoice_type) } } |
#terms ⇒ Object (readonly)
210 |
# File 'app/models/invoice.rb', line 210 validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true |
Class Method Details
.awaiting_transmission ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are awaiting transmission. Active Record Scope
259 |
# File 'app/models/invoice.rb', line 259 scope :awaiting_transmission, -> { where(state: %w[unpaid paid], transmission_state: %w[awaiting_transmission in_transmission_queue]) } |
.calculate_due_date(order, delivery) ⇒ Date
Computes the invoice due date as delivery.shipped_date + billing_entity.terms_in_days. Called once at invoice creation;
the result is persisted onto due_date.
551 552 553 554 |
# File 'app/models/invoice.rb', line 551 def self.calculate_due_date(order, delivery) shipped_date = delivery.shipped_date.to_datetime.to_date shipped_date + order.billing_entity.terms_in_days.days end |
.calculate_terms(order) ⇒ String
Resolves the textual payment-terms string from an Order, appending
(COD) when the order is COD-funded so it prints on the PDF.
578 579 580 581 582 |
# File 'app/models/invoice.rb', line 578 def self.calculate_terms(order) terms = order.billing_entity.terms terms += ' (COD)' if order.funded_by_cod? terms end |
.included_in_notifications ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are included in notifications. Active Record Scope
262 |
# File 'app/models/invoice.rb', line 262 scope :included_in_notifications, -> { where(exclude_fund_capture_notification: false) } |
.like_lookup ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are like lookup. Active Record Scope
265 |
# File 'app/models/invoice.rb', line 265 scope :like_lookup, ->(q) { left_joins(:order).where(Invoice[:reference_number].matches("%#{q}%")).or(Order.where(Order[:reference_number].matches("%#{q}%"))) } |
.lookup ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are lookup. Active Record Scope
264 |
# File 'app/models/invoice.rb', line 264 scope :lookup, ->(q) { where(reference_number: q) } |
.missing_edi_810 ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are missing edi 810. Active Record Scope
266 267 268 269 270 |
# File 'app/models/invoice.rb', line 266 scope :missing_edi_810, -> { joins(customer: :notification_channels) .where(notification_channels: { notification_type: NotificationChannel::INVOICES, transmission_type: NotificationChannel::EDI }) .where(transmission_state: 'awaiting_transmission') } |
.overdue ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are overdue. Active Record Scope
263 |
# File 'app/models/invoice.rb', line 263 scope :overdue, -> { unpaid.where(due_date: ...Date.current) } |
.sales_orders ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are sales orders. Active Record Scope
260 |
# File 'app/models/invoice.rb', line 260 scope :sales_orders, -> { where(invoice_type: 'SO') } |
.unpaid ⇒ ActiveRecord::Relation<Invoice>
A relation of Invoices that are unpaid. Active Record Scope
261 |
# File 'app/models/invoice.rb', line 261 scope :unpaid, -> { where(state: 'unpaid') } |
Instance Method Details
#activities ⇒ ActiveRecord::Relation<Activity>
201 |
# File 'app/models/invoice.rb', line 201 has_many :activities, as: :resource, dependent: :nullify, inverse_of: :resource |
#allow_duplicate_delivery_for_testing? ⇒ Boolean
217 218 219 |
# File 'app/models/invoice.rb', line 217 def allow_duplicate_delivery_for_testing? allow_duplicate_delivery_for_testing == true end |
#amount_due ⇒ BigDecimal
Liquid-template alias for #total; kept distinct from #balance so
the public pay-link template can show the original amount even after
partial payments.
527 528 529 |
# File 'app/models/invoice.rb', line 527 def amount_due total end |
#balance ⇒ BigDecimal
Outstanding balance — invoice total minus the sum of applied
ReceiptDetails (cash receipts, write-offs, applied discounts).
Drives balance_is_zero? / balance_positive? and the AR aging report.
494 495 496 |
# File 'app/models/invoice.rb', line 494 def balance total - receipts_total end |
#balance_is_zero? ⇒ Boolean
485 486 487 |
# File 'app/models/invoice.rb', line 485 def balance_is_zero? balance.zero? end |
#balance_positive? ⇒ Object
Alias for Balance#positive?
281 |
# File 'app/models/invoice.rb', line 281 delegate :positive?, to: :balance, prefix: true, allow_nil: true |
#billing_address ⇒ Address
178 |
# File 'app/models/invoice.rb', line 178 belongs_to :billing_address, class_name: 'Address', optional: true, inverse_of: :billing_invoices |
#billing_customer ⇒ Customer
182 |
# File 'app/models/invoice.rb', line 182 belongs_to :billing_customer, class_name: 'Customer', optional: true, inverse_of: :invoices |
#billing_entity ⇒ Party
1021 1022 1023 |
# File 'app/models/invoice.rb', line 1021 def billing_entity billing_address.party end |
#build_activity ⇒ Activity
390 391 392 |
# File 'app/models/invoice.rb', line 390 def build_activity activities.build resource: self, party: primary_party end |
#business_unit ⇒ BusinessUnit
188 |
# File 'app/models/invoice.rb', line 188 belongs_to :business_unit, optional: true, inverse_of: :invoices |
#buying_group ⇒ BuyingGroup
184 |
# File 'app/models/invoice.rb', line 184 belongs_to :buying_group, optional: true, inverse_of: :invoices |
#calculate_all_cogs ⇒ BigDecimal
Cost-of-goods total computed in SQL across every Item-category
LineItem, ignoring tax-class filtering. Used by the BoB / margin
report. Casts to BigDecimal to avoid float drift downstream.
410 411 412 |
# File 'app/models/invoice.rb', line 410 def calculate_all_cogs BigDecimal(line_items.where(cm_category: 'Item').sum('unit_cogs * quantity')) end |
#calculate_cogs(tax_class = %w[g svc shp])) ⇒ BigDecimal
Cost-of-goods total for this invoice restricted to the supplied tax
classes — defaults to goods (g), services (svc) and shipping (shp).
Walks the in-memory Item-category LineItems so unsaved edits
are reflected; pair with #calculate_all_cogs for the full-set sum.
401 402 403 |
# File 'app/models/invoice.rb', line 401 def calculate_cogs(tax_class = %w[g svc shp]) line_items.where(cm_category: 'Item').select { |li| tax_class.include?(li.calculated_tax_class) }.sum { |li| li.unit_cogs * li.quantity } end |
#capture_funds? ⇒ Boolean
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 |
# File 'app/models/invoice.rb', line 740 def capture_funds? logger.info("#{Time.current}: Capturing funds for invoice id: #{id}, ref: #{reference_number}, delivery id: #{delivery_id}") copy_payments unless delivery.nil? invoice_total = total captured_balance = payments.all_captured.sum(:amount) pending_balance = invoice_total - captured_balance payments_total = payments..sum(:amount) check_total = payments.all_check_captured.sum(:amount) applied_store_credit_total = delivery.nil? ? 0 : delivery.payments.where(state: 'authorized', category: Payment::STORE_CREDIT, currency: currency).sum(:amount) unapplied_credit_memos = billing_customer.credit_memos.available_to_apply.order(:document_date) logger.info("#{Time.current}: Invoice total: #{invoice_total}") logger.info("#{Time.current}: Payments not captured available: #{payments_total}") logger.info("#{Time.current}: Checks already captured but receipt needed: #{check_total}") capture_problem = false if pending_balance <= 0 create_receipts_for_captured_payments paid! if can_paid? if pending_balance.negative? overpaid_amount = pending_balance.abs currency_sym = Money::Currency.new(currency).symbol Mailer.generic_mailer( from: ADMINISTRATOR_EMAIL, to: "#{ADMINISTRATOR_EMAIL},#{ACCOUNTS_RECEIVABLE_EMAIL}", subject: "ORDER OVERPAYMENT — Invoice ##{reference_number} (Order ##{order&.reference_number})", message: "Invoice ##{reference_number} (Order ##{order&.reference_number}) has been overpaid by #{currency_sym}#{'%.2f' % overpaid_amount}.\n\n" \ "Invoice total: #{currency_sym}#{'%.2f' % invoice_total}\n" \ "Total captured: #{currency_sym}#{'%.2f' % captured_balance}\n" \ "Overpaid amount: #{currency_sym}#{'%.2f' % overpaid_amount}\n\n" \ "Customer: #{customer&.full_name} (ID: #{customer_id})\n" \ "CRM link: #{crm_link}\n\n" \ "A manual refund or credit memo needs to be issued for the excess amount.", no_verbage: true ).deliver logger.warn("#{Time.current}: OVERPAYMENT on invoice #{id}: captured #{captured_balance} exceeds total #{invoice_total} by #{overpaid_amount}") elsif invoice_type == 'SO' Mailer.generic_mailer(from: ADMINISTRATOR_EMAIL, to: "#{ADMINISTRATOR_EMAIL},#{ACCOUNTS_RECEIVABLE_EMAIL}", subject: "BALANCE ALREADY PAID FOR INVOICE ID ##{id}", message: "Invoice ##{id} had all the payments already captured before starting the invoicing process. This might be ok, but check all payments are correctly captured.", no_verbage: true).deliver end else # WE SHOULD ALWAYS USE PAYMENT.AMOUNT IN THE RECEIPTS CREATED SINCE THE PAYMENT HAS ALRADY BEEN CAPTURED. THERE IS NO POINT IN USING # THE BALANCE IF THAT NUMBER IS DIFFERENT THAN THE ALREADY CAPTURED PAYMENT # SO WE SHOULD USE A METHOD LIKE create_receipts_for_captured_payments # First let's create the receipts for the payments that have been captured already. create_receipts_for_captured_payments # Second let's add receipts for any store credit used if applied_store_credit_total.positive? unapplied_credit_memos.each do |cm| next if pending_balance.zero? cm_balance = cm.balance * -1 amount = [pending_balance, cm_balance].min new_receipt = Receipt.new(company: company, customer: customer, category: 'Non-Cash', amount: 0, reference: cm.reference_number, currency: currency, gl_date: Date.current, receipt_date: Date.current) new_receipt.receipt_details << ReceiptDetail.new(category: 'Invoice', invoice: self, amount: amount, gl_date: Date.current) new_receipt.receipt_details << ReceiptDetail.new(category: 'Credit Memo', credit_memo: cm, amount: amount * -1, gl_date: Date.current) begin new_receipt.save! logger.info("#{Time.current}: Created new receipt id: #{new_receipt.id}") pending_balance -= amount rescue StandardError => e msg = "#{Time.current}: Unable to create new receipt for Credit Memo ID: #{cm.id} (store credit), Exception: #{e}" logger.error(msg) ErrorReporting.error(e, credit_memo_id: cm.id) capture_problem = true end end end # Finally let's capture the remaining balance from existing authorizations payments..cc_paypal_bread_amazon.each do |payment| next if pending_balance.zero? # If the balance is already zero then we don't need to capture more amount = [pending_balance, payment.amount].min res = payment.gateway_class.new(payment).capture(amount, { order_id: reference_number, currency: payment.currency }) if res.success pending_balance -= amount capture_problem = true if payment.receipts.empty? else capture_problem = true end end end # The rest of payment methods cannot be captured through a gateway and need manual processing. # For example, POs or VPOs. We generate the invoice PDF for those and then accounting has a special # report to find the unpaid invoices and apply a voucher, credit memo, or any other payment type to # mark the invoice as paid # Check if capture_problem is due to legitimate issues or just manual-processing payment types # Don't flag PO/VPO/ECHECK/WIRE payments as problems since they require manual intervention by design if capture_problem = payments..select { |p| p.receipts.empty? } only_manual_payments = .all? { |p| Payment::CATEGORIES_NOT_ALLOWING_CAPTURE.include?(p.category) } if only_manual_payments && .any? logger.info("#{Time.current}: Authorized payments exist that require manual processing (#{.map(&:category).uniq.join(', ')}). This is expected.") capture_problem = false end end # Let's do a security check to make sure the capture balance is the same as the invoice balance # final_captured_balance = payments.all_captured.sum(:amount) # invoice_total = total # capture_problem = true if invoice_total != final_captured_balance if capture_problem == true Mailer.generic_mailer( from: ADMINISTRATOR_EMAIL, to: "#{ADMINISTRATOR_EMAIL},#{ACCOUNTS_RECEIVABLE_EMAIL}", subject: "INVOICE ##{reference_number} FUNDS CAPTURE ERROR", message: "There has been a problem with the funds capture on invoice id #{id}, ref #{reference_number}, delivery id: #{delivery_id}. Please take action to ensure all funds are captured or applied.", no_verbage: true ).deliver logger.error("#{Time.current}: CAPTURE ERROR: Problem with funds capture") else if pending_balance.zero? logger.info("#{Time.current}: Funds captured successfully.") else logger.info("#{Time.current}: Funds captured successfully, but balance has not been completely paid.") end # enqueue pdf generation process InvoicePdfGenerationWorker.perform_async(id) end true end |
#chosen_shipping_cost ⇒ BigDecimal
Numeric cost of #chosen_shipping_method, or 0.00 when the carrier
uses a customer-supplied shipping account number (no charge to bill).
Wrapped in rescue because legacy data has nil shipping methods.
597 598 599 600 601 602 603 604 605 |
# File 'app/models/invoice.rb', line 597 def chosen_shipping_cost cost = BigDecimal('0.00') begin cost = chosen_shipping_method.cost unless chosen_shipping_method.shipping_account_number rescue StandardError => e Rails.logger.warn "Could not get shipping cost for invoice #{id}: #{e.}" end cost end |
#chosen_shipping_method ⇒ ShippingCost?
Cheapest available ShippingCost on the parent Delivery — what
the customer is actually being charged for shipping on this invoice.
588 589 590 |
# File 'app/models/invoice.rb', line 588 def chosen_shipping_method shipping_costs.first end |
#combined_terms ⇒ String
Terms label augmented with the early-payment offer in the standard
"Net X - Y%/Z" notation (e.g. Net 30 - 2%/10). Falls back to plain
#terms when no early-payment offer applies.
971 972 973 974 975 976 977 |
# File 'app/models/invoice.rb', line 971 def combined_terms if early_payment_discount && early_payment_timescale "#{terms} - #{early_payment_discount}%/#{early_payment_timescale}" else terms end end |
#communications ⇒ ActiveRecord::Relation<Communication>
203 |
# File 'app/models/invoice.rb', line 203 has_many :communications, -> { order(:id).reverse_order }, as: :resource, dependent: :nullify, inverse_of: :resource |
#company ⇒ Company
183 |
# File 'app/models/invoice.rb', line 183 belongs_to :company, inverse_of: :invoices |
#copy_payments ⇒ void
479 480 481 482 483 |
# File 'app/models/invoice.rb', line 479 def copy_payments delivery.payments.where(currency: currency).find_each do |pp| pp.update!(invoice_id: id) if pp. || (pp..in?(%w[credit_card check paypal_invoice amazon_pay]) && pp.captured?) end end |
#create_receipts_for_captured_payments ⇒ void
This method returns an undefined value.
Creates Receipts for Payments that have already been captured
at the gateway but don't yet have receipts on this invoice. Used
both by funds-capture and as the catch-up step for CC/PayPal
captures whose webhook receipt creation didn't fire. Idempotent —
skips payments already linked to receipts.
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 |
# File 'app/models/invoice.rb', line 888 def create_receipts_for_captured_payments payments.all_captured.each do |payment| next if payment.receipts.present? next if payment.skip_auto_receipt # Extends the AppSignal #4120 fix (31f69165d3, which guarded the # create_receipt_details path) to this captured-payments path. If the # invoice was already settled by another receipt before this async # catch-up runs, applying payment.amount over-applies; the receipt-detail # balance validation correctly rejects it and the RecordInvalid re-raised # into CaptureFundsHandler for a futile Sidekiq retry. Skip instead — # same net result (no over-applied detail) without the spurious error. if balance.present? && payment.amount > balance Rails.logger.info "[Invoice##{id}] skipping captured-payment receipt for payment ##{payment.id}: amount #{payment.amount} exceeds balance #{balance}" next end begin res = payment.gateway_class.new(payment).create_receipt(self, payment.amount, payment.amount) res.receipt.apply rescue ActiveRecord::RecordInvalid => e # Belt-and-suspenders for the check-then-act race: if a concurrent receipt # settles the invoice between the balance check above and #apply, the # receipt-detail balance validation rejects the over-application. Skip this # one payment and keep going rather than re-raising the deterministic # failure into a futile CaptureFundsHandler Sidekiq retry (#4120). Rails.logger.info "[Invoice##{id}] skipping over-applying receipt for payment ##{payment.id}: #{e.}" end end # create receipt details for already cc and paypal payments with an unapplied receipt payments.all_cc_captured.each do |payment| receipts_with_no_details = payment.receipts.where.missing(:receipt_details) receipts_with_no_details.each do |receipt| receipt.create_receipt_details(payment.invoice, payment.amount) if payment.invoice.present? receipt.apply end end end |
#credit_memos ⇒ ActiveRecord::Relation<CreditMemo>
198 |
# File 'app/models/invoice.rb', line 198 has_many :credit_memos, foreign_key: 'original_invoice_id', dependent: :destroy, inverse_of: :original_invoice |
#crm_link ⇒ String
CRM URL for the invoice show page. Used by activity/comm logs and
admin notification emails so reps can jump straight to the record.
700 701 702 |
# File 'app/models/invoice.rb', line 700 def crm_link UrlHelper.instance.invoice_path(self) end |
#currency_symbol ⇒ String
Currency symbol (e.g. $, €) for this invoice's #currency, used
in the PDF and overpayment notification templates.
502 503 504 |
# File 'app/models/invoice.rb', line 502 def currency_symbol Money::Currency.new(currency).symbol end |
#customer ⇒ Customer
181 |
# File 'app/models/invoice.rb', line 181 belongs_to :customer, optional: true, inverse_of: :invoices |
#customer_name ⇒ String?
Display name for the Customer this invoice bills, falling back to
nil for headless data (rare — most invoices have a customer).
461 462 463 |
# File 'app/models/invoice.rb', line 461 def customer_name customer.try(:full_name) end |
#delivery ⇒ Delivery
189 |
# File 'app/models/invoice.rb', line 189 belongs_to :delivery, optional: true, inverse_of: :invoices |
#disable_auto_coupon? ⇒ Boolean
1012 1013 1014 |
# File 'app/models/invoice.rb', line 1012 def disable_auto_coupon? true end |
#discount_applied ⇒ BigDecimal
Sum of discount written off via ReceiptDetails, e.g. early-payment
discounts or accounting write-downs. Reported on the invoice ledger tab.
1004 1005 1006 |
# File 'app/models/invoice.rb', line 1004 def discount_applied receipt_details.sum(:discount) end |
#discount_days_due ⇒ Integer?
Days between #gl_date and #early_payment_due_date; used by the
PDF "early-payment discount" line. Nil when no early-payment offer applies.
567 568 569 570 571 |
# File 'app/models/invoice.rb', line 567 def discount_days_due return unless early_payment_due_date (early_payment_due_date - gl_date).to_i end |
#do_not_detect_shipping? ⇒ Boolean
1008 1009 1010 |
# File 'app/models/invoice.rb', line 1008 def do_not_detect_shipping? true end |
#drop_ship_purchase_orders ⇒ ActiveRecord::Relation<DropShipPurchaseOrder>
204 |
# File 'app/models/invoice.rb', line 204 has_many :drop_ship_purchase_orders, -> { order(:id) }, through: :delivery, dependent: :destroy |
#early_payment_amount ⇒ BigDecimal
Dollar value of the early-payment discount — early_payment_discount%
of the invoice total, rounded to 2dp. Zero when no early-payment
offer is configured.
933 934 935 936 937 938 939 |
# File 'app/models/invoice.rb', line 933 def early_payment_amount if early_payment_discount.blank? BigDecimal(0) else ((early_payment_discount * total) / 100).round(2) end end |
#early_payment_due_date ⇒ Date?
Date by which the customer must pay to qualify for the
early-payment discount (shipped_date + early_payment_timescale days).
Nil when no early-payment offer applies.
946 947 948 949 950 951 952 |
# File 'app/models/invoice.rb', line 946 def early_payment_due_date if early_payment_timescale.blank? nil else shipped_date + early_payment_timescale.days end end |
#early_payment_total ⇒ BigDecimal
Discounted total the customer would owe if they pay by
#early_payment_due_date — total minus #early_payment_amount.
958 959 960 961 962 963 964 |
# File 'app/models/invoice.rb', line 958 def early_payment_total if early_payment_discount.zero? total else total - early_payment_amount end end |
#edi_communication_logs ⇒ ActiveRecord::Relation<EdiCommunicationLog>
206 |
# File 'app/models/invoice.rb', line 206 has_many :edi_communication_logs, through: :edi_documents, dependent: :destroy |
#edi_documents ⇒ ActiveRecord::Relation<EdiDocument>
205 |
# File 'app/models/invoice.rb', line 205 has_many :edi_documents, dependent: :destroy, inverse_of: :invoice |
#editing_locked? ⇒ Boolean
435 436 437 |
# File 'app/models/invoice.rb', line 435 def editing_locked? unpaid? || paid? end |
#effective_store ⇒ Store?
364 365 366 |
# File 'app/models/invoice.rb', line 364 def effective_store store || order&.store || company.stores.first end |
#file_name(with_extension: true) ⇒ String
Filename used when attaching the invoice PDF to email or storing in S3.
668 669 670 |
# File 'app/models/invoice.rb', line 668 def file_name(with_extension: true) "invoice_#{reference_number}#{'.pdf' if with_extension}" end |
#friendly_shipping_method(show_customer_pays_info: false) ⇒ String
Human-readable shipping-method label for the PDF and CRM —
"Warehouse Pickup" for warehouse addresses, otherwise the carrier
description with an optional COD-charge note.
629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 |
# File 'app/models/invoice.rb', line 629 def friendly_shipping_method(show_customer_pays_info: false) # rubocop:disable Lint/UnusedMethodArgument shipping_method_name = '' method_cod = '' if begin shipping_address.is_warehouse rescue StandardError => e Rails.logger.warn "Could not check if shipping address is warehouse for invoice #{id}: #{e.}" false end shipping_method_name = 'Warehouse Pickup' elsif chosen_shipping_method shipping_method_name = chosen_shipping_method.description method_cod = ' (inc. COD charge)' if chosen_shipping_method.cod end "#{shipping_method_name} #{method_cod}".strip end |
#fully_funded_by_rma? ⇒ Boolean
732 733 734 |
# File 'app/models/invoice.rb', line 732 def fully_funded_by_rma? order.present? && order.fully_funded_by_advance_replacement? end |
#funded_by_cod? ⇒ Boolean
736 737 738 |
# File 'app/models/invoice.rb', line 736 def funded_by_cod? terms.include?('COD') end |
#funded_by_rma? ⇒ Boolean
728 729 730 |
# File 'app/models/invoice.rb', line 728 def funded_by_rma? order.present? && order.funded_by_advance_replacement? end |
#generate_pdf ⇒ Upload
Renders a fresh combined invoice PDF (cover + line-item pages + any
addendums) via Invoicing::CombinedPdfGenerator, uploads it to S3
under the invoice_pdf category and attaches the Upload.
677 678 679 680 681 682 683 684 685 |
# File 'app/models/invoice.rb', line 677 def generate_pdf combined_pdf_result = Invoicing::CombinedPdfGenerator.new.process(self, output_to_file: true) upload = Upload.uploadify(combined_pdf_result.pdf_file_path, 'invoice_pdf', self, combined_pdf_result.file_name) uploads << upload upload end |
#get_or_regen_pdf(logger = nil) ⇒ Upload
Returns the persisted invoice-PDF Upload, regenerating it via
#generate_pdf if the upload row is missing or its file is gone
from S3. Used by transmission and the CRM "Download PDF" action.
652 653 654 655 656 657 658 659 660 661 662 |
# File 'app/models/invoice.rb', line 652 def get_or_regen_pdf(logger = nil) logger ||= Rails.logger pdf = uploads.in_category('invoice_pdf').first logger.info "Retrieving Invoice #{id} pdf, record exists: #{!pdf.nil?}" unless pdf&.file_exists? logger.error ' * Pdf nil or file does not exist, attempting regen' pdf = generate_pdf logger.info "Pdf regenerated with upload id #{pdf.id}" end pdf end |
#gl_offset_account ⇒ LedgerCompanyAccount
187 |
# File 'app/models/invoice.rb', line 187 belongs_to :gl_offset_account, class_name: 'LedgerCompanyAccount', optional: true, inverse_of: :invoices |
#item_ledger_entries ⇒ ActiveRecord::Relation<ItemLedgerEntry>
196 |
# File 'app/models/invoice.rb', line 196 has_many :item_ledger_entries, dependent: :destroy, inverse_of: :invoice |
#ledger_transactions ⇒ ActiveRecord::Relation<LedgerTransaction>
195 |
# File 'app/models/invoice.rb', line 195 has_many :ledger_transactions, dependent: :destroy, inverse_of: :invoice |
#line_discounts ⇒ ActiveRecord::Relation<LineDiscount>
193 |
# File 'app/models/invoice.rb', line 193 has_many :line_discounts, through: :line_items |
#line_items ⇒ ActiveRecord::Relation<LineItem>
192 |
# File 'app/models/invoice.rb', line 192 has_many :line_items, as: :resource, inverse_of: :resource, dependent: :destroy, extend: LineItemExtension, autosave: true |
#marketplace_invoice_format ⇒ Object
Alias for Customer#marketplace_invoice_format
280 |
# File 'app/models/invoice.rb', line 280 delegate :marketplace_invoice_format, to: :customer, allow_nil: true |
#name ⇒ String
Short display name for selection lists and links — just the
reference number.
611 612 613 |
# File 'app/models/invoice.rb', line 611 def name reference_number end |
#non_service_line_items ⇒ Array<LineItem>
Goods/shipping LineItems only — services (tax_class 'svc') excluded.
Used for ship-confirmation logic where service lines should not affect
what's physically shipped.
419 420 421 |
# File 'app/models/invoice.rb', line 419 def non_service_line_items line_items.where(cm_category: 'Item').reject { |li| li.item.tax_class == 'svc' } end |
#non_voided_receipt_details ⇒ ActiveRecord::Relation<ReceiptDetail>
All ReceiptDetails posted against this invoice excluding voided ones.
509 510 511 |
# File 'app/models/invoice.rb', line 509 def non_voided_receipt_details receipt_details.non_voided end |
#not_rma? ⇒ Boolean
724 725 726 |
# File 'app/models/invoice.rb', line 724 def not_rma? !(order.present? && order.funded_by_advance_replacement?) end |
#online_payment_options ⇒ Array<String>
Payment options offered on the public pay-online page. Currently the
only gateway-driven option is credit card; checks/POs are out-of-band.
469 470 471 |
# File 'app/models/invoice.rb', line 469 def [Payment::CREDIT_CARD] end |
#order ⇒ Order
177 |
# File 'app/models/invoice.rb', line 177 belongs_to :order, optional: true, inverse_of: :invoices |
#order_ref ⇒ String?
443 444 445 |
# File 'app/models/invoice.rb', line 443 def order_ref order.try(:reference_number) end |
#order_ref=(ref) ⇒ Order?
Setter pairing with #order_ref — looks up the Order by its
reference number so manual-entry forms can attach an invoice to an
existing order without exposing the integer primary key.
453 454 455 |
# File 'app/models/invoice.rb', line 453 def order_ref=(ref) self.order = Order.find_by(reference_number: ref) if ref.present? end |
#payments ⇒ ActiveRecord::Relation<Payment>
202 |
# File 'app/models/invoice.rb', line 202 has_many :payments, dependent: :nullify, inverse_of: :invoice |
#po_numbers ⇒ Array<String>
Distinct customer purchase-order numbers attached to Payments on
this invoice. Surfaced in the PDF header and EDI 810 PO segment.
619 620 621 |
# File 'app/models/invoice.rb', line 619 def po_numbers payments.where.not(po_number: nil).distinct.pluck(:po_number) end |
#prevent_recalculate_shipping? ⇒ Boolean
431 432 433 |
# File 'app/models/invoice.rb', line 431 def prevent_recalculate_shipping? true end |
#pricing_program_discount_factor ⇒ BigDecimal
Pricing-program discount multiplier — pulled from the parent Order
when one exists, otherwise from the Customer's tier. Used by
Models::Itemizable when re-evaluating discounts on edit.
536 537 538 539 540 541 542 |
# File 'app/models/invoice.rb', line 536 def pricing_program_discount_factor if order.present? order.pricing_program_discount_factor else customer.pricing_program_discount end end |
#primary_party ⇒ Party
692 693 694 |
# File 'app/models/invoice.rb', line 692 def primary_party order&.primary_party || customer end |
#profile ⇒ Profile
185 |
# File 'app/models/invoice.rb', line 185 belongs_to :profile, optional: true, inverse_of: :invoices |
#public_pay_link ⇒ String?
709 710 711 712 713 714 715 716 717 718 |
# File 'app/models/invoice.rb', line 709 def public_pay_link # disabling authenticated links for now return nil unless order # && order.customer.present? # a = order.customer.account # return nil unless a.present? order.public_payment_link # "https://#{WEB_HOSTNAME}#{public_pay_path}" end |
#public_pay_link_has_auth_token? ⇒ Boolean
720 721 722 |
# File 'app/models/invoice.rb', line 720 def public_pay_link_has_auth_token? false end |
#receipt_details ⇒ ActiveRecord::Relation<ReceiptDetail>
194 |
# File 'app/models/invoice.rb', line 194 has_many :receipt_details, dependent: :nullify, inverse_of: :invoice |
#receipts_total ⇒ BigDecimal
518 519 520 |
# File 'app/models/invoice.rb', line 518 def receipts_total non_voided_receipt_details.sum('amount') + non_voided_receipt_details.sum('write_off') + non_voided_receipt_details.sum('discount') end |
#rma_awaiting_return? ⇒ Boolean
381 382 383 |
# File 'app/models/invoice.rb', line 381 def rma_awaiting_return? order.try(:rma).try(:state) == 'awaiting_return' end |
#rma_number ⇒ String
RMA reference for this invoice — the linked Rma's number when one
exists, otherwise a synthesised "RMA # …" label from the order's
rma_reference. Surfaced on the customer-facing PDF for return shipments.
377 378 379 |
# File 'app/models/invoice.rb', line 377 def rma_number order.try(:rma).try(:rma_number) || "RMA # #{order.rma_reference}" end |
#rmas ⇒ ActiveRecord::Relation<Rma>
197 |
# File 'app/models/invoice.rb', line 197 has_many :rmas, foreign_key: 'original_invoice_id', dependent: :destroy, inverse_of: :original_invoice |
#selection_name ⇒ String
Display label for resource pickers / invoice dropdowns —
INV… <Customer Name>.
983 984 985 |
# File 'app/models/invoice.rb', line 983 def selection_name "#{reference_number} #{customer.full_name}" end |
#selection_name_for_rmas ⇒ String
Variant of #selection_name for the RMA picker that appends the
parent Order's reference when present, since RMAs are scoped to
an order, not just a customer.
992 993 994 995 996 997 998 |
# File 'app/models/invoice.rb', line 992 def selection_name_for_rmas if order.nil? "#{reference_number} #{customer.full_name}" else "#{reference_number} #{customer.full_name} (#{order&.reference_number})" end end |
#service_line_items ⇒ Array<LineItem>
Service-class LineItems only (tax_class 'svc'), e.g. SmartInstall
labour. Used by the consignment / service-only invoice rendering path.
427 428 429 |
# File 'app/models/invoice.rb', line 427 def service_line_items line_items.where(cm_category: 'Item').select { |li| li.item.tax_class == 'svc' } end |
#set_consolidated_amount ⇒ void
This method returns an undefined value.
Caches the consolidated-currency exchange rate for gl_date onto
#consolidated_exchange_rate, so downstream financial reports can
express the invoice in CONSOLIDATED_CURRENCY without re-querying
ExchangeRate. Sets the rate to 1.0 when the invoice currency
already matches the consolidated currency, or nil when fields aren't
populated yet.
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 |
# File 'app/models/invoice.rb', line 1033 def set_consolidated_amount if currency && gl_date if currency == CONSOLIDATED_CURRENCY self.consolidated_exchange_rate = 1.0 else exchange_rate = ExchangeRate.get_exchange_rate(currency, CONSOLIDATED_CURRENCY, gl_date) self.consolidated_exchange_rate = exchange_rate end else self.consolidated_exchange_rate = nil end end |
#shipping_address ⇒ Address Also known as: destination_address
Validations:
180 |
# File 'app/models/invoice.rb', line 180 belongs_to :shipping_address, class_name: 'Address', optional: true, inverse_of: :shipping_invoices |
#shipping_costs ⇒ ActiveRecord::Relation<ShippingCost>
199 |
# File 'app/models/invoice.rb', line 199 has_many :shipping_costs, -> { order(:cost) }, through: :delivery |
#show_tax_info? ⇒ Boolean
368 369 370 |
# File 'app/models/invoice.rb', line 368 def show_tax_info? tax_info.present? end |
#sold_to_billing_address ⇒ Address
179 |
# File 'app/models/invoice.rb', line 179 belongs_to :sold_to_billing_address, class_name: 'Address', foreign_key: 'sold_to_billing_address', optional: true, inverse_of: :billing_invoices |
#source ⇒ Source
190 |
# File 'app/models/invoice.rb', line 190 belongs_to :source, optional: true, inverse_of: :invoices |
#store ⇒ Store
186 |
# File 'app/models/invoice.rb', line 186 belongs_to :store, optional: true, inverse_of: :invoices |
#tax_info ⇒ String?
Tax-identification line printed at the top of the invoice PDF — the
destination country's EU VAT number when shipping into the EU,
otherwise the company's tax info for Canada. Returns nil when no
tax identifier applies (e.g. US domestic).
343 344 345 346 347 348 349 350 351 352 353 354 355 356 |
# File 'app/models/invoice.rb', line 343 def tax_info destination_country = shipping_address&.country return unless destination_country if destination_country.eu_country? if destination_country.eu_vat_number.present? "VAT: #{destination_country.eu_vat_number}" else company.tax_info end elsif company.canada? company.tax_info end end |
#technical_support_rep ⇒ Employee
191 |
# File 'app/models/invoice.rb', line 191 belongs_to :technical_support_rep, class_name: 'Employee', optional: true, inverse_of: :technical_support_invoices |
#terms_in_days ⇒ Object
calculate net due date in days
557 558 559 560 561 |
# File 'app/models/invoice.rb', line 557 def terms_in_days return unless due_date (due_date - gl_date).to_i end |
#to_liquid ⇒ Liquid::InvoiceDrop
Liquid drop wrapper used when this invoice is rendered into
transmission email/SMS templates — exposes only the safe-for-template
accessors via Liquid::InvoiceDrop.
1062 1063 1064 |
# File 'app/models/invoice.rb', line 1062 def to_liquid Liquid::InvoiceDrop.new self end |
#to_s ⇒ String
Human-readable identifier used in audit logs and error messages.
1049 1050 1051 1052 1053 1054 1055 |
# File 'app/models/invoice.rb', line 1049 def to_s if respond_to?(:reference_number) "Invoice # #{reference_number}" else "Invoice ID #{id}" end end |
#uploads ⇒ ActiveRecord::Relation<Upload>
200 |
# File 'app/models/invoice.rb', line 200 has_many :uploads, -> { order(:updated_at).reverse_order }, as: :resource, dependent: :destroy, inverse_of: :resource |