Module: Models::ShipQuotable

Extended by:
ActiveSupport::Concern
Includes:
ShipMeasurable
Included in:
Order, Quote
Defined in:
app/concerns/models/ship_quotable.rb

Belongs to collapse

Has many collapse

Instance Method Summary collapse

Methods included from ShipMeasurable

#cartons_total, #crates_total, #pallets_total, #ship_freight_class_from_shipments, #ship_volume_from_shipments, #ship_volume_from_shipments_in_cubic_feet, #ship_weight_from_shipments, #shipment_set, #shipments_for_measure

Instance Method Details

#carrierObject



360
361
362
# File 'app/concerns/models/ship_quotable.rb', line 360

def carrier
  deliveries.first&.carrier
end

#chosen_shipping_methodObject



356
357
358
# File 'app/concerns/models/ship_quotable.rb', line 356

def chosen_shipping_method
  deliveries.first&.chosen_shipping_method
end

#days_commitment(use = :least) ⇒ Object



334
335
336
337
338
339
340
# File 'app/concerns/models/ship_quotable.rb', line 334

def days_commitment(use = :least)
  if use == :least
    deliveries.map { |dq| dq.selected_shipping_cost&.days_commitment || 3 }.min
  else
    deliveries.map { |dq| dq.selected_shipping_cost&.days_commitment || 3 }.max
  end
end

#deliveriesActiveRecord::Relation<Delivery>

Returns:

See Also:



10
# File 'app/concerns/models/ship_quotable.rb', line 10

has_many :deliveries, -> { order(:origin_address_id) }, inverse_of: name.tableize.singularize.to_sym, autosave: true, dependent: :destroy

#determine_origin_address(line_item) ⇒ Object



113
114
115
116
117
118
# File 'app/concerns/models/ship_quotable.rb', line 113

def determine_origin_address(line_item)
  # credit orders should always use the shipping address of the warehouse, in case one of the items was previously shipped from a different location, and STs should always use the shipping address of the store warehouse pending a better solution to multiple warehouse stores (like Amazon)
  return store.warehouse_address if is_a?(Order) && ((order_type == 'CO') || (order_type == 'ST'))

  shipping_address.try(:is_warehouse) || single_origin ? store.warehouse_address : line_item.fulfillment_origin_address
end

#everything_in_stock?Boolean

Returns:

  • (Boolean)


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

def everything_in_stock?
  LineItem.inventory_check(self) == :ok
end

#friendly_shipping_method(show_customer_pays_info = false, for_www = false, with_delivery_commitment = false) ⇒ Object



313
314
315
316
317
318
319
# File 'app/concerns/models/ship_quotable.rb', line 313

def friendly_shipping_method(show_customer_pays_info = false, for_www = false, with_delivery_commitment = false)
  if show_customer_pays_info
    @friendly_shipping_method_with_pay_info ||= retrieve_friendly_shipping_method(show_customer_pays_info, for_www, with_delivery_commitment)
  else
    @friendly_shipping_method ||= retrieve_friendly_shipping_method(show_customer_pays_info, for_www, with_delivery_commitment)
  end
end

#is_drop_ship?Boolean

Returns:

  • (Boolean)


128
129
130
# File 'app/concerns/models/ship_quotable.rb', line 128

def is_drop_ship?
  one_time_shipping_address.present?
end

#is_override?Boolean

Returns:

  • (Boolean)


132
133
134
# File 'app/concerns/models/ship_quotable.rb', line 132

def is_override?
  deliveries.any?(&:override_shipping_method?)
end

#is_warehouse_pickup?Boolean

Returns:

  • (Boolean)


405
406
407
# File 'app/concerns/models/ship_quotable.rb', line 405

def is_warehouse_pickup?
  deliveries.any?(&:is_warehouse_pickup?)
end

#line_items_grouped_by_deliveries_quoting(group_by_item_category = true) ⇒ Object



385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
# File 'app/concerns/models/ship_quotable.rb', line 385

