Class: Rma

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable, Models::LiquidMethods, Models::RmaTransmittable, Models::SupportCaseLinkable
Defined in:
app/models/rma.rb

Overview

== Schema Information

Table name: rmas
Database name: primary

id :integer not null, primary key
arrival_date :date
contact_name :string(255)
credited_date :date
customer_reference :string(255)
description :text
insure_return_shipping :boolean default(FALSE), not null
number_of_return_labels_required :integer default(0), not null
original_po_number :string(255)
payment_method :string(255)
replacement_order_type :enum default("SO")
return_declared_value_override :decimal(10, 2)
return_insurance_data :jsonb not null
return_shipping_carrier :string
returned_date :date
rma_number :string(255)
serial_number_state :string
skip_reminders :boolean default(FALSE), not null
state :string(255)
tracking_numbers :string default([]), is an Array
transmission_email :string(255) default([]), is an Array
transmission_fax :string(255) default([]), is an Array
transmission_state :string(255)
uploads_count :integer
created_at :datetime
updated_at :datetime
company_id :integer
creator_id :integer
customer_id :integer
original_invoice_id :integer
original_order_id :integer
precreate_from_delivery_id :integer
redesign_quote_id :integer
return_delivery_id :integer
return_shipping_address_id :integer
send_from_id :integer
ship_from_address_id :integer
shipping_option_id :integer
support_case_id :integer
updater_id :integer

Indexes

idx_creator_id (creator_id)
idx_rma_number (rma_number)
idx_rmas_original_order_id (original_order_id)
idx_trigram_rma_number (COALESCE((rma_number)::text, ''::text) gist_trgm_ops) USING gist
idx_tsearch_rma_number (to_tsvector('english'::regconfig, COALESCE((rma_number)::text, ''::text))) USING gin
index_rmas_on_customer_id (customer_id)
index_rmas_on_original_invoice_id (original_invoice_id)
index_rmas_on_precreate_from_delivery_id (precreate_from_delivery_id) USING hash
index_rmas_on_return_delivery_id (return_delivery_id) USING hash
index_rmas_on_shipping_option_id (shipping_option_id)
index_rmas_on_support_case_id (support_case_id)
index_rmas_on_tracking_numbers (tracking_numbers) USING gin

Foreign Keys

fk_rails_... (precreate_from_delivery_id => deliveries.id)
fk_rails_... (return_delivery_id => deliveries.id)

Defined Under Namespace

Classes: CodeMerger, ReplacementOrderError

Constant Summary collapse

PAYMENT_METHODS =
['Original Payment Method', 'Check'].freeze
DEFAULT_ORDER_REPLACEMENT_TYPE =
'SO'
REFERENCE_NUMBER_PATTERN =
/^RMA\d+$/i
PENDING_ORDERS_STATES =
%w[pending pending_payment in_cr_hold profit_review crm_back_order pending_release_authorization
needs_serial_number_reservation].freeze
NOTIFICATIONS_RMA_STATES =
%w[auto_return_review awaiting_inspection partially_returned returned credit_in_process credited_partially_refunded].freeze
EMAILS_FOR_NOTIFICATIONS =
%w[bwinings@warmlyyours.com ar@warmlyyours.com].freeze
URLS =
{
  'US' => {
    'UPS'       => { url_name: 'www.ups.com/dropoff', url: 'https://www.ups.com/dropoff' },
    'FedEx'     => { url_name: 'www.fedex.com/locate', url: 'https://www.fedex.com/locate' },
    'USPS'      => { url_name: 'tools.usps.com/find-location.htm', url: 'https://tools.usps.com/find-location.htm' }
  },
  'CA' => {
    'UPS'       => { url_name: 'www.ups.com/ca/en/dropoff', url: 'https://www.ups.com/ca/en/dropoff' },
    'FedEx'     => { url_name: 'www.fedex.com/locate', url: 'https://www.fedex.com/locate' },
    'Purolator' => { url_name: 'www.purolator.com/en/shipping-locations', url: 'https://www.purolator.com/en/shipping-locations' },
    'Canpar'    => { url_name: 'www.canpar.com/en/ship/drop-off-locations.jsp', url: 'https://www.canpar.com/en/ship/drop-off-locations.jsp' },
    'Canadapost' => { url_name: 'www.canadapost-postescanada.ca/information/app/fpo/personal/findpostoffice', url: 'https://www.canadapost-postescanada.ca/information/app/fpo/personal/findpostoffice' }
  }
}
RETURN_SHIPPING_METHODS =
{ '1' => 'ground', '2' => 'standard' }.freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::SupportCaseLinkable

#support_case

Methods included from Models::Auditable

#updater

Has one collapse

Has many collapse

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::SupportCaseLinkable

#support_case_ref, #support_case_ref=

Methods included from Models::RmaTransmittable

#rma_available_email_addresses, #rma_available_fax_numbers

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

Returns the value of attribute allow_invoice_link.



164
165
166
# File 'app/models/rma.rb', line 164

def allow_invoice_link
  @allow_invoice_link
end

#company_idObject (readonly)



142
143
# File 'app/models/rma.rb', line 142

validates :company_id, :customer_id, :return_shipping_address_id, :payment_method,
:rma_number, :ship_from_address_id, presence: true

#customer_idObject (readonly)



142
143
# File 'app/models/rma.rb', line 142

validates :company_id, :customer_id, :return_shipping_address_id, :payment_method,
:rma_number, :ship_from_address_id, presence: true

#customer_referenceObject (readonly)



148
149
150
# File 'app/models/rma.rb', line 148

validates :customer_reference, presence: { if: proc { |rma|
  rma.customer.present? && rma.customer.requires_rma_reference?
}, message: 'is required for this customer' }

#original_invoice_idObject (readonly)



146
# File 'app/models/rma.rb', line 146

validates :original_invoice_id, numericality: { allow_nil: true }

#original_order_idObject (readonly)



145
# File 'app/models/rma.rb', line 145

validates :original_order_id, numericality: { allow_nil: true }

#payment_methodObject (readonly)



142
143
# File 'app/models/rma.rb', line 142

validates :company_id, :customer_id, :return_shipping_address_id, :payment_method,
:rma_number, :ship_from_address_id, presence: true

#return_shipping_address_idObject (readonly)



142
143
# File 'app/models/rma.rb', line 142

validates :company_id, :customer_id, :return_shipping_address_id, :payment_method,
:rma_number, :ship_from_address_id, presence: true

#rma_numberObject (readonly)



141
# File 'app/models/rma.rb', line 141

validates :rma_number, uniqueness: true

#ship_from_address_idObject (readonly)



142
143
# File 'app/models/rma.rb', line 142

validates :company_id, :customer_id, :return_shipping_address_id, :payment_method,
:rma_number, :ship_from_address_id, presence: true

#shipping_option_idObject (readonly)



147
# File 'app/models/rma.rb', line 147

validates :shipping_option_id, presence: { if: proc { |rma| rma.should_validate_shipping_option? }, message: 'is required to generate return labels' }

#skip_shipping_option_validationObject

Returns the value of attribute skip_shipping_option_validation.



164
165
166
# File 'app/models/rma.rb', line 164

def skip_shipping_option_validation
  @skip_shipping_option_validation
end

#transmission_emailObject (readonly)



151
# File 'app/models/rma.rb', line 151

validates :transmission_email, email_format: true, allow_nil: true

#transmission_faxObject (readonly)



152
# File 'app/models/rma.rb', line 152

validates :transmission_fax, phone_format: true, allow_nil: true

Class Method Details

.awaiting_returnActiveRecord::Relation<Rma>

A relation of Rmas that are awaiting return. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Rma>)

See Also:



135
# File 'app/models/rma.rb', line 135

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

.contains_tracking_numberActiveRecord::Relation<Rma>

A relation of Rmas that are contains tracking number. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Rma>)

See Also:



136
# File 'app/models/rma.rb', line 136

scope :contains_tracking_number, ->(tracking_number) { joins(:return_shipments).where(Rma[:tracking_numbers].any(tracking_number).or(Shipment[:tracking_number].eq(tracking_number))) }

.create_rma_from_delivery(d) ⇒ Object



937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
# File 'app/models/rma.rb', line 937

def self.create_rma_from_delivery(d)
  return d.precreated_rma if d.precreated_rma.present? && !d.precreated_rma.voided?

  ship_from_address = d.destination_address.is_warehouse? ? (d.order.customer.shipping_address || d.order.customer.billing_address || d.order.customer.addresses.first) : d.destination_address
  shipping_option_id = if d.shipping_option.country == 'US'
                         1
                       elsif d.shipping_option.country == 'CA'
                         4
                       else
                         nil
                       end
  rma = Rma.new(company_id: d.order.company.id,
                customer_id: d.order.customer.id,
                original_order_id: d.order.id,
                ship_from_address_id: ship_from_address.id,
                return_shipping_address_id: d.order.store.warehouse_address_id,
                number_of_return_labels_required: d.shipments.where.not(state: %w[label_voided
                                                                                  manually_voided]).size,
                shipping_option_id: shipping_option_id,
                payment_method: 'Original Payment Method',
                original_po_number: d.order.po_number,
                transmission_email: d.order.transmission_email,
                transmission_fax: d.order.transmission_fax,
                precreate_from_delivery_id: d.id,
                support_case_id: d.order.support_case_ids.first,
                contact_name: (d.order.contact.present? ? d.order.contact.full_name : d.order.customer.full_name),
                description: 'Precreated RMA',
                customer_reference: d.order.customer_reference)
  d.order.line_items.parents_only.non_shipping.each do |li|
    reason_code = li.item.sku == 'SHORTSTOP' ? 'SSR' : 'TBD'

    rma.rma_items << RmaItem.new(returned_item_id: li.item.id,
                                 returned_line_item_id: li.id,
                                 returned_item_quantity: li.quantity,
                                 returned_item_location: 'AVAILABLE',
                                 returned_reason: reason_code,
                                 liable: 'WARMLYYOURS',
                                 under_warranty: true,
                                 replacement_required: false)
  end
  rma.save!
  rma.create_return_delivery
  rma.generate_return_labels if rma.return_label_required?
  rma
