Class: ShippingOption

Inherits:
ApplicationRecord show all
Defined in:
app/models/shipping_option.rb

Overview

== Schema Information

Table name: shipping_options
Database name: primary

id :integer not null, primary key
carrier :string(255)
country :string(255)
days_commitment :float default(3.5), not null
delivery_commitment :text
description :string(255)
information :text
is_beta :boolean default(FALSE), not null
is_freight :boolean default(FALSE), not null
is_inactive :boolean default(FALSE), not null
is_third_party_only :boolean default(FALSE), not null
jde_shipping_stop_code :string(255)
name :string(255)
service_code :string(255)
service_level :string(255)
supported_for_st :boolean default(FALSE), not null
tax_class :string(3) not null
created_at :datetime
updated_at :datetime
item_id :integer

Indexes

index_shipping_options_on_service_level (service_level)

Constant Summary collapse

SERVICE_LEVELS =

Service levels.

{ standard: 3, expedited: 2, express: 1 }.freeze
OPTIONS_FOR_RMAS =

Options for rmas.

[1, 139, 207, 4, 30, 63, 146].freeze
EXPRESS_DAYS_COMMITMENT =

Express/expedited service — under 3.5-day commitment, excluding SpeeDee (a
regional ground carrier). Matches the EXPEDITED/RUSH bucketing in
Delivery::GroupWwwShippingCosts, which likewise excludes SpeedeeDelivery.
Commitment below which an option counts as express/expedited. Shared by the
express scope and #express? so the row-level and set-level answers can't
drift apart.

3.5
PAPERLESS_RETURN_OPTION_LABEL =

Human-readable tag appended to a paperless-eligible RMA shipping option.
Shared with the picker view (rmas/calculate_shipping_costs) which detects
this exact substring to render a highlighted badge.

'USPS Label Broker Paperless QR return'
SHIPENGINE_LTL_CARRIERS =
%w[
  ShipengineRlCarriers ShipengineFedExFreight ShipengineFedExFreightEconomy
  ShipengineSaia ShipengineRoadrunner ShipengineXpo
].freeze
MANUAL_PICKUP_CARRIERS =
{
  'ShipengineRoadrunner' => {
    name: 'Roadrunner Freight',
    phone: '(630) 679-2800',
    email: 'CustomerService@RoadrunnerLTL.com',
    url: 'https://tools.rrts.com/QuickPickup',
    location: 'Chicago/Bolingbrook, IL'
  }
}.freeze

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Has many collapse

Belongs to collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Class Method Details

.:CANActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are :CAN. Active Record Scope

Returns:

See Also:



85
# File 'app/models/shipping_option.rb', line 85

scope :CAN, -> { for_country_iso('CA') }

.:NLDActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are :NLD. Active Record Scope

Returns:

See Also:



86
# File 'app/models/shipping_option.rb', line 86

scope :NLD, -> { for_country_iso('NL') }

.:USAActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are :USA. Active Record Scope

Returns:

See Also:



84
# File 'app/models/shipping_option.rb', line 84

scope :USA, -> { for_country_iso('US') }

.activeActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are active. Active Record Scope

Returns:

See Also:



80
# File 'app/models/shipping_option.rb', line 80

scope :active, -> { where('shipping_options.is_inactive IS NOT TRUE') }

.by_days_commitmentActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are by days commitment. Active Record Scope

Returns:

See Also:



83
# File 'app/models/shipping_option.rb', line 83

scope :by_days_commitment, -> { order('shipping_options.days_commitment DESC') }

.carrier_options_for_select(country_iso3 = nil) ⇒ Object



132
133
134
135
136
# File 'app/models/shipping_option.rb', line 132

def self.carrier_options_for_select(country_iso3 = nil)
  opts = ShippingOption.active.where.not(carrier: nil).order(:carrier)
  opts = opts.where(country: country_iso3[0..1]) if country_iso3.present?
  opts.pluck(:carrier).uniq
end

.expressActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are express. Active Record Scope

Returns:

See Also:



49
50
51
52
# File 'app/models/shipping_option.rb', line 49

scope :express, lambda {
  where(shipping_options: { days_commitment: ...EXPRESS_DAYS_COMMITMENT })
    .where.not(carrier: 'SpeedeeDelivery')
}

.express_name_to_carrierObject

{ option name => carrier } for express options — lets Order resolve the carrier
(hence its pickup cutoff) from the denormalized shipping_method string with no
per-row query. Memoized alongside express_names.



76
77
78
# File 'app/models/shipping_option.rb', line 76

def self.express_name_to_carrier
  @express_name_to_carrier ||= express.pluck(:name, :carrier).to_h
end

.express_namesObject

Names of every express option, for matching Order#shipping_method (a denormalized
ShippingOption.name string). Memoized — shipping options change rarely.
ponytail: process-lifetime cache; bounce the app if you edit express options' days_commitment.