def line_items_grouped_by_deliveries_quoting(group_by_item_category = true)
  items = {}
  # group by origin address (warehouse pickups all have the same origin/destination address)
  deliveries.quoting.to_a.sort_by(&:origin_address_id).each do |dq|
    items[dq] ||= [] # Required to set order
  end
  line_items.parents_only.non_shipping.includes([{ catalog_item: { store_item: :item } }, :item, :delivery, { children: :item }]).to_a.each do |li|
    dq = li.delivery
    items[dq] ||= []
    items[dq] << li
    if dq.nil? && Rails.env.production?
      logger.error "ship_quotable#line_items_grouped_by_deliveries_quoting had non shipping line item(s) with delivery nil #{self.class} ID: #{id}, line item ID: #{li.id}, Line Item:\n#{li.inspect}"
      # Mailer.admin_notification("ship_quotable#line_items_grouped_by_deliveries_quoting had non shipping line item(s) with delivery nil #{self.class} ID: #{self.id}, line item ID: #{li.id}","Line Item:\n#{li.inspect}").deliver_now
    end
  end

  items.each { |key, lines| items[key] = LineItem.group_by_category(lines) } if group_by_item_category
  items
end

#line_items_match_deliveries_if_anyObject



415
416
417
418
419
420
421
422
423
424
425
# File 'app/concerns/models/ship_quotable.rb', line 415

def line_items_match_deliveries_if_any
  res = true
  quoting_deliveries = deliveries.select?(&:quoting?)
  quoting_deliveries_line_items = quoting_deliveries.map { |dq| dq.line_items.active_goods_lines }.flatten
  if quoting_deliveries.present? &&
     (md5_hash_items != md5_hash_items(quoting_deliveries_line_items))
    res = false
    errors.add(:base, "line items don't match all deliveries.select{|d| d.state == 'quoting'} line items")
  end
  res
end

#need_to_pre_pack_reasonsObject



431
432
433
434
435
436
437
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
# File 'app/concerns/models/ship_quotable.rb', line 431

def need_to_pre_pack_reasons
  res = []
  weight_key = :package
  weight_key = :ltl if deliveries.any? { |d| d.ships_ltl_freight? }
  # return [] if all shipments packed
  return res if deliveries.all? { |d| d.shipments.all? { |s| s.packed? } }
  # return [] if we are dealing with a single pallet delivery of less than 400lbs, even a legacy suggested pallet is a pretty decent guess, and skips a lot of back and forth on quote revisions or tweaks of some accessories etc, no need to bother warehouse
  return res if deliveries.size == 1 && deliveries.first.shipments.size == 1 && deliveries.first.shipments.first.pallet? && (ship_weight <= PRE_PACK_SHIPPING_WEIGHT_THRESHOLD[weight_key])

  # see if we can find a packing solution that is not from legacy_packaging algorithm but actually a packing from delivery history, item or manual ie a previous pre-pack
  # Also trust packing_calculator and item_group_packaging — both are computed from actual item shipping dimensions
  # and provide a reliable box plan. Treating them as legacy_packaging would incorrectly trigger pre_pack for
  # any quote whose item combo has no delivery history but whose items have known dimensions (introduced by PackingCalculator, Feb 2026).
  packing_solutions = deliveries.map { |d| Shipping::DeterminePackaging.new.process(delivery: d, is_freight: d.ships_ltl_freight?) }
  return res if packing_solutions.all? { |packing_solution| packing_solution.present? && %w[from_delivery from_item from_manual_entry packing_calculator item_group_packaging].include?(packing_solution.source_type.to_s) }

  # eliminate any custom or drop-ship items (e.g. custom LED mirrors or custom mats) since our warehouse won't know how to estimate packaging, better to use specialized suggested shipping
  if (!line_items.any? do |li|
    li.dropship?
  end) && !is_warehouse_pickup? && ((calculate_shipping_cost > PRE_PACK_SHIPPING_COST_THRESHOLD) || (ship_weight > PRE_PACK_SHIPPING_WEIGHT_THRESHOLD[weight_key]) || (days_commitment < PRE_PACK_DAYS_COMMITMENT_THRESHOLD) || # tweak to pair too many packages criteria with a high enough shipping cost
deliveries.any? do |d|
d.shipments.count > PRE_PACK_NUM_PACKAGES_THRESHOLD && calculate_shipping_cost > 0.5 * PRE_PACK_SHIPPING_COST_THRESHOLD
end)
    res << "Shipping cost of $#{calculate_shipping_cost.round(2)} is too high: exceeds current threshold of $#{PRE_PACK_SHIPPING_COST_THRESHOLD.round(2)} for suggested packaging" if calculate_shipping_cost > PRE_PACK_SHIPPING_COST_THRESHOLD
    res << "Shipping weight of #{ship_weight.round} lbs is too high:  exceeds current threshold of #{PRE_PACK_SHIPPING_WEIGHT_THRESHOLD[weight_key].round} lbs for suggested packaging" if ship_weight > PRE_PACK_SHIPPING_WEIGHT_THRESHOLD[weight_key]
    res << 'Expedited/Express shipping speed needs accurate non-suggested packaging' if days_commitment < PRE_PACK_DAYS_COMMITMENT_THRESHOLD && 2 * calculate_shipping_cost > PRE_PACK_SHIPPING_COST_THRESHOLD
    res << "Too many suggested packages: exceeds current threshold of #{PRE_PACK_NUM_PACKAGES_THRESHOLD}" if deliveries.any? do |d|
      d.shipments.count > PRE_PACK_NUM_PACKAGES_THRESHOLD && calculate_shipping_cost > 0.5 * PRE_PACK_SHIPPING_COST_THRESHOLD
    end
  end
  res
