class ReactiveShipping::FWDA

Constants

JSON_HEADERS
REACTIVE_FREIGHT_CARRIER

Public Instance Methods

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

Rates

# File lib/reactive_freight/carriers/fwda.rb, line 46
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(request))
end
serviceable_accessorials?(accessorials) click to toggle source

Override Carrier#serviceable_accessorials? since we have separate delivery/pickup accessorials

# File lib/reactive_freight/carriers/fwda.rb, line 18
def serviceable_accessorials?(accessorials)
  return true if accessorials.blank?

  if !self.class::REACTIVE_FREIGHT_CARRIER ||
     !@conf.dig(:accessorials, :mappable) ||
     !@conf.dig(:accessorials, :unquotable) ||
     !@conf.dig(:accessorials, :unserviceable)
    raise NotImplementedError, "#{self.class.name}: #serviceable_accessorials? not supported"
  end

  serviceable_accessorials = @conf.dig(:accessorials, :mappable, :delivery).keys + 
                             @conf.dig(:accessorials, :mappable, :pickup).keys + 
                             @conf.dig(:accessorials, :unquotable)
  serviceable_count = (serviceable_accessorials & accessorials).size

  unserviceable_accessorials = @conf.dig(:accessorials, :unserviceable)
  unserviceable_count = (unserviceable_accessorials & accessorials).size

  if serviceable_count != accessorials.size || !unserviceable_count.zero?
    raise ArgumentError, "#{self.class.name}: Some accessorials unserviceable"
  end

  true
end

Protected Instance Methods

base_url() click to toggle source
# File lib/reactive_freight/carriers/fwda.rb, line 65
def base_url
  "https://#{@conf.dig(:api, :domain)}"
end
build_headers(options = {}) click to toggle source
# File lib/reactive_freight/carriers/fwda.rb, line 69
def build_headers(options = {})
  options = @options.merge(options)
  if !options[:username].blank? && !options[:password].blank? && !options[:account].blank?
    return JSON_HEADERS.merge(
      'user': options[:username],
      'password': options[:password],
      'customerId': options[:account]
    )
  end

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

Rates

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

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

  unless delivery_accessorials.blank?
    # Remove duplicate delivery appointment accessorial when residential delivery (included with RDE)
    delivery_accessorials -= ['ADE'] if delivery_accessorials.include?('RDE')
  end

  unless pickup_accessorials.blank?
    # Remove duplicate pickup appointment accessorial when residential pickup (included with RPU)
    pickup_accessorials -= ['APP'] if pickup_accessorials.include?('RPU')
  end

  delivery_accessorials = delivery_accessorials.uniq
  pickup_accessorials = pickup_accessorials.uniq

  # API doesn't like empty arrays
  delivery_accessorials = nil if delivery_accessorials.blank?
  pickup_accessorials = nil if pickup_accessorials.blank?

  freight_details = []
  packages.each do |package|
    freight_details << {
      description: 'Freight',
      freightClass: package.freight_class.to_s,
      pieces: '1',
      weightType: 'L',
      weight: package.pounds.ceil.to_s
    }
  end

  request = {
    url: build_url(:rates, options),
    headers: build_headers(options),
    method: @conf.dig(:api, :methods, :rates),
    body: {
      billToCustomerNumber: options[:account],
      origin: {
        originZipCode: origin.to_hash[:postal_code].to_s.upcase,
        pickup: {
          airportPickup: pickup_accessorials&.include?('ALP') ? 'Y' : 'N',
          pickupAccessorials: { pickupAccessorial: pickup_accessorials }
        }
      },
      destination: {
        destinationZipCode: destination.to_hash[:postal_code].to_s.upcase,
        delivery: {
          airportDelivery: delivery_accessorials&.include?('ALD') ? 'Y' : 'N',
          deliveryAccessorials: { deliveryAccessorial: delivery_accessorials }
        }
      },
      freightDetails: { freightDetail: freight_details },
      hazmat: 'N',
      inBondShipment: 'N',
      declaredValue: '0.00',
      shipmentDate: Date.current.strftime('%Y-%m-%d')
    }.to_json
  }

  save_request(request)
  request
end
build_request(action, options = {}) click to toggle source
# File lib/reactive_freight/carriers/fwda.rb, line 82
def build_request(action, options = {})
  options = @options.merge(options)
  headers = JSON_HEADERS
  headers = headers.merge(options[:headers]) unless options[:headers].blank?
  body = options[:body].to_json unless options[:body].blank?

  request = {
    url: build_url(action, options),
    headers: headers,
    method: @conf.dig(:api, :methods, action),
    body: body
  }

  save_request(request)
  request
end
build_url(action, options = {}) click to toggle source

Tracking

# File lib/reactive_freight/carriers/fwda.rb, line 60
def build_url(action, options = {})
  options = @options.merge(options)
  "#{base_url}#{@conf.dig(:api, :endpoints, action)}"
end
commit(request) click to toggle source
# File lib/reactive_freight/carriers/fwda.rb, line 99
def commit(request)
  url = request[:url]
  headers = request[:headers]
  method = request[:method]
  body = request[:body]

  response = case method
             when :post
               HTTParty.post(url, headers: headers, body: body)
             else
               HTTParty.get(url, headers: headers)
             end

  JSON.parse(response.body)
end
parse_rate_response(origin, destination, response) click to toggle source
# File lib/reactive_freight/carriers/fwda.rb, line 195
def parse_rate_response(origin, destination, response)
  success = true
  message = ''

  if !response
    success = false
    message = 'API Error: Unknown response'
  elsif response.key?('errorMessage')
    success = false
    message = response.dig('errorMessage')
  else
    cost = response.dig('quoteTotal')
    if cost
      cost = (cost.to_f * 100).to_i
      transit_days = response.dig('transitDaysTotal')

      rate_estimates = [
        RateEstimate.new(
          origin,
          destination,
          self.class,
          :standard,
          transit_days: transit_days,
          estimate_reference: nil,
          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

  RateResponse.new(
    success,
    message,
    response.to_hash,
    rates: rate_estimates,
    response: response,
    request: last_request
  )
end