Class: Invoice

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 =
'ST'
MI =
'MI'
MO =
'MO'
TO =
'TO'
CI =
'CI'
SS =
'SS'
INVOICE_TYPES =
[SO, ST, MI, MO, TO, CI, SS].freeze
REFERENCE_NUMBER_PATTERN =
/^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

Instance Attribute Summary collapse

Attributes included from Models::Profitable

#min_profit_markup

Attributes included from Models::Itemizable

#force_total_reset, #total_reset

Belongs to collapse

Methods included from Models::TaxableResource

#resource_tax_rate

Methods included from Models::Itemizable

#account_specialist, #local_sales_rep, #primary_sales_rep, #secondary_sales_rep

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Methods included from Models::Itemizable

#coupons, #discounts

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

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

#quick_note

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#allow_duplicate_delivery_for_testingObject

Returns the value of attribute allow_duplicate_delivery_for_testing.



163
164
165
# File 'app/models/invoice.rb', line 163

def allow_duplicate_delivery_for_testing
  @allow_duplicate_delivery_for_testing
end

#billing_address_idObject (readonly)



198
# File 'app/models/invoice.rb', line 198

validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true

#customer_idObject (readonly)



198
# File 'app/models/invoice.rb', line 198

validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true

#delivery_idObject (readonly)



203
# File 'app/models/invoice.rb', line 203

validates :delivery_id, uniqueness: { allow_nil: true }, unless: :allow_duplicate_delivery_for_testing?

#disable_auto_couponObject

Returns the value of attribute disable_auto_coupon.



163
164
165
# File 'app/models/invoice.rb', line 163

def disable_auto_coupon
  @disable_auto_coupon
end

#do_not_detect_shippingObject

Returns the value of attribute do_not_detect_shipping.



163
164
165
# File 'app/models/invoice.rb', line 163

def do_not_detect_shipping
  @do_not_detect_shipping
end

#do_not_set_totalsObject

Returns the value of attribute do_not_set_totals.



163
164
165
# File 'app/models/invoice.rb', line 163

def do_not_set_totals
  @do_not_set_totals
end

#document_dateObject (readonly)



198
# File 'app/models/invoice.rb', line 198

validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true

#due_dateObject (readonly)



198
# File 'app/models/invoice.rb', line 198

validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true

#enter_new_addressObject

Returns the value of attribute enter_new_address.



163
164
165
# File 'app/models/invoice.rb', line 163

def enter_new_address
  @enter_new_address
end

#gl_dateObject (readonly)



198
# File 'app/models/invoice.rb', line 198

validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true

#gl_offset_account_idObject (readonly)



198
# File 'app/models/invoice.rb', line 198

validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true

#gl_offset_account_refObject

Returns the value of attribute gl_offset_account_ref.



163
164
165
# File 'app/models/invoice.rb', line 163

def 
  @gl_offset_account_ref
end

#invoice_typeObject (readonly)



198
# File 'app/models/invoice.rb', line 198

validates :due_date, :terms, :invoice_type, :billing_address_id, :customer_id, :document_date, :gl_date, :gl_offset_account_id, presence: true

#order_idObject (readonly)



199
# File 'app/models/invoice.rb', line 199

validates :order_id, presence: { if: proc { |i| [MI, CI].exclude?(i.invoice_type) } }

#original_order_refObject

Returns the value of attribute original_order_ref.



163
164
165
# File 'app/models/invoice.rb', line 163

def original_order_ref
  @original_order_ref
end

#skip_initial_state_checkObject

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)



240
241
242
# File 'app/models/invoice.rb', line 240

def skip_initial_state_check
  @skip_initial_state_check
end

#skip_line_item_integrity_checkObject

Flag to skip integrity check during initial creation (set by CreateInvoiceFromDelivery)



236
237
238
# File 'app/models/invoice.rb', line 236

def skip_line_item_integrity_check
  @skip_line_item_integrity_check
end

#skip_pdfObject

Returns the value of attribute skip_pdf.



163
164
165
# File 'app/models/invoice.rb', line 163

def skip_pdf
  @skip_pdf
end

#store_idObject (readonly)



200
# File 'app/models/invoice.rb', line 200

validates :store_id, presence: { if: proc { |i| [MI, CI].include?(i.invoice_type) } }