end

#need_to_recalculate_shippingObject



325
326
327
328
# File 'app/concerns/models/ship_quotable.rb', line 325

def need_to_recalculate_shipping
  self.recalculate_shipping = true
  self.do_not_detect_shipping = false
end

#one_time_shipping_addressObject



124
125
126
# File 'app/concerns/models/ship_quotable.rb', line 124

def one_time_shipping_address
  shipping_address if shipping_address && shipping_address.one_time_only
end

#one_time_shipping_address=(val) ⇒ Object



120
121
122
# File 'app/concerns/models/ship_quotable.rb', line 120

def one_time_shipping_address=(val)
  self.shipping_address = val
end

#qualifies_for_cod?Boolean

Returns:

  • (Boolean)


346
347
348
349
350
351
352
353
354
# File 'app/concerns/models/ship_quotable.rb', line 346

def qualifies_for_cod?
  # we limit COD orders to single delivery origin domestic orders from one of our warehouses
  res = false
  res = true if (begin
    (deliveries.quoting.count == 1) && deliveries.quoting.first.origin_address.is_warehouse? && (deliveries.quoting.first.origin_address.country_iso3 == deliveries.quoting.first.destination_address.country_iso3)
  rescue StandardError
    false
  end) && line_items.goods.any?
end

#refresh_deliveries_quoting(include_shipping_lines = false) ⇒ Object

include shipping line used mostly to convert for quote => order conversion so we preserve



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'app/concerns/models/ship_quotable.rb', line 157