end

.in_auto_return_reviewObject



600
601
602
# File 'app/models/rma.rb', line 600

def self.in_auto_return_review
  where(state: 'auto_return_review')
end

.in_awaiting_inspectionObject



604
605
606
# File 'app/models/rma.rb', line 604

def self.in_awaiting_inspection
  where(state: 'awaiting_inspection')
end

.return_labels_select_optionsObject



376
377
378
# File 'app/models/rma.rb', line 376

def self.return_labels_select_options
  [['None', 0], ['1', 1], ['2', 2], ['3', 3], ['4', 4], ['5', 5], ['Auto', '-1']]
end

.returned_and_need_attention_immediatelyObject



608
609
610
611
612
613
614
615
# File 'app/models/rma.rb', line 608

def self.returned_and_need_attention_immediately
  returned = where(state: 'returned').where.not(returned_date: nil).order(returned_date: :desc)
  rmas_list = []
  returned.each do |rma|
    rmas_list << rma if rma.returned_date&.working_days_until(Date.current).to_i > 3
  end
  rmas_list
end

.rma_activeActiveRecord::Relation<Rma>

A relation of Rmas that are rma active. Active Record Scope

Returns:

  • (ActiveRecord::Relation<Rma>)

See Also:



134
# File 'app/models/rma.rb', line 134

scope :rma_active, -> { where(state: %w[awaiting_return requested awaiting_inspection credit_in_process partially_returned returned]) }

.rma_count(company_id = nil, where_conditions = nil, where_not_conditions = nil) ⇒ Object



592
593
594
595
596
597
598
# File 'app/models/rma.rb', line 592

def self.rma_count(company_id = nil, where_conditions = nil, where_not_conditions = nil)
  r = Rma.order('id')
  r = r.where(company_id: company_id) unless company_id.nil?
  r = r.where(where_conditions) unless where_conditions.nil?
  r = r.where.not(where_not_conditions) unless where_not_conditions.nil?
  r.count
end

.states_for_selectObject



832
833
834
# File 'app/models/rma.rb', line 832

def self.states_for_select
  state_machines[:state].states.sort_by(&:human_name).map { |s| [s.value.titleize, s.value] }
end

.transmission_state_for_selectObject



836
837
838
# File 'app/models/rma.rb', line 836

def self.transmission_state_for_select
  %w[awaiting_transmission in_transmission_queue transmitted].map { |e| [e.titleize, e] }
end

.with_credit_memos_not_transmitted_and_are_not_fullyoffsetObject



617
618
619
620
621
622
623
624
625
# File 'app/models/rma.rb', line 617

def self.with_credit_memos_not_transmitted_and_are_not_fullyoffset
  with_credit_memos_not_transmitted = where(id: CreditMemo.where(state: %w[printed processing_refund
                                                                           partially_offset]).where(transmission_state: 'awaiting_transmission').map(&:rma_id).uniq.compact).where.not(returned_date: nil).order(returned_date: :desc)
  rmas_list = []
  with_credit_memos_not_transmitted.each do |rma|
    rmas_list << rma if rma.returned_date&.working_days_until(Date.current).to_i > 3
  end
  rmas_list
end

.with_credit_memos_transmitted_and_are_not_fullyoffsetObject



627
628
629
630
631
632
633
634
# File 'app/models/rma.rb', line 627

def self.with_credit_memos_transmitted_and_are_not_fullyoffset
  with_credit_memos_transmitted = where(id: CreditMemo.where(state: %w[printed processing_refund partially_offset]).where(transmission_state: 'transmitted').map(&:rma_id).uniq.compact).where.not(returned_date: nil).order(returned_date: :desc)
  rmas_list = []
  with_credit_memos_transmitted.each do |rma|
    rmas_list << rma if rma.returned_date && (Date.current - rma.returned_date).to_i > 90
  end
  rmas_list
end

Instance Method Details

#active_rma_itemsObject



1014
1015
1016
# File 'app/models/rma.rb', line 1014

def active_rma_items
  rma_items.active.reload
end

#activitiesActiveRecord::Relation<Activity>

Returns:

See Also:



121
# File 'app/models/rma.rb', line 121

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

#all_credit_memos_fully_offset?Boolean

Returns:

  • (Boolean)


703
704
705
# File 'app/models/rma.rb', line 703

def all_credit_memos_fully_offset?
  credit_memos.present? && credit_memos.all?(&:fully_offset?)
end

#all_credit_memos_partially_refunded?Boolean

Returns:

  • (Boolean)


699
700
701
# File 'app/models/rma.rb', line 699

def all_credit_memos_partially_refunded?
  credit_memos.present? && (credit_memos.all?(&:partially_offset?) || credit_memos.any?(&:partially_offset?))
end

#all_credit_orders_awaiting_return?Boolean

Returns:

  • (Boolean)


848
849
850
# File 'app/models/rma.rb', line 848

def all_credit_orders_awaiting_return?
  credit_orders.all? { |co| co.awaiting_return? || co.cancelled? }
end

#all_items_requested?Boolean

Returns:

  • (Boolean)


999
1000
1001
# File 'app/models/rma.rb', line 999

def all_items_requested?
  rma_items.reload.active.present? and rma_items.reload.active.all?(&:requested?)
end

#all_items_requested_and_ready_to_return?Boolean

Returns:

  • (Boolean)


852
853
854
855
856
857
# File 'app/models/rma.rb', line 852

def all_items_requested_and_ready_to_return?
  all_items_requested? && return_labels_correctly_generated?
  # any_items_requested? && credit_orders.all? do |co|
  #   co.awaiting_return? || co.cancelled?
  # end && return_labels_correctly_generated?
end

#all_items_returned?Boolean

Returns:

  • (Boolean)


991
992
993
# File 'app/models/rma.rb', line 991

def all_items_returned?
  rma_items.reload.active.all? { |rma_item| rma_item.returned? || rma_item.voided? }
end

#all_items_voided?Boolean

Returns:

  • (Boolean)


726
727
728
# File 'app/models/rma.rb', line 726

def all_items_voided?
  rma_items.reload.all?(&:voided?)
end

#all_uploadsObject



585
586
587
588
589
590
# File 'app/models/rma.rb', line 585

def all_uploads
  all_uploads = []
  all_uploads += uploads
  all_uploads += return_delivery&.uploads || []
  all_uploads.uniq
end

#any_credit_memo_printed?Boolean

Returns:

  • (Boolean)


695
696
697
# File 'app/models/rma.rb', line 695

def any_credit_memo_printed?
  credit_memos.present? && credit_memos.all?(&:printed?)
end

#any_items_awaiting_inspection?Boolean

Returns:

  • (Boolean)


730
731
732
# File 'app/models/rma.rb', line 730

def any_items_awaiting_inspection?
  rma_items.reload.any?(&:awaiting_inspection?)
end

#any_items_requested?Boolean

Returns:

  • (Boolean)


995
996
997
# File 'app/models/rma.rb', line 995

def any_items_requested?
  rma_items.reload.active.where(state: 'requested').any?
end

#arrival_datetimeObject



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

def arrival_datetime
  rma_items.active.maximum(:arrival_datetime)
end

#auto_return_rma_itemsObject



983
984
985
# File 'app/models/rma.rb', line 983

def auto_return_rma_items
  rma_items.where(will_not_be_returned: true, state: 'requested').find_each(&:auto_return)
end

#build_activityObject



676
677
678
# File 'app/models/rma.rb', line 676

def build_activity
  activities.new(resource: self, party: customer)
end

#can_be_edited?Boolean

Returns:

  • (Boolean)


662
663
664
# File 'app/models/rma.rb', line 662

def can_be_edited?
  new_record? || requested? || auto_return_review? || awaiting_return? || awaiting_inspection? || partially_returned?
end

#can_be_received?Boolean

Returns:

  • (Boolean)


642
643
644
# File 'app/models/rma.rb', line 642

def can_be_received?
  requested? || awaiting_return? || awaiting_inspection? || partially_returned?
end

#can_be_transmitted?Boolean

Returns:

  • (Boolean)


646
647
648
# File 'app/models/rma.rb', line 646

def can_be_transmitted?
  awaiting_return? || credit_in_process? || credited_partially_refunded? || credited_fully_refunded?
end

#can_be_unreturned?Boolean

Returns:

  • (Boolean)


658
659
660
# File 'app/models/rma.rb', line 658

def can_be_unreturned?
  returned?
end

#can_be_unvoided?Boolean

Returns:

  • (Boolean)


654
655
656
# File 'app/models/rma.rb', line 654

def can_be_unvoided?
  voided?
end

#can_be_voided?Boolean

Returns:

  • (Boolean)


650
651
652
# File 'app/models/rma.rb', line 650

def can_be_voided?
  requested? || auto_return_review? || (awaiting_return? && no_items_returned?)
end

#can_edit_return_labels?Boolean

Returns:

  • (Boolean)


666
667
668
669
670
# File 'app/models/rma.rb', line 666

def can_edit_return_labels?
  return false if ship_from_address.is_warehouse?

  requested? || auto_return_review? || awaiting_return?
end

#communicationsActiveRecord::Relation<Communication>

Returns:

