class ReactiveShipping::DPHE

Constants

REACTIVE_FREIGHT_CARRIER

Public Instance Methods

find_rates(origin, destination, packages, options = {}) click to toggle source

Rates

# File lib/reactive_freight/carriers/dphe.rb, line 14
def find_rates(origin, destination, packages, options = {})
  options = @options.merge(options)
  origin = Location.from(origin)
  destination = Location.from(destination)
  packages = Array(packages)

  request = build_rate_request(origin, destination, packages, options)
  parse_rate_response(origin, destination, commit_soap(:rates, request))
end
find_tracking_info(tracking_number) click to toggle source

Tracking

# File lib/reactive_freight/carriers/dphe.rb, line 25
def find_tracking_info(tracking_number)
  request = build_tracking_request(tracking_number)
  parse_tracking_response(commit_soap(:track, request))
end

Protected Instance Methods

build_rate_request(origin, destination, packages, options = {}) click to toggle source

Rates

# File lib/reactive_freight/carriers/dphe.rb, line 61
def build_rate_request(origin, destination, packages, options = {})
  options = @options.merge(options)

  accessorials = []
  unless options[:accessorials].blank?
    serviceable_accessorials?(options[:accessorials])
    options[:accessorials].each do |a|
      unless @conf.dig(:accessorials, :unserviceable).include?(a)
        accessorials << @conf.dig(:accessorials, :mappable)[a]
      end
    end
  end

  longest_dimension = packages.inject([]) { |_arr, p| [p.length(:in), p.width(:in)] }.max.ceil
  if longest_dimension >= 336
    accessorials << 'X29'
  elsif longest_dimension >= 240 && longest_dimension < 336
    accessorials << 'X28'
  elsif longest_dimension >= 144 && longest_dimension < 240
    accessorials << 'X20'
  elsif longest_dimension >= 96 && longest_dimension < 144
    accessorials << 'X12'
  end

  accessorials = accessorials.uniq.join(',')

  shipment_detail = []
  packages.each do |package|
    shipment_detail << "1|#{package.freight_class}|#{package.pounds.ceil}"
  end
  shipment_detail = shipment_detail.join('|')

  request = {
    customer_code: @options[:account],
    origin_zip: origin.to_hash[:postal_code].to_s.upcase,
    destination_zip: destination.to_hash[:postal_code].to_s.upcase,
    shipment_detail: shipment_detail,
    rating_type: '', # per API documentation
    accessorials: accessorials
  }

  save_request(request)
  request
end
build_soap_header(_action) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 32
def build_soap_header(_action)
  {
    authentication_header: {
      user_name: @options[:username],
      password: @options[:password]
    }
  }
end
build_tracking_request(tracking_number) click to toggle source

Tracking

# File lib/reactive_freight/carriers/dphe.rb, line 160
def build_tracking_request(tracking_number)
  request = { pro_number: tracking_number }
  save_request(request)
  request
end
commit_soap(action, request) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 41
def commit_soap(action, request)
  Savon.client(
    wsdl: request_url(action),
    convert_request_keys_to: :camelcase,
    env_namespace: :soap,
    element_form_default: :qualified
  ).call(
    @conf.dig(:api, :actions, action),
    message: request
  ).body.to_hash
end
parse_city(str) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 176
def parse_city(str)
  return nil if str.blank?

  Location.new(
    city: str.squeeze.strip.titleize,
    state: nil,
    country: ActiveUtils::Country.find('USA')
  )
end
parse_city_state(str) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 166
def parse_city_state(str)
  return nil if str.blank?

  Location.new(
    city: str.split(', ')[0].titleize,
    state: str.split(', ')[1].split(' ')[0].upcase,
    country: ActiveUtils::Country.find('USA')
  )
end
parse_date(date) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 186
def parse_date(date)
  date ? DateTime.strptime(date, '%m/%d/%Y %l:%M:%S %p').to_s(:db) : nil
end
parse_location(comment, delimiters) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 190
def parse_location(comment, delimiters)
  city = comment.split(delimiters[0])[0].split(delimiters[1])[1].split(', ')[0].titleize
  state = comment.split(delimiters[0])[0].split(delimiters[1])[1].split(', ')[1].upcase

  Location.new(
    city: city,
    province: state,
    state: state,
    country: ActiveUtils::Country.find('USA')
  )