def refresh_deliveries_quoting(include_shipping_lines = false)
  logger.tagged "#{self.class.name}/#{id}: ShipQuotable#refresh_deliveries_quoting" do
    logger.debug('refresh_deliveries_quoting',
                 include_shipping_lines: include_shipping_lines,
                 shipping_address_id: shipping_address_id,
                 quoting_delivery_count: deliveries.count { |d| d.state == 'quoting' })
    return [] unless shipping_address.present? || installation_postal_code.present? # just skip if no shipping address or installation postal code

    cod_collection_type_to_use = nil
    if respond_to?(:cod_collection_type)
      self.cod_collection_type = nil unless qualifies_for_cod?
      cod_collection_type_to_use = cod_collection_type
    end
    # Reload deliveries from DB so we see any deliveries created by a concurrent process.
    # Without this, a stale in-memory association can miss deliveries, leaving orphan
    # duplicates that each carry their own shipping charge (see quote 601367).
    deliveries.reload unless new_record?

    # Keep a reference to the old delivery quotes now we will delete them later
    # Keep track of the old delivery quotes attributes, they will be used to merge into the new one.
    # IMPORTANT: ensure we skip the "id" attribute or it will run afoul of the unique delivery_id_idx constraint!
    quoting_deliveries = deliveries.select { |d| (d.quoting? || d.pre_pack?) && !d.marked_for_destruction? && d.persisted? }
    # Remapped object arrays
    remapped_pre_payment_ids = []
    remapped_shipment_ids = []
    old_override_attributes = []
    old_deliveries_quoting_attributes = quoting_deliveries.map do |dq|
      remapped_pre_payment_ids += dq.payments.pluck(:id)
      remapped_shipment_ids += dq.shipments.packed.pluck(:id) unless dq.is_smart_service?
      if dq.selected_shipping_cost.present? && dq.selected_shipping_cost.is_override? && !dq.is_warehouse_pickup? # Here we want to preserve a manually selected override, but not warehouse pickups
        old_override_attributes << {
                  origin_address_id: dq.origin_address_id,
                  cost: dq.selected_shipping_cost.cost,
                  description_override: dq.selected_shipping_cost.description_override
                }
      end
      # Dupe removes the id which is what you want
      if dq.order&.shipment_reference_number.blank? # Here we want to copy the shipment reference (or FBA ID) to carrier_bol when present
        dq.dup.attributes.merge(carrier_bol: dq.order&.shipment_reference_number)
      else
        dq.dup.attributes
      end
    end
    if remapped_pre_payment_ids.empty? && respond_to?(:payment_ids) && payment_ids.present?
      # Let's just assume each payment gets mapped
      payment_ids.each { |pid| remapped_pre_payment_ids << pid }
    end
    remapped_pre_payment_ids.uniq!
    remapped_shipment_ids.uniq!
    # Unmap those pre payments and shipments from their original deliveries from a db point of view
    Payment.where(id: remapped_pre_payment_ids).update_all(delivery_id: nil) if remapped_pre_payment_ids.present?
    Shipment.where(id: remapped_shipment_ids).update_all(delivery_id: nil) if remapped_shipment_ids.present?

    # Kill the quoting deliveries! cascading foreign key might not do things in the right order, so manually
    # do it
    quoting_deliveries.each do |qd|
      logger.debug('Removing quoting delivery', delivery_id: qd.id)
      next unless qd.id && (qd = Delivery.where(id: qd.id).first).present? # Fresh associations if found

      qd.shipments.delete_all # We disassociated earlier the one that matters
      qd.shipping_costs.each do |sc|
        sc.line_items.delete_all
        sc.delete
      end
      qd.delete
    end

    logger.debug('Processing deliveries', old_delivery_count: old_deliveries_quoting_attributes.size)

    if shipping_address || installation_postal_code
      if include_shipping_lines
        line_items_to_use = line_items.active_lines
      else
        line_items_to_use = line_items.active_non_shipping_lines # IMPORTANT, if you use scope you will lose any line items that have not yet been saved
        line_items.active_shipping_lines.each(&:destroy)
      end

      # IMPORTANT: Only process line items that are NOT already assigned to a non-quoting delivery.
      # This prevents orphaning deliveries that have already transitioned to awaiting_po_fulfillment
      # or other non-quoting states (which may have POs or other commitments).
      non_quoting_delivery_ids = deliveries.reject { |d| d.quoting? || d.pre_pack? || d.new_record? }.map(&:id)
      if non_quoting_delivery_ids.any?
        line_items_to_use = line_items_to_use.reject { |li| non_quoting_delivery_ids.include?(li.delivery_id) }
        logger.debug('Filtered out line items already in non-quoting deliveries',
                     non_quoting_delivery_ids: non_quoting_delivery_ids,
                     remaining_count: line_items_to_use.size)
      end

      logger.debug('Line items to process', count: line_items_to_use.size)
      # Group our line items by delivery origin address
      line_items_to_use.to_a.group_by { |li| determine_origin_address(li) }.each do |fulfillment_origin_address, grouped_line_items|
        logger.debug('Processing origin address group',
                     origin_address_id: fulfillment_origin_address.id,
                     line_item_count: grouped_line_items.size)
        # here we try to preserve the same attributes if the delivery quotes are from the same origin
        matched_old_delivery_quoting_attributes = old_deliveries_quoting_attributes.detect do |dqa|
          (dqa['origin_address_id'].to_i == fulfillment_origin_address.id) ||
            ((dqa['origin_address_id'].to_i == 1) && fulfillment_origin_address.is_warehouse?)
        end # little patch for new warehouse address matching
        matched_old_delivery_quoting_attributes ||= {}

        logger.debug('Matched old delivery attributes', matched: matched_old_delivery_quoting_attributes.present?)
        dq_attributes = matched_old_delivery_quoting_attributes.merge(
          state: 'quoting', # here we force state quoting, overrides pre_pack
          supplier_id: fulfillment_origin_address.party_id,
          origin_address_id: fulfillment_origin_address.id,
          bill_shipping_to_customer: customer.bill_shipping_to_customer,
          do_not_recalculate: false,
          cod_collection_type: cod_collection_type_to_use
        )

        # handle signature_confirmation and saturday_delivery options, assume nil means default ie unselected manually as false or true
        dq_attributes[:signature_confirmation] = signature_confirmation if dq_attributes[:signature_confirmation].nil?
        dq_attributes[:saturday_delivery] = saturday_delivery if dq_attributes[:saturday_delivery].nil?

        delivery = deliveries.build(dq_attributes)
        unless shipping_address.nil? || (installation_postal_code && try(:is_www_ship_by_zip))
          if shipping_address.new_record?
            delivery.destination_address = shipping_address # A new one so we have to assign this way and not rely on shipping_address_id
          else
            delivery.destination_address_id = shipping_address.id # Not a new one so we assign using shipping_address_id
          end
        end
        grouped_line_items.each { |li| li.delivery = delivery }

        delivery.line_items = grouped_line_items
        delivery.save!
        # here we try to preserve the same override shipping cost attributes if the delivery quotes are from the same origin
        matched_override_attributes = old_override_attributes.detect do |oa|
          (oa[:origin_address_id] == fulfillment_origin_address.id) ||
            ((oa[:origin_address_id] == 1) && fulfillment_origin_address.is_warehouse?)
        end
        if matched_override_attributes
          # we have something, so create a shipping cost with these attributes
          so = ShippingOption.where(name: 'override', country: delivery.resource.store.country.iso).first
          shpcst = ShippingCost.new(shipping_option: so, cost: matched_override_attributes[:cost], description_override: matched_override_attributes[:description_override])
          delivery.shipping_costs << shpcst
          # Update both selected_shipping_cost_id AND shipping_option_id to preserve the override
          # This ensures the override is fully restored after the delivery is recreated
          delivery.update_columns(selected_shipping_cost_id: shpcst.id, shipping_option_id: so.id)
        end
        # Remap our unmapped payments and shipments! doing it this way so we get an audit trail
        Payment.where(id: remapped_pre_payment_ids).each { |pp| pp.update_attribute(:delivery_id, delivery.id) } if remapped_pre_payment_ids.present?
        Shipment.where(id: remapped_shipment_ids).each { |shp| shp.update_attribute(:delivery_id, delivery.id) } if remapped_shipment_ids.present? && !delivery.is_smart_service?
        if remapped_shipment_ids.present? && delivery.quantities_remaining_to_allocate > 0 && delivery.delta_weight_factor_remaining_to_allocate > DELTA_WEIGHT_FACTOR_THRESHOLD_TO_REPACK_LTL # && delivery.delta_volume_factor_remaining_to_allocate > DELTA_VOLUME_FACTOR_THRESHOLD_TO_REPACK_LTL)
          # We want to make these unpacked since delivery items have changed and enough are unallocated
          # Here we do not unpack LTL freight if the delta_weight_remaining_to_allocate is less than DELTA_WEIGHT_FACTOR_THRESHOLD_TO_REPACK, because pallets do not change even if we change a lot items, and all weights are measured before ship labeling.
          Shipment.where(id: remapped_shipment_ids).each { |shp| shp.unpack }
        end
        logger.debug('Created delivery', delivery_id: delivery.id, line_item_count: delivery.line_items.size)
      end
    end
  end
  deliveries.quoting
