Module: DeliveriesHelper

Defined in:
app/helpers/deliveries_helper.rb

Overview

View helper: deliveries.

Instance Method Summary collapse

Instance Method Details



190
191
192
# File 'app/helpers/deliveries_helper.rb', line 190

def add_delivery_shipment_link(name)
  link_to name, '#', onclick: "$('#shipments').append('#{j(render(partial: 'shipment', locals: { remove_link: true, package: WarehousePackage.new, shipment: Shipment.new }))}');return false", class: 'btn btn-outline-primary me-3'
end


186
187
188
# File 'app/helpers/deliveries_helper.rb', line 186

def add_shipment_link(name)
  link_to name, '#', onclick: "$('#shipments').append('#{j(render(partial: 'shipment', object: Shipment.new))}')"
end

#address_hash_to_s(address_hash) ⇒ Object



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'app/helpers/deliveries_helper.rb', line 306

def address_hash_to_s(address_hash)
  freight_fields_text = if address_hash[:is_residential]
                          '(Residential'
                        else
                          '(Commercial'
                        end
  freight_fields_text += (address_hash[:require_signature_by_default] ? ', require signature by default' : '')
  freight_fields_text += (address_hash[:has_loading_dock] ? ', has loading dock' : '')
  freight_fields_text += (address_hash[:requires_inside_delivery] ? ', requires inside delivery' : '')
  freight_fields_text += (address_hash[:requires_liftgate] ? ', requires lift-gate' : '')
  freight_fields_text += (address_hash[:is_construction_site] ? ', is construction site' : '')
  freight_fields_text += (address_hash[:limited_access] ? ', limited access' : '')
  freight_fields_text += (address_hash[:requires_appointment] ? ', requires delivery appointment' : '')
  freight_fields_text += (address_hash[:timezone_name].present? ? ", TZ: #{address_hash[:timezone_name]}" : '')
  freight_fields_text += ')'
  "#{[address_hash[:street1], address_hash[:street2], address_hash[:city], address_hash[:state_code], address_hash[:country_iso]].compact.join(', ')} #{freight_fields_text}"
end

#already_pre_packed?(delivery) ⇒ Boolean

Returns:

  • (Boolean)


221
222
223
# File 'app/helpers/deliveries_helper.rb', line 221

def already_pre_packed?(delivery)
  delivery.shipments.all?(&:packed_or_pre_packed?) && !(current_user.has_role?('warehouse_rep') || current_user.is_manager?) # don't allow pre-pack if already pre-packed, unless warehouse staff or admin
end

#amz_bs_badgeString

Get a badge for Amazon Buy Shipping carrier type

Returns:

  • (String)

    HTML-safe badge span



401
402
403
# File 'app/helpers/deliveries_helper.rb', line 401

def amz_bs_badge
  (:span, 'Amazon Buy Shipping', class: 'badge bg-warning text-dark', title: 'Amazon Buy Shipping rate with A-to-Z protections')
end

#delivery_request_estimated_packaging_form(delivery, return_path = nil) ⇒ Object



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'app/helpers/deliveries_helper.rb', line 225

def delivery_request_estimated_packaging_form(delivery, return_path = nil)
  return unless show_delivery_pre_pack_option(delivery)

  pp_disabled = already_pre_packed?(delivery)
  simple_form_for delivery, url: request_estimated_packaging_delivery_path(delivery, return_path: return_path || polymorphic_url(delivery.resource, tab: 'shipping')),
                            html: { data: { turbo_frame: '_top' } } do |f|
    (:div, class: 'btn-group') do
      concat f.input_field(:suggested_packaging_text,
                           as: :string,
                           placeholder: 'Packaging directions (optional)',
                           size: 40,
                           class: 'form-control rounded-0 rounded-start')
      concat f.button(:submit, (pp_disabled ? 'Already packaged, contact Warehouse to request repackaging' : 'Request Estimated Packaging').to_s, class: 'btn btn-outline-primary', disabled: pp_disabled)
    end
  end
end


242
243
244
245
246
247
248
# File 'app/helpers/deliveries_helper.rb', line 242

def delivery_request_estimated_packaging_link(delivery, return_path = nil)
  return unless show_delivery_pre_pack_option(delivery)

  pp_disabled = already_pre_packed?(delivery)
  link_to((pp_disabled ? 'Already packaged, contact Warehouse to request repackaging' : 'Request Estimated Packaging').to_s,
          (pp_disabled ? 'javascript:void(0);' : workflow_action_delivery_path(delivery, return_path: return_path || polymorphic_url(delivery.resource, tab: 'shipping'), wf_action: 'request_estimated_packaging')), data: { turbo_method: :post, turbo_frame: '_top' }, class: 'btn btn-outline-primary')