See Also:



122
# File 'app/models/rma.rb', line 122

has_many :communications, as: :resource, dependent: :nullify

#companyCompany

Returns:

See Also:



102
# File 'app/models/rma.rb', line 102

belongs_to :company, optional: true

#create_credit_order(rma_item_ids) ⇒ Object



1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
# File 'app/models/rma.rb', line 1449

def create_credit_order(rma_item_ids)
  # take only lines that are returned and which don't have a credit order already
  rma_items_list = rma_items.where(id: rma_item_ids.map { |ri| ri[:rma_item_id] })
  items = rma_items_list.reject do |ri|
    ri.marked_for_destruction? || ri.voided? || ri.credit_order_line_item
  end
  return if items.blank?

  order = Order.new(order_type: Order::CREDIT_ORDER)
  transaction do
    order.customer = customer
    order.customer_reference = customer_reference
    if ship_from_address.present?
      order.shipping_address_id = ship_from_address_id
    else
      # Address was deleted - fall back to customer's current address
      fallback_address = customer.shipping_address || customer.billing_address || customer.addresses.first
      raise "Cannot create credit order: no valid shipping address available for RMA #{rma_number}" if fallback_address.nil?

      order.shipping_address_id = fallback_address.id
      Rails.logger.warn("RMA #{rma_number}: ship_from_address_id #{ship_from_address_id} not found, using fallback address #{fallback_address.id}")
    end
    order.shipped_date = original_invoice.try(:shipped_date) || original_invoice.try(:order).try(:shipped_date)
    order.currency = original_invoice.try(:currency) || customer.catalog.currency
    order.disable_auto_coupon = true
    order.tax_exempt = original_invoice.try(:tax_exempt).to_b
    order.rma_id = id
    order.tax_date = if rma_items.active.any?(&:replacement_required?)
                       # if there is a replacement order, then we use current tax rates so they match up
                       Date.current
                     else
                       original_invoice.try(:shipped_date) || original_invoice.try(:order).try(:shipped_date) || Date.current
                     end

    from_ci_invoice = original_invoice&.invoice_type == Invoice::CI

    items.each do |rma_item|
      credit_percentage = rma_item.credit_percentage.to_f / 100
      if rma_item.returned_line_item.present? && (rma_item.returned_line_item.item == rma_item.returned_item)
        # it's linked to an existing line item, so get pricing from that
        if rma_item.returned_line_item.parent_id.present?
          price = discounted_price = rma_item.returned_line_item.catalog_item.amount
        else
          price = rma_item.returned_line_item.price
          discounted_price = rma_item.returned_line_item.discounted_price
        end

        catalog_item = rma_item.returned_line_item.catalog_item
        if catalog_item.nil?
          catalog_item = rma_item.rma.customer.catalog.catalog_items.includes(:store_item).where(store_items: { item_id: rma_item.returned_item_id }).first
          raise "Can't find matching CatalogItem" if catalog_item.nil?
        end
        qty_to_receive = rma_item_ids.map { |r| r[:qty_to_receive] if r[:rma_item_id] == rma_item.id }.compact.first
        li_attrs = {
          catalog_item_id: catalog_item.id,
          quantity: -qty_to_receive,
          price: price * credit_percentage,
          discounted_price: discounted_price * credit_percentage,
          credit_rma_item: rma_item
        }
        if from_ci_invoice && rma_item.returned_line_item.taxable_amount.present?
          li_attrs[:taxable_amount] = rma_item.returned_line_item.taxable_amount * credit_percentage
        end
        li = LineItem.new(li_attrs)
        li.do_not_calculate_tax = true if from_ci_invoice
        order.line_items << li
      else
        # it's not linked to an existing line item, so need to get pricing from current item pricing
        catalog_item = customer.catalog.catalog_items.includes(:store_item).where(store_items: { item_id: rma_item.returned_item_id }).first
        raise "Can't find matching CatalogItem" if catalog_item.nil?

        qty_to_receive = rma_item_ids.map { |r| r[:qty_to_receive] if r[:rma_item_id] == rma_item.id }.compact.first
        li_attrs = {
          catalog_item_id: catalog_item.id,
          quantity: -qty_to_receive,
          price: catalog_item.amount * credit_percentage,
          discounted_price: catalog_item.amount * credit_percentage,
          credit_rma_item: rma_item
        }
        li = LineItem.new(li_attrs)
        li.do_not_calculate_tax = true if from_ci_invoice
        order.line_items << li
      end
    end
    order.recalculate_shipping = false
    order.save!

    # I'm not entirely sure about this one but i don't like how it works, can't this be figured out in the iteration above?
    order.line_items.non_shipping.parents_only.joins(credit_rma_item: :returned_line_item).includes(credit_rma_item: :returned_line_item).find_each do |li|
      credit_percentage = li.credit_rma_item.credit_percentage.to_f / 100
      # copy the original discounts over
      li.credit_rma_item.returned_line_item.line_discounts.each do |ld|
        li.line_discounts.create(coupon_id: ld.coupon_id, discount_id: ld.discount_id,
                                 amount: (ld.amount / li.credit_rma_item.returned_line_item.quantity) * li.credit_rma_item.returned_item_quantity * credit_percentage * -1)
      end
    end

    effective_dates = {}
    original_invoice&.discounts&.each { |d| effective_dates[d.coupon_id] = d.effective_date }
    order.line_items.non_shipping.collect(&:line_discounts).flatten.group_by(&:coupon_id).each do |coupon_id, line_discounts|
      discount = order.discounts.create(coupon_id: coupon_id,
                                        amount: line_discounts.sum(&:amount),
                                        user_amount: line_discounts.sum(&:amount),
                                        effective_date: effective_dates[coupon_id] || Date.current)
      line_discounts.each { |ld| ld.update_attribute(:discount_id, discount.id) }
    end

    if original_invoice&.discounts&.any? && order.discounts.empty?
      # the original order has discounts but we couldn't work out the amounts as it was too old
      # so we just flag the credit order as needing an adjustment
      order.discount_adjustment_needed = true
    end

    unless order.valid?
      # this adds the errors to the RMA so they will show up on the RMA form
      errors.add :base, "Credit Order not valid. Exc: #{order.errors.full_messages}"
    end

    # adding this so that Itemizable.set_totals will be called, which will call
    # Itemizable.calculate_discounts, which will correctly set the discounted prices
    order.force_total_reset = true
    order.save!

    if from_ci_invoice
      inherit_ci_invoice_line_values(order)
    elsif !has_replacement_items? && original_invoice.present?
      # When the original invoice is present and there are no replacement items, the
      # tax rate was already inherited from the invoice in set_initial_tax_rate.
      # Re-fetching via refresh_tax_rate would overwrite it with current TaxJar rates,
      # which may differ from the original transaction. Only apply the inherited rate
      # to line items without re-querying the tax service.
      order.reload
      order.apply_tax_rate_to_line_items
    else
      order.refresh_tax_rate
    end
    order.returned!
  end
  order
end

#create_documentsObject



1026
1027
1028
1029
1030
# File 'app/models/rma.rb', line 1026

def create_documents
  # return unless rma_items_changed?
  # create_credit_order
  create_replacement_order
end

#create_replacement_orderObject



1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
# File 'app/models/rma.rb', line 1359

def create_replacement_order
  return unless can_be_edited?

  # take only lines that are non voided and which don't have a replacement order already

  items = rma_items.select { |ri| ri.replacement_required? && ri.replacement_order.nil? }

  return if items.empty?

  res = {}
  transaction do
    order_type = replacement_order_type || items.first.rma_reason_code&.replacement_order_type || Order::SALES_ORDER
    order = Order.new(order_type: order_type, creator: creator)
    order.customer = customer
    order.customer_reference = customer_reference
    if original_invoice.present?
      order.opportunity_id = original_invoice.order.opportunity_id if original_invoice.order.present?
      order.shipping_address_id = original_invoice.shipping_address_id
      order.currency = original_invoice.currency
    else
      order.shipping_address = customer.shipping_address
      order.currency = company.currency
    end
    order.disable_auto_coupon = true
    order.rma_id = id

    # If this RMA is linked to a support case, auto-populate the tracking email
    # with the primary participant's email so they receive shipment notifications.
    # This is important for warranty replacements being shipped directly to end consumers.
    if support_case.present?
      participant_email = support_case.primary_party&.email
      order.tracking_email = [participant_email].compact if participant_email.present?
    end

    catalog = customer.catalog
    items.each do |rma_item|
      cat_item = CatalogItem.includes(:store_item).where(catalog_id: catalog.id,
                                                         store_items: { item_id: rma_item.returned_item_id }).first
      raise ReplacementOrderError, "Unable to find replacement item in customer's catalog" if cat_item.nil?

      order.line_items << LineItem.new(catalog_item_id: cat_item.id,
                                       quantity: rma_item.returned_item_quantity,
                                       price: cat_item.amount,
                                       discounted_price: cat_item.amount,
                                       replacement_rma_item_id: rma_item.id)
    end

    errors.add :base, "Replacement Order not valid. Exc: #{order.errors.full_messages}" unless order.valid?
    order.do_not_set_totals = true
    order.save!
    order.reload

    order.retrieve_shipping_costs
    order.calculate_discounts
    order.save!
    order.reload

    # add the price match coupon
    coupon = Coupon.find_by(code: 'OPM')
    discount = Discount.new(itemizable: order, coupon_id: coupon.id, effective_date: Date.current)
    order.line_items.each do |li|
      unless (original_line = li.try(:replacement_rma_item).try(:returned_line_item)) && (li.discounted_price > original_line.discounted_price)
        next
      end

      # add a price match coupon to match the price on the original order
      coupon_amount = (li.discounted_price - original_line.discounted_price) * li.quantity
      discount.line_discounts.build(coupon_id: coupon.id, amount: -coupon_amount,
                                    line_item_id: li.id)
    end

    if discount.line_discounts.any?
      discount.amount = discount.user_amount = discount.line_discounts.to_a.sum(&:amount)
      discount.save!
    end

    items.each do |rma_item|
      rma_item.update(replacement_order_id: order.id)
    end

    order.reload.save

    # If the rma is tied to a support case, also link that order to the support case
    support_case.orders << order if support_case

    res[:order] = order
  end
  OpenStruct.new(res).freeze
