freight_kit 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (434) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -2
  3. data/README.md +1 -2
  4. data/Rakefile +6 -0
  5. data/VERSION +1 -1
  6. data/configuration/locations/amazon_warehouses.yml +3330 -0
  7. data/freight_kit.gemspec +13 -13
  8. data/lib/freight_kit/address_normalizer.rb +26 -0
  9. data/lib/freight_kit/amazon_warehouse.rb +33 -0
  10. data/lib/freight_kit/api_clients/soap_client.rb +7 -9
  11. data/lib/freight_kit/carrier.rb +250 -303
  12. data/lib/freight_kit/carriers/a1_express.rb +25 -0
  13. data/lib/freight_kit/carriers/a_duie_pyle.rb +30 -0
  14. data/lib/freight_kit/carriers/aaa_cooper.rb +11 -0
  15. data/lib/freight_kit/carriers/aaa_expedited_services.rb +10 -0
  16. data/lib/freight_kit/carriers/ab_brothers_usa_inc.rb +12 -0
  17. data/lib/freight_kit/carriers/aberdeen_express_inc.rb +28 -0
  18. data/lib/freight_kit/carriers/abf_freight_system.rb +492 -0
  19. data/lib/freight_kit/carriers/access_transport.rb +10 -0
  20. data/lib/freight_kit/carriers/action_express.rb +10 -0
  21. data/lib/freight_kit/carriers/adams_and_companies.rb +10 -0
  22. data/lib/freight_kit/carriers/advance_distribution.rb +28 -0
  23. data/lib/freight_kit/carriers/alcon_transport.rb +10 -0
  24. data/lib/freight_kit/carriers/all_freight.rb +10 -0
  25. data/lib/freight_kit/carriers/alliance_transportation_group.rb +10 -0
  26. data/lib/freight_kit/carriers/allstate_transport.rb +10 -0
  27. data/lib/freight_kit/carriers/aloha_freight_forwarders.rb +35 -0
  28. data/lib/freight_kit/carriers/amado_trucking.rb +35 -0
  29. data/lib/freight_kit/carriers/american_fast_freight.rb +28 -0
  30. data/lib/freight_kit/carriers/amigos_service_transportation.rb +10 -0
  31. data/lib/freight_kit/carriers/anderson_towing_and_transport.rb +10 -0
  32. data/lib/freight_kit/carriers/apex_transporting.rb +10 -0
  33. data/lib/freight_kit/carriers/archer_cargo.rb +35 -0
  34. data/lib/freight_kit/carriers/armstrong_transport_group.rb +10 -0
  35. data/lib/freight_kit/carriers/autobahn_logistics.rb +10 -0
  36. data/lib/freight_kit/carriers/averitt_express.rb +30 -0
  37. data/lib/freight_kit/carriers/ba_transportation.rb +10 -0
  38. data/lib/freight_kit/carriers/balkin_express.rb +10 -0
  39. data/lib/freight_kit/carriers/bay_area_express.rb +10 -0
  40. data/lib/freight_kit/carriers/bc_freightways.rb +11 -0
  41. data/lib/freight_kit/carriers/benjamin_best_freight_inc.rb +37 -0
  42. data/lib/freight_kit/carriers/bennett_trucking.rb +10 -0
  43. data/lib/freight_kit/carriers/best_overnite_express.rb +78 -0
  44. data/lib/freight_kit/carriers/best_trucking.rb +10 -0
  45. data/lib/freight_kit/carriers/bestway_transport.rb +10 -0
  46. data/lib/freight_kit/carriers/bryan_systems.rb +10 -0
  47. data/lib/freight_kit/carriers/c_and_w_express.rb +10 -0
  48. data/lib/freight_kit/carriers/campbell_trucking.rb +10 -0
  49. data/lib/freight_kit/carriers/carde_transportation.rb +10 -0
  50. data/lib/freight_kit/carriers/cardinal_logistics.rb +28 -0
  51. data/lib/freight_kit/carriers/cdc_transport.rb +10 -0
  52. data/lib/freight_kit/carriers/cdo_express_diversified.rb +10 -0
  53. data/lib/freight_kit/carriers/cedar_supply_trucking.rb +10 -0
  54. data/lib/freight_kit/carriers/central_freight_lines.rb +12 -0
  55. data/lib/freight_kit/carriers/central_transport.rb +12 -0
  56. data/lib/freight_kit/carriers/chicago_suburban_express.rb +28 -0
  57. data/lib/freight_kit/carriers/city_logistics.rb +11 -0
  58. data/lib/freight_kit/carriers/clear_lane_freight_systems.rb +483 -0
  59. data/lib/freight_kit/carriers/cleopatra_trucking.rb +10 -0
  60. data/lib/freight_kit/carriers/clifford_transportation.rb +10 -0
  61. data/lib/freight_kit/carriers/cms_domestic.rb +25 -0
  62. data/lib/freight_kit/carriers/cnd_logistics.rb +10 -0
  63. data/lib/freight_kit/carriers/coast_to_coast_transport.rb +27 -0
  64. data/lib/freight_kit/carriers/countrywide_carriers.rb +10 -0
  65. data/lib/freight_kit/carriers/cpl_express.rb +35 -0
  66. data/lib/freight_kit/carriers/crosby_trucking_service.rb +29 -0
  67. data/lib/freight_kit/carriers/cross_country_freight_solutions.rb +419 -0
  68. data/lib/freight_kit/carriers/crowley.rb +10 -0
  69. data/lib/freight_kit/carriers/csa_transportation.rb +40 -0
  70. data/lib/freight_kit/carriers/csx_transportation.rb +40 -0
  71. data/lib/freight_kit/carriers/custom_global_logistics.rb +14 -0
  72. data/lib/freight_kit/carriers/d_and_a_transport.rb +10 -0
  73. data/lib/freight_kit/carriers/dak_transport.rb +10 -0
  74. data/lib/freight_kit/carriers/dash_courier_and_logistics_north_carolina.rb +10 -0
  75. data/lib/freight_kit/carriers/daylight_transport.rb +29 -0
  76. data/lib/freight_kit/carriers/dayton_freight_lines.rb +30 -0
  77. data/lib/freight_kit/carriers/dbr_logistics.rb +10 -0
  78. data/lib/freight_kit/carriers/deep_south_freight.rb +27 -0
  79. data/lib/freight_kit/carriers/delta_express.rb +10 -0
  80. data/lib/freight_kit/carriers/delta_transport.rb +10 -0
  81. data/lib/freight_kit/carriers/delta_trucking_logistics.rb +10 -0
  82. data/lib/freight_kit/carriers/demco_express.rb +10 -0
  83. data/lib/freight_kit/carriers/dependable_global_express.rb +10 -0
  84. data/lib/freight_kit/carriers/dependable_hawaiian_express.rb +24 -0
  85. data/lib/freight_kit/carriers/dependable_highway_express.rb +585 -0
  86. data/lib/freight_kit/carriers/desert_coastal_transport.rb +10 -0
  87. data/lib/freight_kit/carriers/dhl.rb +11 -0
  88. data/lib/freight_kit/carriers/diamond_line_delivery.rb +174 -0
  89. data/lib/freight_kit/carriers/ding_tai_trucking.rb +10 -0
  90. data/lib/freight_kit/carriers/diversified_transfer_and_storage.rb +29 -0
  91. data/lib/freight_kit/carriers/dohrn_transfer_company.rb +30 -0
  92. data/lib/freight_kit/carriers/dot_line_transportation.rb +29 -0
  93. data/lib/freight_kit/carriers/drt_transportation.rb +13 -0
  94. data/lib/freight_kit/carriers/dugan_truck_line.rb +29 -0
  95. data/lib/freight_kit/carriers/dupont.rb +10 -0
  96. data/lib/freight_kit/carriers/e_transport_carriers.rb +29 -0
  97. data/lib/freight_kit/carriers/echo_global_logistics.rb +13 -0
  98. data/lib/freight_kit/carriers/ecklund_logistics.rb +10 -0
  99. data/lib/freight_kit/carriers/el_emplumado_transport.rb +10 -0
  100. data/lib/freight_kit/carriers/elite_carriers.rb +28 -0
  101. data/lib/freight_kit/carriers/emerald_express.rb +10 -0
  102. data/lib/freight_kit/carriers/era_transport.rb +10 -0
  103. data/lib/freight_kit/carriers/estes_express_lines.rb +25 -0
  104. data/lib/freight_kit/carriers/euro_kam.rb +28 -0
  105. data/lib/freight_kit/carriers/ew_riverside_trucking.rb +10 -0
  106. data/lib/freight_kit/carriers/express_2000_inc.rb +12 -0
  107. data/lib/freight_kit/carriers/express_services_international.rb +10 -0
  108. data/lib/freight_kit/carriers/f_and_a_express.rb +27 -0
  109. data/lib/freight_kit/carriers/fairmont_logistics.rb +13 -0
  110. data/lib/freight_kit/carriers/fargo_cargo.rb +10 -0
  111. data/lib/freight_kit/carriers/fast_freight_transportation.rb +10 -0
  112. data/lib/freight_kit/carriers/fast_load_transport.rb +10 -0
  113. data/lib/freight_kit/carriers/fed_ex_express.rb +30 -0
  114. data/lib/freight_kit/carriers/fed_ex_ground.rb +24 -0
  115. data/lib/freight_kit/carriers/fed_ex_ltl_economy.rb +11 -0
  116. data/lib/freight_kit/carriers/fed_ex_ltl_priority.rb +11 -0
  117. data/lib/freight_kit/carriers/first_trucking_express.rb +10 -0
  118. data/lib/freight_kit/carriers/flash_trucking.rb +10 -0
  119. data/lib/freight_kit/carriers/fort_transportation_and_service_company_inc.rb +37 -0
  120. data/lib/freight_kit/carriers/forward_air.rb +789 -0
  121. data/lib/freight_kit/carriers/frahtex.rb +10 -0
  122. data/lib/freight_kit/carriers/frozen_food_express.rb +10 -0
  123. data/lib/freight_kit/carriers/gholia_bros_trucking.rb +36 -0
  124. data/lib/freight_kit/carriers/global_cargo.rb +10 -0
  125. data/lib/freight_kit/carriers/global_logistics_and_transportation.rb +10 -0
  126. data/lib/freight_kit/carriers/gls_freight.rb +32 -0
  127. data/lib/freight_kit/carriers/go_trucking_services.rb +10 -0
  128. data/lib/freight_kit/carriers/gold_eagle_transportation.rb +11 -0
  129. data/lib/freight_kit/carriers/golden_drop_trucking.rb +10 -0
  130. data/lib/freight_kit/carriers/gonzalez_transportation.rb +10 -0
  131. data/lib/freight_kit/carriers/gps_transport.rb +10 -0
  132. data/lib/freight_kit/carriers/greene_enterprise.rb +10 -0
  133. data/lib/freight_kit/carriers/gsa_international.rb +10 -0
  134. data/lib/freight_kit/carriers/gsp_express.rb +24 -0
  135. data/lib/freight_kit/carriers/gsv_transportation.rb +10 -0
  136. data/lib/freight_kit/carriers/gta_transportation.rb +10 -0
  137. data/lib/freight_kit/carriers/guam_freight_services.rb +10 -0
  138. data/lib/freight_kit/carriers/harrah_transportation.rb +23 -0
  139. data/lib/freight_kit/carriers/her_trucking.rb +10 -0
  140. data/lib/freight_kit/carriers/hercules_freight.rb +20 -0
  141. data/lib/freight_kit/carriers/highlander_xpress.rb +36 -0
  142. data/lib/freight_kit/carriers/hn_transport.rb +10 -0
  143. data/lib/freight_kit/carriers/honolulu_freight_service.rb +10 -0
  144. data/lib/freight_kit/carriers/hot_line_freight_system_inc.rb +12 -0
  145. data/lib/freight_kit/carriers/hotshot_freight_of_texas.rb +13 -0
  146. data/lib/freight_kit/carriers/houser_transport.rb +27 -0
  147. data/lib/freight_kit/carriers/infiniti_freight_logistics.rb +28 -0
  148. data/lib/freight_kit/carriers/infinity_trucking.rb +10 -0
  149. data/lib/freight_kit/carriers/interstate_tma.rb +10 -0
  150. data/lib/freight_kit/carriers/intrade_industries.rb +10 -0
  151. data/lib/freight_kit/carriers/jasper_trucking.rb +28 -0
  152. data/lib/freight_kit/carriers/jb_hunt.rb +11 -0
  153. data/lib/freight_kit/carriers/jfj_transportation.rb +8 -8
  154. data/lib/freight_kit/carriers/jkb_express.rb +10 -0
  155. data/lib/freight_kit/carriers/jlm_transport.rb +10 -0
  156. data/lib/freight_kit/carriers/johansson_transport.rb +10 -0
  157. data/lib/freight_kit/carriers/jones_express.rb +10 -0
  158. data/lib/freight_kit/carriers/jp_logistics.rb +41 -0
  159. data/lib/freight_kit/carriers/just_in_time_cargo.rb +29 -0
  160. data/lib/freight_kit/carriers/kengkok_times.rb +10 -0
  161. data/lib/freight_kit/carriers/kings_express.rb +10 -0
  162. data/lib/freight_kit/carriers/klc_transportation.rb +10 -0
  163. data/lib/freight_kit/carriers/km_trucking.rb +10 -0
  164. data/lib/freight_kit/carriers/kma_logistics.rb +10 -0
  165. data/lib/freight_kit/carriers/krc_logistics.rb +10 -0
  166. data/lib/freight_kit/carriers/ktx.rb +10 -0
  167. data/lib/freight_kit/carriers/landstar.rb +25 -0
  168. data/lib/freight_kit/carriers/lanter_distributing_llc.rb +30 -0
  169. data/lib/freight_kit/carriers/ld_trucking.rb +10 -0
  170. data/lib/freight_kit/carriers/legacy_logistics_solutions.rb +10 -0
  171. data/lib/freight_kit/carriers/leonards_express.rb +10 -0
  172. data/lib/freight_kit/carriers/liberty_linehaul_west.rb +28 -0
  173. data/lib/freight_kit/carriers/liberty_transport.rb +10 -0
  174. data/lib/freight_kit/carriers/lightning_express.rb +10 -0
  175. data/lib/freight_kit/carriers/lj_express.rb +24 -0
  176. data/lib/freight_kit/carriers/lkp_enterprises.rb +10 -0
  177. data/lib/freight_kit/carriers/ltl_pros.rb +28 -0
  178. data/lib/freight_kit/carriers/m_and_m_and_sons.rb +10 -0
  179. data/lib/freight_kit/carriers/mag_lines_transport.rb +10 -0
  180. data/lib/freight_kit/carriers/magic_messenger.rb +24 -0
  181. data/lib/freight_kit/carriers/majors_transit.rb +10 -0
  182. data/lib/freight_kit/carriers/map_global_logistics.rb +10 -0
  183. data/lib/freight_kit/carriers/matths_trucking.rb +11 -0
  184. data/lib/freight_kit/carriers/mav_canadian_transport.rb +13 -0
  185. data/lib/freight_kit/carriers/mc_systems.rb +35 -0
  186. data/lib/freight_kit/carriers/mega_fleet.rb +10 -0
  187. data/lib/freight_kit/carriers/mega_trans_group.rb +28 -0
  188. data/lib/freight_kit/carriers/meta_express.rb +10 -0
  189. data/lib/freight_kit/carriers/mia_transportation.rb +10 -0
  190. data/lib/freight_kit/carriers/midwest_motor_express.rb +14 -0
  191. data/lib/freight_kit/carriers/midwest_transport_and_trucking.rb +10 -0
  192. data/lib/freight_kit/carriers/mikade_delivery_service.rb +10 -0
  193. data/lib/freight_kit/carriers/mohoff_freight_lines.rb +23 -0
  194. data/lib/freight_kit/carriers/montata_travelers_express.rb +10 -0
  195. data/lib/freight_kit/carriers/morgan_van_lines.rb +29 -0
  196. data/lib/freight_kit/carriers/moto_transportation.rb +24 -0
  197. data/lib/freight_kit/carriers/mountain_valley_express.rb +197 -0
  198. data/lib/freight_kit/carriers/ms_freight.rb +27 -0
  199. data/lib/freight_kit/carriers/mvg_transport.rb +10 -0
  200. data/lib/freight_kit/carriers/n_and_m_transfer_co_inc.rb +29 -0
  201. data/lib/freight_kit/carriers/nabot_transport_solutions.rb +10 -0
  202. data/lib/freight_kit/carriers/national_freight_xpress.rb +10 -0
  203. data/lib/freight_kit/carriers/navigation_group.rb +10 -0
  204. data/lib/freight_kit/carriers/nelson_trucking.rb +10 -0
  205. data/lib/freight_kit/carriers/network_1_transportation.rb +10 -0
  206. data/lib/freight_kit/carriers/new_century_transport.rb +28 -0
  207. data/lib/freight_kit/carriers/new_penn_motor_express.rb +11 -0
  208. data/lib/freight_kit/carriers/nieves_transportation.rb +10 -0
  209. data/lib/freight_kit/carriers/nolan_transportation_group.rb +26 -0
  210. data/lib/freight_kit/carriers/northland_trucking.rb +10 -0
  211. data/lib/freight_kit/carriers/northwest_carrier_logistics.rb +27 -0
  212. data/lib/freight_kit/carriers/nt_usa.rb +10 -0
  213. data/lib/freight_kit/carriers/nu_line_transportation.rb +10 -0
  214. data/lib/freight_kit/carriers/numark_transportation.rb +80 -0
  215. data/lib/freight_kit/carriers/nvk_transport.rb +10 -0
  216. data/lib/freight_kit/carriers/ny_transport.rb +10 -0
  217. data/lib/freight_kit/carriers/oak_harbor.rb +28 -0
  218. data/lib/freight_kit/carriers/oakline_transportation.rb +10 -0
  219. data/lib/freight_kit/carriers/old_dominion_freight_line.rb +30 -0
  220. data/lib/freight_kit/carriers/on_trac.rb +573 -0
  221. data/lib/freight_kit/carriers/one_hundred_one_transport.rb +10 -0
  222. data/lib/freight_kit/carriers/one_way_delivery.rb +10 -0
  223. data/lib/freight_kit/carriers/oracle_transportation_solutions.rb +10 -0
  224. data/lib/freight_kit/carriers/orange_courier.rb +27 -0
  225. data/lib/freight_kit/carriers/ortas_trucking.rb +10 -0
  226. data/lib/freight_kit/carriers/pac_trux_transportation.rb +10 -0
  227. data/lib/freight_kit/carriers/pacer.rb +10 -0
  228. data/lib/freight_kit/carriers/pacific_logistics.rb +10 -0
  229. data/lib/freight_kit/carriers/pacific_states_truck_lines.rb +10 -0
  230. data/lib/freight_kit/carriers/paradise_distributing.rb +10 -0
  231. data/lib/freight_kit/carriers/paramount_freight_systems.rb +10 -0
  232. data/lib/freight_kit/carriers/peninsula_truck_lines.rb +222 -0
  233. data/lib/freight_kit/carriers/pg_transport.rb +10 -0
  234. data/lib/freight_kit/carriers/pgs_360.rb +10 -0
  235. data/lib/freight_kit/carriers/pilot_freight_services.rb +10 -0
  236. data/lib/freight_kit/carriers/pious_trans.rb +27 -0
  237. data/lib/freight_kit/carriers/pitt_ohio_express.rb +30 -0
  238. data/lib/freight_kit/carriers/pki_logistics.rb +12 -0
  239. data/lib/freight_kit/carriers/plana_transportation.rb +28 -0
  240. data/lib/freight_kit/carriers/prestige.rb +10 -0
  241. data/lib/freight_kit/carriers/pro_trans_solutions.rb +28 -0
  242. data/lib/freight_kit/carriers/puma_logistics.rb +10 -0
  243. data/lib/freight_kit/carriers/pwc_international.rb +10 -0
  244. data/lib/freight_kit/carriers/quality_logistics.rb +10 -0
  245. data/lib/freight_kit/carriers/queen_star_trucking.rb +27 -0
  246. data/lib/freight_kit/carriers/r_and_l_carriers.rb +11 -0
  247. data/lib/freight_kit/carriers/rac_transport.rb +10 -0
  248. data/lib/freight_kit/carriers/re_logistics.rb +23 -0
  249. data/lib/freight_kit/carriers/reddaway.rb +11 -0
  250. data/lib/freight_kit/carriers/reefer_van_network.rb +13 -0
  251. data/lib/freight_kit/carriers/refrigerated_trucking_and_ltl_transportation_services.rb +10 -0
  252. data/lib/freight_kit/carriers/ridgeline_express.rb +10 -0
  253. data/lib/freight_kit/carriers/rist_transport.rb +29 -0
  254. data/lib/freight_kit/carriers/river_city_transport.rb +10 -0
  255. data/lib/freight_kit/carriers/roadrunner_transportation_services.rb +675 -0
  256. data/lib/freight_kit/carriers/roadtex.rb +13 -0
  257. data/lib/freight_kit/carriers/ronald_transport_services.rb +10 -0
  258. data/lib/freight_kit/carriers/rose_trucking.rb +24 -0
  259. data/lib/freight_kit/carriers/rp_and_j_transport.rb +24 -0
  260. data/lib/freight_kit/carriers/rpm_transportation.rb +11 -0
  261. data/lib/freight_kit/carriers/rude_transportation.rb +30 -0
  262. data/lib/freight_kit/carriers/s_and_s_logistics.rb +10 -0
  263. data/lib/freight_kit/carriers/saia.rb +470 -361
  264. data/lib/freight_kit/carriers/sb_transportation.rb +27 -0
  265. data/lib/freight_kit/carriers/schneider_national.rb +29 -0
  266. data/lib/freight_kit/carriers/schwarz_trucking.rb +10 -0
  267. data/lib/freight_kit/carriers/sewell_motor_express.rb +29 -0
  268. data/lib/freight_kit/carriers/shawnee_trucking.rb +28 -0
  269. data/lib/freight_kit/carriers/shi_logistics.rb +10 -0
  270. data/lib/freight_kit/carriers/sinbad_transport.rb +10 -0
  271. data/lib/freight_kit/carriers/skyway.rb +10 -0
  272. data/lib/freight_kit/carriers/smith_cargo_transportation_services.rb +23 -0
  273. data/lib/freight_kit/carriers/sna_transportation.rb +10 -0
  274. data/lib/freight_kit/carriers/solid_group.rb +10 -0
  275. data/lib/freight_kit/carriers/solution_carrier_express.rb +10 -0
  276. data/lib/freight_kit/carriers/southeastern_freight_lines.rb +450 -0
  277. data/lib/freight_kit/carriers/southwestern_motor_transport.rb +30 -0
  278. data/lib/freight_kit/carriers/span_alaska.rb +10 -0
  279. data/lib/freight_kit/carriers/specialize_transportation.rb +27 -0
  280. data/lib/freight_kit/carriers/ssi_refrigerated_express.rb +10 -0
  281. data/lib/freight_kit/carriers/standard_forwarding_llc.rb +28 -0
  282. data/lib/freight_kit/carriers/star_american_enterprise.rb +10 -0
  283. data/lib/freight_kit/carriers/sterling_transportation.rb +10 -0
  284. data/lib/freight_kit/carriers/stg.rb +163 -0
  285. data/lib/freight_kit/carriers/supervan_service.rb +10 -0
  286. data/lib/freight_kit/carriers/sutton_transport_inc.rb +12 -0
  287. data/lib/freight_kit/carriers/swift_transportation.rb +28 -0
  288. data/lib/freight_kit/carriers/t_force_worldwide.rb +617 -0
  289. data/lib/freight_kit/carriers/tax_airfreight_inc.rb +35 -0
  290. data/lib/freight_kit/carriers/tforce_freight_inc.rb +30 -0
  291. data/lib/freight_kit/carriers/the_custom_companies.rb +162 -0
  292. data/lib/freight_kit/carriers/thk_trucking.rb +28 -0
  293. data/lib/freight_kit/carriers/tiger_cool_express.rb +11 -0
  294. data/lib/freight_kit/carriers/tili_logistics.rb +10 -0
  295. data/lib/freight_kit/carriers/tmt_industries.rb +10 -0
  296. data/lib/freight_kit/carriers/top_ten_express.rb +28 -0
  297. data/lib/freight_kit/carriers/total_quality_logistics.rb +338 -0
  298. data/lib/freight_kit/carriers/total_transportation.rb +258 -0
  299. data/lib/freight_kit/carriers/trans_international_trucking.rb +29 -0
  300. data/lib/freight_kit/carriers/treasure_transportation.rb +10 -0
  301. data/lib/freight_kit/carriers/tri_state_services.rb +10 -0
  302. data/lib/freight_kit/carriers/tritex_freighters.rb +10 -0
  303. data/lib/freight_kit/carriers/triways.rb +10 -0
  304. data/lib/freight_kit/carriers/tsi_kansas.rb +29 -0
  305. data/lib/freight_kit/carriers/u_and_r_transportation.rb +23 -0
  306. data/lib/freight_kit/carriers/union_pacific.rb +11 -0
  307. data/lib/freight_kit/carriers/united_freight_logistics.rb +10 -0
  308. data/lib/freight_kit/carriers/unlimited_distance_transportation.rb +10 -0
  309. data/lib/freight_kit/carriers/up_streamline.rb +10 -0
  310. data/lib/freight_kit/carriers/ups_freight.rb +12 -0
  311. data/lib/freight_kit/carriers/us_freightways.rb +10 -0
  312. data/lib/freight_kit/carriers/usa_truck.rb +28 -0
  313. data/lib/freight_kit/carriers/usf_holland.rb +11 -0
  314. data/lib/freight_kit/carriers/valley_cartage.rb +11 -0
  315. data/lib/freight_kit/carriers/vc_transport.rb +10 -0
  316. data/lib/freight_kit/carriers/vision_express.rb +11 -0
  317. data/lib/freight_kit/carriers/vision_logistics_group.rb +10 -0
  318. data/lib/freight_kit/carriers/vmg_transportation_group.rb +10 -0
  319. data/lib/freight_kit/carriers/volpe_express.rb +10 -0
  320. data/lib/freight_kit/carriers/ward_trucking.rb +29 -0
  321. data/lib/freight_kit/carriers/wdwb.rb +10 -0
  322. data/lib/freight_kit/carriers/western_flyer_express.rb +28 -0
  323. data/lib/freight_kit/carriers/western_regional_delivery_service.rb +271 -0
  324. data/lib/freight_kit/carriers/western_specialized_trucking.rb +22 -0
  325. data/lib/freight_kit/carriers/win_distribution.rb +28 -0
  326. data/lib/freight_kit/carriers/wlb_transport.rb +10 -0
  327. data/lib/freight_kit/carriers/x_trux.rb +29 -0
  328. data/lib/freight_kit/carriers/xpo_logistics.rb +427 -0
  329. data/lib/freight_kit/carriers/xpress_global_systems.rb +30 -0
  330. data/lib/freight_kit/carriers/xylo_trucking.rb +10 -0
  331. data/lib/freight_kit/carriers/yrc_freight.rb +24 -0
  332. data/lib/freight_kit/carriers/zentner_transportation.rb +28 -0
  333. data/lib/freight_kit/carriers.rb +331 -34
  334. data/lib/freight_kit/countries.rb +22 -0
  335. data/lib/freight_kit/error.rb +0 -2
  336. data/lib/freight_kit/errors/document_not_found_error.rb +0 -2
  337. data/lib/freight_kit/errors/expired_credentials_error.rb +0 -2
  338. data/lib/freight_kit/errors/http_error.rb +0 -2
  339. data/lib/freight_kit/errors/invalid_credentials_error.rb +0 -2
  340. data/lib/freight_kit/errors/response_error.rb +0 -2
  341. data/lib/freight_kit/errors/shipment_not_found_error.rb +0 -2
  342. data/lib/freight_kit/errors/unserviceable_accessorials_error.rb +2 -4
  343. data/lib/freight_kit/errors/unserviceable_error.rb +0 -2
  344. data/lib/freight_kit/helpers/documentable.rb +0 -2
  345. data/lib/freight_kit/helpers/pickupable.rb +3 -5
  346. data/lib/freight_kit/helpers/rateable.rb +0 -2
  347. data/lib/freight_kit/helpers/trackable.rb +0 -2
  348. data/lib/freight_kit/http_client/cache_store.rb +2 -4
  349. data/lib/freight_kit/http_client/log_device.rb +0 -2
  350. data/lib/freight_kit/http_client/response.rb +0 -2
  351. data/lib/freight_kit/http_client/revalidation.rb +0 -2
  352. data/lib/freight_kit/http_client/safe_retries.rb +0 -2
  353. data/lib/freight_kit/http_client/zstd.rb +2 -4
  354. data/lib/freight_kit/http_client.rb +8 -12
  355. data/lib/freight_kit/model.rb +1 -3
  356. data/lib/freight_kit/models/address.rb +15 -0
  357. data/lib/freight_kit/models/contact.rb +3 -4
  358. data/lib/freight_kit/models/country.rb +20 -0
  359. data/lib/freight_kit/models/credential.rb +14 -16
  360. data/lib/freight_kit/models/date_time.rb +0 -2
  361. data/lib/freight_kit/models/document_response.rb +0 -2
  362. data/lib/freight_kit/models/label.rb +0 -2
  363. data/lib/freight_kit/models/location.rb +10 -25
  364. data/lib/freight_kit/models/pickup_response.rb +0 -2
  365. data/lib/freight_kit/models/price.rb +0 -2
  366. data/lib/freight_kit/models/rate.rb +0 -2
  367. data/lib/freight_kit/models/rate_response.rb +0 -2
  368. data/lib/freight_kit/models/region.rb +19 -0
  369. data/lib/freight_kit/models/response.rb +0 -2
  370. data/lib/freight_kit/models/shipment.rb +4 -6
  371. data/lib/freight_kit/models/shipment_event.rb +0 -2
  372. data/lib/freight_kit/models/terminal.rb +10 -0
  373. data/lib/freight_kit/models/tracking_response.rb +0 -2
  374. data/lib/freight_kit/package.rb +51 -53
  375. data/lib/freight_kit/package_item.rb +14 -14
  376. data/lib/freight_kit/packaging.rb +21 -26
  377. data/lib/freight_kit/platform.rb +0 -32
  378. data/lib/freight_kit/platforms/carrier_logistics.rb +331 -345
  379. data/lib/freight_kit/platforms/next_tms.rb +93 -0
  380. data/lib/freight_kit/platforms/the_great_information_factory.rb +500 -425
  381. data/lib/freight_kit/regions.rb +27 -0
  382. data/lib/freight_kit/shipment_packer.rb +36 -38
  383. data/lib/freight_kit/tariff/overlength_rule.rb +1 -5
  384. data/lib/freight_kit/tariff.rb +0 -2
  385. data/lib/freight_kit.rb +84 -11
  386. metadata +361 -51
  387. data/configuration/carriers/abfs.yml +0 -124
  388. data/configuration/carriers/btvp.yml +0 -84
  389. data/configuration/carriers/ccyq.yml +0 -121
  390. data/configuration/carriers/clni.yml +0 -113
  391. data/configuration/carriers/cnwy.yml +0 -113
  392. data/configuration/carriers/ctbv.yml +0 -117
  393. data/configuration/carriers/dcha.yml +0 -105
  394. data/configuration/carriers/dlds.yml +0 -111
  395. data/configuration/carriers/dphe.yml +0 -130
  396. data/configuration/carriers/drrq.yml +0 -131
  397. data/configuration/carriers/fcsy.yml +0 -102
  398. data/configuration/carriers/fwda.yml +0 -137
  399. data/configuration/carriers/jfj_transportation.yml +0 -2
  400. data/configuration/carriers/mtvl.yml +0 -12
  401. data/configuration/carriers/numk.yml +0 -14
  402. data/configuration/carriers/otcl.yml +0 -124
  403. data/configuration/carriers/pens.yml +0 -22
  404. data/configuration/carriers/rdfs.yml +0 -142
  405. data/configuration/carriers/saia.yml +0 -129
  406. data/configuration/carriers/sefl.yml +0 -115
  407. data/configuration/carriers/totl.yml +0 -111
  408. data/configuration/carriers/tqyl.yml +0 -28
  409. data/configuration/carriers/wrds.yml +0 -20
  410. data/configuration/platforms/carrier_logistics.yml +0 -25
  411. data/configuration/platforms/next.yml +0 -12
  412. data/configuration/platforms/the_great_information_factory.yml +0 -122
  413. data/lib/freight_kit/carriers/abfs.rb +0 -421
  414. data/lib/freight_kit/carriers/btvp.rb +0 -29
  415. data/lib/freight_kit/carriers/ccyq.rb +0 -310
  416. data/lib/freight_kit/carriers/clni.rb +0 -396
  417. data/lib/freight_kit/carriers/cnwy.rb +0 -327
  418. data/lib/freight_kit/carriers/ctbv.rb +0 -53
  419. data/lib/freight_kit/carriers/dcha.rb +0 -76
  420. data/lib/freight_kit/carriers/dlds.rb +0 -49
  421. data/lib/freight_kit/carriers/dphe.rb +0 -472
  422. data/lib/freight_kit/carriers/drrq.rb +0 -580
  423. data/lib/freight_kit/carriers/fcsy.rb +0 -57
  424. data/lib/freight_kit/carriers/fwda.rb +0 -744
  425. data/lib/freight_kit/carriers/mtvl.rb +0 -34
  426. data/lib/freight_kit/carriers/numk.rb +0 -58
  427. data/lib/freight_kit/carriers/otcl.rb +0 -520
  428. data/lib/freight_kit/carriers/pens.rb +0 -204
  429. data/lib/freight_kit/carriers/rdfs.rb +0 -521
  430. data/lib/freight_kit/carriers/sefl.rb +0 -342
  431. data/lib/freight_kit/carriers/totl.rb +0 -172
  432. data/lib/freight_kit/carriers/tqyl.rb +0 -337
  433. data/lib/freight_kit/carriers/wrds.rb +0 -246
  434. data/lib/freight_kit/platforms/next.rb +0 -101