end

#reset_deliveries_shipping_optionObject



105
106
107
108
109
110
111
# File 'app/concerns/models/ship_quotable.rb', line 105

def reset_deliveries_shipping_option
  # this could be called on a before_save callback so don't assume arive_record associations or save, just reset all deliveries shipping_option field to nil. This is so that on say a change of shipping address, we don't inherit the previous delivery's shipping option when recalculating shipping, like we might on a change of items, but fall back to customer's default.
  Rails.logger.debug { "reset_deliveries_shipping_option called for shipquotable: #{self.class} #{id}, #{reference_number}" }
  deliveries.each do |d|
    d.shipping_option = nil
  end
end

#reset_shipping(message = nil, autosave = true) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/concerns/models/ship_quotable.rb', line 91

def reset_shipping(message = nil, autosave = true)
  message ||= 'Shipping was reset'
  self.shipping_cost = 0.0
  self.recalculate_shipping = false
  self.do_not_detect_shipping = true
  self.last_shipping_rate_request_result = [{ code: :shipping_reset, message: message }]
  line_items.shipping_only.destroy_all
  deliveries.select { |d| %w[quoting pre_pack].include?(d.state) }.each(&:destroy!)
  return unless autosave

  save
  reload
end

#retrieve_friendly_shipping_method(show_customer_pays_info = false, for_www = false, with_delivery_commitment = false) ⇒ Object