end

#create_return_deliveryObject



1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
# File 'app/models/rma.rb', line 1032

def create_return_delivery
  return unless return_label_required?

  d = return_delivery
  if d.present?
    update_column(:return_delivery_id, nil)
    d.destroy
  end
  self.return_delivery = Delivery.create(
    rma_for_return: self,
    origin_address_id: ship_from_address_id,
    destination_address_id: customer.store.warehouse_address_id,
    bill_shipping_to_customer: customer.bill_shipping_to_customer,
    shipping_option_id: shipping_option_id,
    state: 'quoting' # here we force state quoting, overrides pre_pack
  )

  items = rma_items.reject { |ri| ri.marked_for_destruction? || ri.voided? }
  items.each do |rma_item|
    credit_percentage = rma_item.credit_percentage.to_f / 100
    if rma_item.returned_line_item.present? && (rma_item.returned_line_item.item == rma_item.returned_item)
      # it's linked to an existing line item, so get pricing from that
      if rma_item.returned_line_item.parent_id.present?
        price = discounted_price = rma_item.returned_line_item.catalog_item.amount
      else
        price = rma_item.returned_line_item.price
        discounted_price = rma_item.returned_line_item.discounted_price
      end
      catalog_item = rma_item.returned_line_item.catalog_item
      catalog_item ||= customer.catalog.catalog_items.by_skus(rma_item.returned_item.sku).first
      raise "Can't find matching CatalogItem" if catalog_item.nil?

      li = LineItem.new(catalog_item_id: catalog_item.id,
                        quantity: -rma_item.returned_item_quantity,
                        price: price * credit_percentage,
                        discounted_price: discounted_price * credit_percentage)
    else
      # it's not linked to an existing line item, so need to get pricing from current item pricing
      catalog_item = customer.catalog.catalog_items.includes(:store_item).where(store_items: { item_id: rma_item.returned_item_id }).first
      raise "Can't find matching CatalogItem" if catalog_item.nil?

      li = LineItem.new(catalog_item_id: catalog_item.id,
                        quantity: -rma_item.returned_item_quantity,
                        price: catalog_item.amount * credit_percentage,
                        discounted_price: catalog_item.amount * credit_percentage)
    end
    return_delivery.line_items << li
  end

  # Use packaging for shipping calculation but create shipments based on user's label requirement
  if original_order&.precreate_rma? && precreate_from_delivery_id == return_delivery.id
    # we are precreating this RMA, it is not a new manual RMA flow
    # grab the shipment info from the original order delivery which should now be properly packed
    return_delivery.create_shipments_from_equivalent_delivery(original_order.deliveries.first,
                                                              'awaiting_label')
  else
    # Use packaging algorithm for shipping cost calculation only
    packaging_result = return_delivery.search_deliveries_for_equivalent_packaging
    Rails.logger.info "RMA #{rma_number}: Packaging algorithm created #{return_delivery.shipments.count} shipments"
  end

  return_delivery.retrieve_shipping_costs

  # Handle user's label requirement vs packaging recommendation
  if number_of_return_labels_required == -1
    # Auto: Use original order's shipment structure if available, otherwise use packaging algorithm
    # IMPORTANT: Skip original shipment structure if it was LTL freight, as RMAs can only use package carriers
    original_delivery_used_ltl = original_order&.deliveries&.first&.ships_ltl_freight?

    if original_order&.shipments&.completed&.any? && !original_delivery_used_ltl
      # Use original order's completed shipments as the template
      original_shipments = original_order.shipments.completed.includes(:shipment_contents)

      # Map RMA line items to original order line items for content matching
      rma_line_item_map = {}
      return_delivery.line_items.non_shipping.each do |rma_line_item|
        # Find corresponding original line item by item_id
        original_line_item = original_order.line_items.find { |oli| oli.item_id == rma_line_item.item_id }
        rma_line_item_map[original_line_item.id] = rma_line_item if original_line_item
      end

      # FILTER: Only include original shipments that contain items being returned
      original_shipments_with_rma_items = original_shipments.select do |original_shipment|
        original_shipment.shipment_contents.any? do |content|
          rma_line_item_map.key?(content.line_item_id)
        end
      end

      target_shipment_count = original_shipments_with_rma_items.count
      current_shipment_count = return_delivery.shipments.count

      Rails.logger.info "RMA #{rma_number}: Auto selected - using #{target_shipment_count} of #{original_shipments.count} original shipments (filtered by RMA items)"

      if target_shipment_count.zero?
        # Safety: If no original shipments matched (shouldn't happen), fall back to packaging algorithm
        Rails.logger.warn "RMA #{rma_number}: No original shipments contain RMA items, falling back to packaging algorithm"
        # Keep the shipments created by packaging algorithm
      elsif target_shipment_count != current_shipment_count
        # Clear existing shipments and recreate based on original order
        return_delivery.shipments.destroy_all

        created_shipments = []
        original_shipments_with_rma_items.each_with_index do |original_shipment, shipment_index|
          # Create shipment matching original dimensions
          new_shipment = return_delivery.shipments.create!(
            weight: original_shipment.weight,
            length: original_shipment.length,
            width: original_shipment.width,
            height: original_shipment.height,
            state: 'suggested',
            container_type: original_shipment.container_type || 'carton'
          )
          created_shipments << new_shipment

          # Replicate the original shipment's content distribution
          original_shipment.shipment_contents.each do |original_content|
            corresponding_rma_line_item = rma_line_item_map[original_content.line_item_id]

            next unless corresponding_rma_line_item

            # Create shipment content for this RMA line item in the same shipment structure
            new_shipment.shipment_contents.create!(
              line_item: corresponding_rma_line_item,
              quantity: [original_content.quantity, corresponding_rma_line_item.quantity.abs].min
            )
            Rails.logger.info "RMA #{rma_number}: Added #{corresponding_rma_line_item.sku} to shipment #{shipment_index + 1} (matching original)"
          end
        end

        # Handle any RMA line items that don't have a corresponding original (shouldn't happen, but safety net)
        unassigned_line_items = return_delivery.line_items.non_shipping.reject do |rma_line_item|
          created_shipments.any? { |s| s.shipment_contents.exists?(line_item: rma_line_item) }
        end

        if unassigned_line_items.any?
          Rails.logger.warn "RMA #{rma_number}: #{unassigned_line_items.count} items couldn't be matched to original shipments, distributing evenly"
          distribute_line_items_across_shipments(return_delivery, created_shipments, unassigned_line_items)
        end
      end
    else
      # Fallback to packaging algorithm recommendation
      # This handles: no original shipments, LTL freight orders, or when packaging is more appropriate
      shipment_count = return_delivery.shipments.count

      if original_delivery_used_ltl
        Rails.logger.info "RMA #{rma_number}: Auto selected - original order used LTL freight, using packaging algorithm for RMA package shipping (#{shipment_count} shipments)"
      else
        Rails.logger.info "RMA #{rma_number}: Auto selected - no original order shipments, using packaging recommendation of #{shipment_count} shipments"
      end

      # If packaging algorithm didn't create any shipments, create a default one
      if shipment_count == 0
        Rails.logger.warn "RMA #{rma_number}: No shipments created by packaging, creating default shipment"
        return_delivery.shipments.create!(
          weight: return_delivery.ship_weight,
          length: 12, width: 12, height: 6,
          state: 'suggested',
          container_type: 'carton'
        )
        # Distribute line items to the default shipment
        distribute_line_items_across_shipments(return_delivery, return_delivery.shipments)
      end
    end
  elsif number_of_return_labels_required.positive?
    # User specified count: Override packaging recommendation
    target_shipment_count = number_of_return_labels_required
    current_shipment_count = return_delivery.shipments.count

    if target_shipment_count != current_shipment_count
      Rails.logger.info "RMA #{rma_number}: Overriding packaging recommendation (#{current_shipment_count}) with user requirement (#{target_shipment_count})"

      # Keep the largest shipment from packaging as a template and clear existing shipments
      template_shipment = return_delivery.shipments.max_by(&:volume) if return_delivery.shipments.any?
      return_delivery.shipments.destroy_all

      # Create shipments based on user's requirement, not packaging algorithm
      created_shipments = []
      target_shipment_count.times do
        shipment = if template_shipment
                     # Use packaging algorithm's optimal box dimensions
                     return_delivery.shipments.create!(
                       weight: template_shipment.weight,
                       length: template_shipment.length,
                       width: template_shipment.width,
                       height: template_shipment.height,
                       state: 'suggested',
                       container_type: template_shipment.container_type
                     )
                   else
                     # Fallback to default dimensions if no template available
                     return_delivery.shipments.create!(
                       weight: return_delivery.ship_weight / target_shipment_count,
                       length: 12, width: 12, height: 6,
                       state: 'suggested',
                       container_type: 'carton'
                     )
                   end
        created_shipments << shipment
      end

      # Distribute line items across shipments to avoid duplication in package details
      distribute_line_items_across_shipments(return_delivery, created_shipments)
    end
  end
  # If number_of_return_labels_required is 0 or nil, no return labels are required
  return_delivery.return_labels_ready