#tax_dateObject (readonly)



201
# File 'app/models/invoice.rb', line 201

validates :tax_date, :store_id, presence: { if: proc { |i| [MI, CI].include?(i.invoice_type) } }

#termsObject (readonly)



198
# File 'app/models/invoice.rb', line 198

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_transmissionActiveRecord::Relation<Invoice>

A relation of Invoices that are awaiting transmission. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



247
# File 'app/models/invoice.rb', line 247

scope :awaiting_transmission, -> { where(state: %w[unpaid paid], transmission_state: %w[awaiting_transmission in_transmission_queue]) }

.calculate_due_date(order, delivery) ⇒ Object



440
441
442
443
# File 'app/models/invoice.rb', line 440

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) ⇒ Object



458
459
460
461
462
# File 'app/models/invoice.rb', line 458

def self.calculate_terms(order)
  terms = order.billing_entity.terms
  terms += ' (COD)' if order.funded_by_cod?
  terms
end

.included_in_notificationsActiveRecord::Relation<Invoice>

A relation of Invoices that are included in notifications. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



250
# File 'app/models/invoice.rb', line 250

scope :included_in_notifications, -> { where(exclude_fund_capture_notification: false) }

.like_lookupActiveRecord::Relation<Invoice>

A relation of Invoices that are like lookup. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



253
# File 'app/models/invoice.rb', line 253

scope :like_lookup, ->(q) { left_joins(:order).where(Invoice[:reference_number].matches("%#{q}%")).or(Order.where(Order[:reference_number].matches("%#{q}%"))) }

.lookupActiveRecord::Relation<Invoice>

A relation of Invoices that are lookup. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



252
# File 'app/models/invoice.rb', line 252

scope :lookup, ->(q) { where(reference_number: q) }

.missing_edi_810ActiveRecord::Relation<Invoice>

A relation of Invoices that are missing edi 810. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



254
255
256
257
258
# File 'app/models/invoice.rb', line 254

scope :missing_edi_810, -> {
  joins(customer: :notification_channels)
    .where(notification_channels: { notification_type: NotificationChannel::INVOICES, transmission_type: NotificationChannel::EDI })
    .where(transmission_state: 'awaiting_transmission')
}

.overdueActiveRecord::Relation<Invoice>

A relation of Invoices that are overdue. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



251
# File 'app/models/invoice.rb', line 251

scope :overdue, -> { unpaid.where(due_date: ...Date.current) }

.sales_ordersActiveRecord::Relation<Invoice>

A relation of Invoices that are sales orders. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



248
# File 'app/models/invoice.rb', line 248

scope :sales_orders, -> { where(invoice_type: 'SO') }

.unpaidActiveRecord::Relation<Invoice>

A relation of Invoices that are unpaid. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Invoice>)

See Also:



249
# File 'app/models/invoice.rb', line 249

scope :unpaid, -> { where(state: 'unpaid') }

Instance Method Details

#activitiesActiveRecord::Relation<Activity>

Returns:

See Also:



189
# File 'app/models/invoice.rb', line 189

has_many   :activities, as: :resource, dependent: :nullify, inverse_of: :resource

#allow_duplicate_delivery_for_testing?Boolean

Returns:

  • (Boolean)


205
206
207
# File 'app/models/invoice.rb', line 205

def allow_duplicate_delivery_for_testing?
  allow_duplicate_delivery_for_testing == true
end

#amount_dueObject



428
429
430
# File 'app/models/invoice.rb', line 428

def amount_due
  total
end

#balanceObject



412
413
414
# File 'app/models/invoice.rb', line 412

def balance
  total - receipts_total
end

#balance_is_zero?Boolean

Returns:

  • (Boolean)


408
409
410
# File 'app/models/invoice.rb', line 408

def balance_is_zero?
  balance.zero?
end

#balance_positive?Object

Alias for Balance#positive?

Returns:

  • (Object)

    Balance#balance_positive?

See Also:



270
# File 'app/models/invoice.rb', line 270

delegate :positive?, to: :balance, prefix: true, allow_nil: true

#billing_addressAddress

Returns:

See Also:



166
# File 'app/models/invoice.rb', line 166

