class ReactiveShipping::RDFS

Constants

REACTIVE_FREIGHT_CARRIER

Public Instance Methods

find_bol(tracking_number, options = {}) click to toggle source

Documents

# File lib/reactive_freight/carriers/rdfs.rb, line 16
def find_bol(tracking_number, options = {})
  options = @options.merge(options)
  parse_document_response(:bol, tracking_number, options)
end
find_pod(tracking_number, options = {}) click to toggle source
# File lib/reactive_freight/carriers/rdfs.rb, line 21
def find_pod(tracking_number, options = {})
  options = @options.merge(options)
  parse_document_response(:pod, tracking_number, options)
end
find_rates(origin, destination, packages, options = {}) click to toggle source

Rates

# File lib/reactive_freight/carriers/rdfs.rb, line 27
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/rdfs.rb, line 38
def find_tracking_info(tracking_number)
  tracking_request = build_tracking_request(tracking_number)
  parse_tracking_response(tracking_request)
end
requirements() click to toggle source
# File lib/reactive_freight/carriers/rdfs.rb, line 11
def requirements
  %i[username password account]
end

Protected Instance Methods

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

Rates

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

  service_deliveryoptions = [
    serviceoptions: { service_code: 'SS' }
  ]

  unless options[:accessorials].blank?
    serviceable_accessorials?(options[:accessorials])
    options[:accessorials].each do |a|
      unless @conf.dig(:accessorials, :unserviceable).include?(a)
        service_deliveryoptions << { serviceoptions: { service_code: @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 > 144
    service_deliveryoptions << { serviceoptions: { service_code: 'EXL' } }
  elsif longest_dimension > 96
    service_deliveryoptions << { serviceoptions: { service_code: 'EXM' } }
  end

  service_deliveryoptions = service_deliveryoptions.uniq.to_a

  request = {
    'request' => {
      origin_zip: origin.to_hash[:postal_code].to_s,
      destination_zip: destination.to_hash[:postal_code].to_s,
      shipment_details: {
        shipment_detail: packages.inject([]) do |arr, package|
          arr << {
            'ActualClass' => package.freight_class,
            'Weight' => package.pounds.ceil
          }
        end
      },
      service_deliveryoptions: service_deliveryoptions,
      origin_type: options[:origin_type] || 'B', # O for shipper, I for consignee, B for third party
      payment_type: options[:payment_type] || 'P', # Prepaid
      pallet_count: packages.size,
      # :linear_feet => linear_ft(packages),
      pieces: packages.size,
      account: options[:account]
    }
  }

  save_request(request)
  request
end
build_soap_header(action) click to toggle source
# File lib/reactive_freight/carriers/rdfs.rb, line 45
def build_soap_header(action)
  {
    authentication_header: {
      :@xmlns => @conf.dig(:api, :soap, :namespaces, action),
      :user_name => @options[:username],
      :password => @options[:password]
    }
  }
end
build_tracking_request(tracking_number) click to toggle source

Tracking

# File lib/reactive_freight/carriers/rdfs.rb, line 215
def build_tracking_request(tracking_number)
  URI.parse("#{request_url(:track)}/#{tracking_number}").open
end
commit_soap(action, request) click to toggle source
# File lib/reactive_freight/carriers/rdfs.rb, line 55
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),
    soap_header: build_soap_header(action),
    message: request
  ).body.to_json
end
parse_date(date) click to toggle source
# File lib/reactive_freight/carriers/rdfs.rb, line 68
def parse_date(date)
  date ? DateTime.strptime(date, '%Y-%m-%dT%H:%M:%S').to_s(:db) : nil
end
parse_document_response(type, tracking_number, options = {}) click to toggle source

Documents

# File lib/reactive_freight/carriers/rdfs.rb, line 82
def parse_document_response(type, tracking_number, options = {})
  url = request_url(type).sub('%%TRACKING_NUMBER%%', tracking_number.to_s)

  begin
    doc = Nokogiri::HTML(URI.parse(url).open)
  rescue OpenURI::HTTPError
    raise ReactiveShipping::ResponseError, "API Error: #{@@name}: Document not found"
  end

  data = Base64.decode64(doc.css('img').first['src'].split('data:image/jpg;base64,').last)
  path = if options[:path].blank?
           File.join(Dir.tmpdir, "#{@@name} #{tracking_number} #{type.to_s.upcase}.pdf")
         else
           options[:path]
         end

  file = Tempfile.new
  file.write(data)
  file = Magick::ImageList.new(file.path)
  file.write(path)
  File.exist?(path) ? path : false