end

#creatorEmployee

Returns:

See Also:



113
# File 'app/models/rma.rb', line 113

belongs_to :creator, class_name: 'Employee', optional: true

#credit_availableObject



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

def credit_available
  credit = credit_total - payments.all_authorized.sum(:amount)
  credit.negative? ? 0 : credit.round(2)
end

#credit_available_before_taxObject



787
788
789
790
# File 'app/models/rma.rb', line 787

def credit_available_before_tax
  credit = credit_total_before_tax - payments.all_authorized.sum(:amount)
  credit.negative? ? 0 : credit.round(2)
end

#credit_deliveriesActiveRecord::Relation<Delivery>

Returns:

See Also:



130
# File 'app/models/rma.rb', line 130

has_many :credit_deliveries, through: :credit_orders, source: :deliveries, class_name: 'Delivery'

#credit_memosActiveRecord::Relation<CreditMemo>

Returns:

See Also:



118
# File 'app/models/rma.rb', line 118

has_many :credit_memos

#credit_order_line_itemsActiveRecord::Relation<CreditOrderLineItem>

Returns:

  • (ActiveRecord::Relation<CreditOrderLineItem>)

See Also:



126
# File 'app/models/rma.rb', line 126

has_many :credit_order_line_items, through: :rma_items

#credit_order_shipmentsObject



734
735
736
# File 'app/models/rma.rb', line 734

def credit_order_shipments
  credit_deliveries.map { |d| d.shipments.where.not(tracking_number: nil) }.flatten
end

#credit_ordersActiveRecord::Relation<Order>

Returns:

  • (ActiveRecord::Relation<Order>)

See Also:



127
128
129
# File 'app/models/rma.rb', line 127

has_many :credit_orders, -> {
  distinct
}, through: :credit_order_line_items, source: :resource, source_type: 'Order', class_name: 'Order'

#credit_totalObject



738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
# File 'app/models/rma.rb', line 738

def credit_total
  BigDecimal(0)
  total_credit_orders = BigDecimal(0)
  total_credit_orders_line_items = BigDecimal(0)
  total_rma_items_requested = BigDecimal(0)

  credit_orders.each { |o| total_credit_orders += o.total }
  credit_orders.each do |co|
    co.line_items.non_shipping.parents_only.each { |li| total_credit_orders_line_items += (li.discounted_total + li.tax_total) unless li.credit_rma_item.is_customer_fault? }
  end
  rma_items.each do |ri|
    next unless !ri.is_customer_fault? and ri.returned_line_item.present?

    li = ri.returned_line_item
    # Calculate proportional amounts for partial returns
    proportion = BigDecimal(ri.returned_item_quantity) / BigDecimal(li.quantity)
    total_rma_items_requested += (li.discounted_total * proportion) + (li.tax_total * proportion)
  end
  total = total_rma_items_requested + total_credit_orders_line_items + total_credit_orders
  total.negative? ? -total : total
end

#credit_total_before_taxObject



760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
# File 'app/models/rma.rb', line 760

def credit_total_before_tax
  BigDecimal(0)
  total_credit_orders = BigDecimal(0)
  total_credit_orders_line_items = BigDecimal(0)
  total_rma_items_requested = BigDecimal(0)

  credit_orders.each { |o| total_credit_orders += o.line_total }
  credit_orders.each do |co|
    co.line_items.non_shipping.parents_only.each { |li| total_credit_orders_line_items += li.discounted_total unless li.credit_rma_item.is_customer_fault? }
  end
  rma_items.each do |ri|
    next unless !ri.is_customer_fault? and ri.returned_line_item.present?

    li = ri.returned_line_item
    # Calculate proportional amount for partial returns
    proportion = BigDecimal(ri.returned_item_quantity) / BigDecimal(li.quantity)
    total_rma_items_requested += li.discounted_total * proportion
  end
  total = total_rma_items_requested + total_credit_orders_line_items + total_credit_orders
  total.negative? ? -total : total
end


828
829
830
# File 'app/models/rma.rb', line 828

def crm_link
  UrlHelper.instance.rma_path(self)
end

#customerCustomer

Returns:

See Also:



105
# File 'app/models/rma.rb', line 105

belongs_to :customer, optional: true

#customer_nameObject



714
715
716
# File 'app/models/rma.rb', line 714

def customer_name
  customer.try(:full_name)
end

#earliest_return_label_ship_dateObject

Earliest +date_shipped+ on return shipments (matches return labels tab / audited shipment table).



495
496
497
# File 'app/models/rma.rb', line 495

def earliest_return_label_ship_date
  return_shipments.minimum(:date_shipped)
end

#effective_return_deliveryObject



863
864
865
# File 'app/models/rma.rb', line 863

def effective_return_delivery
  return_delivery || legacy_return_shipments.last&.delivery
end

#exclude_manually_initiated_event?(event) ⇒ Boolean

Returns:

  • (Boolean)


581
582
583
# File 'app/models/rma.rb', line 581

def exclude_manually_initiated_event?(event)
  %i[returned returned_or_voided credit_in_process void transmit].include?(event.to_sym)
end

#external_return_labels?Boolean

Returns:

  • (Boolean)


687
688
689
# File 'app/models/rma.rb', line 687

def external_return_labels?
  number_of_return_labels_required.to_i.zero?
end

#full_invoice_returned?Boolean

Returns:

  • (Boolean)


707
708
709
710
711
712
# File 'app/models/rma.rb', line 707

def full_invoice_returned?
  return false unless credit_orders.present?
  return true unless original_invoice.present?

  (original_invoice.line_total.abs == credit_orders.first.line_total.abs) && (original_invoice.line_items.non_shipping.parents_only.sum(&:quantity).abs == credit_orders.first.line_items.non_shipping.parents_only.sum(&:quantity).abs)
end

#generate_and_send_return_email_to_customerObject



801
802
803
804
805
806
# File 'app/models/rma.rb', line 801

def generate_and_send_return_email_to_customer
  return unless rma_items.active.will_be_returned.any?

  # Create draft communication - instructions will be generated and attached by controller
  send_return_email_to_customer(keep_as_draft: true)
end

#generate_return_instructions_pdfObject



1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
# File 'app/models/rma.rb', line 1289

def generate_return_instructions_pdf
  uploads.in_category('rma_return_instructions_pdf').destroy_all
  pdf_result = Pdf::Document::ReturnInstructions.call(self, output_to_file: true)
  upload = Upload.uploadify(pdf_result.pdf_file_path,
                            'rma_return_instructions_pdf',
                            self,
                            pdf_result.file_name)
  uploads << upload

  # Update draft communications with new return instructions
  update_draft_communications_with_new_attachments

  upload
end

#generate_return_labelsObject



1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
# File 'app/models/rma.rb', line 1304

def generate_return_labels
  status = :error
  errs = []
  if return_delivery&.valid_for_generating_ship_labels?
    begin
      return_delivery.generate_labels
    rescue StandardError => e
      ErrorReporting.error(e,
                    "Could not generate return labels, RMA ID: #{id}, RMA number: #{rma_number}, return_delivery ID: #{return_delivery.id}")
      status = :error
      errs << e.to_s
    end
    status = :ok
    unless return_labels_correctly_generated?
      status = :error
      errs += return_label_issues
    end
  end

  # Update draft communications with new labels if generation was successful
  if status == :ok
    awaiting_return
    update_draft_communications_with_new_attachments
  end

  { status: status, errors: errs }
end

#handle_return_shipment_tracking_state_updated(return_shipment) ⇒ Object



506
507
508
509
510
511
512
513
# File 'app/models/rma.rb', line 506

def handle_return_shipment_tracking_state_updated(return_shipment)
  # method is called when return_shipment tracking_state transitions to one of:
  # state :in_transit
  # state :delivered
  # state :exception
  # state :delivery_attempt
  # state :delivered_to_collection_location
end

#has_auto_return_items?Boolean

Returns:

  • (Boolean)


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

def has_auto_return_items?
  rma_items.where(will_not_be_returned: true, state: 'requested').any?
end

#has_different_replacement_items?Boolean

Returns:

  • (Boolean)


1010
1011
1012
# File 'app/models/rma.rb', line 1010

def has_different_replacement_items?
  !has_same_replacement_items?
end

#has_no_active_items?Boolean

+true+ when there are no non-voided lines (+RmaItem.active+ / +where.not(state: 'voided')+).

Returns:

  • (Boolean)


542
543
544
# File 'app/models/rma.rb', line 542

def has_no_active_items?
  rma_items.active.none?
end

#has_replacement_items?Boolean

Returns:

  • (Boolean)


883
884
885
# File 'app/models/rma.rb', line 883

def has_replacement_items?
  rma_items.reload.active.any?(&:replacement_required?)
end

#has_rma_items_requested?Boolean

Returns:

  • (Boolean)


537
538
539
# File 'app/models/rma.rb', line 537

def has_rma_items_requested?
  rma_items.where(state: 'requested').any?
end

#has_same_replacement_items?Boolean

Returns:

  • (Boolean)


1003
1004
1005
1006
1007
1008
# File 'app/models/rma.rb', line 1003

def has_same_replacement_items?
  # check that all replacement items are linked to the rma_item and match quantity
  rma_items.replacement_required.all? do |ri|
    ri.replacement_order_line_item.present? && ri.returned_item_quantity == ri.replacement_order_line_item.quantity
  end
end

#inherit_ci_invoice_line_values(order) ⇒ Object

