class ReactiveShipping::SAIA
Constants
- REACTIVE_FREIGHT_CARRIER
Public Instance Methods
find_rates(origin, destination, packages, options = {})
click to toggle source
Rates
# File lib/reactive_freight/carriers/saia.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/saia.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/saia.rb, line 52 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 << { 'AccessorialItem': { 'Code': @conf.dig(:accessorials, :mappable)[a] } } end end end excessive_length_total_inches = 0 longest_dimension = packages.inject([]) { |_arr, p| [p.length(:in), p.width(:in)] }.max.ceil if longest_dimension >= 96 accessorials << { 'AccessorialItem': { 'Code': 'ExcessiveLength' } } excessive_length_total_inches += longest_dimension end excessive_length_total_inches = excessive_length_total_inches.ceil.to_s accessorials = accessorials.uniq details = [] dimensions = [] packages.each do |package| details << { 'DetailItem': { 'Weight': package.pounds.ceil, 'Class': package.freight_class.to_s, 'Length': package.length(:in).ceil, 'Width': package.width(:in).ceil, 'Height': package.height(:in).ceil } } dimensions << { 'DimensionItem': { 'Units': 1, 'Length': package.length(:in).round(2), 'Width': package.width(:in).round(2), 'Height': package.height(:in).round(2), 'Type': 'IN' # inches } } end request = { 'request': { 'Application': 'ThirdParty', 'AccountNumber': options[:account], 'UserID': options[:username], 'Password': options[:password], 'TestMode': options[:debug].blank? ? 'N' : 'Y', 'BillingTerms': 'Prepaid', 'OriginCity': origin.city, 'OriginState': origin.state, 'OriginZipcode': origin.to_hash[:postal_code].to_s.upcase, 'DestinationCity': destination.city, 'DestinationState': destination.state, 'DestinationZipcode': destination.to_hash[:postal_code].to_s.upcase, 'WeightUnits': 'LBS', 'TotalCube': packages.inject(0) { |_sum, p| _sum += p.cubic_ft }.to_f.round(2), 'TotalCubeUnits': 'CUFT', # cubic ft 'ExcessiveLengthTotalInches': excessive_length_total_inches, 'Details': details, 'Dimensions': dimensions, 'Accessorials': accessorials } } save_request(request) request end
build_tracking_request(tracking_number)
click to toggle source
Tracking
# File lib/reactive_freight/carriers/saia.rb, line 200 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/saia.rb, line 32 def commit_soap(action, request) Savon.client( wsdl: request_url(action), convert_request_keys_to: :none, 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/saia.rb, line 216 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/saia.rb, line 206 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/saia.rb, line 226 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/saia.rb, line 230 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/saia.rb, line 125 def parse_rate_response(origin, destination, response) success = true message = '' if !response success = false message = 'API Error: Unknown response' else error = response.dig(:create_response, :create_result, :code) if !error.blank? success = false message = response.dig(:create_response, :create_result, :message) else response = response.dig(:create_response, :create_result) cost = response.dig(:total_invoice) if cost cost = cost.sub('.', '').to_i transit_days = response.dig(:standard_service_days).to_i estimate_reference = response.dig(:quote_number) rate_estimates = [] 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) ) [ { guaranteed_ltl: response.dig(:guarantee_amount) }, { guaranteed_ltl_am: response.dig(:guarantee_amount12pm) }, { guaranteed_ltl_pm: response.dig(:guarantee_amount2pm) } ].each do |service| if !service.values[0] == '0' && !service.values[0].blank? cost = service.values[0].sub('.', '').to_i rate_estimates << RateEstimate.new( origin, destination, { scac: self.class.scac.upcase, name: self.class.name }, service.keys[0], delivery_range: delivery_range, estimate_reference: estimate_reference, total_cost: cost, total_price: cost, currency: 'USD', with_excessive_length_fees: @conf.dig(:attributes, :rates, :with_excessive_length_fees) ) end rate_estimates end 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/saia.rb, line 242 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/saia.rb, line 44 def request_url(action) scheme = @conf.dig(:api, :use_ssl) ? 'https://' : 'http://' "#{scheme}#{@conf.dig(:api, :domain)}#{@conf.dig(:api, :endpoints, action)}" end