belongs_to :billing_address, class_name: 'Address', optional: true, inverse_of: :billing_invoices

#billing_customerCustomer

Returns:

See Also:



170
# File 'app/models/invoice.rb', line 170

belongs_to :billing_customer, class_name: 'Customer', optional: true, inverse_of: :invoices

#billing_entityObject



786
787
788
# File 'app/models/invoice.rb', line 786

def billing_entity
  billing_address.party
end

#build_activityObject



358
359
360
# File 'app/models/invoice.rb', line 358

def build_activity
  activities.build resource: self, party: primary_party
end

#business_unitBusinessUnit



176
# File 'app/models/invoice.rb', line 176

belongs_to :business_unit, optional: true, inverse_of: :invoices

#buying_groupBuyingGroup



172
# File 'app/models/invoice.rb', line 172

belongs_to :buying_group, optional: true, inverse_of: :invoices

#calculate_all_cogsObject



366
367
368
# File 'app/models/invoice.rb', line 366

def calculate_all_cogs
  BigDecimal(line_items.where(cm_category: 'Item').sum('unit_cogs * quantity'))
end

#calculate_cogs(tax_class = %w[g svc shp])) ⇒ Object



362
363
364
# File 'app/models/invoice.rb', line 362

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

Returns:

  • (Boolean)