For CI invoices (e.g. Amazon Seller Central), the marketplace sets the
per-unit price, promotional discount, and tax on the original invoice
line. Inherit those exactly on the credit order instead of recalculating
via current catalog pricing or tax rates:

  • create_credit_order seeds the correct values, but the Order autosave
    chain (reset_discount_on_auto_coupon_toggle → reset_discount)
    rewrites price to catalog_item.amount, and calculate_discounts
    then resets discounted_price to the same.
  • Tax on a credit must also carry the sign of the negative credit
    quantity so grand totals and taxes_grouped_by_type are negative.


1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
# File 'app/models/rma.rb', line 1600

def inherit_ci_invoice_line_values(order)
  order.reload
  order.line_items.non_shipping.includes(credit_rma_item: :returned_line_item).each do |li|
    source_line = li.credit_rma_item&.returned_line_item
    next unless source_line

    credit_pct = li.credit_rma_item.credit_percentage.to_f / 100
    source_qty = source_line.quantity.to_d
    per_unit_tax = source_qty.zero? ? 0 : source_line.tax_total.to_d / source_qty
    inherited_tax = (per_unit_tax * li.quantity.to_d * credit_pct).round(2)
    li.update_columns(
      price: (source_line.price.to_d * credit_pct).round(2),
      discounted_price: (source_line.discounted_price.to_d * credit_pct).round(2),
      tax_total: inherited_tax
    )
  end
end

#instructions_file_name(with_extension = true) ⇒ Object



875
876
877
# File 'app/models/rma.rb', line 875

def instructions_file_name(with_extension = true)
  "#{rma_number}_return_instructions#{'.pdf' if with_extension}"
end

#invoiceInvoice

Returns:

See Also:



115
# File 'app/models/rma.rb', line 115

has_one  :invoice, class_name: 'Invoice', foreign_key: :id, primary_key: :original_invoice_id

#invoice_add_blocked_for_kit_component_line?(component_line_item) ⇒ Boolean

add-items UI: true if this kit component line cannot be added (the parent kit line is already on the RMA).

Returns:

  • (Boolean)


1744
1745
1746
1747
1748
1749
# File 'app/models/rma.rb', line 1744

def invoice_add_blocked_for_kit_component_line?(component_line_item)
  pid = component_line_item.parent_id
  return false if pid.blank?

  selected_returned_line_item_ids_for_invoice_kit_rules.include?(pid)
end

#invoice_add_blocked_for_kit_parent_line?(parent_line_item) ⇒ Boolean

add-items UI: true if this parent kit invoice line cannot be added (a component line is already on the RMA).
Callers should preload +parent_line_item.children+ (e.g. +includes(children: :item)+) so child ids are read from memory.

Returns:

  • (Boolean)


1734
1735
1736
1737
1738
1739
1740
1741
# File 'app/models/rma.rb', line 1734

def invoice_add_blocked_for_kit_parent_line?(parent_line_item)
  children = parent_line_item.children
  return false if children.blank?

  child_ids = children.loaded? ? children.map(&:id) : children.ids
  selected_line_ids = selected_returned_line_item_ids_for_invoice_kit_rules
  child_ids.any? { |cid| selected_line_ids.include?(cid) }
end

#invoice_line_already_on_return_list?(line_item) ⇒ Boolean

add-items UI: true if this invoice line already has a return row (each line at most once).

Returns:

  • (Boolean)


1752
1753
1754
1755
# File 'app/models/rma.rb', line 1752

def invoice_line_already_on_return_list?(line_item)
  line_item.id.present? &&
    selected_returned_line_item_ids_for_invoice_kit_rules.include?(line_item.id)
end

#items_partially_returned?Boolean

Returns:

  • (Boolean)


987
988
989
# File 'app/models/rma.rb', line 987

def items_partially_returned?
  rma_items.reload.active.any?(&:returned?) && rma_items.reload.active.any?(&:requested?)
end

#labels_file_name(with_extension = true) ⇒ Object



879
880
881
# File 'app/models/rma.rb', line 879

def labels_file_name(with_extension = true)
  "#{rma_number}_return_labels#{'.pdf' if with_extension}"
end

#legacy_return_shipmentsObject



859
860
861
# File 'app/models/rma.rb', line 859

def legacy_return_shipments
  Shipment.label_complete.joins(:order).merge(credit_orders).order(:id)
end

#next_step_after_items_createdObject



1714
1715
1716
1717
1718
1719
1720
# File 'app/models/rma.rb', line 1714

def next_step_after_items_created
  if return_label_required?
    'return_label_options'
  else
    'show'
  end
end

#no_items_returned?Boolean

Returns:

  • (Boolean)


672
673
674
# File 'app/models/rma.rb', line 672

def no_items_returned?
  rma_items.none?(&:returned?)
end

#ordersActiveRecord::Relation<Order>

Returns:

  • (ActiveRecord::Relation<Order>)

See Also:



123
# File 'app/models/rma.rb', line 123

has_many :orders, inverse_of: :rma

#original_invoiceInvoice

Returns:

See Also:



104
# File 'app/models/rma.rb', line 104

belongs_to :original_invoice, class_name: 'Invoice', optional: true

#original_invoice_selectObject



558
559
560
561
562
# File 'app/models/rma.rb', line 558

def original_invoice_select
  return [] unless original_invoice

  [[original_invoice.selection_name_for_rmas, original_invoice.id]]
end

#original_orderOrder

Returns:

See Also:



103
# File 'app/models/rma.rb', line 103

belongs_to :original_order, class_name: 'Order', optional: true

#original_order_refObject



718
719
720
# File 'app/models/rma.rb', line 718

def original_order_ref
  original_order.try(:reference_number)
end

#original_order_ref=(ref) ⇒ Object



722
723
724
# File 'app/models/rma.rb', line 722

def original_order_ref=(ref)
  self.original_order = Order.find_by(reference_number: ref) if ref.present?
end

#original_order_selectObject



552
553
554
555
556
# File 'app/models/rma.rb', line 552

def original_order_select
  return [] unless original_order

  [[original_order.selection_name, original_order.id]]
end

#payment_statusObject



564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
# File 'app/models/rma.rb', line 564

def payment_status
  return nil unless %w[returned credit_in_process credited_partially_refunded credited_fully_refunded].include?(state)

  statuses = []
  credit_orders.each do |co|
    cm = co.try(:credit_memo)
    statuses << if co.pending_review? || co.ready_for_printing?
                  { status: 'Under Review', color: 'amber', ref: co.reference_number }
                elsif cm
                  cm.payment_status
                else
                  { status: 'Unknown', color: 'red', ref: co.reference_number }
                end
  end
  statuses
end

#paymentsActiveRecord::Relation<Payment>

Returns:

  • (ActiveRecord::Relation<Payment>)

See Also:



119
# File 'app/models/rma.rb', line 119

has_many :payments

#pending_ordersActiveRecord::Relation<Order>

Returns:

  • (ActiveRecord::Relation<Order>)

See Also:



124
# File 'app/models/rma.rb', line 124

has_many :pending_orders, -> { where(state: PENDING_ORDERS_STATES) }, class_name: 'Order'

#possible_eventsObject



380
381
382
# File 'app/models/rma.rb', line 380

def possible_events
  state_transitions.map(&:event).sort
end

#possible_events_for_selectObject



384
385
386
# File 'app/models/rma.rb', line 384

def possible_events_for_select
  possible_events.map { |evt| [evt.to_s.titleize, evt] }
end

#post_communication_sent_hookObject



823
824
825
826
# File 'app/models/rma.rb', line 823

def post_communication_sent_hook
  transmit if awaiting_transmission?
  true
end

#precreate_from_deliveryDelivery

Returns:

See Also:



109
# File 'app/models/rma.rb', line 109

belongs_to :precreate_from_delivery, class_name: 'Delivery', optional: true

#primary_partyObject



1728
1729
1730
# File 'app/models/rma.rb', line 1728

def primary_party
  customer
end

#quoteQuote

Returns:

See Also:



132
# File 'app/models/rma.rb', line 132

has_one :quote

#receive_item_events_for_selectObject



388
389
390
391
392
393
394
# File 'app/models/rma.rb', line 388

def receive_item_events_for_select
  res = []
  res << ["Keep current status: #{human_state_name.titleize}", '']
  res << ['Inspect Return', 'inspect_return'] if (awaiting_return? || partially_returned?) && can_inspect_return?
  res << ['Complete Return', 'returned'] if awaiting_return? || partially_returned?
  res
end

#redesign_quoteQuote

Returns:

See Also:



108
# File 'app/models/rma.rb', line 108

belongs_to :redesign_quote, class_name: 'Quote', optional: true

#return_addressObject



844
845
846
# File 'app/models/rma.rb', line 844

def return_address
  return_shipping_address.full_address(true, "\n")
end

#return_deliveryDelivery

Returns:

See Also:



110
# File 'app/models/rma.rb', line 110

belongs_to :return_delivery, class_name: 'Delivery', optional: true

#return_instructionsObject



871
872
873
# File 'app/models/rma.rb', line 871

def return_instructions
  uploads.in_category('rma_return_instructions_pdf').order('created_at DESC').first
end

#return_label_issuesObject



1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
# File 'app/models/rma.rb', line 1332

def return_label_issues
  issues = []
  return issues unless requested? || awaiting_return?

  if return_label_required?
    if number_of_return_labels_required.to_i >= 0
      num_labels_requested = number_of_return_labels_required.to_i
    elsif number_of_return_labels_required.to_i == -1
      num_labels_requested = return_delivery&.shipments&.size || 1
    end
    num_labels_generated = return_delivery&.shipments&.label_complete&.size
    if return_delivery&.return_labels_complete? || return_delivery&.valid_for_generating_ship_labels?
      if num_labels_requested.positive? && num_labels_requested == num_labels_generated
      else
        issues << "#{num_labels_requested} labels requested but #{num_labels_generated} labels generated."
      end
    else
      issues << (return_delivery&.errors&.full_messages&.join('. ').presence || 'Return delivery could not be generated!')
    end
  end
  issues.compact