end

#delivery_tab_options(delivery_presenter) ⇒ Object



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
# File 'app/helpers/deliveries_helper.rb', line 256

def delivery_tab_options(delivery_presenter)
  delivery = delivery_presenter.delivery
  tab_panels = {
    main: { remote_href: tab_main_delivery_path(delivery) },
    shipping: { remote_href: tab_shipping_delivery_path(delivery) },
    line_items: { remote_href: tab_line_items_delivery_path(delivery) },
    activities: { counter: delivery_presenter.open_activities_counter, remote_href: delivery_activities_path(delivery, selected_activity: params[:selected_activity]) },
    attachments: { counter: delivery_presenter.uploads.size, remote_href: delivery_uploads_path(delivery) }
  }
  if delivery.order
    tab_panels.merge!({
                        invoices: { remote_href: tab_invoices_delivery_path(delivery) },
                        item_ledger: { remote_href: tab_item_ledger_delivery_path(delivery) },
                        account_ledger: { remote_href: (delivery) }
                      })
  end
  log_size = Array(delivery.shipping_api_log).size
  if log_size.positive?
    tab_panels[:shipping_api_log] = {
      title: 'Shipping API Log',
      counter: log_size,
      remote_href: tab_shipping_api_log_delivery_path(delivery)
    }
  end
  freight_events_count = delivery.freight_events.size
  if freight_events_count.positive?
    tab_panels[:freight_events] = {
      title: 'Freight Events',
      counter: freight_events_count,
      remote_href: tab_freight_events_delivery_path(delivery)
    }
  end
  # Surface the Tracking Events tab whenever the delivery has accumulated
  # ShipEngine scan history. Counts events by tracking_number(s), not by
  # shipment_id — offbook ST shipments legitimately share a tracking number
  # across multiple Shipment rows, and ShipEngine LTL keys off the delivery's
  # PRO (no per-pallet tracking_number). `shipment_event_tracking_numbers`
  # unifies both. Matches the partial's query shape.
  tracking_numbers = delivery.shipment_event_tracking_numbers
  tracking_events_count = tracking_numbers.any? ? ShipmentEvent.where(tracking_number: tracking_numbers).count : 0
  if tracking_events_count.positive?
    tab_panels[:tracking_events] = {
      title: 'Tracking Events',
      counter: tracking_events_count,
      remote_href: tab_tracking_events_delivery_path(delivery)
    }
  end
  tab_panels
end


152
153
154
155
156
157
158
159
160
161
# File 'app/helpers/deliveries_helper.rb', line 152

def edit_delivery_packing_link(delivery = @delivery, return_path: nil)
  return unless delivery.shipment_contents_editable?(current_user)

  data_hsh = if current_user.has_role?('warehouse_rep')
               nil
             else
               { turbo_confirm: "Are you sure? Packaging is usually defined by the warehouse, but if you know what you're doing, please go ahead." }
             end
  link_to 'Edit Packing', picked_delivery_path(delivery, return_path:), data: (data_hsh || {}).merge(turbo_frame: '_top'), class: 'btn btn-outline-primary m-2'
end

#electronic_commercial_invoice_messageObject



324
325
326
327
328
# File 'app/helpers/deliveries_helper.rb', line 324

def electronic_commercial_invoice_message
  return unless @delivery.electronic_ship_ci_pdf || @delivery.should_have_electronic_commercial_invoice?

  'DO NOT PRINT COMMERCIAL INVOICE WHEN UPS OR FEDEX LABEL INDICATES EDI, EDI-PULL or ETD: IT IS HANDLED ELECTRONICALLY.'
end

#fields_for_shipment(shipment) ⇒ Object



181
182
183
184
# File 'app/helpers/deliveries_helper.rb', line 181

def fields_for_shipment(shipment, &)
  prefix = shipment.new_record? ? 'new' : 'existing'
  fields_for("delivery[#{prefix}_shipment_attributes][]", shipment, &)
end

#format_shipping_rate_with_marketplace_badge(shipping_cost, show_price: true) ⇒ String Also known as: format_shipping_rate_with_sww_badge

Format a shipping rate with appropriate badge if it's a marketplace rate
(Ship with Walmart or Amazon Buy Shipping)

Parameters:

  • shipping_cost (ShippingCost)

    The shipping cost record

  • show_price (Boolean) (defaults to: true)

    Whether to show the price

Returns:

  • (String)

    HTML-safe formatted string with optional marketplace badge