571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
# File 'app/models/invoice.rb', line 571

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.all_authorized.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.all_authorized.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
    authorized_without_receipts = payments.all_authorized.select { |p| p.receipts.empty? }
    only_manual_payments = authorized_without_receipts.all? { |p| Payment::CATEGORIES_NOT_ALLOWING_CAPTURE.include?(p.category) }

    if only_manual_payments && authorized_without_receipts.any?
      logger.info("#{Time.current}: Authorized payments exist that require manual processing (#{authorized_without_receipts.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_costObject



468
469
470
471
472
473
474
475
476
# File 'app/models/invoice.rb', line 468

def chosen_shipping_cost
  cost = BigDecimal('0.00')
  begin
    cost = chosen_shipping_method.cost unless chosen_shipping_method.
  rescue StandardError => e
    Rails.logger.warn "Could not get shipping cost for invoice #{id}: #{e.message}"
  end
  cost
end

#chosen_shipping_methodObject



464
465
466
# File 'app/models/invoice.rb', line 464

def chosen_shipping_method
  shipping_costs.first
end

#combined_termsObject



754
755
756
757
758
759
760
# File 'app/models/invoice.rb', line 754

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

#communicationsActiveRecord::Relation<Communication>

Returns:

See Also:



191
# File 'app/models/invoice.rb', line 191

has_many   :communications, -> { order(:id).reverse_order }, as: :resource, dependent: :nullify, inverse_of: :resource

#companyCompany

Returns:

See Also:



171
# File 'app/models/invoice.rb', line 171

belongs_to :company, inverse_of: :invoices

#copy_paymentsObject



402
403
404
405
406
# File 'app/models/invoice.rb', line 402

def copy_payments
  delivery.payments.where(currency: currency).find_each do |pp|
    pp.update!(invoice_id: id) if pp.authorized? || (pp.authorization_type.in?(%w[credit_card check paypal_invoice amazon_pay]) && pp.captured?)
  end
end

#create_receipts_for_captured_paymentsObject



712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
# File 'app/models/invoice.rb', line 712

def create_receipts_for_captured_payments
  payments.all_captured.each do |payment|
    next if payment.receipts.present?
    next if payment.skip_auto_receipt

    res = payment.gateway_class.new(payment).create_receipt(self, payment.amount, payment.amount)
    res.receipt.apply
  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?
    end
  end
end

#credit_memosActiveRecord::Relation<CreditMemo>

Returns:

See Also:



186
# File 'app/models/invoice.rb', line 186

has_many   :credit_memos, foreign_key: 'original_invoice_id', dependent: :destroy, inverse_of: :original_invoice


533
534
535
# File 'app/models/invoice.rb', line 533

def crm_link
  UrlHelper.instance.invoice_path(self)
end

#currency_symbolObject



416
417
418
# File 'app/models/invoice.rb', line 416

def currency_symbol
  Money::Currency.new(currency).symbol
end

#customerCustomer

Returns:

See Also:



169
# File 'app/models/invoice.rb', line 169

belongs_to :customer, optional: true, inverse_of: :invoices

#customer_nameObject



394
395
396
# File 'app/models/invoice.rb', line 394

def customer_name
  customer.try(:full_name)
end

#deliveryDelivery

Returns:

See Also:



177
# File 'app/models/invoice.rb', line 177

belongs_to :delivery, optional: true, inverse_of: :invoices

#disable_auto_coupon?Boolean

Returns:

  • (Boolean)


782
783
784
# File 'app/models/invoice.rb', line 782

def disable_auto_coupon?
  true
end

#discount_appliedObject



774
775
776
# File 'app/models/invoice.rb', line 774

def discount_applied
  receipt_details.sum(:discount)
end

#discount_days_dueObject



452
453
454
455
456
# File 'app/models/invoice.rb', line 452

def discount_days_due
  return unless early_payment_due_date

  (early_payment_due_date - gl_date).to_i
end

#do_not_detect_shipping?Boolean

Returns:

  • (Boolean)


778
779
780
# File 'app/models/invoice.rb', line 778

def do_not_detect_shipping?
  true
end

#drop_ship_purchase_ordersActiveRecord::Relation<DropShipPurchaseOrder>

Returns:

  • (ActiveRecord::Relation<DropShipPurchaseOrder>)

See Also:



192
# File 'app/models/invoice.rb', line 192

has_many   :drop_ship_purchase_orders, -> { order(:id) }, through: :delivery, dependent: :destroy

#early_payment_amountObject



730
731
732
733
734
735
736
# File 'app/models/invoice.rb', line 730

def early_payment_amount
  if early_payment_discount.blank?
    BigDecimal(0)
  else
    ((early_payment_discount * total) / 100).round(2)
  end
end

#early_payment_due_dateObject



738
739
740
741
742
743
744
# File 'app/models/invoice.rb', line 738

def early_payment_due_date
  if early_payment_timescale.blank?
    nil
  else
    shipped_date + early_payment_timescale.days
  end
end

#early_payment_totalObject



746
747
748
749
750
751
752
# File 'app/models/invoice.rb', line 746

def early_payment_total
  if early_payment_discount.zero?
    total
  else
    total - early_payment_amount
  end
end

#edi_communication_logsActiveRecord::Relation<EdiCommunicationLog>

Returns:

See Also:



194
# File 'app/models/invoice.rb', line 194

has_many   :edi_communication_logs, through: :edi_documents, dependent: :destroy

#edi_documentsActiveRecord::Relation<EdiDocument>

Returns:

See Also:



193
# File 'app/models/invoice.rb', line 193

has_many   :edi_documents, dependent: :destroy, inverse_of: :invoice

#editing_locked?Boolean

Returns:

  • (Boolean)


382
383
384
# File 'app/models/invoice.rb', line 382

def editing_locked?
  unpaid? || paid?
end

#effective_storeObject



342
343
344
# File 'app/models/invoice.rb', line 342

def effective_store
  store || order&.store || company.stores.first
end

#file_name(with_extension: true) ⇒ Object



515
516
517
# File 'app/models/invoice.rb', line 515

def file_name(with_extension: true)
  "invoice_#{reference_number}#{'.pdf' if with_extension}"
end

#friendly_shipping_method(_show_customer_pays_info: false) ⇒ Object



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'app/models/invoice.rb', line 486

def friendly_shipping_method(_show_customer_pays_info: false)
  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.message}"
    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

Returns:

  • (Boolean)


563
564
565
# File 'app/models/invoice.rb', line 563

def fully_funded_by_rma?
  order.present? && order.fully_funded_by_advance_replacement?
end

#funded_by_cod?Boolean

Returns:

  • (Boolean)


567
568
569
# File 'app/models/invoice.rb', line 567

def funded_by_cod?
  terms.include?('COD')
end

#funded_by_rma?Boolean

Returns:

  • (Boolean)


559
560
561
# File 'app/models/invoice.rb', line 559

def funded_by_rma?
  order.present? && order.funded_by_advance_replacement?
end

#generate_pdfObject



519
520
521
522
523
524
525
526
527
# File 'app/models/invoice.rb', line 519

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) ⇒ Object