end

#return_label_required?Boolean Also known as: return_label_required

Returns:

  • (Boolean)


680
681
682
683
684
# File 'app/models/rma.rb', line 680

def return_label_required?
  return false if all_items_voided? # this is to prevent this validation from blocking RMAs from being voided

  number_of_return_labels_required.to_i.positive? || (number_of_return_labels_required == -1) # here, -1 means let Heatwave decide
end

#return_labelsObject



867
868
869
# File 'app/models/rma.rb', line 867

def return_labels
  return_shipments.map { |s| s.ship_label_pdf(true) }.compact
end

#return_labels_correctly_generated?Boolean

Returns:

  • (Boolean)


1355
1356
1357
# File 'app/models/rma.rb', line 1355

def return_labels_correctly_generated?
  return_label_issues.empty?
end

#return_shipmentsActiveRecord::Relation<Shipment>

Returns:

See Also:



116
# File 'app/models/rma.rb', line 116

has_many :return_shipments, -> { label_complete }, source: :shipments, class_name: 'Shipment', through: :return_delivery

#return_shipping_addressAddress

Returns:

See Also:



106
# File 'app/models/rma.rb', line 106

belongs_to :return_shipping_address, class_name: 'Address', optional: true

#return_shipping_methodObject



691
692
693
# File 'app/models/rma.rb', line 691

def return_shipping_method
  RETURN_SHIPPING_METHODS[customer.store.id.to_s]
end

#returned_rma_itemsObject



1018
1019
1020
# File 'app/models/rma.rb', line 1018

def returned_rma_items
  rma_items.returned.reload
end

#rma_inspect_emailObject

Alias for Company#rma_inspect_email

Returns:

  • (Object)

    Company#rma_inspect_email

See Also:



162
# File 'app/models/rma.rb', line 162

delegate :rma_inspect_email, to: :company

#rma_itemsActiveRecord::Relation<RmaItem>

Returns:

  • (ActiveRecord::Relation<RmaItem>)

See Also:



117
# File 'app/models/rma.rb', line 117

has_many :rma_items, inverse_of: :rma, dependent: :destroy, before_add: :set_rma_items_defaults

#rma_items_awaiting_inspectionObject



1022
1023
1024
# File 'app/models/rma.rb', line 1022

def rma_items_awaiting_inspection
  rma_items.awaiting_inspection.reload
end

#rma_suggested_quotes_optionsObject



519
520
521
522
523
524
525
526
527
528
529
530
531
# File 'app/models/rma.rb', line 519

def rma_suggested_quotes_options
  quotes = []
  if customer
    quotes += customer.quotes.order('quotes.reference_number desc').map do |q|
      [q.selection_name, q.id]
    end
  end
  if redesign_quote
    new_selection = [redesign_quote.selection_name, redesign_quote.id]
    quotes |= [new_selection]
  end
  quotes
end

#send_fromEmployee

Returns:

See Also:



112
# File 'app/models/rma.rb', line 112

belongs_to :send_from, class_name: 'Employee', optional: true

#send_inspection_notification_to_customerObject



813
814
815
816
# File 'app/models/rma.rb', line 813

def send_inspection_notification_to_customer
  CommunicationBuilder.new(resource: self,
                           sender_party: send_from || customer.primary_sales_rep, template_system_code: 'RMAINSPECT').create
end

#send_return_confirmation_email_to_customerObject



818
819
820
821
# File 'app/models/rma.rb', line 818

def send_return_confirmation_email_to_customer
  CommunicationBuilder.new(resource: self,
                           sender_party: send_from || customer.primary_sales_rep, template_system_code: 'RMARC').create
end

#send_return_email_to_customer(keep_as_draft: false) ⇒ Object



808
809
810
811
# File 'app/models/rma.rb', line 808

def send_return_email_to_customer(keep_as_draft: false)
  CommunicationBuilder.new(resource: self,
                           sender_party: send_from || customer.primary_sales_rep, template_system_code: 'RMA').create(keep_as_draft: keep_as_draft)
end

#serial_numbersActiveRecord::Relation<SerialNumber>

Returns:

See Also:



125
# File 'app/models/rma.rb', line 125

has_many :serial_numbers, through: :rma_items

#service_only?Boolean

Returns:

  • (Boolean)


840
841
842
# File 'app/models/rma.rb', line 840

def service_only?
  rma_items.all? { |rma_item| rma_item.returned_item.tax_class == 'svc' }
end

#set_default_notification_channelsObject



917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
# File 'app/models/rma.rb', line 917

def set_default_notification_channels
  return nil if customer.nil?

  notification_channels = []
  notification_channels.concat(customer.notification_channels.rmas.collect(&:contact_point))
  notification_channels.concat(customer.billing_entity.notification_channels.rmas.collect(&:contact_point))
  self.transmission_email ||= []
  self.transmission_fax ||= []
  notification_channels.uniq.each do |cp|
    next if ((cp.category == 'email') && transmission_email.include?(cp.detail)) || ((cp.category == 'fax') && transmission_fax.include?(cp.detail))

    case cp.category
    when 'email'
      self.transmission_email << cp.detail
    when 'fax'
      self.transmission_fax << cp.detail
    end
  end
end

#ship_from_addressAddress

Returns:

See Also:



107
# File 'app/models/rma.rb', line 107

belongs_to :ship_from_address, class_name: 'Address', optional: true

#ship_from_attributesObject



1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
# File 'app/models/rma.rb', line 1687

def ship_from_attributes
  res = {}
  # this is an RMA return label
  # address is original order address
  res[:address] = ship_from_address
  # attention_name cannot be blank!!!!
  res[:attention_name] = res[:address].person_name
  res[:attention_name] = 'Customer' if res[:attention_name].blank?
  if res[:address].company_name
    res[:name] = res[:address].company_name
    res[:name] = 'Customer' if res[:name].blank?
  else
    res[:name] = res[:address].person_name if res[:name].blank?
    res[:name] = 'Customer' if res[:name].blank?
  end
  res[:phone] =
    (original_order&.shipping_phone || customer.phone || customer.cell_phone || customer.sales_reps.first&.direct_phone || SHIPPING_SHIPPER_CONFIGURATION[customer.country.iso3.to_sym][:shipper_phone])
  res[:email] = transmission_email
  if res[:email].to_s.strip.blank?
    res[:email] =
      customer.primary_sales_rep&.email || SHIPPING_SHIPPER_CONFIGURATION[customer.country.iso3.to_sym][:shipper_email]
  end
  res[:phone] = res[:phone] if res[:phone]

  res
end

#ship_to_attributesObject



1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
# File 'app/models/rma.rb', line 1667

def ship_to_attributes
  res = {}
  res[:address] = return_shipping_address
  if res[:address].company_name
    # attention_name cannot be blank!!!!
    res[:attention_name] = res[:address].person_name
    res[:attention_name] = 'Returns Department' if res[:attention_name].blank?
    res[:name] = res[:address].company_name
  else
    res[:attention_name] = attention_name
    res[:attention_name] = res[:address].person_name if res[:attention_name].blank?
    res[:name] = attention_name
    res[:name] = res[:address].person_name if res[:name].blank?
  end
  # sort of a kludge but for now use shipping configuration's sender phone for legacy matching
  res[:phone] = SHIPPING_SHIPPER_CONFIGURATION[customer.country.iso3.to_sym][:shipper_phone]
  res[:email] = SHIPPING_SHIPPER_CONFIGURATION[customer.country.iso3.to_sym][:shipper_email]
  res
end

#shipment_tracking_numbersObject



499
500
501
502
503
504
# File 'app/models/rma.rb', line 499

def shipment_tracking_numbers
  stn = []
  stn += return_shipments.flat_map(&:shipment_tracking_number)
  stn += tracking_numbers.map { |tn_raw| ShipmentTrackingNumber.new(tn_raw) }
  stn.uniq
end

#shipping_optionShippingOption



111
# File 'app/models/rma.rb', line 111

belongs_to :shipping_option, optional: true

#should_validate_shipping_option?Boolean

Returns:

  • (Boolean)


1722
1723
1724
1725
1726
# File 'app/models/rma.rb', line 1722

def should_validate_shipping_option?
  return false if skip_shipping_option_validation

  number_of_return_labels_required.present? && !number_of_return_labels_required.zero?
end

#stores_for_return_selectObject



546
547
548
549
550
# File 'app/models/rma.rb', line 546

def stores_for_return_select
  return Address.none unless company_id

  Address.joins(:warehouse_store).where(stores: { company_id: company_id })
end

#suggested_contact_namesObject



792
793
794
795
796
797
798
799
# File 'app/models/rma.rb', line 792

def suggested_contact_names
  contact_names = []
  contact_names += customer.contacts.active.distinct.pluck(:full_name) if customer
  contact_names << original_order.contact.full_name if original_order&.contact&.full_name.present?
  contact_names << original_invoice.shipping_address.person_name if original_invoice&.shipping_address&.person_name.present?
  contact_names << contact_name if contact_name.present?
  contact_names.uniq.sort
end

#to_sObject Also known as: name, reference_number



636
637
638
# File 'app/models/rma.rb', line 636

def to_s
  rma_number
end

#unreturn_all_itemsObject



913
914
915
# File 'app/models/rma.rb', line 913

def unreturn_all_items
  rma_items.where(state: 'returned').find_each(&:unreturn)
end

#unvoid_all_itemsObject