@@ -0,0 +1,789 @@
1
+ module FreightKit
2
+ module Carriers
3
+ class ForwardAir < FreightKit::Carrier
4
+ class << self
5
+ def check_mailing_addresses
6
+ [
7
+ Address.new(
8
+ city: 'Knoxville',
9
+ country: Countries.find_by(iso31661_alpha2: 'US'),
10
+ id: 'a73d400c-4ed9-4bfb-a9d9-6842cb3bde7d',
11
+ postal_code: '37995',
12
+ region_abbreviation: 'TN',
13
+ street: 'Dept 888155'
14
+ )
15
+ ]
16
+ end
17
+
18
+ def find_rates_with_declared_value? = true
19
+ def id = 'c69bfbcd-3a1f-4d2f-9dd6-c357ab9193de'
20
+ def legal_name = 'Forward Air, LLC'
21
+ def mappable_accessorials = DELIVERY_ACCESSORIALS.merge(PICKUP_ACCESSORIALS)
22
+ def maximum_height = Measured::Length.new(105, :inches)
23
+ def maximum_weight = Measured::Weight.new(10_000, :pounds)
24
+ def minimum_length_for_overlength_fees = Measured::Length.new(6, :feet)
25
+ def name = 'Forward Air'
26
+ def overlength_fees_require_tariff? = false
27
+ def pickup_number_is_tracking_number? = true
28
+ def rates_with_excessive_length_fees? = false
29
+ def required_credential_types = %i[api]
30
+ def requirements = %i[credentials]
31
+ def scac = 'FWDA'
32
+ def services = %i[standard_ltl]
33
+
34
+ def unquotable_accessorials
35
+ %i[
36
+ athletic_facility_delivery
37
+ athletic_facility_pickup
38
+ cemetery_delivery
39
+ cemetery_pickup
40
+ church_delivery
41
+ church_pickup
42
+ construction_delivery
43
+ construction_pickup
44
+ dock_dropoff
45
+ dock_pickup
46
+ driver_assist
47
+ farm_delivery
48
+ farm_pickup
49
+ fitness_center_delivery
50
+ fitness_center_pickup
51
+ golf_course_delivery
52
+ golf_course_pickup
53
+ government_delivery
54
+ government_pickup
55
+ grocery_store_delivery
56
+ grocery_store_pickup
57
+ jobsite_delivery
58
+ jobsite_pickup
59
+ marina_delivery
60
+ marina_pickup
61
+ mine_site_delivery
62
+ mine_site_pickup
63
+ nursing_home_delivery
64
+ nursing_home_pickup
65
+ park_delivery
66
+ park_pickup
67
+ prison_delivery
68
+ prison_pickup
69
+ ranch_delivery
70
+ ranch_pickup
71
+ reservation_delivery
72
+ reservation_pickup
73
+ restaurant_delivery
74
+ restaurant_pickup
75
+ school_pickup
76
+ steel_mill_delivery
77
+ steel_mill_pickup
78
+ storage_facility_delivery
79
+ storage_facility_pickup
80
+ university_pickup
81
+ utility_site_delivery
82
+ utility_site_pickup
83
+ ]
84
+ end
85
+
86
+ def unserviceable_accessorials
87
+ %i[
88
+ brewery_delivery
89
+ brewery_pickup
90
+ customs_brokerage
91
+ flatbed_delivery
92
+ flatbed_pickup
93
+ grocery_warehouse_delivery
94
+ grocery_warehouse_pickup
95
+ guaranteed_delivery
96
+ guaranteed_delivery_am
97
+ guaranteed_delivery_pm
98
+ inspection_site_delivery
99
+ inspection_site_pickup
100
+ winery_delivery
101
+ winery_pickup
102
+ ]
103
+ end
104
+
105
+ def us_dot_number = 2_215_557
106
+ def us_mc_number = 249_708
107
+ end
108
+
109
+ include FreightKit::Rateable
110
+ include FreightKit::Trackable
111
+ include FreightKit::Pickupable
112
+
113
+ API = {
114
+ domains: { production: 'api.forwardair.com', test: 'test-api.forwardair.com' },
115
+ endpoints: {
116
+ documents: '/ltlservices/v2/rest/waybills/%TRACKING_NUMBER%/documents',
117
+ document: '/ltlservices/v2/rest/waybills/%TRACKING_NUMBER%/documents/download' \
118
+ '?docId=%DOC_ID%',
119
+ locations: '/ltlservices/v2/rest/ltlinfo/liststations',
120
+ pickup: '/ltlservices/v2/rest/waybills/book/json',
121
+ rates: '/ltlservices/v2/rest/waybills/quote/json',
122
+ track: '/ltlservices/v2/rest/waybills/%TRACKING_NUMBER%/statuses'
123
+ },
124
+ methods: { locations: :get, pickup: :post, rates: :post, track: :get },
125
+ use_ssl: true
126
+ }.freeze
127
+ DELIVERY_ACCESSORIALS = {
128
+ afterhours_delivery: 'XPD',
129
+ airport_delivery: 'ALD',
130
+ amusement_park_delivery: 'CCD',
131
+ appointment_delivery: 'ADE',
132
+ convention_delivery: 'CCD',
133
+ early_morning_delivery: 'XPD',
134
+ fair_delivery: 'CCD',
135
+ hospital_delivery: 'HDC',
136
+ hotel_delivery: 'HOD',
137
+ inside_delivery: 'IDE',
138
+ liftgate_delivery: 'LGD',
139
+ mall_delivery: 'MDC',
140
+ military_site_delivery: 'MBD',
141
+ motel_delivery: 'HOD',
142
+ residential_delivery: 'RDE',
143
+ resort_delivery: 'HOD',
144
+ school_delivery: 'UND',
145
+ university_delivery: 'UND'
146
+ }.freeze
147
+ EVENTS = {
148
+ types: {
149
+ arrived_at_terminal: 'ARR',
150
+ delivered: 'DLV',
151
+ delivery_appointment_scheduled: 'DPL',
152
+ departed: 'DEP',
153
+ located: 'ARRD',
154
+ out_for_delivery: 'OUTDV',
155
+ picked_up: 'PUP',
156
+ pickup_driver_assigned: 'DPUP'
157
+ }
158
+ }.freeze
159
+ JSON_HEADERS = { Accept: 'application/json', charset: 'utf-8', 'Content-Type' => 'application/json' }.freeze
160
+ PICKUP_ACCESSORIALS = {
161
+ afterhours_pickup: 'XPU',
162
+ airport_pickup: 'ALP',
163
+ amusement_park_pickup: 'CCP',
164
+ appointment_pickup: 'APP',
165
+ convention_pickup: 'CCP',
166
+ early_morning_pickup: 'XPU',
167
+ fair_pickup: 'CCP',
168
+ hospital_pickup: 'HPC',
169
+ hotel_pickup: 'HOT',
170
+ inside_pickup: 'IPU',
171
+ liftgate_pickup: 'LGP',
172
+ mall_pickup: 'MPC',
173
+ military_site_pickup: 'MBP',
174
+ motel_pickup: 'HOT',
175
+ residential_pickup: 'RPU',
176
+ resort_pickup: 'HOP'
177
+ }.freeze
178
+
179
+ # Documents
180
+
181
+ def find_locations(country)
182
+ raise ArgumentError, 'country must be a ActiveUtils::Country' unless country.is_a?(ActiveUtils::Country)
183
+
184
+ request = build_locations_request
185
+ parse_locations_response(country:, response: commit(request))
186
+ end
187
+
188
+ def find_rates(shipment:)
189
+ if shipment.packages.map { |package| package.height(:in) }.any?(&:blank?) ||
190
+ shipment.packages.map { |package| package.length(:in) }.any?(&:blank?) ||
191
+ shipment.packages.map { |package| package.width(:in) }.any?(&:blank?)
192
+
193
+ raise UnserviceableError, 'Dimensions required for quoting'
194
+ end
195
+
196
+ packages = shipment.packages.select { |package| package.height(:in) > 89 }
197
+ if packages.any?
198
+ message = <<~MESSAGE.squish
199
+ #{'Height'.pluralize(packages)}
200
+ #{packages.map { |package| "#{package.height(:in)} inches" }.join(', ')}
201
+ greater than maximum allowed of 89 inches.
202
+ MESSAGE
203
+ raise UnserviceableError, message
204
+ end
205
+
206
+ packages = shipment.packages.select { |package| package.length(:in) > 240 }
207
+ if packages.any?
208
+ message = <<~MESSAGE.squish
209
+ #{'Length'.pluralize(packages)}
210
+ #{packages.map { |package| "#{package.length(:in)} inches" }.join(', ')}
211
+ greater than maximum allowed of 240 inches.
212
+ MESSAGE
213
+ raise UnserviceableError, message
214
+ end
215
+
216
+ packages = shipment.packages.select { |package| package.width(:in) > 82 }
217
+ if packages.any?
218
+ message = <<~MESSAGE.squish
219
+ #{'Width'.pluralize(packages)}
220
+ #{packages.map { |package| "#{package.width(:in)} inches" }.join(', ')}
221
+ greater than maximum allowed of 82 inches.
222
+ MESSAGE
223
+ raise UnserviceableError, message
224
+ end
225
+
226
+ super
227
+ end
228
+
229
+ def pod(tracking_number)
230
+ # Retrieve list of available documents first
231
+ begin
232
+ documents = commit(build_documents_request(tracking_number))
233
+ rescue FreightKit::ResponseError => e
234
+ return DocumentResponse.new(error: FreightKit::DocumentNotFoundError) if e.message.downcase.include?('no airbills found')
235
+
236
+ return DocumentResponse.new(error: e)
237
+ end
238
+
239
+ begin
240
+ doc_id = get_doc_id(documents:, tracking_number:, type: :pod)
241
+ rescue StandardError => e
242
+ return DocumentResponse.new(error: e)
243
+ end
244
+
245
+ request = build_document_request(doc_id:, tracking_number:)
246
+ response = commit(request)
247
+
248
+ parse_document_response(:pod, tracking_number, response)
249
+ end
250
+
251
+ def scanned_bol(tracking_number, _options = {})
252
+ # Retrieve list of available documents first
253
+ begin
254
+ documents = commit(build_documents_request(tracking_number))
255
+ rescue FreightKit::ResponseError => e
256
+ return DocumentResponse.new(error: FreightKit::DocumentNotFoundError) if e.message.downcase.include?('no airbills found')
257
+
258
+ return DocumentResponse.new(error: e)
259
+ end
260
+
261
+ begin
262
+ doc_id = get_doc_id(documents:, tracking_number:, type: :bol)
263
+ rescue StandardError => e
264
+ return DocumentResponse.new(e:)
265
+ end
266
+
267
+ request = build_document_request(doc_id:, tracking_number:)
268
+ response = commit(request)
269
+
270
+ parse_document_response(:bol, tracking_number, response)
271
+ end
272
+
273
+ protected
274
+
275
+ def base_url = "https://#{API.dig(:domains, :production)}"
276
+
277
+ def build_accessorials(accessorials)
278
+ delivery_accessorials = []
279
+ pickup_accessorials = []
280
+
281
+ if accessorials.present?
282
+ serviceable_accessorials?(accessorials)
283
+
284
+ accessorials.excluding(unserviceable_accessorials).each do |a|
285
+ delivery_accessorials << DELIVERY_ACCESSORIALS[a] if DELIVERY_ACCESSORIALS.include?(a)
286
+ pickup_accessorials << PICKUP_ACCESSORIALS[a] if PICKUP_ACCESSORIALS.include?(a)
287
+ end
288
+ end
289
+
290
+ if delivery_accessorials.present? && delivery_accessorials.include?('RDE')
291
+ # Remove duplicate delivery appointment accessorial when residential delivery (included with RDE)
292
+ delivery_accessorials -= ['ADE']
293
+ end
294
+
295
+ if pickup_accessorials.present? && pickup_accessorials.include?('RPU')
296
+ # Remove duplicate pickup appointment accessorial when residential pickup (included with RPU)
297
+ pickup_accessorials -= ['APP']
298
+ end
299
+
300
+ # API doesn't like empty arrays
301
+ delivery_accessorials = nil if delivery_accessorials.blank?
302
+ pickup_accessorials = nil if pickup_accessorials.blank?
303
+
304
+ [pickup_accessorials&.uniq, delivery_accessorials&.uniq]
305
+ end
306
+
307
+ def build_dimensions(packages)
308
+ packages.map do |package|
309
+ {
310
+ height: package.height(:in).ceil,
311
+ length: package.length(:in).ceil,
312
+ pieces: package.quantity,
313
+ width: package.width(:in).ceil
314
+ }
315
+ end
316
+ end
317
+
318
+ def build_document_request(doc_id:, tracking_number:)
319
+ request = {
320
+ url: build_url(:document, doc_id:, tracking_number:),
321
+ headers: build_headers,
322
+ method: API.dig(:methods, :documents)
323
+ }
324
+
325
+ save_request(request)
326
+ request
327
+ end
328
+
329
+ def build_documents_request(tracking_number)
330
+ request = {
331
+ url: build_url(:documents, tracking_number:),
332
+ headers: build_headers,
333
+ method: API.dig(:methods, :documents)
334
+ }
335
+
336
+ save_request(request)
337
+ request
338
+ end
339
+
340
+ def build_freight_details(packages)
341
+ packages.map do |package|
342
+ {
343
+ description: package.description || 'Freight',
344
+ freightClass: package.freight_class.to_s,
345
+ pieces: package.quantity.to_s,
346
+ weightType: 'L',
347
+ weight: package.pounds(:total).ceil.to_s
348
+ }
349
+ end
350
+ end
351
+
352
+ def build_headers
353
+ api_credentials = fetch_credential(:api)
354
+
355
+ JSON_HEADERS.merge(
356
+ {
357
+ billToAccountNumber: api_credentials.account,
358
+ customerId: api_credentials.username.upcase,
359
+ password: api_credentials.password,
360
+ user: api_credentials.username
361
+ }
362
+ )
363
+ end
364
+
365
+ def build_locations_request
366
+ request = { url: build_url(:locations), headers: build_headers, method: API.dig(:methods, :locations) }
367
+
368
+ save_request(request)
369
+ request
370
+ end
371
+
372
+ def build_pickup_request(
373
+ delivery_from:,
374
+ delivery_to:,
375
+ dispatcher:,
376
+ pickup_from:,
377
+ pickup_to:,
378
+ scac:,
379
+ service:,
380
+ shipment:
381
+ )
382
+ pickup_accessorials, delivery_accessorials = build_accessorials(shipment.accessorials)
383
+
384
+ dispatcher_phone = dispatcher.phone.delete('^0-9')
385
+ shipper_phone = shipment.origin.contact.phone.delete('^0-9')
386
+ receiver_phone = shipment.destination.contact.phone.delete('^0-9')
387
+
388
+ api_credentials = fetch_credential(:api)
389
+
390
+ declared_value = if shipment.declared_value_cents.blank?
391
+ '0'
392
+ else
393
+ format('%.2f', (shipment.declared_value_cents.to_f / 100).ceil)
394
+ end
395
+
396
+ delivery = {
397
+ airportDelivery: delivery_accessorials&.include?('ALD') ? 'Y' : 'N',
398
+ deliveryAccessorials: { deliveryAccessorial: delivery_accessorials }
399
+ }
400
+ description = shipment.packages.map(&:description).reject(&:blank?).uniq.join(', ')
401
+
402
+ request = {
403
+ headers: build_headers,
404
+ method: API.dig(:methods, :pickup),
405
+ url: build_url(:pickup),
406
+ body: {
407
+ testmode: 'N',
408
+ consignee: {
409
+ consigneeAddress1: shipment.destination.address1,
410
+ consigneeAddress2: '',
411
+ consigneeCity: shipment.destination.city,
412
+ consigneeCloseTime: delivery_to.strftime('%H:%M:00'),
413
+ consigneeContactEmail: '',
414
+ consigneeContactName: shipment.destination.contact.name || 'Receiving',
415
+ consigneeContactPhone: receiver_phone || '',
416
+ consigneeCountry: shipment.destination.country.code(:alpha2).value,
417
+ consigneeLocationName: shipment.destination.contact.name,
418
+ consigneeOpenTime: delivery_from.strftime('%H:%M:00'),
419
+ consigneeState: shipment.destination.province,
420
+ consigneeZipCode: shipment.destination.postal_code.to_s
421
+ },
422
+ shipper: {
423
+ shipperAddress1: shipment.origin.address1,
424
+ shipperAddress2: '',
425
+ shipperCity: shipment.origin.city,
426
+ shipperCloseTime: pickup_to.strftime('%H:%M:00'),
427
+ shipperContactEmail: '',
428
+ shipperContactName: shipment.origin.contact.name || 'Shipping',
429
+ shipperContactPhone: shipper_phone || '',
430
+ shipperCountry: shipment.origin.country.code(:alpha2).value,
431
+ shipperLocationName: shipment.origin.contact.name,
432
+ shipperOpenTime: pickup_from.strftime('%H:%M:00'),
433
+ shipperState: shipment.origin.province,
434
+ shipperZipCode: shipment.origin.postal_code.to_s
435
+ },
436
+ orderDetails: {
437
+ airbillNumber: '00000000',
438
+ billToCustomerNumber: api_credentials.account.to_s,
439
+ customerReferenceNumber: shipment.po_number,
440
+ declaredValue: declared_value,
441
+ description:,
442
+ destinationAirportCode: '',
443
+ guaranteedService: 'N',
444
+ hazmat: shipment.hazmat? ? 'Y' : 'N',
445
+ inBondShipment: declared_value.to_f.positive? ? 'Y' : 'N',
446
+ orderAction: 'CREATE',
447
+ originAirportCode: '',
448
+ shippingDate: pickup_from.strftime('%Y-%m-%d'),
449
+ shipperCustomerNumber: api_credentials.account.to_s,
450
+ specialInstructions: '',
451
+ dimensions: {
452
+ dimension: shipment.packages.map do |package|
453
+ {
454
+ height: package.inches(:height).ceil.to_s,
455
+ length: package.inches(:length).ceil.to_s,
456
+ width: package.inches(:width).ceil.to_s,
457
+ pieces: package.quantity.to_s
458
+ }
459
+ end
460
+ },
461
+ freightDetails: { freightDetail: build_freight_details(shipment.packages) },
462
+ delivery:,
463
+ emergencyContact: { email: dispatcher.email, name: dispatcher.name, phone: dispatcher_phone },
464
+ pickup: {
465
+ airportPickup: pickup_accessorials&.include?('ALP') ? 'Y' : 'N',
466
+ pickupAccessorials: { pickupAccessorial: pickup_accessorials },
467
+ pickupReadyTime: pickup_from.strftime('%H:%M:00')
468
+ },
469
+ referenceNumbers: { referenceNumber: [shipment.order_number, shipment.po_number, ''] }
470
+ }
471
+ }.to_json
472
+ }
473
+
474
+ save_request(request)
475
+ request
476
+ end
477
+
478
+ def build_rate_request(shipment:)
479
+ api_credentials = fetch_credential(:api)
480
+
481
+ freight_details = build_freight_details(shipment.packages)
482
+ dimensions = build_dimensions(shipment.packages)
483
+ declared_value = if shipment.declared_value_cents.blank?
484
+ '0'
485
+ else
486
+ format('%.2f', (shipment.declared_value_cents.to_f / 100).ceil)
487
+ end
488
+ pickup_accessorials, delivery_accessorials = build_accessorials(shipment.accessorials)
489
+
490
+ delivery = {
491
+ airportDelivery: delivery_accessorials&.include?('ALD') ? 'Y' : 'N',
492
+ deliveryAccessorials: { deliveryAccessorial: delivery_accessorials }
493
+ }
494
+
495
+ request = {
496
+ url: build_url(:rates),
497
+ headers: build_headers,
498
+ method: API.dig(:methods, :rates),
499
+ body: {
500
+ billToCustomerNumber: api_credentials.account,
501
+ origin: {
502
+ originZipCode: shipment.origin.postal_code.to_s.upcase,
503
+ pickup: {
504
+ airportPickup: pickup_accessorials&.include?('ALP') ? 'Y' : 'N',
505
+ pickupAccessorials: { pickupAccessorial: pickup_accessorials }
506
+ }
507
+ },
508
+ destination: { destinationZipCode: shipment.destination.postal_code.to_s.upcase, delivery: },
509
+ dimensions: { dimension: dimensions },
510
+ freightDetails: { freightDetail: freight_details },
511
+ hazmat: shipment.hazmat? ? 'Y' : 'N',
512
+ inBondShipment: 'N',
513
+ declaredValue: declared_value,
514
+ shippingDate: Date.current.strftime('%Y-%m-%d')
515
+ }.to_json
516
+ }
517
+
518
+ save_request(request)
519
+ request
520
+ end
521
+
522
+ def build_request(action, options = {})
523
+ headers = JSON_HEADERS
524
+ headers = headers.merge(options[:headers]) if options[:headers].present?
525
+ body = options[:body].to_json if options[:body].present?
526
+
527
+ request = { url: build_url(action, options), headers:, method: API.dig(:methods, action), body: }
528
+
529
+ save_request(request)
530
+ request
531
+ end
532
+
533
+ def build_tracking_request(tracking_number)
534
+ request = {
535
+ url: build_url(:track, tracking_number:),
536
+ headers: build_headers,
537
+ method: API.dig(:methods, :track),
538
+ body: { billToCustomerNumber: fetch_credential(:api).account, referenceNumber: tracking_number.to_s }.to_json
539
+ }
540
+
541
+ save_request(request)
542
+ request
543
+ end
544
+
545
+ def build_url(action, options = {})
546
+ url = "#{base_url}#{API.dig(:endpoints, action)}"
547
+ url = url.gsub('%TRACKING_NUMBER%', options[:tracking_number]) if options[:tracking_number]
548
+ url = url.gsub('%DOC_ID%', options[:doc_id]) if options[:doc_id]
549
+
550
+ url
551
+ end
552
+
553
+ def charge_line_item_description(charge_line_item)
554
+ description = charge_line_item['description'] || ''
555
+ description = description.gsub('-', '')
556
+ description = description.capitalize
557
+
558
+ code = charge_line_item['code']&.upcase || ''
559
+ description = "#{description} (#{code})" if code.present?
560
+ description = description.gsub('Fsc', 'FSC') if description.include?('Fsc')
561
+
562
+ description.squish
563
+ end
564
+
565
+ def commit(request)
566
+ url = request[:url]
567
+ headers = request[:headers]
568
+ method = request[:method]
569
+ body = request[:body]
570
+
571
+ response = case method
572
+ when :post
573
+ HTTPClient.post(url, headers:, body:)
574
+ else
575
+ HTTPClient.get(url, headers:)
576
+ end
577
+
578
+ if (200..299).exclude?(response.code)
579
+ message = begin
580
+ JSON.parse(response.body)['errorMessage'] || "HTTP #{response.code}"
581
+ rescue JSON::ParserError
582
+ "HTTP #{response.code}"
583
+ end
584
+
585
+ raise FreightKit::ResponseError, message
586
+ end
587
+
588
+ return response if response.content_type != 'application/json'
589
+
590
+ json = JSON.parse(response.body)
591
+ error = json.is_a?(Array) ? nil : json['errorMessage']
592
+
593
+ return json if error.blank?
594
+
595
+ raise FreightKit::InvalidCredentialsError, error if error.downcase.include?('not authorized')
596
+ raise FreightKit::InvalidCredentialsError, error if error.downcase.include?('shipper client does not exist')
597
+ raise FreightKit::ShipmentNotFoundError, error if error.downcase.include?('no history found')
598
+ raise FreightKit::UnserviceableError, error if error.downcase.include?('not serviced')
599
+
600
+ raise FreightKit::ResponseError, error
601
+ end
602
+
603
+ def get_doc_id(documents:, tracking_number:, type:)
604
+ type = type.to_s.upcase
605
+ link = nil
606
+
607
+ documents.each do |document|
608
+ next unless document['documentType'] == type
609
+
610
+ link = document['link']
611
+ end
612
+
613
+ raise FreightKit::DocumentNotFoundError, "API Error: #{name}: Document not found" unless link
614
+
615
+ query = URI.parse(link).query
616
+ doc_id = URI.decode_www_form(query).assoc('docId')&.last
617
+
618
+ raise FreightKit::DocumentNotFoundError, "API Error: #{name}: Document not found" unless doc_id
619
+
620
+ doc_id
621
+ end
622
+
623
+ def parse_api_date_time(date_time, location)
624
+ return if date_time.blank?
625
+
626
+ local_date_time = ::Time.strptime(date_time, '%m/%d/%y %H:%M').to_fs(:db)
627
+ ::FreightKit::DateTime.new(local_date_time:, location:)
628
+ end
629
+
630
+ def parse_document_response(_type, _tracking_number, response)
631
+ DocumentResponse.new(content_type: response.headers['content-type'], data: response.body, request: last_request)
632
+ end
633
+
634
+ def parse_locations_response(country:, response:)
635
+ raise ResponseError, 'API Error: Unknown response' if response.blank?
636
+
637
+ raise ResponseError, 'API Error: Unknown response' unless response.is_a?(Array)
638
+
639
+ locations = response
640
+
641
+ locations = locations.map do |location|
642
+ Location.new(
643
+ address1: location['address1'],
644
+ city: location['city'],
645
+ province: location['state'],
646
+ country: ActiveUtils::Country.find(location['countrycd']),
647
+ contact: Contact.new(fax: location['fax'], phone: location['phone'])
648
+ )
649
+ end
650
+
651
+ locations.select { |location| location.country == country }
652
+ end
653
+
654
+ def parse_pickup_response(response)
655
+ pickup_response = PickupResponse.new(request: last_request, response:)
656
+ pickup_number = response['airbillNumber']
657
+
658
+ if pickup_number.blank?
659
+ pickup_response.error = FreightKit::ResponseError.new('Unknown response')
660
+ return pickup_response
661
+ end
662
+
663
+ pickup_response.pickup_number = pickup_number
664
+ pickup_response
665
+ end
666
+
667
+ def parse_rate_response(shipment:, response:)
668
+ rate_response = RateResponse.new(request: last_request, response:)
669
+
670
+ if response.blank?
671
+ rate_response.error = ResponseError.new('API Error: Unknown response')
672
+ return rate_response
673
+ end
674
+
675
+ error = response.key?('errorMessage')
676
+
677
+ if error.present?
678
+ rate_response.error = ResponseError.new(error)
679
+ return rate_response
680
+ end
681
+
682
+ if response['quoteTotal'].blank?
683
+ rate_response.error = ResponseError.new('Cost is blank')
684
+ return rate_response
685
+ end
686
+
687
+ transit_days = response['transitDaysTotal']
688
+
689
+ charge_line_items = response.dig('chargeLineItems', 'chargeLineItem')
690
+ prices = []
691
+
692
+ charge_line_items.each do |charge_line_item|
693
+ cents = (charge_line_item['amount'] * 100).to_i
694
+ next if cents.zero?
695
+
696
+ description = charge_line_item_description(charge_line_item)
697
+
698
+ prices << Price.new(blame: :api, cents:, description:)
699
+ end
700
+
701
+ rate = Rate.new(
702
+ carrier: self,
703
+ carrier_name: name,
704
+ currency: 'USD',
705
+ scac:,
706
+ service_name: :standard,
707
+ shipment:,
708
+ prices:,
709
+ transit_days:,
710
+ with_excessive_length_fees: rates_with_excessive_length_fees?
711
+ )
712
+
713
+ rate_response.rates = [rate]
714
+ rate_response
715
+ end
716
+
717
+ def parse_tracking_response(response)
718
+ tracking_response = TrackingResponse.new(carrier: self, request: last_request, response:)
719
+
720
+ actual_delivery_date = nil
721
+ estimated_delivery_date = nil
722
+ receiver_location = nil
723
+ scheduled_delivery_date = nil
724
+ ship_time = nil
725
+ shipper_location = nil
726
+
727
+ shipment_events = []
728
+
729
+ api_events = response
730
+ api_events.each do |api_event|
731
+ event = nil
732
+ EVENTS[:types].each do |key, val|
733
+ if api_event['statusCode'].upcase == val
734
+ event = key
735
+ break
736
+ end
737
+ end
738
+ next if event.blank?
739
+
740
+ location = Location.new(
741
+ city: api_event['city'].titleize,
742
+ province: api_event['state'].upcase,
743
+ postal_code: api_event['zip'].upcase,
744
+ country: ActiveUtils::Country.find(api_event['country'])
745
+ )
746
+
747
+ date_time = parse_api_date_time(api_event['recordDate'], location)
748
+
749
+ api_estimated_delivery_date = api_event['estimatedArrivalDate']
750
+ estimated_delivery_date = parse_api_date_time(api_estimated_delivery_date, nil)
751
+
752
+ case event
753
+ when :delivered
754
+ actual_delivery_date = date_time
755
+ receiver_location = location
756
+ when :delivery_appointment_scheduled
757
+ api_date_time = api_event['scheduledDeliveryFromDate']
758
+ scheduled_delivery_date = parse_api_date_time(api_date_time, location)
759
+ when :picked_up
760
+ ship_time = date_time
761
+ shipper_location = location
762
+ end
763
+
764
+ shipment_events << ShipmentEvent.new(date_time:, location:, type_code: event)
765
+ end
766
+
767
+ estimated_delivery_date = scheduled_delivery_date if scheduled_delivery_date.present?
768
+
769
+ status = shipment_events.last&.type_code
770
+
771
+ tracking_number = api_events.last['airbillNumber']
772
+
773
+ tracking_response.assign_attributes(
774
+ actual_delivery_date:,
775
+ destination: receiver_location,
776
+ estimated_delivery_date:,
777
+ origin: shipper_location,
778
+ scheduled_delivery_date:,
779
+ ship_time:,
780
+ shipment_events:,
781
+ status:,
782
+ tracking_number:
783
+ )
784
+
785
+ tracking_response
786
+ end
787
+ end
788
+ end
789
+ end