end
parse_location(comment, delimiters) click to toggle source
# File lib/reactive_freight/carriers/rdfs.rb, line 219
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/rdfs.rb, line 157
def parse_rate_response(origin, destination, response)
  success = true
  message = ''

  if !response
    success = false
    message = 'API Error: Unknown response'
  else
    response = JSON.parse(response)
    if response[:error]
      success = false
      message = response[:error]
    else
      cost = response.dig('rate_quote_by_account_response', 'rate_quote_by_account_result', 'net_charge')
      transit_days = response.dig(
        'rate_quote_by_account_response',
        'rate_quote_by_account_result',
        'routing_info',
        'estimated_transit_days'
      ).to_i
      estimate_reference = response.dig(
        'rate_quote_by_account_response',
        'rate_quote_by_account_result',
        'quote_number'
      )
      if cost
        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/rdfs.rb, line 231
def parse_tracking_response(response)
  json = JSON.parse(response.read)

  if (response.status[0] != '200') || !json.dig('SearchResults')
    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 = json.dig('SearchResults')[0]
  if search_result.dig('Shipment', 'ProNumber').downcase.include?('not available')
    status = "API Error: #{@@name} tracking number not found"
    return TrackingResponse.new(false, status, json, carrier: "#{@@scac}, #{@@name}", json: json, response: response, request: last_request)
  end

  receiver_address = Location.new(
    city: search_result.dig('Shipment', 'Consignee', 'City').titleize,
    province: search_result.dig('Shipment', 'Consignee', 'State').upcase,
    state: search_result.dig('Shipment', 'Consignee', 'State').upcase,
    country: ActiveUtils::Country.find('USA')
  )

  shipper_address = Location.new(
    city: search_result.dig('Shipment', 'Origin', 'City').titleize,
    province: search_result.dig('Shipment', 'Origin', 'State').upcase,
    state: search_result.dig('Shipment', 'Origin', 'State').upcase,
    country: ActiveUtils::Country.find('USA')
  )

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

  last_location = nil
  shipment_events = []
  search_result.dig('Shipment', 'Comments').each do |api_event|
    type_code = api_event.dig('ActivityCode')
    next if !type_code || type_code == 'ARQ'

    event = @conf.dig(:events, :types).key(type_code)
    next if event.blank?

    datetime_without_time_zone = parse_date(api_event.dig('StatusDateTime'))
    comment = strip_date(api_event.dig('StatusComment'))

    case event
    when :arrived_at_terminal
      location = parse_location(comment, [' to ', 'in '])
    when :delivered
      location = receiver_address
    when :departed
      location = parse_location(comment, [' to ', 'from '])
    when :out_for_delivery
      location = parse_location(comment, [' to ', 'from '])
    when :picked_up
      location = shipper_address
    when :trailer_closed
      location = last_location
    when :trailer_unloaded
      location = parse_location(comment, [' to ', 'in '])
    end
    last_location = location

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

  shipment_events = shipment_events.sort_by(&:time)

  TrackingResponse.new(
    true,
    shipment_events.last.status,
    json,
    carrier: "#{@@scac}, #{@@name}",
    json: json,
    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
  )
end
request_url(action) click to toggle source
# File lib/reactive_freight/carriers/rdfs.rb, line 72
def request_url(action)
  scheme = @conf.dig(:api, :use_ssl, action) ? 'https://' : 'http://'
  "#{scheme}#{@conf.dig(:api, :domains, action)}#{@conf.dig(:api, :endpoints, action)}"
end
strip_date(str) click to toggle source
# File lib/reactive_freight/carriers/rdfs.rb, line 77
def strip_date(str)
  str ? str.split(/[A|P]M /)[1] : nil
end