69
70
71
# File 'app/models/shipping_option.rb', line 69

def self.express_names
  @express_names ||= express.distinct.pluck(:name).compact
end

.for_country_isoActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are for country iso. Active Record Scope

Returns:

See Also:



87
# File 'app/models/shipping_option.rb', line 87

scope :for_country_iso, ->(country_iso) { where(country: country_iso).by_days_commitment }

.for_country_iso3ActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are for country iso3. Active Record Scope

Returns:

See Also:



88
# File 'app/models/shipping_option.rb', line 88

scope :for_country_iso3, ->(country_iso3) { for_country_iso(Country.iso3_to_iso_map[country_iso3]) }

.for_rmasActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are for rmas. Active Record Scope

Returns:

See Also:



92
93
94
95
# File 'app/models/shipping_option.rb', line 92

scope :for_rmas, lambda {
  where(shipping_options: { id: OPTIONS_FOR_RMAS })
    .or(where(shipping_options: { paperless_eligible: true }))
}

.freightActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are freight. Active Record Scope

Returns:

See Also:



96
# File 'app/models/shipping_option.rb', line 96

scope :freight, -> { where("shipping_options.carrier = 'UPSFreight'") }

.get_appropriate(country_code, _is_residential, purolator_ltl = nil) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'app/models/shipping_option.rb', line 99

def self.get_appropriate(country_code, _is_residential, purolator_ltl = nil)
  opts = []
  if country_code.in?(Country::EU_COUNTRIES_ISO)
    opts = ShippingOption.active.NLD.to_a # This should be more generic and add any european country?
  elsif country_code == 'CA'
    # puts "ShippingOption.get_appropriate: country_code: CA, purolator_ltl: #{purolator_ltl}"
    opts = ShippingOption.active.CAN.to_a
    unless purolator_ltl
      # puts "ShippingOption.get_appropriate: purolator_ltl IS NOT TRUE"
      purolator_freight_sos = ShippingOption.where(carrier: 'PurolatorFreight')
      purolator_freight_sos.each do |purolator_freight_so|
        opts.delete(purolator_freight_so)
      end
    end
  else
    opts = ShippingOption.active.USA.to_a
  end
  opts
end

.non_freightActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are non freight. Active Record Scope

Returns:

See Also:



97
# File 'app/models/shipping_option.rb', line 97

scope :non_freight, -> { where("shipping_options.carrier <> 'UPSFreight'") }

.non_overrideActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are non override. Active Record Scope

Returns:

See Also:



82
# File 'app/models/shipping_option.rb', line 82

scope :non_override, -> { where.not(shipping_options: { carrier: nil }) }

.options_for_preferred_shipping_method_select(country = 'USA') ⇒ Object



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

def self.options_for_preferred_shipping_method_select(country = 'USA')
  # must only use supported shipping options here or free shipping becomes almost default
  ShippingOption.active.supported.for_country_iso3(country).where.not(carrier: nil).order(days_commitment: :desc).map { |so| ["#{so.description} #{so.country}, code: #{so.service_code}", so.name] }
end

.select_options(country_iso3 = nil, supported = nil, include_override = false) ⇒ Object



119
120
121
122
123
124
125
# File 'app/models/shipping_option.rb', line 119

def self.select_options(country_iso3 = nil, supported = nil, include_override = false)
  opts = ShippingOption.active
  opts = opts.non_override unless include_override == true
  opts = opts.supported if supported == true
  opts = opts.send(country_iso3.to_sym) if country_iso3.present?
  opts.order(:carrier, days_commitment: :desc, description: :asc, country: :desc).map { |so| ["#{so.carrier}: #{so.description} (#{so.country}), code: #{so.service_code}", so.id] }
end

.supportedActiveRecord::Relation<ShippingOption>

A relation of ShippingOptions that are supported. Active Record Scope

Returns:

See Also:



81
# File 'app/models/shipping_option.rb', line 81

scope :supported, -> { where('shipping_options.is_third_party_only IS NOT TRUE and shipping_options.carrier IS NOT NULL') }

.supported_carrier_options_for_rmas(country = 'US') ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'app/models/shipping_option.rb', line 155

def self.supported_carrier_options_for_rmas(country = 'US')
  supported_carriers = supported_carrier_options_for_select
  opts = ShippingOption.active.for_rmas.where(carrier: supported_carriers).where(country: country)
  # `paperless_eligible: :desc` sorts the paperless variant ahead of its
  # standard sibling within the same carrier (true > false).
  opts.order(:carrier, paperless_eligible: :desc, days_commitment: :desc, description: :asc, country: :desc).map do |so|
    # Suffix is purely informational so staff can tell a customer "this
    # method supports a QR code at the counter" while picking. Does not
    # gate the API request — see `Shipping::ShipengineBase#get_label`.
    label = "#{so.carrier}: #{so.description} (#{so.country}), code: #{so.service_code}"
    label += "#{PAPERLESS_RETURN_OPTION_LABEL}" if so.paperless_eligible?
    [label, so.id]
  end