908
909
910
911
# File 'app/models/rma.rb', line 908

def unvoid_all_items
  rma_items.each(&:unvoid)
  return_shipments.update_all(tracking_state: 'not_yet_in_system') if return_shipments.present? && return_shipments.all?(&:tracking_ignore?)
end

#unvoid_all_items_and_selfObject



895
896
897
898
899
900
901
# File 'app/models/rma.rb', line 895

def unvoid_all_items_and_self
  Rma.transaction do
    unvoid_all_items
    self.skip_reminders = false
    sync_state
  end
end

#update_draft_communications_with_new_attachmentsObject



1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
# File 'app/models/rma.rb', line 1240

def update_draft_communications_with_new_attachments
  # Find draft communications for this RMA
  draft_communications = Communication.where(
    resource: self,
    state: 'draft'
  )

  return if draft_communications.empty?

  # Get the new attachments that should be included
  new_uploads = []
  if awaiting_return?
    new_uploads << return_instructions if return_instructions

    # Also include return labels if available
    return_labels_uploads = return_labels
    new_uploads += return_labels_uploads if return_labels_uploads.present?
  end

  return if new_uploads.empty?
  new_uploads = new_uploads.compact.uniq(&:id)

  # Update each draft communication
  draft_communications.each do |communication|
    communication.with_lock do
      # Remove old RMA-related attachments (return instructions and labels)
      communication.uploads.where(
        category: %w[rma_return_instructions_pdf ship_label_pdf]
      ).each do |upload|
        communication.uploads.delete(upload)
      end

      # Add new attachments; duplicate rows can happen under concurrent submits.
      new_uploads.each do |upload|
        next if communication.uploads.exists?(id: upload.id)

        begin
          communication.uploads << upload
        rescue ActiveRecord::RecordNotUnique
          # Another request attached this upload first; join already exists.
          next
        end
      end

      communication.save!
    end
  end
end

#update_linked_credit_orders(allow_transition_failure: true) ⇒ Object

This pushes the credit order to the return state



1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
# File 'app/models/rma.rb', line 1619

def update_linked_credit_orders(allow_transition_failure: true)
  Order.transaction do
    credit_orders.each do |credit_order|
      # If a shipping line already exists, don't touch
      unless credit_order.line_items.shipping_only.exists?
        # First we will copy over the shipping line from the original order
        shipping_lines = []
        credit_order.line_items.joins(credit_rma_item: :returned_line_item).find_each do |coli|
          shipping_lines += coli.credit_rma_item.returned_line_item.resource.line_items.shipping_only
        end
        shipping_lines.uniq!
        shipping_lines.each do |shipping_line|
          co_shipping_line = shipping_line.dup
          # It's always one for shipping
          co_shipping_line.quantity = co_shipping_line.qty_shipped = -1
          # Detaching to see if we can eliminate it
          co_shipping_line.shipping_cost_id = nil
          # co_shipping_line.price = -co_shipping_line.price
          # co_shipping_line.discounted_price = -co_shipping_line.discounted_price
          co_shipping_line.resource = credit_order
          co_shipping_line.delivery_id = nil # Let's see if this takes
          co_shipping_line.save!
          # Now copy the discounts from the original
          shipping_line.line_discounts.each do |ld|
            credit_order_discount = credit_order.discounts.find_by(coupon_id: ld.discount.coupon_id)
            unless credit_order_discount
              # If the discount doesn't already exist, we clone and create it
              credit_order_discount = ld.discount.dup
              credit_order_discount.itemizable = credit_order
              credit_order_discount.amount = credit_order_discount.user_amount = -credit_order_discount.amount
              credit_order_discount.save!
            end
            # Now line discount can be cloned
            co_shipping_line.line_discounts.create!(discount_id: credit_order_discount.id,
                                                    coupon_id: ld.coupon_id, amount: -ld.amount)
          end
        end
      end
      # Then mark the credit order returned
      if allow_transition_failure
        credit_order.returned
      else
        credit_order.returned!
      end
    end
  end
end

#uploadsActiveRecord::Relation<Upload>

Returns:

  • (ActiveRecord::Relation<Upload>)

See Also:



120
# File 'app/models/rma.rb', line 120

has_many :uploads, as: :resource, dependent: :destroy

#versions_for_audit_trail(_params = {}) ⇒ Object



396
397
398
399
400
401
402
403
404
405
# File 'app/models/rma.rb', line 396

def versions_for_audit_trail(_params = {})
  query_sql = %q{
                (item_type = 'Rma' and item_id = :id)
                OR (
                  item_type = 'RmaItem'
                    AND reference_data @> '{"rma_id": :id}'
                )
              }
  RecordVersion.where(query_sql, id: id)
end

#versions_for_dates_trackerObject



438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'app/models/rma.rb', line 438

def versions_for_dates_tracker
  shipment_id = return_shipments.first&.id
  shipment_sql = if shipment_id.present?
                   "select object_changes #>> '{" + '"tracking_state"' + ",1}' as state,timezone('America/Chicago', timestamptz(created_at))::date
                                         from versions
                                         where (object_changes->'tracking_state'->>1 in ('in_transit','delivered'))
                                         and item_type = 'Shipment'
                                         and item_id = #{shipment_id}
                                         union all"
                 else
                   ''
                 end
  query_sql = <<-SQL
                #{shipment_sql}
                select object_changes #>> '{"state",1}' as state,timezone('America/Chicago', timestamptz(created_at))::date
                from versions
                where (object_changes->'state'->>1 in ('awaiting_inspection','returned','credit_in_process','credited_partially_refunded','credited_fully_refunded'))
                and item_type = 'Rma'
                and item_id = #{id}
  SQL
  results = RecordVersion.connection.execute(query_sql).to_a.map { |r| r.symbolize_keys }

  dates_tracker = if return_shipments.present?
                    { dropped_off: nil, warehouse_received: nil, submitted_for_inspection: nil, returned: nil, process_refund: nil,
                      refunded: nil, fully_refunded: nil }
                  else
                    { warehouse_received: nil, submitted_for_inspection: nil, returned: nil, process_refund: nil,
                      refunded: nil, fully_refunded: nil }
                  end
  results.each do |r|
    dates_tracker.each do |k, v|
      if r[:state] == k.to_s
        dates_tracker[k] = r[:timezone]
      elsif r[:state] == 'in_transit'
        dates_tracker[:dropped_off] = r[:timezone]
      elsif r[:state] == 'delivered'
        dates_tracker[:warehouse_received] = r[:timezone]
      elsif r[:state] == 'awaiting_inspection'
        dates_tracker[:submitted_for_inspection] = r[:timezone]
      elsif k.to_s == 'warehouse_received' && v.nil? && r[:state] == 'returned'
        dates_tracker[:warehouse_received] = r[:timezone]
      elsif k.to_s == 'submitted_for_inspection' && v.nil? && r[:state] == 'returned'
        dates_tracker[:submitted_for_inspection] = r[:timezone]
      elsif r[:state] == 'credit_in_process'
        dates_tracker[:process_refund] = r[:timezone]
      elsif r[:state] == 'credited_partially_refunded'
        dates_tracker[:refunded] = r[:timezone]
      elsif r[:state] == 'credited_fully_refunded'
        dates_tracker[:refunded] = r[:timezone]
        dates_tracker[:fully_refunded] = r[:timezone]
      end
    end
  end
  dates_tracker
end

#versions_for_state_trackerObject



407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'app/models/rma.rb', line 407

def versions_for_state_tracker
  where_sql = "object_changes ? 'state'"
  select_sql = %q{
                id,object_changes #>> '{"state",1}' as state,created_at,
                (lag(object_changes #>> '{"state",1}') OVER (ORDER BY id desc)) as new_state,
                (lag(created_at) OVER (ORDER BY id desc)) as new_state_created_at
              }
  version_records = versions_for_audit_trail.where(where_sql).select(select_sql).order('id,created_at')
  state_tracker = []
  version_records.each do |vr|
    NOTIFICATIONS_RMA_STATES.each do |nrs|
      state_info = {}
      next unless vr.state == nrs && vr.state != vr.new_state

      new_state_created_at = vr.new_state_created_at.present? ? vr.new_state_created_at.to_date : Date.current
      state_info[:state] = vr.state
      state_info[:date] = vr.created_at
      state_info[:days] = vr.created_at.to_date.working_days_until(new_state_created_at)
      if %w[auto_return_review awaiting_inspection].include?(vr.state)
        state_info[:sent_to] = vr.state == 'auto_return_review' ? EMAILS_FOR_NOTIFICATIONS.second : EMAILS_FOR_NOTIFICATIONS.first
        state_info[:number_of_notifications] = vr.created_at.working_days_until(new_state_created_at)
      elsif vr.created_at.working_days_until(new_state_created_at) > 3
        state_info[:sent_to] = EMAILS_FOR_NOTIFICATIONS.second
        state_info[:number_of_notifications] = vr.created_at.working_days_until(new_state_created_at) - 3
      end
      state_tracker << state_info if state_info[:number_of_notifications].present?
    end
  end
  state_tracker
end

#void_all_itemsObject



903
904
905
906
# File 'app/models/rma.rb', line 903

def void_all_items
  rma_items.each(&:void)
  return_shipments.update_all(tracking_state: 'tracking_ignore') if return_shipments.present? && return_shipments.all?(&:not_yet_in_system?)
end

#void_all_items_and_selfObject



887
888
889
890
891
892
893
# File 'app/models/rma.rb', line 887

def void_all_items_and_self
  Rma.transaction do
    void_all_items
    self.skip_reminders = true # prevent email from triggering
    sync_state # some items might have been received already
  end
end