321
322
323
# File 'app/concerns/models/ship_quotable.rb', line 321

def retrieve_friendly_shipping_method(show_customer_pays_info = false, for_www = false, with_delivery_commitment = false)
  deliveries.map { |dq| dq.friendly_shipping_method(show_customer_pays_info, for_www, with_delivery_commitment) }.join(', ')
end

#retrieve_shipping_costs(rate_ship_date: nil) ⇒ Object

Retrieve shipping costs for all deliveries

Parameters:

  • rate_ship_date (Date, nil) (defaults to: nil)

    Optional date to use for rate calculation (user can override)



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/concerns/models/ship_quotable.rb', line 20

def retrieve_shipping_costs(rate_ship_date: nil)
  # Guard against recursion - this can happen when delivery.save! triggers
  # callbacks (like reset_ships_economy_if_unselected) that save the parent order again
  return [{ code: :already_processing, message: 'Shipping costs are already being retrieved' }] if @retrieve_shipping_costs_guard

  @retrieve_shipping_costs_guard = true

  lock_key = "#{self.class.name.downcase}|#{id || object_id}|retrieve_shipping_costs".freeze
  result = self.class.with_advisory_lock_result(lock_key, timeout_seconds: 0) do
    logger.tagged lock_key do
      if new_record?
        self.retrieving_shipping_costs = true
      else
        update_column(:retrieving_shipping_costs, true)
      end
      logger.debug('retrieve_shipping_costs starting', shipping_address_id: shipping_address_id, rate_ship_date: rate_ship_date)
      retrieve = try(:editing_locked?) ||
                 shipping_address.present? ||
                 (installation_postal_code.present? && try(:draft?))

      if retrieve
        unless begin
          editing_locked?
        rescue StandardError
          false
        end
          if shipping_address_id_changed? && shipping_address_id_was && WAREHOUSE_ADDRESS_IDS.include?(shipping_address_id_was)
            # we reset selected shipping options because this is like a new shipping calculation and we want sensible defaults, not override
            deliveries.each { |d| d.update_columns(selected_shipping_cost_id: nil, shipping_option_id: nil) }
          end

          deliveries_to_process = refresh_deliveries_quoting.reject(&:do_not_recalculate)

          deliveries_to_process.each do |delivery|
            delivery.retrieve_shipping_costs(rate_ship_date: rate_ship_date)
            # Trigger set_proper_shipping_cost callback now that shipping costs are available
            delivery.reload # Ensure shipping_costs association is fresh
            delivery.updated_at = Time.current # Mark as changed to trigger before_save
            delivery.save! # Trigger before_save callback which calls set_proper_shipping_cost
          end
        end
        # Reload the association so deleted deliveries (removed by refresh_deliveries_quoting)
        # are not included — stale in-memory records would carry ghost error messages.
        self.last_shipping_rate_request_result = deliveries.reload.select { |d| d.state == 'quoting' }.map(&:last_shipping_rate_request_result)
        # Persist the newly built messaging_log so the next request (after redirect) can read it.
        messaging_logs.select(&:new_record?).each(&:save!) unless new_record?
      else
        reset_shipping 'No shipping address provided - shipping will not be calculated', false
      end

      self.recalculate_shipping = false
      self.shipping_issue_alerted = false
      logger.debug('retrieve_shipping_costs complete')
      if new_record?
        self.retrieving_shipping_costs = false
      else
        update_columns(recalculate_shipping: false, retrieving_shipping_costs: false)
      end
    end
    last_shipping_rate_request_result
  end

  # If lock was not acquired, return already processing message
  return [{ code: :already_processing, message: 'Shipping costs are already being retrieved' }] unless result.lock_was_acquired?

  # Return the result from the block (last_shipping_rate_request_result)
  result.result