end

.supported_carrier_options_for_select(country = nil) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
# File 'app/models/shipping_option.rb', line 138

def self.supported_carrier_options_for_select(country = nil)
  if country
    SUPPORTED_SHIPPING_CARRIERS[country.to_sym] || []
  else
    carrier_options = []
    SUPPORTED_SHIPPING_CARRIERS.each do |_country, carrier_options_arr|
      carrier_options.concat(carrier_options_arr)
    end
    carrier_options.uniq
  end
end

.with_mirrored_paperless_costs(option_ids, costs_by_option_id) ⇒ Hash{Integer=>ShippingCost}

A paperless option is a separate ShippingOption row sharing a carrier +
service_code with a standard option but with its own name, so the rate
engine (which keys quotes by option name) never produces a cost row for it.
Returns a copy of costs_by_option_id with each paperless option's cost
mirrored from its standard sibling (same carrier + service_code) — same
postage, just a different label format. Paperless options whose sibling has
no quote stay absent (no quote shown).

Parameters:

  • option_ids (Array<Integer>)

    shipping_option ids in play

  • costs_by_option_id (Hash{Integer=>ShippingCost})

    real costs by option id

Returns:



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'app/models/shipping_option.rb', line 181

def self.with_mirrored_paperless_costs(option_ids, costs_by_option_id)
  options = where(id: option_ids).index_by(&:id)
  result = costs_by_option_id.dup
  options.each_value do |opt|
    next unless opt.paperless_eligible?
    next if result[opt.id]

    sibling_cost = costs_by_option_id.values.find do |sc|
      sibling = options[sc.shipping_option_id]
      sibling && !sibling.paperless_eligible? &&
        sibling.carrier == opt.carrier && sibling.service_code == opt.service_code
    end
    result[opt.id] = sibling_cost if sibling_cost
  end
  result
end

Instance Method Details

#country_iso3Object



247
248
249
# File 'app/models/shipping_option.rb', line 247

def country_iso3
  Country.find_by(iso: country).iso3
end

#express?Boolean

Row-level counterpart to the express scope, for code holding a loaded
option rather than building a query — e.g. grading how late a delivery is
(ProblematicDeliverySweep.severity), where missing an overnight commitment
by a day is a different event from missing a ground one.

Returns:

  • (Boolean)


60
61
62
63
64
# File 'app/models/shipping_option.rb', line 60

def express?
  days_commitment.present? &&
    days_commitment < EXPRESS_DAYS_COMMITMENT &&
    carrier != 'SpeedeeDelivery'
end

#is_economy?Boolean

Returns:

  • (Boolean)


198
199
200
201
# File 'app/models/shipping_option.rb', line 198

def is_economy?
  %w[ground standard fedex_ground fedex_ground_residential usps_media_mail usps_standard_post usps_parcel_select usps_priority_mail canada_post_regular_parcel canpar_ground purolator_ground
     purolator_ground_evening].include? name
end

#is_fedex_ca?Boolean

Returns:

  • (Boolean)


207
208
209
210
# File 'app/models/shipping_option.rb', line 207

def is_fedex_ca?
  nm = name.to_s.downcase
  nm.index('fedex_').present? && nm.index('_ca').present?
end

#is_freightquote?Boolean

Returns:

  • (Boolean)


212
213
214
# File 'app/models/shipping_option.rb', line 212

def is_freightquote?
  carrier.to_s.downcase.index('freightquote').present?
end

#is_override?Boolean

Returns:

  • (Boolean)


243
244
245
# File 'app/models/shipping_option.rb', line 243

def is_override?
  name.to_s.downcase == 'override'
end

#is_postal?Boolean

Returns:

  • (Boolean)


203
204
205
# File 'app/models/shipping_option.rb', line 203

def is_postal?
  POSTAL_CARRIERS.include? carrier
end

#is_shipengine_ltl?Boolean

Returns:

  • (Boolean)


221
222
223
# File 'app/models/shipping_option.rb', line 221

def is_shipengine_ltl?
  SHIPENGINE_LTL_CARRIERS.include?(carrier)
end

#itemItem

Returns:

See Also:



34
# File 'app/models/shipping_option.rb', line 34

belongs_to :item, optional: true

#manual_pickup_contactObject



239
240
241
# File 'app/models/shipping_option.rb', line 239

def manual_pickup_contact
  MANUAL_PICKUP_CARRIERS[carrier]
end

#requires_manual_pickup?Boolean

Returns:

  • (Boolean)


235
236
237
# File 'app/models/shipping_option.rb', line 235

def requires_manual_pickup?
  MANUAL_PICKUP_CARRIERS.key?(carrier)
end

#shipping_costsActiveRecord::Relation<ShippingCost>

Returns:

See Also:



33
# File 'app/models/shipping_option.rb', line 33

has_many :shipping_costs