503
504
505
506
507
508
509
510
511
512
513
# File 'app/models/invoice.rb', line 503

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_accountLedgerCompanyAccount



175
# File 'app/models/invoice.rb', line 175

belongs_to :gl_offset_account, class_name: 'LedgerCompanyAccount', optional: true, inverse_of: :invoices

#item_ledger_entriesActiveRecord::Relation<ItemLedgerEntry>

Returns:

See Also:



184
# File 'app/models/invoice.rb', line 184

has_many   :item_ledger_entries, dependent: :destroy, inverse_of: :invoice

#ledger_transactionsActiveRecord::Relation<LedgerTransaction>

Returns:

See Also:



183
# File 'app/models/invoice.rb', line 183

has_many   :ledger_transactions, dependent: :destroy, inverse_of: :invoice

#line_discountsActiveRecord::Relation<LineDiscount>

Returns:

See Also:



181
# File 'app/models/invoice.rb', line 181

has_many   :line_discounts, through: :line_items

#line_itemsActiveRecord::Relation<LineItem>

Returns:

See Also:



180
# File 'app/models/invoice.rb', line 180

has_many   :line_items, as: :resource, inverse_of: :resource, dependent: :destroy, extend: LineItemExtension, autosave: true

#marketplace_invoice_formatObject

Alias for Customer#marketplace_invoice_format

Returns:

  • (Object)

    Customer#marketplace_invoice_format

See Also:



269
# File 'app/models/invoice.rb', line 269

delegate :marketplace_invoice_format, to: :customer, allow_nil: true

#nameObject



478
479
480
# File 'app/models/invoice.rb', line 478

def name
  reference_number
end

#non_service_line_itemsObject



370
371
372
# File 'app/models/invoice.rb', line 370

def non_service_line_items
  line_items.where(cm_category: 'Item').reject { |li| li.item.tax_class == 'svc' }
end

#non_voided_receipt_detailsObject



420
421
422
# File 'app/models/invoice.rb', line 420

def non_voided_receipt_details
  receipt_details.non_voided
end

#not_rma?Boolean

Returns:

  • (Boolean)


555
556
557
# File 'app/models/invoice.rb', line 555

def not_rma?
  !(order.present? && order.funded_by_advance_replacement?)
end

#online_payment_optionsObject



398
399
400
# File 'app/models/invoice.rb', line 398

def online_payment_options
  [Payment::CREDIT_CARD]
end

#orderOrder

Returns:

See Also:



165
# File 'app/models/invoice.rb', line 165

belongs_to :order, optional: true, inverse_of: :invoices

#order_refObject



386
387
388
# File 'app/models/invoice.rb', line 386

def order_ref
  order.try(:reference_number)
end

#order_ref=(ref) ⇒ Object



390
391
392
# File 'app/models/invoice.rb', line 390

def order_ref=(ref)
  self.order = Order.find_by(reference_number: ref) if ref.present?
end

#paymentsActiveRecord::Relation<Payment>

Returns:

  • (ActiveRecord::Relation<Payment>)

See Also:



190
# File 'app/models/invoice.rb', line 190

has_many   :payments, dependent: :nullify, inverse_of: :invoice

#po_numbersObject



482
483
484
# File 'app/models/invoice.rb', line 482

def po_numbers
  payments.where.not(po_number: nil).distinct.pluck(:po_number)
end

#prevent_recalculate_shipping?Boolean

Returns:

  • (Boolean)


378
379
380
# File 'app/models/invoice.rb', line 378

def prevent_recalculate_shipping?
  true
end

#pricing_program_discount_factorObject



432
433
434
435
436
437
438
# File 'app/models/invoice.rb', line 432

def pricing_program_discount_factor
  if order.present?
    order.pricing_program_discount_factor
  else
    customer.pricing_program_discount
  end
end

#primary_partyObject



529
530
531
# File 'app/models/invoice.rb', line 529

def primary_party
  order&.primary_party || customer
end

#profileProfile

Returns:

See Also:



173
# File 'app/models/invoice.rb', line 173

belongs_to :profile, optional: true, inverse_of: :invoices


537
538
539
540
541
542
543
544
545
546
# File 'app/models/invoice.rb', line 537

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

Returns:

  • (Boolean)


548
549
550
551
552
553
# File 'app/models/invoice.rb', line 548