336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'app/helpers/deliveries_helper.rb', line 336

def format_shipping_rate_with_marketplace_badge(shipping_cost, show_price: true)
  return '' unless shipping_cost

  carrier = shipping_cost.respond_to?(:carrier) ? shipping_cost.carrier : shipping_cost.shipping_option&.carrier
  rd = shipping_cost.rate_data&.with_indifferent_access || {}

  parts = []

  if carrier == 'WalmartSeller' || is_sww_rate?(shipping_cost)
    parts << (:span, 'Ship with Walmart', class: 'badge bg-primary me-1')
    parts << [rd[:sww_carrier_name] || 'Unknown', rd[:sww_service_name].presence].compact.join(' - ')
  elsif carrier == 'AmazonSeller' || is_amz_bs_rate?(shipping_cost)
    parts << (:span, 'Amazon Buy Shipping', class: 'badge bg-warning text-dark me-1')
    parts << [rd[:amz_carrier_name] || 'Unknown', rd[:amz_service_name].presence].compact.join(' - ')
  else
    parts << (shipping_cost.try(:description) || shipping_cost.shipping_option&.name)
  end

  # Add price if requested
  if show_price && shipping_cost.cost.present?
    parts << ' - '
    parts << number_to_currency(shipping_cost.cost)
  end

  safe_join(parts)
end

#freight_event_status_icons(delivery, size: nil) ⇒ ActiveSupport::SafeBuffer?

Render the inline freight-event status icon row for a Freightquote LTL
delivery — one icon each for load / pickup appointment / delivery,
appearing only when at least one event in that bucket has landed.
Returns +nil+ (no markup) when the delivery is not Freightquote or has
no relevant events yet, so callers can splat this into any cell without
adding stray whitespace.

Used in two places:

  • app/views/crm/warehouses/_delivery.html.erb — beside the shipping
    description cell on the warehouse dashboard
  • app/views/deliveries/show.html.erb — beside the delivery heading

Parameters:

  • delivery (Delivery)
  • size (Symbol, String, nil) (defaults to: nil)

    optional Font Awesome size class
    (e.g. +:lg+ → 'fa-lg') for the heading vs row variants.

Returns:

  • (ActiveSupport::SafeBuffer, nil)


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
# File 'app/helpers/deliveries_helper.rb', line 20

def freight_event_status_icons(delivery, size: nil)
  return nil unless delivery&.carrier == 'Freightquote'

  badges = FreightEventStatusSummary.for(delivery)
  return nil if badges.empty?

  # Wrap in `data-controller="tooltip"` so the project's tooltip
  # Stimulus controller initialises the Bootstrap tooltips inside
  # this scope, even when the surrounding view doesn't already.
  icons = safe_join(
    badges.map do |badge|
      icon_class = ["text-#{badge.color}", 'ms-2']
      icon_class << "fa-#{size}" if size.present?
      fa_icon(
        badge.icon,
        family: :solid,
        class: icon_class.compact.join(' '),
        'data-bs-toggle': 'tooltip',
        title: badge.tooltip,
        'aria-label': badge.tooltip
      )
    end
  )
  tag.span(icons, class: 'freight-event-status-icons', data: { controller: 'tooltip' })
end

#is_amz_bs_rate?(shipping_cost) ⇒ Boolean

Check if a shipping cost is an Amazon Buy Shipping rate

Parameters:

Returns:

  • (Boolean)


381
382
383
384
385
386
387
388
# File 'app/helpers/deliveries_helper.rb', line 381

def is_amz_bs_rate?(shipping_cost)
  return false unless shipping_cost

  rate_data = shipping_cost.rate_data
  return false unless rate_data.is_a?(Hash)

  rate_data['amz_carrier_id'].present? || rate_data[:amz_carrier_id].present?
end

#is_sww_rate?(shipping_cost) ⇒ Boolean

Check if a shipping cost is a Ship with Walmart rate

Parameters:

Returns:

  • (Boolean)


368
369
370
371
372
373
374
375
# File 'app/helpers/deliveries_helper.rb', line 368

def is_sww_rate?(shipping_cost)
  return false unless shipping_cost

  rate_data = shipping_cost.rate_data
  return false unless rate_data.is_a?(Hash)

  rate_data['sww_carrier_id'].present? || rate_data[:sww_carrier_id].present?
end

#pack_shipment_delete_button(f) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
# File 'app/helpers/deliveries_helper.rb', line 169