end
parse_rate_response(origin, destination, response) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 106
def parse_rate_response(origin, destination, response)
  success = true
  message = ''

  if !response
    success = false
    message = 'API Error: Unknown response'
  else
    error = response.dig(:get_rates_response, :get_rates_result, :rate_error)
    quote_number = response.dig(:get_rates_response, :get_rates_result, :rate_quote_number).blank?

    # error on its own isn't reliable indicator of error - returns false on error
    if !error.blank? || quote_number
      success = false
      message = response.dig(:get_rates_response, :get_rates_result, :return_line)
    else
      cost = response.dig(:get_rates_response, :get_rates_result, :totals)
      if cost
        cost = cost.sub('$', '').sub(',', '').sub('.', '').to_i
        transit_days = response.dig(:get_rates_response, :get_rates_result, :transit_days).to_i
        estimate_reference = response.dig(:get_rates_response, :get_rates_result, :rate_quote_number)

        rate_estimates = [
          RateEstimate.new(
            origin,
            destination,
            { scac: self.class.scac.upcase, name: self.class.name },
            :standard,
            transit_days: transit_days,
            estimate_reference: estimate_reference,
            total_cost: cost,
            total_price: cost,
            currency: 'USD',
            with_excessive_length_fees: @conf.dig(:attributes, :rates, :with_excessive_length_fees)
          )
        ]
      else
        success = false
        message = 'API Error: Cost is emtpy'
      end
    end
  end

  RateResponse.new(
    success,
    message,
    response.to_hash,
    rates: rate_estimates,
    response: response,
    request: last_request
  )
end
parse_tracking_response(response) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 202
def parse_tracking_response(response)
  unless response.dig(:get_tracking_response, :get_tracking_result, :tracking_status_response)
    status = json.dig('error') || "API Error: HTTP #{response.status[0]}"
    return TrackingResponse.new(false, status, json, carrier: "#{@@scac}, #{@@name}", json: json, response: response, request: last_request)
  end

  search_result = response.dig(:get_tracking_response, :get_tracking_result)

  shipper_address = Location.new(
    street: search_result.dig(:shipperaddress).squeeze.strip.titleize,
    city: search_result.dig(:shipper_city).squeeze.strip.titleize,
    state: search_result.dig(:shipper_state).strip.upcase,
    postal_code: search_result.dig(:shipper_zip).strip,
    country: ActiveUtils::Country.find('USA')
  )

  receiver_address = Location.new(
    street: search_result.dig(:consaddress).squeeze.strip.titleize,
    city: search_result.dig(:cons_city).squeeze.strip.titleize,
    state: search_result.dig(:cons_state).strip.upcase,
    postal_code: search_result.dig(:cons_zip).strip,
    country: ActiveUtils::Country.find('USA')
  )

  actual_delivery_date = parse_date(search_result.dig('Shipment', 'DeliveredDateTime'))
  pickup_date = parse_date(search_result.dig(:pickup_date))
  scheduled_delivery_date = parse_date(search_result.dig('Shipment', 'ApptDateTime'))
  tracking_number = search_result.dig('Shipment', 'SearchItem')

  shipment_events = []
  shipment_events << ShipmentEvent.new(
    :picked_up,
    pickup_date,
    shipper_address
  )

  api_events = search_result.dig(:tracking_status_response, :tracking_status_row).reverse
  api_events.each do |api_event|
    event_key = nil
    comment = api_event.dig(:tracking_status)

    @conf.dig(:events, :types).each do |key, val|
      if comment.downcase.include?(val)
        event_key = key
        break
      end
    end
    next if event_key.blank?

    case event_key
    when :arrived_at_terminal
      location = parse_city(comment.split('arrived')[1].upcase.split('SERVICE CENTER')[0])
    when :delivered
      location = parse_city_state(comment.split('in ')[1].split('completed')[0])
    when :departed
      location = parse_city(comment.split('departed')[1].upcase.split('SERVICE CENTER')[0])
    when :out_for_delivery
      location = receiver_address
    when :trailer_closed
      location = parse_city(comment.split('Location:')[1])
    when :trailer_unloaded
      location = parse_city(comment.split('Location:')[1])
    end

    datetime_without_time_zone = parse_date(api_event.dig(:tracking_date))

    # status and type_code set automatically by ActiveFreight based on event
    shipment_events << ShipmentEvent.new(event_key, datetime_without_time_zone, location)
  end

  shipment_events = shipment_events.sort_by(&:time)

  TrackingResponse.new(
    true,
    shipment_events.last.status,
    response,
    carrier: "#{@@scac}, #{@@name}",
    hash: response,
    response: response,
    status: status,
    type_code: shipment_events.last.status,
    ship_time: parse_date(search_result.dig('Shipment', 'ProDateTime')),
    scheduled_delivery_date: scheduled_delivery_date,
    actual_delivery_date: actual_delivery_date,
    delivery_signature: nil,
    shipment_events: shipment_events,
    shipper_address: shipper_address,
    origin: shipper_address,
    destination: receiver_address,
    tracking_number: tracking_number,
    request: last_request
  )
end
request_url(action) click to toggle source
# File lib/reactive_freight/carriers/dphe.rb, line 53
def request_url(action)
  scheme = @conf.dig(:api, :use_ssl, action) ? 'https://' : 'http://'
  "#{scheme}#{@conf.dig(:api, :domain)}#{@conf.dig(:api, :endpoints, action)}"
end