ensure
  @retrieve_shipping_costs_guard = false
end

#ship_quotedObject



427
428
429
# File 'app/concerns/models/ship_quotable.rb', line 427

def ship_quoted
  deliveries.any?(&:quoting?) && prevent_recalculate_shipping?
end

#ship_weightObject



330
331
332
# File 'app/concerns/models/ship_quotable.rb', line 330

def ship_weight
  @ship_weight ||= [0.1, line_items.includes(:item).non_shipping.without_children.map(&:total_shipping_weight).sum.round(1)].max
end

#shipmentsActiveRecord::Relation<Shipment>

Returns:

See Also:



11
# File 'app/concerns/models/ship_quotable.rb', line 11

has_many :shipments, through: :deliveries

#shipping_addressAddress

Returns:

See Also:



8
# File 'app/concerns/models/ship_quotable.rb', line 8

belongs_to :shipping_address, class_name: 'Address', validate: true, optional: true

#shipping_non_db_default_but_db_customer?Boolean

Returns:

  • (Boolean)


372
373
374
375
376
377
378
379
# File 'app/concerns/models/ship_quotable.rb', line 372

def shipping_non_db_default_but_db_customer?
  (begin
    customer.is_direct_buy? && chosen_shipping_method && (Customer.find(Customer::DB_PARENT_ID).(carrier, billing_address,
shipping_address).nil? || (chosen_shipping_method. != Customer.find(Customer::DB_PARENT_ID).(carrier, billing_address, shipping_address)))
  rescue StandardError
    false
  end)
end

#shipping_signature_confirmation_non_db_default_but_db_customer?Boolean

Returns:

  • (Boolean)


381
382
383
# File 'app/concerns/models/ship_quotable.rb', line 381

def shipping_signature_confirmation_non_db_default_but_db_customer?
  customer&.is_direct_buy? && chosen_shipping_method && !signature_confirmation
end

#shipping_via_wy_but_has_shipping_account?Boolean

Returns:

  • (Boolean)


364
365
366
367
368
369
370
# File 'app/concerns/models/ship_quotable.rb', line 364

def shipping_via_wy_but_has_shipping_account?
  (begin
    chosen_shipping_method && chosen_shipping_method. && customer.(carrier, billing_address, shipping_address).index(chosen_shipping_method.).nil?
  rescue StandardError
    false
  end)
end

#ships_economy_package?Boolean

Returns:

  • (Boolean)


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

def ships_economy_package?
  deliveries.all?(&:ships_economy_package?)
end

#ships_freight?Boolean

Returns:

  • (Boolean)


144
145
146
# File 'app/concerns/models/ship_quotable.rb', line 144

def ships_freight?
  deliveries.any?(&:ships_ltl_freight?)
end

#ships_freight_but_address_not_freight_ready?Boolean

Returns:

  • (Boolean)


136
137
138
# File 'app/concerns/models/ship_quotable.rb', line 136

def ships_freight_but_address_not_freight_ready?
  ships_freight? && shipping_address && !shipping_address.is_valid_for_freight?
end

#should_ship_freight?Boolean

Returns:

  • (Boolean)


152
153
154
# File 'app/concerns/models/ship_quotable.rb', line 152

def should_ship_freight?
  deliveries.any?(&:should_ship_ltl_freight?)
end

#should_ship_freight_but_address_not_freight_ready?Boolean

Returns:

  • (Boolean)


140
141
142
# File 'app/concerns/models/ship_quotable.rb', line 140

def should_ship_freight_but_address_not_freight_ready?
  should_ship_freight? && shipping_address && !shipping_address.is_valid_for_freight?
end

#validate_deliveriesObject



409
410
411
412
413
# File 'app/concerns/models/ship_quotable.rb', line 409

def validate_deliveries
  deliveries.quoting.each do |dq|
    errors.add(:base, {}.merge(eval(dq.last_shipping_rate_request_result.to_s))[:message]) if dq.shipping_costs.empty?
  end
end