def pack_shipment_delete_button(f)
  if f.object.persisted?
    capture do
      concat f.hidden_field '_destroy'
      concat f.label '_destroy', fa_icon('trash'), class: 'inline'
      concat f.check_box '_destroy', class: 'destroy'
    end
  else
    button_tag(fa_icon('trash'), type: 'button', class: 'btn pack-shipment-remove', data: { action: 'delivery-nested-form#remove' })
  end
end

#render_ship_quotable_command_options(ship_quotable) ⇒ Object



250
251
252
253
254
# File 'app/helpers/deliveries_helper.rb', line 250

def render_ship_quotable_command_options(ship_quotable)
  (:ul, class: 'list-inline') do
    ship_quotable_command_options(ship_quotable).map { |o| (:li, o, class: 'list-inline-item') }.join.html_safe
  end
end

#render_tracking_status_badge(badge, size: nil) ⇒ ActiveSupport::SafeBuffer?

Shared markup for a single tracking-status badge (parcel + LTL). Returns
+nil+ for a nil badge so callers can splat without stray whitespace.

Parameters:

Returns:

  • (ActiveSupport::SafeBuffer, nil)


136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'app/helpers/deliveries_helper.rb', line 136

def render_tracking_status_badge(badge, size: nil)
  return nil unless badge

  icon_class = ["text-#{badge.color}", 'ms-2']
  icon_class << "fa-#{size}" if size.present?
  icon = fa_icon(
    badge.icon,
    family: :solid,
    class: icon_class.compact.join(' '),
    'data-bs-toggle': 'tooltip',
    title: badge.tooltip,
    'aria-label': badge.tooltip
  )
  tag.span(icon, class: 'shipment-event-status-icon', data: { controller: 'tooltip' })
end

#setup_delivery_for_packing(delivery) ⇒ Object



163
164
165
166
167
# File 'app/helpers/deliveries_helper.rb', line 163

def setup_delivery_for_packing(delivery)
  delivery.tap do |d|
    d.shipments.build(state: 'suggested') unless d.shipments.any? { |s| s.suggested? || s.packed? || s.awaiting_label? }
  end
end

#ship_quotable_command_options(ship_quotable) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'app/helpers/deliveries_helper.rb', line 194

def ship_quotable_command_options(ship_quotable)
  opts = []
  unless ship_quotable.is_warehouse_pickup? || (ship_quotable.try(:order_type) == Order::CREDIT_ORDER)
    # Use data attributes to pass ship dates when recalculating
    opts << link_to('Recalculate Shipping for all Deliveries',
                    recalculate_shipping_all_deliveries_path(ship_quotable_type: @ship_quotable.class.to_s, ship_quotable_id: @ship_quotable.id, return_path: @return_path),
                    class: 'btn btn-outline-primary',
                    data: { controller: 'recalculate-shipping', action: 'click->recalculate-shipping#recalculate', turbo_frame: 'shipping_services' })
    if ship_quotable.single_origin
      opts << link_to('Allow multiple deliveries (drop ships)', multiple_origin_deliveries_path(ship_quotable_type: @ship_quotable.class.to_s, ship_quotable_id: @ship_quotable.id, return_path: @return_path),
                      class: 'btn btn-outline-primary',
                      data: { turbo_frame: 'shipping_services' })
    elsif can?(
      :change_delivery_origin, @ship_quotable
    )
      opts << link_to('Force one delivery from our warehouse', single_origin_deliveries_path(ship_quotable_type: @ship_quotable.class.to_s, ship_quotable_id: @ship_quotable.id, return_path: @return_path),
                      class: 'btn btn-outline-primary',
                      data: { turbo_frame: 'shipping_services' })
    end
  end
  opts
end

#shipengine_ltl_status_icon(delivery, size: nil, events_by_number: nil) ⇒ ActiveSupport::SafeBuffer?

Render the ShipEngine LTL freight tracking status icon for a delivery —
ONE badge per delivery (LTL is a single freight shipment under one PRO,
unlike parcel which is per-package). Reuses ShipmentEventStatusSummary
off the delivery's ltl_pro_number, so the icon/color/tooltip are
identical to the ShipEngine package badge. Returns +nil+ when the delivery
isn't ShipEngine LTL, has no PRO, or has no scans yet — so callers can
splat it into any cell without stray whitespace.

Parameters:

  • delivery (Delivery)
  • size (Symbol, String, nil) (defaults to: nil)

    optional Font Awesome size class

  • events_by_number (Hash, nil) (defaults to: nil)

    pre-loaded events for list views.

Returns:

  • (ActiveSupport::SafeBuffer, nil)


117
118
119
120
121
122
123
124
125
126
127
128
# File 'app/helpers/deliveries_helper.rb', line 117