def public_pay_link_has_auth_token?
  # disabling authenticated links for now
  # return nil unless (self.order and self.order.customer and self.order.customer.account.present?)
  # return self.order.customer.account.auth_token_required?
  false
end

#receipt_detailsActiveRecord::Relation<ReceiptDetail>

Returns:

See Also:



182
# File 'app/models/invoice.rb', line 182

has_many   :receipt_details, dependent: :nullify, inverse_of: :invoice

#receipts_totalObject



424
425
426
# File 'app/models/invoice.rb', line 424

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

Returns:

  • (Boolean)


354
355
356
# File 'app/models/invoice.rb', line 354

def rma_awaiting_return?
  order.try(:rma).try(:state) == 'awaiting_return'
end

#rma_numberObject



350
351
352
# File 'app/models/invoice.rb', line 350

def rma_number
  order.try(:rma).try(:rma_number) || "RMA # #{order.rma_reference}"
end

#rmasActiveRecord::Relation<Rma>

Returns:

  • (ActiveRecord::Relation<Rma>)

See Also:



185
# File 'app/models/invoice.rb', line 185

has_many   :rmas, foreign_key: 'original_invoice_id', dependent: :destroy, inverse_of: :original_invoice

#selection_nameObject



762
763
764
# File 'app/models/invoice.rb', line 762

def selection_name
  "#{reference_number} #{customer.full_name}"
end

#selection_name_for_rmasObject



766
767
768
769
770
771
772
# File 'app/models/invoice.rb', line 766

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_itemsObject



374
375
376
# File 'app/models/invoice.rb', line 374

def service_line_items
  line_items.where(cm_category: 'Item').select { |li| li.item.tax_class == 'svc' }
end

#set_consolidated_amountObject



790
791
792
793
794
795
796
797
798
799
800
801
# File 'app/models/invoice.rb', line 790

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_addressAddress Also known as: destination_address

Returns:

See Also:

Validations:



168
# File 'app/models/invoice.rb', line 168

belongs_to :shipping_address, class_name: 'Address', optional: true, inverse_of: :shipping_invoices

#shipping_costsActiveRecord::Relation<ShippingCost>

Returns:

See Also:



187
# File 'app/models/invoice.rb', line 187

has_many   :shipping_costs, -> { order(:cost) }, through: :delivery

#show_tax_info?Boolean

Returns:

  • (Boolean)


346
347
348
# File 'app/models/invoice.rb', line 346

def show_tax_info?
  tax_info.present?
end

#sold_to_billing_addressAddress

Returns:

See Also:



167
# File 'app/models/invoice.rb', line 167

belongs_to :sold_to_billing_address, class_name: 'Address', foreign_key: 'sold_to_billing_address', optional: true, inverse_of: :billing_invoices

#sourceSource

Returns:

See Also:



178
# File 'app/models/invoice.rb', line 178

belongs_to :source, optional: true, inverse_of: :invoices

#storeStore

Returns:

See Also:



174
# File 'app/models/invoice.rb', line 174

belongs_to :store, optional: true, inverse_of: :invoices

#tax_infoObject



327
328
329
330
331
332
333
334
335
336
337
338
339
340
# File 'app/models/invoice.rb', line 327

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_repEmployee

Returns:

See Also:



179
# File 'app/models/invoice.rb', line 179

belongs_to :technical_support_rep, class_name: 'Employee', optional: true, inverse_of: :technical_support_invoices

#terms_in_daysObject

calculate net due date in days



446
447
448
449
450
# File 'app/models/invoice.rb', line 446

def terms_in_days
  return unless due_date

  (due_date - gl_date).to_i
end

#to_liquidObject



811
812
813
# File 'app/models/invoice.rb', line 811

def to_liquid
  Liquid::InvoiceDrop.new self
end

#to_sObject



803
804
805
806
807
808
809
# File 'app/models/invoice.rb', line 803

def to_s
  if respond_to?(:reference_number)
    "Invoice # #{reference_number}"
  else
    "Invoice ID #{id}"
  end
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



188
# File 'app/models/invoice.rb', line 188

has_many   :uploads, -> { order(:updated_at).reverse_order }, as: :resource, dependent: :destroy, inverse_of: :resource