class ReactiveShipping::CarrierLogistics
Constants
- REACTIVE_FREIGHT_CARRIER
Public Instance Methods
build_rate_params(origin, destination, packages, options = {})
click to toggle source
Rates
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 235 def build_rate_params(origin, destination, packages, options = {}) options = @options.merge(options) params = ''.dup params << "xmlv=yes&xmluser=#{@options[:username]}" params << "&xmlpass=#{@options[:password]}" params << "&vozip=#{origin.to_hash[:postal_code]}" params << "&vdzip=#{destination.to_hash[:postal_code]}" i = 0 packages.each do |package| i += 1 # API starts at 1 (not 0) params << "&wpieces[#{i}]=1" params << "&wpallets[#{i}]=1" params << "&vclass[#{i}]=#{package.freight_class}" params << "&wweight[#{i}]=#{package.pounds.ceil}" end 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 calculated_accessorials = build_calculated_accessorials(packages, origin, destination) accessorials = accessorials + calculated_accessorials unless calculated_accessorials.blank? accessorials.uniq! params << accessorials.join unless accessorials.blank? save_request({ params: params }) params end
build_url(action, options = {})
click to toggle source
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 42 def build_url(action, options = {}) options = @options.merge(options) scheme = @conf.dig(:api, :use_ssl, action) ? 'https://' : 'http://' url = ''.dup url << "#{scheme}#{@conf.dig(:api, :domain)}#{@conf.dig(:api, :endpoints, action)}" url = url.sub('@CARRIER_CODE@', @conf.dig(:api, :carrier_code)) if url.include?('@CARRIER_CODE@') url << options[:params] unless options[:params].blank? url end
commit(action, options = {})
click to toggle source
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 52 def commit(action, options = {}) options = @options.merge(options) url = build_url(action, params: options[:params]) response = HTTParty.get(url) response.parsed_response if response&.parsed_response end
debug?()
click to toggle source
protected
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 36 def debug? return false if @options[:debug].blank? @options[:debug] end
find_bol(tracking_number, options = {})
click to toggle source
Documents
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 8 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/platforms/carrier_logistics.rb, line 13 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/platforms/carrier_logistics.rb, line 19 def find_rates(origin, destination, packages, options = {}) options = @options.merge(options) origin = Location.from(origin) destination = Location.from(destination) packages = Array(packages) params = build_rate_params(origin, destination, packages, options) parse_rate_response(origin, destination, packages, commit(:rates, params: params)) end
find_tracking_info(tracking_number)
click to toggle source
Tracking
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 30 def find_tracking_info(tracking_number) parse_tracking_response(tracking_number) end
parse_city_state(str)
click to toggle source
Tracking
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 121 def parse_city_state(str) return nil if str.blank? Location.new( city: str.split(', ')[0].titleize, state: str.split(', ')[1].upcase, country: ActiveUtils::Country.find('USA') ) end
parse_city_state_zip(str)
click to toggle source
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 131 def parse_city_state_zip(str) return nil if str.blank? Location.new( city: str.split(', ')[0].titleize, state: str.split(', ')[1].split(' ')[0].upcase, zip_code: str.split(', ')[1].split(' ')[1], country: ActiveUtils::Country.find('USA') ) end
parse_date(date)
click to toggle source
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 142 def parse_date(date) date ? DateTime.strptime(date, '%m/%d/%Y %I:%M %p').to_s(:db) : nil end
parse_document_response(action, tracking_number, options = {})
click to toggle source
Documents
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 61 def parse_document_response(action, tracking_number, options = {}) options = @options.merge(options) browser = Watir::Browser.new(:chrome, headless: !debug?) browser.goto(build_url(action)) browser.text_field(name: 'wlogin').set(@options[:username]) browser.text_field(name: 'wpword').set(@options[:password]) browser.button(name: 'BtnAction1').click browser.frameset.frames[1].text_field(id: 'menuquicktrack').set(tracking_number) browser.browser.frameset.frames[1].button(id: 'menusubmit').click if action == :bol element = browser.frameset.frames[1].button(value: 'View Bill Of Lading Image') if element.exists? element.click else browser.close raise ReactiveShipping::ResponseError, "API Error: #{self.class.name}: Document not found" end else element = browser.frameset.frames[1].button(value: 'View Delivery Receipt Image') if element.exists? element.click else browser.close raise ReactiveShipping::ResponseError, "API Error: #{self.class.name}: Document not found" end end url = nil browser.windows.last.use do url = browser.url if url.include?('viewdoc.php') browser.close raise ReactiveShipping::ResponseError, "API Error: #{self.class.name}: Documnent cannot be downloaded" end end browser.close path = if options[:path].blank? File.join(Dir.tmpdir, "#{self.class.name} #{tracking_number} #{action.to_s.upcase}.pdf") else options[:path] end file = File.new(path, 'w') File.open(file.path, 'wb') do |file| URI.parse(url).open do |input| file.write(input.read) end rescue OpenURI::HTTPError raise ReactiveShipping::ResponseError, "API Error: #{self.class.name}: Document not found" end File.exist?(path) ? path : false end
parse_rate_response(origin, destination, _packages, response)
click to toggle source
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 272 def parse_rate_response(origin, destination, _packages, response) success = true message = '' if !response success = false message = 'API Error: Unknown response' else if response['error'] success = false message = response['error'] else cost = response.dig('ratequote', 'quotetotal').delete(',').delete('.').to_i transit_days = response.dig('ratequote', 'busdays').to_i if cost rate_estimates = [ RateEstimate.new( origin, destination, { scac: self.class.scac.upcase, name: self.class.name }, :standard, transit_days: transit_days, estimate_reference: nil, 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(tracking_number)
click to toggle source
# File lib/reactive_freight/platforms/carrier_logistics.rb, line 146 def parse_tracking_response(tracking_number) url = "#{build_url(:track)}wbtn=PRO&wpro1=#{tracking_number}" save_request({ url: url }) begin response = HTTParty.get(url) if !response.code == 200 raise ReactiveShipping::ResponseError, "API Error: #{self.class.name}: HTTP #{response.code}" end rescue StandardError raise ReactiveShipping::ResponseError, "API Error: #{self.class.name}: Unknown response:\n#{response.inspect}" end if response.body.downcase.include?('please enter a valid pro') raise ReactiveShipping::ResponseError, "API Error: #{self.class.name}: Invalid tracking number" end html = Nokogiri::HTML(response.body) tracking_table = html.css('.newtables2')[0] actual_delivery_date = nil receiver_address = nil ship_time = nil shipper_address = nil shipment_events = [] tracking_table.css('tr').reverse.each do |tr| next if tr.text.include?('shipment status') next if tr.css('td').blank? datetime_without_time_zone = "#{tr.css('td')[2].text} #{tr.css('td')[3].text}".squeeze event = tr.css('td')[0].text location = tr.css('td')[1].text event_key = nil @conf.dig(:events, :types).each do |key, val| if event.downcase.include?(val) && !event.downcase.include?('estimated') event_key = key break end end next if event_key.blank? location = (parse_city_state(location.squeeze) if !location.blank? && location.downcase.include?(',')) event = event_key datetime_without_time_zone = parse_date(datetime_without_time_zone) case event_key when :delivered actual_delivery_date = datetime_without_time_zone receiver_address = location when :picked_up shipper_address = location ship_time = datetime_without_time_zone end # status and type_code set automatically by ActiveFreight based on event shipment_events << ShipmentEvent.new(event, datetime_without_time_zone, location) end scheduled_delivery_date = nil status = shipment_events.last.status shipment_events = shipment_events.sort_by(&:time) TrackingResponse.new( true, status, { html: html.to_s }, carrier: "#{self.class.scac}, #{self.class.name}", html: html, response: html.to_s, status: status, type_code: status, ship_time: ship_time, 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