def shipengine_ltl_status_icon(delivery, size: nil, events_by_number: nil)
  return nil unless delivery
  return nil unless ShippingOption::SHIPENGINE_LTL_CARRIERS.include?(delivery.shipping_option&.carrier)
  return nil if delivery.ltl_pro_number.blank?

  badge = if events_by_number
            ShipmentEventStatusSummary.from_events(events_by_number[delivery.ltl_pro_number])
          else
            ShipmentEventStatusSummary.for_tracking_number(delivery.ltl_pro_number)
          end
  render_tracking_status_badge(badge, size: size)
end

#shipment_event_status_icon(shipment, size: nil, events_by_number: nil) ⇒ ActiveSupport::SafeBuffer?

Render the inline ShipEngine tracking status icon for a parcel
shipment — at most one badge per shipment, reflecting its most-
advanced scan (delivered > exception > delivery attempt > in transit

accepted). Returns +nil+ when the shipment has no scans yet so
callers can splat into any cell without adding stray whitespace.

Parameters:

  • shipment (Shipment)
  • size (Symbol, String, nil) (defaults to: nil)

    optional Font Awesome size class
    (e.g. +:lg+ → 'fa-lg') for heading vs row variants.

  • events_by_number (Hash, nil) (defaults to: nil)

    tracking_number => Array.
    When provided (list views), the badge is computed from this pre-loaded
    slice instead of querying per shipment. nil → query (show-page path).

Returns:

  • (ActiveSupport::SafeBuffer, nil)


59
60
61
62
63
64
65
66
67
68
# File 'app/helpers/deliveries_helper.rb', line 59

def shipment_event_status_icon(shipment, size: nil, events_by_number: nil)
  return nil if shipment.blank?

  badge = if events_by_number
            ShipmentEventStatusSummary.from_events(events_by_number[shipment.tracking_number])
          else
            ShipmentEventStatusSummary.for(shipment)
          end
  render_tracking_status_badge(badge, size: size)
end

#shipment_event_status_icons_for_delivery(delivery, size: nil, events_by_number: nil) ⇒ ActiveSupport::SafeBuffer?

Aggregate variant of #shipment_event_status_icon for the delivery-
level heading on the show page. Renders one icon per parcel shipment
under the delivery, in shipment-creation order, mirroring
#freight_event_status_icons's inline placement next to the delivery
title. Returns +nil+ when none of the shipments have any scans yet.

Scope: every shipment with a tracking_number. We do NOT filter by
PARCEL_CARRIER_INTERNAL_TO_SHIPENGINE_CODE here — shipments.carrier
often holds a service-level name ("UPS Standard", "FedEx Ground")
rather than the canonical carrier, and an exact-match filter
silently drops those shipments from the heading even when their
ShipmentEvent rows are populated. The per-shipment
shipment_event_status_icon already short-circuits to nil when no
events exist, so any shipment without scans contributes nothing.

Parameters:

  • delivery (Delivery)
  • size (Symbol, String, nil) (defaults to: nil)

    optional Font Awesome size class

  • events_by_number (Hash, nil) (defaults to: nil)

    pre-loaded events for list views (see
    #shipment_event_status_icon); when given, shipments are read from the
    already-loaded association in memory rather than re-queried.

Returns:

  • (ActiveSupport::SafeBuffer, nil)


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

def shipment_event_status_icons_for_delivery(delivery, size: nil, events_by_number: nil)
  return nil unless delivery

  shipments = if events_by_number
                delivery.shipments.select { |s| s.tracking_number.present? }.sort_by(&:id)
              else
                delivery.shipments.where.not(tracking_number: [nil, '']).order(:id)
              end
  return nil if shipments.empty?

  icons = shipments.filter_map { |s| shipment_event_status_icon(s, size: size, events_by_number: events_by_number) }
  safe_join(icons).presence
end

#show_delivery_pre_pack_option(delivery) ⇒ Object



217
218
219
# File 'app/helpers/deliveries_helper.rb', line 217

def show_delivery_pre_pack_option(delivery)
  !delivery.pre_pack? && delivery.resource&.can_request_estimated_packaging? && delivery.can_request_estimated_packaging? && !delivery.has_dropship_items? # don't allow pre-pack on custom or dropship items
end

#sww_badgeString

Get a badge for Walmart SWW carrier type
Can be used inline in any view to identify Walmart rates

Returns:

  • (String)

    HTML-safe badge span



394
395
396
# File 'app/helpers/deliveries_helper.rb', line 394

def sww_badge
  (:span, 'Ship with Walmart', class: 'badge bg-primary', title: 'Discounted shipping rate from Walmart marketplace')
end