Class: ShippingOption
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- ShippingOption
- 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
-
.:CAN ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are :CAN.
-
.:NLD ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are :NLD.
-
.:USA ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are :USA.
-
.active ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are active.
-
.by_days_commitment ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are by days commitment.
- .carrier_options_for_select(country_iso3 = nil) ⇒ Object
-
.express ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are express.
-
.express_name_to_carrier ⇒ Object
{ 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.
-
.express_names ⇒ Object
Names of every express option, for matching Order#shipping_method (a denormalized ShippingOption.name string).
-
.for_country_iso ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are for country iso.
-
.for_country_iso3 ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are for country iso3.
-
.for_rmas ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are for rmas.
-
.freight ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are freight.
- .get_appropriate(country_code, _is_residential, purolator_ltl = nil) ⇒ Object
-
.non_freight ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are non freight.
-
.non_override ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are non override.
- .options_for_preferred_shipping_method_select(country = 'USA') ⇒ Object
- .select_options(country_iso3 = nil, supported = nil, include_override = false) ⇒ Object
-
.supported ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are supported.
- .supported_carrier_options_for_rmas(country = 'US') ⇒ Object
- .supported_carrier_options_for_select(country = nil) ⇒ Object
-
.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.
Instance Method Summary collapse
- #country_iso3 ⇒ Object
-
#express? ⇒ Boolean
Row-level counterpart to the ShippingOption.express scope, for code holding a loaded option rather than building a query — e.g.
- #is_economy? ⇒ Boolean
- #is_fedex_ca? ⇒ Boolean
- #is_freightquote? ⇒ Boolean
- #is_override? ⇒ Boolean
- #is_postal? ⇒ Boolean
- #is_shipengine_ltl? ⇒ Boolean
- #manual_pickup_contact ⇒ Object
- #requires_manual_pickup? ⇒ Boolean
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Class Method Details
.:CAN ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are :CAN. Active Record Scope
85 |
# File 'app/models/shipping_option.rb', line 85 scope :CAN, -> { for_country_iso('CA') } |
.:NLD ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are :NLD. Active Record Scope
86 |
# File 'app/models/shipping_option.rb', line 86 scope :NLD, -> { for_country_iso('NL') } |
.:USA ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are :USA. Active Record Scope
84 |
# File 'app/models/shipping_option.rb', line 84 scope :USA, -> { for_country_iso('US') } |
.active ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are active. Active Record Scope
80 |
# File 'app/models/shipping_option.rb', line 80 scope :active, -> { where('shipping_options.is_inactive IS NOT TRUE') } |
.by_days_commitment ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are by days commitment. Active Record Scope
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.(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 |
.express ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are express. Active Record Scope
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_carrier ⇒ Object
{ 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_names ⇒ Object
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_iso ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are for country iso. Active Record Scope
87 |
# File 'app/models/shipping_option.rb', line 87 scope :for_country_iso, ->(country_iso) { where(country: country_iso).by_days_commitment } |
.for_country_iso3 ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are for country iso3. Active Record Scope
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_rmas ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are for rmas. Active Record Scope
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 })) } |
.freight ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are freight. Active Record Scope
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_freight ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are non freight. Active Record Scope
97 |
# File 'app/models/shipping_option.rb', line 97 scope :non_freight, -> { where("shipping_options.carrier <> 'UPSFreight'") } |
.non_override ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are non override. Active Record Scope
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.(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.(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 |
.supported ⇒ ActiveRecord::Relation<ShippingOption>
A relation of ShippingOptions that are supported. Active Record Scope
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.(country = 'US') supported_carriers = 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.(country = nil) if country SUPPORTED_SHIPPING_CARRIERS[country.to_sym] || [] else = [] SUPPORTED_SHIPPING_CARRIERS.each do |_country, | .concat() end .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).
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) = where(id: option_ids).index_by(&:id) result = costs_by_option_id.dup .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 = [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_iso3 ⇒ Object
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.
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
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
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
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
243 244 245 |
# File 'app/models/shipping_option.rb', line 243 def is_override? name.to_s.downcase == 'override' end |
#is_postal? ⇒ Boolean
203 204 205 |
# File 'app/models/shipping_option.rb', line 203 def is_postal? POSTAL_CARRIERS.include? carrier end |
#is_shipengine_ltl? ⇒ Boolean
221 222 223 |
# File 'app/models/shipping_option.rb', line 221 def is_shipengine_ltl? SHIPENGINE_LTL_CARRIERS.include?(carrier) end |
#manual_pickup_contact ⇒ Object
239 240 241 |
# File 'app/models/shipping_option.rb', line 239 def manual_pickup_contact MANUAL_PICKUP_CARRIERS[carrier] end |
#requires_manual_pickup? ⇒ Boolean
235 236 237 |
# File 'app/models/shipping_option.rb', line 235 def requires_manual_pickup? MANUAL_PICKUP_CARRIERS.key?(carrier) end |
#shipping_costs ⇒ ActiveRecord::Relation<ShippingCost>
33 |
# File 'app/models/shipping_option.rb', line 33 has_many :shipping_costs |