Module: Models::ShipQuotable

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

Overview

ActiveSupport::Concern mixin: ship quotable.

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



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

def carrier
  deliveries.first&.carrier
end

#chosen_shipping_methodObject



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

def chosen_shipping_method
  deliveries.first&.chosen_shipping_method
end

#days_commitment(use = :least) ⇒ Object



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

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:



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

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

#determine_origin_address(line_item) ⇒ Object



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

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)


344
345
346
# File 'app/concerns/models/ship_quotable.rb', line 344

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



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

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)


130
131
132
# File 'app/concerns/models/ship_quotable.rb', line 130

def is_drop_ship?
  one_time_shipping_address.present?
end

#is_override?Boolean

Returns:

  • (Boolean)


134
135
136
# File 'app/concerns/models/ship_quotable.rb', line 134

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

#is_warehouse_pickup?Boolean

Returns:

  • (Boolean)


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

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

#line_items_grouped_by_deliveries_quoting(group_by_item_category = true) ⇒ Object



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

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



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

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



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
463
464
# File 'app/concerns/models/ship_quotable.rb', line 432

def need_to_pre_pack_reasons
  res = []
  weight_key = :package
  weight_key = :ltl if deliveries.any?(&:ships_ltl_freight?)
  # return [] if all shipments packed
  return res if deliveries.all? { |d| d.shipments.all?(&: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])

  # Skip pre-pack only when EVERY delivery's packing comes from a trusted source —
  # actual history (from_delivery / from_item / from_manual_entry) or a modern
  # parcel calculator (packing_calculator / item_group_packaging) computed from
  # real item shipping dimensions. The freight fallback (`:freight_calculator`,
  # formerly `:legacy_packaging`) stays OUT of the trusted list because its
  # density-weighted approximation is less reliable for billing — those orders
  # still get the pre_pack reason check below.
  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.none?(&:dropship?) && !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



327
328
329
330
# File 'app/concerns/models/ship_quotable.rb', line 327

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

#one_time_shipping_addressObject



126
127
128
# File 'app/concerns/models/ship_quotable.rb', line 126

def one_time_shipping_address
  shipping_address if shipping_address&.one_time_only
end

#one_time_shipping_address=(val) ⇒ Object



122
123
124
# File 'app/concerns/models/ship_quotable.rb', line 122

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

#qualifies_for_cod?Boolean

Returns:

  • (Boolean)


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

def qualifies_for_cod?
  # we limit COD orders to single delivery origin domestic orders from one of our warehouses
  true if (begin
    deliveries.quoting.one? && 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



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
312
313
# File 'app/concerns/models/ship_quotable.rb', line 159

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.ids
      remapped_shipment_ids += dq.shipments.packed.ids 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.destroy_all # We disassociated earlier the one that matters; destroy_all runs Shipment#destroy so shipment_events nullify (FK is RESTRICT)
      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.find 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
        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.find 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).find_each { |pp| pp.update_attribute!(:delivery_id, delivery.id) } if remapped_pre_payment_ids.present?
        Shipment.where(id: remapped_shipment_ids).find_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).find_each(&: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



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

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



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

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



323
324
325
# File 'app/concerns/models/ship_quotable.rb', line 323

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)



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
90
91
# File 'app/concerns/models/ship_quotable.rb', line 22

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



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

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

#ship_weightObject



332
333
334
# File 'app/concerns/models/ship_quotable.rb', line 332

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

#shipmentsActiveRecord::Relation<Shipment>

Returns:

See Also:



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

has_many :shipments, through: :deliveries

#shipping_addressAddress

Returns:

See Also:



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

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

#shipping_non_db_default_but_db_customer?Boolean

Returns:

  • (Boolean)


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

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)


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

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)


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

def shipping_via_wy_but_has_shipping_account?
  (begin
    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)


150
151
152
# File 'app/concerns/models/ship_quotable.rb', line 150

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

#ships_freight?Boolean

Returns:

  • (Boolean)


146
147
148
# File 'app/concerns/models/ship_quotable.rb', line 146

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

#ships_freight_but_address_not_freight_ready?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'app/concerns/models/ship_quotable.rb', line 138

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)


154
155
156
# File 'app/concerns/models/ship_quotable.rb', line 154

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

#should_ship_freight_but_address_not_freight_ready?Boolean

Returns:

  • (Boolean)


142
143
144
# File 'app/concerns/models/ship_quotable.rb', line 142

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

#validate_deliveriesObject



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

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? # rubocop:disable Security/Eval
  end
end