freight_kit 0.1.20 → 0.1.22
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.
- checksums.yaml +4 -4
- data/Gemfile +0 -2
- data/README.md +6 -5
- data/Rakefile +4 -0
- data/VERSION +1 -1
- data/configuration/locations/amazon_warehouses.yml +3330 -0
- data/freight_kit-0.1.21.gem +0 -0
- data/freight_kit.gemspec +11 -22
- data/lib/freight_kit/address_normalizer.rb +24 -0
- data/lib/freight_kit/amazon_warehouse.rb +33 -0
- data/lib/freight_kit/api_clients/soap_client.rb +33 -10
- data/lib/freight_kit/carrier.rb +248 -302
- data/lib/freight_kit/carriers/a1_express.rb +25 -0
- data/lib/freight_kit/carriers/a_duie_pyle.rb +30 -0
- data/lib/freight_kit/carriers/aaa_cooper.rb +11 -0
- data/lib/freight_kit/carriers/aaa_expedited_services.rb +10 -0
- data/lib/freight_kit/carriers/ab_brothers_usa_inc.rb +12 -0
- data/lib/freight_kit/carriers/aberdeen_express_inc.rb +28 -0
- data/lib/freight_kit/carriers/abf_freight_system.rb +475 -0
- data/lib/freight_kit/carriers/access_transport.rb +10 -0
- data/lib/freight_kit/carriers/action_express.rb +10 -0
- data/lib/freight_kit/carriers/adams_and_companies.rb +10 -0
- data/lib/freight_kit/carriers/advance_distribution.rb +28 -0
- data/lib/freight_kit/carriers/alcon_transport.rb +10 -0
- data/lib/freight_kit/carriers/all_freight.rb +10 -0
- data/lib/freight_kit/carriers/alliance_transportation_group.rb +10 -0
- data/lib/freight_kit/carriers/allstate_transport.rb +10 -0
- data/lib/freight_kit/carriers/aloha_freight_forwarders.rb +31 -0
- data/lib/freight_kit/carriers/amado_trucking.rb +31 -0
- data/lib/freight_kit/carriers/american_fast_freight.rb +28 -0
- data/lib/freight_kit/carriers/amigos_service_transportation.rb +10 -0
- data/lib/freight_kit/carriers/anderson_towing_and_transport.rb +10 -0
- data/lib/freight_kit/carriers/apex_transporting.rb +10 -0
- data/lib/freight_kit/carriers/archer_cargo.rb +35 -0
- data/lib/freight_kit/carriers/armstrong_transport_group.rb +10 -0
- data/lib/freight_kit/carriers/autobahn_logistics.rb +10 -0
- data/lib/freight_kit/carriers/averitt_express.rb +30 -0
- data/lib/freight_kit/carriers/ba_transportation.rb +10 -0
- data/lib/freight_kit/carriers/balkin_express.rb +10 -0
- data/lib/freight_kit/carriers/bay_area_express.rb +10 -0
- data/lib/freight_kit/carriers/bc_freightways.rb +11 -0
- data/lib/freight_kit/carriers/benjamin_best_freight_inc.rb +37 -0
- data/lib/freight_kit/carriers/bennett_trucking.rb +10 -0
- data/lib/freight_kit/carriers/best_overnite_express.rb +78 -0
- data/lib/freight_kit/carriers/best_trucking.rb +10 -0
- data/lib/freight_kit/carriers/bestway_transport.rb +10 -0
- data/lib/freight_kit/carriers/bryan_systems.rb +10 -0
- data/lib/freight_kit/carriers/c_and_w_express.rb +10 -0
- data/lib/freight_kit/carriers/campbell_trucking.rb +10 -0
- data/lib/freight_kit/carriers/carde_transportation.rb +10 -0
- data/lib/freight_kit/carriers/cardinal_logistics.rb +28 -0
- data/lib/freight_kit/carriers/cdc_transport.rb +10 -0
- data/lib/freight_kit/carriers/cdo_express_diversified.rb +10 -0
- data/lib/freight_kit/carriers/cedar_supply_trucking.rb +10 -0
- data/lib/freight_kit/carriers/central_freight_lines.rb +12 -0
- data/lib/freight_kit/carriers/central_transport.rb +12 -0
- data/lib/freight_kit/carriers/chicago_suburban_express.rb +28 -0
- data/lib/freight_kit/carriers/city_logistics.rb +11 -0
- data/lib/freight_kit/carriers/clear_lane_freight_systems.rb +474 -0
- data/lib/freight_kit/carriers/cleopatra_trucking.rb +10 -0
- data/lib/freight_kit/carriers/clifford_transportation.rb +10 -0
- data/lib/freight_kit/carriers/cms_domestic.rb +25 -0
- data/lib/freight_kit/carriers/cnd_logistics.rb +10 -0
- data/lib/freight_kit/carriers/coast_to_coast_transport.rb +27 -0
- data/lib/freight_kit/carriers/countrywide_carriers.rb +10 -0
- data/lib/freight_kit/carriers/cpl_express.rb +31 -0
- data/lib/freight_kit/carriers/crosby_trucking_service.rb +29 -0
- data/lib/freight_kit/carriers/cross_country_freight_solutions.rb +408 -0
- data/lib/freight_kit/carriers/crowley.rb +10 -0
- data/lib/freight_kit/carriers/csa_transportation.rb +40 -0
- data/lib/freight_kit/carriers/csx_transportation.rb +40 -0
- data/lib/freight_kit/carriers/custom_global_logistics.rb +14 -0
- data/lib/freight_kit/carriers/d_and_a_transport.rb +10 -0
- data/lib/freight_kit/carriers/dak_transport.rb +10 -0
- data/lib/freight_kit/carriers/dash_courier_and_logistics_north_carolina.rb +10 -0
- data/lib/freight_kit/carriers/daylight_transport.rb +29 -0
- data/lib/freight_kit/carriers/dayton_freight_lines.rb +30 -0
- data/lib/freight_kit/carriers/dbr_logistics.rb +10 -0
- data/lib/freight_kit/carriers/deep_south_freight.rb +27 -0
- data/lib/freight_kit/carriers/delta_express.rb +10 -0
- data/lib/freight_kit/carriers/delta_transport.rb +10 -0
- data/lib/freight_kit/carriers/delta_trucking_logistics.rb +10 -0
- data/lib/freight_kit/carriers/demco_express.rb +10 -0
- data/lib/freight_kit/carriers/dependable_global_express.rb +10 -0
- data/lib/freight_kit/carriers/dependable_hawaiian_express.rb +24 -0
- data/lib/freight_kit/carriers/dependable_highway_express.rb +573 -0
- data/lib/freight_kit/carriers/desert_coastal_transport.rb +10 -0
- data/lib/freight_kit/carriers/dhl.rb +11 -0
- data/lib/freight_kit/carriers/diamond_line_delivery.rb +174 -0
- data/lib/freight_kit/carriers/ding_tai_trucking.rb +10 -0
- data/lib/freight_kit/carriers/diversified_transfer_and_storage.rb +29 -0
- data/lib/freight_kit/carriers/dohrn_transfer_company.rb +30 -0
- data/lib/freight_kit/carriers/dot_line_transportation.rb +29 -0
- data/lib/freight_kit/carriers/drt_transportation.rb +13 -0
- data/lib/freight_kit/carriers/dugan_truck_line.rb +29 -0
- data/lib/freight_kit/carriers/dupont.rb +10 -0
- data/lib/freight_kit/carriers/e_transport_carriers.rb +29 -0
- data/lib/freight_kit/carriers/echo_global_logistics.rb +13 -0
- data/lib/freight_kit/carriers/ecklund_logistics.rb +10 -0
- data/lib/freight_kit/carriers/el_emplumado_transport.rb +10 -0
- data/lib/freight_kit/carriers/elite_carriers.rb +28 -0
- data/lib/freight_kit/carriers/emerald_express.rb +10 -0
- data/lib/freight_kit/carriers/era_transport.rb +10 -0
- data/lib/freight_kit/carriers/estes_express_lines.rb +25 -0
- data/lib/freight_kit/carriers/euro_kam.rb +28 -0
- data/lib/freight_kit/carriers/ew_riverside_trucking.rb +10 -0
- data/lib/freight_kit/carriers/express_2000_inc.rb +12 -0
- data/lib/freight_kit/carriers/express_services_international.rb +10 -0
- data/lib/freight_kit/carriers/f_and_a_express.rb +27 -0
- data/lib/freight_kit/carriers/fairmont_logistics.rb +13 -0
- data/lib/freight_kit/carriers/fargo_cargo.rb +10 -0
- data/lib/freight_kit/carriers/fast_freight_transportation.rb +10 -0
- data/lib/freight_kit/carriers/fast_load_transport.rb +10 -0
- data/lib/freight_kit/carriers/fed_ex_express.rb +30 -0
- data/lib/freight_kit/carriers/fed_ex_ground.rb +24 -0
- data/lib/freight_kit/carriers/fed_ex_ltl_economy.rb +11 -0
- data/lib/freight_kit/carriers/fed_ex_ltl_priority.rb +11 -0
- data/lib/freight_kit/carriers/first_trucking_express.rb +10 -0
- data/lib/freight_kit/carriers/flash_trucking.rb +10 -0
- data/lib/freight_kit/carriers/fort_transportation_and_service_company_inc.rb +37 -0
- data/lib/freight_kit/carriers/forward_air.rb +781 -0
- data/lib/freight_kit/carriers/frahtex.rb +10 -0
- data/lib/freight_kit/carriers/frozen_food_express.rb +10 -0
- data/lib/freight_kit/carriers/gholia_bros_trucking.rb +36 -0
- data/lib/freight_kit/carriers/global_cargo.rb +10 -0
- data/lib/freight_kit/carriers/global_logistics_and_transportation.rb +10 -0
- data/lib/freight_kit/carriers/gls_freight.rb +32 -0
- data/lib/freight_kit/carriers/go_trucking_services.rb +10 -0
- data/lib/freight_kit/carriers/gold_eagle_transportation.rb +11 -0
- data/lib/freight_kit/carriers/golden_drop_trucking.rb +10 -0
- data/lib/freight_kit/carriers/gonzalez_transportation.rb +10 -0
- data/lib/freight_kit/carriers/gps_transport.rb +10 -0
- data/lib/freight_kit/carriers/greene_enterprise.rb +10 -0
- data/lib/freight_kit/carriers/gsa_international.rb +10 -0
- data/lib/freight_kit/carriers/gsp_express.rb +24 -0
- data/lib/freight_kit/carriers/gsv_transportation.rb +10 -0
- data/lib/freight_kit/carriers/gta_transportation.rb +10 -0
- data/lib/freight_kit/carriers/guam_freight_services.rb +10 -0
- data/lib/freight_kit/carriers/harrah_transportation.rb +23 -0
- data/lib/freight_kit/carriers/her_trucking.rb +10 -0
- data/lib/freight_kit/carriers/hercules_freight.rb +20 -0
- data/lib/freight_kit/carriers/highlander_xpress.rb +36 -0
- data/lib/freight_kit/carriers/hn_transport.rb +10 -0
- data/lib/freight_kit/carriers/honolulu_freight_service.rb +10 -0
- data/lib/freight_kit/carriers/hot_line_freight_system_inc.rb +12 -0
- data/lib/freight_kit/carriers/hotshot_freight_of_texas.rb +13 -0
- data/lib/freight_kit/carriers/houser_transport.rb +27 -0
- data/lib/freight_kit/carriers/infiniti_freight_logistics.rb +28 -0
- data/lib/freight_kit/carriers/infinity_trucking.rb +10 -0
- data/lib/freight_kit/carriers/interstate_tma.rb +10 -0
- data/lib/freight_kit/carriers/intrade_industries.rb +10 -0
- data/lib/freight_kit/carriers/jasper_trucking.rb +28 -0
- data/lib/freight_kit/carriers/jb_hunt.rb +11 -0
- data/lib/freight_kit/carriers/jfj_transportation.rb +8 -8
- data/lib/freight_kit/carriers/jkb_express.rb +10 -0
- data/lib/freight_kit/carriers/jlm_transport.rb +10 -0
- data/lib/freight_kit/carriers/johansson_transport.rb +10 -0
- data/lib/freight_kit/carriers/jones_express.rb +10 -0
- data/lib/freight_kit/carriers/jp_logistics.rb +41 -0
- data/lib/freight_kit/carriers/just_in_time_cargo.rb +29 -0
- data/lib/freight_kit/carriers/kengkok_times.rb +10 -0
- data/lib/freight_kit/carriers/kings_express.rb +10 -0
- data/lib/freight_kit/carriers/klc_transportation.rb +10 -0
- data/lib/freight_kit/carriers/km_trucking.rb +10 -0
- data/lib/freight_kit/carriers/kma_logistics.rb +10 -0
- data/lib/freight_kit/carriers/krc_logistics.rb +10 -0
- data/lib/freight_kit/carriers/ktx.rb +10 -0
- data/lib/freight_kit/carriers/landstar.rb +25 -0
- data/lib/freight_kit/carriers/lanter_distributing_llc.rb +30 -0
- data/lib/freight_kit/carriers/ld_trucking.rb +10 -0
- data/lib/freight_kit/carriers/legacy_logistics_solutions.rb +10 -0
- data/lib/freight_kit/carriers/leonards_express.rb +10 -0
- data/lib/freight_kit/carriers/liberty_linehaul_west.rb +28 -0
- data/lib/freight_kit/carriers/liberty_transport.rb +10 -0
- data/lib/freight_kit/carriers/lightning_express.rb +10 -0
- data/lib/freight_kit/carriers/lj_express.rb +24 -0
- data/lib/freight_kit/carriers/lkp_enterprises.rb +10 -0
- data/lib/freight_kit/carriers/ltl_pros.rb +28 -0
- data/lib/freight_kit/carriers/m_and_m_and_sons.rb +10 -0
- data/lib/freight_kit/carriers/mag_lines_transport.rb +10 -0
- data/lib/freight_kit/carriers/magic_messenger.rb +24 -0
- data/lib/freight_kit/carriers/majors_transit.rb +10 -0
- data/lib/freight_kit/carriers/map_global_logistics.rb +10 -0
- data/lib/freight_kit/carriers/matths_trucking.rb +11 -0
- data/lib/freight_kit/carriers/mav_canadian_transport.rb +13 -0
- data/lib/freight_kit/carriers/mc_systems.rb +35 -0
- data/lib/freight_kit/carriers/mega_fleet.rb +10 -0
- data/lib/freight_kit/carriers/mega_trans_group.rb +28 -0
- data/lib/freight_kit/carriers/meta_express.rb +10 -0
- data/lib/freight_kit/carriers/mia_transportation.rb +10 -0
- data/lib/freight_kit/carriers/midwest_motor_express.rb +14 -0
- data/lib/freight_kit/carriers/midwest_transport_and_trucking.rb +10 -0
- data/lib/freight_kit/carriers/mikade_delivery_service.rb +10 -0
- data/lib/freight_kit/carriers/mohoff_freight_lines.rb +23 -0
- data/lib/freight_kit/carriers/montata_travelers_express.rb +10 -0
- data/lib/freight_kit/carriers/morgan_van_lines.rb +29 -0
- data/lib/freight_kit/carriers/moto_transportation.rb +24 -0
- data/lib/freight_kit/carriers/mountain_valley_express.rb +197 -0
- data/lib/freight_kit/carriers/ms_freight.rb +27 -0
- data/lib/freight_kit/carriers/mvg_transport.rb +10 -0
- data/lib/freight_kit/carriers/n_and_m_transfer_co_inc.rb +29 -0
- data/lib/freight_kit/carriers/nabot_transport_solutions.rb +10 -0
- data/lib/freight_kit/carriers/national_freight_xpress.rb +10 -0
- data/lib/freight_kit/carriers/navigation_group.rb +10 -0
- data/lib/freight_kit/carriers/nelson_trucking.rb +10 -0
- data/lib/freight_kit/carriers/network_1_transportation.rb +10 -0
- data/lib/freight_kit/carriers/new_century_transport.rb +28 -0
- data/lib/freight_kit/carriers/new_penn_motor_express.rb +11 -0
- data/lib/freight_kit/carriers/nieves_transportation.rb +10 -0
- data/lib/freight_kit/carriers/nolan_transportation_group.rb +26 -0
- data/lib/freight_kit/carriers/northland_trucking.rb +10 -0
- data/lib/freight_kit/carriers/northwest_carrier_logistics.rb +27 -0
- data/lib/freight_kit/carriers/nt_usa.rb +10 -0
- data/lib/freight_kit/carriers/nu_line_transportation.rb +10 -0
- data/lib/freight_kit/carriers/numark_transportation.rb +80 -0
- data/lib/freight_kit/carriers/nvk_transport.rb +10 -0
- data/lib/freight_kit/carriers/ny_transport.rb +10 -0
- data/lib/freight_kit/carriers/oak_harbor.rb +28 -0
- data/lib/freight_kit/carriers/oakline_transportation.rb +10 -0
- data/lib/freight_kit/carriers/old_dominion_freight_line.rb +30 -0
- data/lib/freight_kit/carriers/on_trac.rb +555 -0
- data/lib/freight_kit/carriers/one_hundred_one_transport.rb +10 -0
- data/lib/freight_kit/carriers/one_way_delivery.rb +10 -0
- data/lib/freight_kit/carriers/oracle_transportation_solutions.rb +10 -0
- data/lib/freight_kit/carriers/orange_courier.rb +27 -0
- data/lib/freight_kit/carriers/ortas_trucking.rb +10 -0
- data/lib/freight_kit/carriers/pac_trux_transportation.rb +10 -0
- data/lib/freight_kit/carriers/pacer.rb +10 -0
- data/lib/freight_kit/carriers/pacific_logistics.rb +10 -0
- data/lib/freight_kit/carriers/pacific_states_truck_lines.rb +10 -0
- data/lib/freight_kit/carriers/paradise_distributing.rb +10 -0
- data/lib/freight_kit/carriers/paramount_freight_systems.rb +10 -0
- data/lib/freight_kit/carriers/peninsula_truck_lines.rb +206 -0
- data/lib/freight_kit/carriers/pg_transport.rb +10 -0
- data/lib/freight_kit/carriers/pgs_360.rb +10 -0
- data/lib/freight_kit/carriers/pilot_freight_services.rb +10 -0
- data/lib/freight_kit/carriers/pious_trans.rb +27 -0
- data/lib/freight_kit/carriers/pitt_ohio_express.rb +30 -0
- data/lib/freight_kit/carriers/pki_logistics.rb +12 -0
- data/lib/freight_kit/carriers/plana_transportation.rb +28 -0
- data/lib/freight_kit/carriers/prestige.rb +10 -0
- data/lib/freight_kit/carriers/pro_trans_solutions.rb +28 -0
- data/lib/freight_kit/carriers/puma_logistics.rb +10 -0
- data/lib/freight_kit/carriers/pwc_international.rb +10 -0
- data/lib/freight_kit/carriers/quality_logistics.rb +10 -0
- data/lib/freight_kit/carriers/queen_star_trucking.rb +27 -0
- data/lib/freight_kit/carriers/r_and_l_carriers.rb +11 -0
- data/lib/freight_kit/carriers/rac_transport.rb +10 -0
- data/lib/freight_kit/carriers/re_logistics.rb +23 -0
- data/lib/freight_kit/carriers/reddaway.rb +11 -0
- data/lib/freight_kit/carriers/reefer_van_network.rb +13 -0
- data/lib/freight_kit/carriers/refrigerated_trucking_and_ltl_transportation_services.rb +10 -0
- data/lib/freight_kit/carriers/ridgeline_express.rb +10 -0
- data/lib/freight_kit/carriers/rist_transport.rb +29 -0
- data/lib/freight_kit/carriers/river_city_transport.rb +10 -0
- data/lib/freight_kit/carriers/roadrunner_transportation_services.rb +669 -0
- data/lib/freight_kit/carriers/roadtex.rb +13 -0
- data/lib/freight_kit/carriers/ronald_transport_services.rb +10 -0
- data/lib/freight_kit/carriers/rose_trucking.rb +24 -0
- data/lib/freight_kit/carriers/rp_and_j_transport.rb +24 -0
- data/lib/freight_kit/carriers/rpm_transportation.rb +11 -0
- data/lib/freight_kit/carriers/rude_transportation.rb +30 -0
- data/lib/freight_kit/carriers/s_and_s_logistics.rb +10 -0
- data/lib/freight_kit/carriers/saia.rb +456 -362
- data/lib/freight_kit/carriers/sb_transportation.rb +27 -0
- data/lib/freight_kit/carriers/schneider_national.rb +29 -0
- data/lib/freight_kit/carriers/schwarz_trucking.rb +10 -0
- data/lib/freight_kit/carriers/sewell_motor_express.rb +29 -0
- data/lib/freight_kit/carriers/shawnee_trucking.rb +28 -0
- data/lib/freight_kit/carriers/shi_logistics.rb +10 -0
- data/lib/freight_kit/carriers/sinbad_transport.rb +10 -0
- data/lib/freight_kit/carriers/skyway.rb +10 -0
- data/lib/freight_kit/carriers/smith_cargo_transportation_services.rb +23 -0
- data/lib/freight_kit/carriers/sna_transportation.rb +10 -0
- data/lib/freight_kit/carriers/solid_group.rb +10 -0
- data/lib/freight_kit/carriers/solution_carrier_express.rb +10 -0
- data/lib/freight_kit/carriers/southeastern_freight_lines.rb +441 -0
- data/lib/freight_kit/carriers/southwestern_motor_transport.rb +30 -0
- data/lib/freight_kit/carriers/span_alaska.rb +10 -0
- data/lib/freight_kit/carriers/specialize_transportation.rb +27 -0
- data/lib/freight_kit/carriers/ssi_refrigerated_express.rb +10 -0
- data/lib/freight_kit/carriers/standard_forwarding_llc.rb +28 -0
- data/lib/freight_kit/carriers/star_american_enterprise.rb +10 -0
- data/lib/freight_kit/carriers/sterling_transportation.rb +10 -0
- data/lib/freight_kit/carriers/stg.rb +163 -0
- data/lib/freight_kit/carriers/supervan_service.rb +10 -0
- data/lib/freight_kit/carriers/sutton_transport_inc.rb +12 -0
- data/lib/freight_kit/carriers/swift_transportation.rb +28 -0
- data/lib/freight_kit/carriers/t_force_worldwide.rb +605 -0
- data/lib/freight_kit/carriers/tax_airfreight_inc.rb +35 -0
- data/lib/freight_kit/carriers/tforce_freight_inc.rb +30 -0
- data/lib/freight_kit/carriers/the_custom_companies.rb +162 -0
- data/lib/freight_kit/carriers/thk_trucking.rb +28 -0
- data/lib/freight_kit/carriers/tiger_cool_express.rb +11 -0
- data/lib/freight_kit/carriers/tili_logistics.rb +10 -0
- data/lib/freight_kit/carriers/tmt_industries.rb +10 -0
- data/lib/freight_kit/carriers/top_ten_express.rb +28 -0
- data/lib/freight_kit/carriers/total_quality_logistics.rb +339 -0
- data/lib/freight_kit/carriers/total_transportation.rb +258 -0
- data/lib/freight_kit/carriers/trans_international_trucking.rb +29 -0
- data/lib/freight_kit/carriers/treasure_transportation.rb +10 -0
- data/lib/freight_kit/carriers/tri_state_services.rb +10 -0
- data/lib/freight_kit/carriers/tritex_freighters.rb +10 -0
- data/lib/freight_kit/carriers/triways.rb +10 -0
- data/lib/freight_kit/carriers/tsi_kansas.rb +29 -0
- data/lib/freight_kit/carriers/u_and_r_transportation.rb +23 -0
- data/lib/freight_kit/carriers/union_pacific.rb +11 -0
- data/lib/freight_kit/carriers/united_freight_logistics.rb +10 -0
- data/lib/freight_kit/carriers/unlimited_distance_transportation.rb +10 -0
- data/lib/freight_kit/carriers/up_streamline.rb +10 -0
- data/lib/freight_kit/carriers/ups_freight.rb +12 -0
- data/lib/freight_kit/carriers/us_freightways.rb +10 -0
- data/lib/freight_kit/carriers/usa_truck.rb +28 -0
- data/lib/freight_kit/carriers/usf_holland.rb +11 -0
- data/lib/freight_kit/carriers/valley_cartage.rb +11 -0
- data/lib/freight_kit/carriers/vc_transport.rb +10 -0
- data/lib/freight_kit/carriers/vision_express.rb +11 -0
- data/lib/freight_kit/carriers/vision_logistics_group.rb +10 -0
- data/lib/freight_kit/carriers/vmg_transportation_group.rb +10 -0
- data/lib/freight_kit/carriers/volpe_express.rb +10 -0
- data/lib/freight_kit/carriers/ward_trucking.rb +29 -0
- data/lib/freight_kit/carriers/wdwb.rb +10 -0
- data/lib/freight_kit/carriers/western_flyer_express.rb +28 -0
- data/lib/freight_kit/carriers/western_regional_delivery_service.rb +265 -0
- data/lib/freight_kit/carriers/western_specialized_trucking.rb +22 -0
- data/lib/freight_kit/carriers/win_distribution.rb +28 -0
- data/lib/freight_kit/carriers/wlb_transport.rb +10 -0
- data/lib/freight_kit/carriers/x_trux.rb +29 -0
- data/lib/freight_kit/carriers/xpo_logistics.rb +415 -0
- data/lib/freight_kit/carriers/xpress_global_systems.rb +30 -0
- data/lib/freight_kit/carriers/xylo_trucking.rb +10 -0
- data/lib/freight_kit/carriers/yrc_freight.rb +24 -0
- data/lib/freight_kit/carriers/zentner_transportation.rb +28 -0
- data/lib/freight_kit/carriers.rb +331 -34
- data/lib/freight_kit/countries.rb +22 -0
- data/lib/freight_kit/error.rb +0 -2
- data/lib/freight_kit/errors/document_not_found_error.rb +0 -2
- data/lib/freight_kit/errors/expired_credentials_error.rb +0 -2
- data/lib/freight_kit/errors/http_error.rb +0 -2
- data/lib/freight_kit/errors/invalid_credentials_error.rb +0 -2
- data/lib/freight_kit/errors/rate_limit_error.rb +12 -0
- data/lib/freight_kit/errors/response_error.rb +0 -2
- data/lib/freight_kit/errors/shipment_not_found_error.rb +0 -2
- data/lib/freight_kit/errors/unserviceable_accessorials_error.rb +1 -5
- data/lib/freight_kit/errors/unserviceable_error.rb +0 -2
- data/lib/freight_kit/helpers/documentable.rb +0 -2
- data/lib/freight_kit/helpers/pickupable.rb +3 -14
- data/lib/freight_kit/helpers/rateable.rb +0 -2
- data/lib/freight_kit/helpers/trackable.rb +0 -2
- data/lib/freight_kit/http_client/cache_store.rb +2 -4
- data/lib/freight_kit/http_client/httpi_adapter.rb +37 -0
- data/lib/freight_kit/http_client/log_device.rb +0 -2
- data/lib/freight_kit/http_client/response.rb +0 -2
- data/lib/freight_kit/http_client/revalidation.rb +0 -2
- data/lib/freight_kit/http_client/safe_retries.rb +17 -3
- data/lib/freight_kit/http_client/zstd.rb +2 -4
- data/lib/freight_kit/http_client.rb +21 -21
- data/lib/freight_kit/model.rb +1 -3
- data/lib/freight_kit/models/address.rb +15 -0
- data/lib/freight_kit/models/contact.rb +3 -4
- data/lib/freight_kit/models/country.rb +20 -0
- data/lib/freight_kit/models/credential.rb +3 -18
- data/lib/freight_kit/models/date_time.rb +0 -2
- data/lib/freight_kit/models/document_response.rb +0 -2
- data/lib/freight_kit/models/label.rb +0 -2
- data/lib/freight_kit/models/location.rb +10 -25
- data/lib/freight_kit/models/pickup_response.rb +0 -2
- data/lib/freight_kit/models/price.rb +0 -2
- data/lib/freight_kit/models/rate.rb +0 -2
- data/lib/freight_kit/models/rate_response.rb +0 -2
- data/lib/freight_kit/models/region.rb +19 -0
- data/lib/freight_kit/models/response.rb +0 -2
- data/lib/freight_kit/models/shipment.rb +5 -14
- data/lib/freight_kit/models/shipment_event.rb +0 -2
- data/lib/freight_kit/models/terminal.rb +10 -0
- data/lib/freight_kit/models/tracking_response.rb +0 -2
- data/lib/freight_kit/package.rb +52 -64
- data/lib/freight_kit/package_item.rb +14 -14
- data/lib/freight_kit/packaging.rb +6 -26
- data/lib/freight_kit/platform.rb +0 -32
- data/lib/freight_kit/platforms/carrier_logistics.rb +326 -345
- data/lib/freight_kit/platforms/dsdc.rb +82 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/mutation/quote_create.rb +32 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/mutation/shipment_create.rb +29 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_carrier.rb +37 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_carriers.rb +45 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_customer.rb +54 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/broker_customers.rb +62 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/carrier.rb +22 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/carriers.rb +30 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/customer.rb +20 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/customers.rb +28 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/invoice.rb +31 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/invoices.rb +39 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipment.rb +102 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipment_documents.rb +29 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client/query/shipments.rb +110 -0
- data/lib/freight_kit/platforms/next_tms/graphql_client.rb +22 -0
- data/lib/freight_kit/platforms/next_tms/orm/address.rb +22 -0
- data/lib/freight_kit/platforms/next_tms/orm/base.rb +120 -0
- data/lib/freight_kit/platforms/next_tms/orm/broker_carrier.rb +18 -0
- data/lib/freight_kit/platforms/next_tms/orm/broker_customer.rb +22 -0
- data/lib/freight_kit/platforms/next_tms/orm/carrier.rb +14 -0
- data/lib/freight_kit/platforms/next_tms/orm/collection.rb +335 -0
- data/lib/freight_kit/platforms/next_tms/orm/customer.rb +12 -0
- data/lib/freight_kit/platforms/next_tms/orm/invoice.rb +21 -0
- data/lib/freight_kit/platforms/next_tms/orm/record_not_found_error.rb +9 -0
- data/lib/freight_kit/platforms/next_tms/orm/shipment.rb +50 -0
- data/lib/freight_kit/platforms/next_tms/orm.rb +38 -0
- data/lib/freight_kit/platforms/next_tms.rb +452 -0
- data/lib/freight_kit/platforms/the_great_information_factory.rb +492 -425
- data/lib/freight_kit/regions.rb +27 -0
- data/lib/freight_kit/shipment_packer.rb +36 -38
- data/lib/freight_kit/tariff/overlength_rule.rb +1 -5
- data/lib/freight_kit/tariff.rb +0 -2
- data/lib/freight_kit.rb +103 -11
- data/schemas/next_tms.json +110710 -0
- metadata +443 -55
- data/configuration/carriers/abfs.yml +0 -124
- data/configuration/carriers/btvp.yml +0 -84
- data/configuration/carriers/ccyq.yml +0 -121
- data/configuration/carriers/clni.yml +0 -113
- data/configuration/carriers/cnwy.yml +0 -113
- data/configuration/carriers/ctbv.yml +0 -117
- data/configuration/carriers/dcha.yml +0 -105
- data/configuration/carriers/dlds.yml +0 -111
- data/configuration/carriers/dphe.yml +0 -130
- data/configuration/carriers/drrq.yml +0 -131
- data/configuration/carriers/fcsy.yml +0 -102
- data/configuration/carriers/fwda.yml +0 -137
- data/configuration/carriers/jfj_transportation.yml +0 -2
- data/configuration/carriers/mtvl.yml +0 -12
- data/configuration/carriers/numk.yml +0 -14
- data/configuration/carriers/otcl.yml +0 -124
- data/configuration/carriers/pens.yml +0 -22
- data/configuration/carriers/rdfs.yml +0 -142
- data/configuration/carriers/saia.yml +0 -129
- data/configuration/carriers/sefl.yml +0 -115
- data/configuration/carriers/totl.yml +0 -111
- data/configuration/carriers/tqyl.yml +0 -28
- data/configuration/carriers/wrds.yml +0 -20
- data/configuration/platforms/carrier_logistics.yml +0 -25
- data/configuration/platforms/next.yml +0 -12
- data/configuration/platforms/the_great_information_factory.yml +0 -122
- data/lib/freight_kit/carriers/abfs.rb +0 -421
- data/lib/freight_kit/carriers/btvp.rb +0 -29
- data/lib/freight_kit/carriers/ccyq.rb +0 -310
- data/lib/freight_kit/carriers/clni.rb +0 -396
- data/lib/freight_kit/carriers/cnwy.rb +0 -327
- data/lib/freight_kit/carriers/ctbv.rb +0 -53
- data/lib/freight_kit/carriers/dcha.rb +0 -76
- data/lib/freight_kit/carriers/dlds.rb +0 -49
- data/lib/freight_kit/carriers/dphe.rb +0 -472
- data/lib/freight_kit/carriers/drrq.rb +0 -580
- data/lib/freight_kit/carriers/fcsy.rb +0 -57
- data/lib/freight_kit/carriers/fwda.rb +0 -744
- data/lib/freight_kit/carriers/mtvl.rb +0 -34
- data/lib/freight_kit/carriers/numk.rb +0 -58
- data/lib/freight_kit/carriers/otcl.rb +0 -520
- data/lib/freight_kit/carriers/pens.rb +0 -204
- data/lib/freight_kit/carriers/rdfs.rb +0 -521
- data/lib/freight_kit/carriers/sefl.rb +0 -342
- data/lib/freight_kit/carriers/totl.rb +0 -172
- data/lib/freight_kit/carriers/tqyl.rb +0 -337
- data/lib/freight_kit/carriers/wrds.rb +0 -246
- data/lib/freight_kit/platforms/next.rb +0 -101
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module FreightKit
|
|
2
|
+
module Platforms
|
|
3
|
+
class NextTMS
|
|
4
|
+
class GraphQLClient
|
|
5
|
+
module Query
|
|
6
|
+
class Shipments
|
|
7
|
+
DETAILS = Client.parse(<<~GRAPHQL)
|
|
8
|
+
query($filter: ShipmentFilter, $page: Int, $pageSize: Int, $sort: [String!]) {
|
|
9
|
+
shipments(filter: $filter, page: $page, pageSize: $pageSize, sort: $sort) {
|
|
10
|
+
items {
|
|
11
|
+
bol
|
|
12
|
+
bookedAt
|
|
13
|
+
brokerCarrier {
|
|
14
|
+
account
|
|
15
|
+
carrier {
|
|
16
|
+
id
|
|
17
|
+
mcNumber
|
|
18
|
+
name
|
|
19
|
+
scac
|
|
20
|
+
}
|
|
21
|
+
id
|
|
22
|
+
}
|
|
23
|
+
brokerCustomer {
|
|
24
|
+
customer {
|
|
25
|
+
id
|
|
26
|
+
name
|
|
27
|
+
}
|
|
28
|
+
id
|
|
29
|
+
}
|
|
30
|
+
canceledAt
|
|
31
|
+
createdAt
|
|
32
|
+
customerReference
|
|
33
|
+
deliveredAt
|
|
34
|
+
deliveryAppointmentAt
|
|
35
|
+
deliveryAppointmentOn
|
|
36
|
+
deliveryEtaOn
|
|
37
|
+
departedOriginTerminalAt
|
|
38
|
+
dispatchedAt
|
|
39
|
+
events {
|
|
40
|
+
city
|
|
41
|
+
countryAlpha2
|
|
42
|
+
id
|
|
43
|
+
name
|
|
44
|
+
occurredAt
|
|
45
|
+
regionIso31662
|
|
46
|
+
typeCode
|
|
47
|
+
zipCode
|
|
48
|
+
}
|
|
49
|
+
id
|
|
50
|
+
loadId
|
|
51
|
+
lostOn
|
|
52
|
+
outForDeliveryAt
|
|
53
|
+
pendingAppointmentSince
|
|
54
|
+
pickedUpAt
|
|
55
|
+
pickupDriverAssignedAt
|
|
56
|
+
pickupOn
|
|
57
|
+
pro
|
|
58
|
+
receiverAddress {
|
|
59
|
+
city
|
|
60
|
+
country {
|
|
61
|
+
alpha2Code
|
|
62
|
+
}
|
|
63
|
+
id
|
|
64
|
+
region {
|
|
65
|
+
abbreviation
|
|
66
|
+
}
|
|
67
|
+
street
|
|
68
|
+
street2
|
|
69
|
+
timeZoneName
|
|
70
|
+
zipCode
|
|
71
|
+
}
|
|
72
|
+
receiverName
|
|
73
|
+
shipperAddress {
|
|
74
|
+
city
|
|
75
|
+
country {
|
|
76
|
+
alpha2Code
|
|
77
|
+
}
|
|
78
|
+
id
|
|
79
|
+
region {
|
|
80
|
+
abbreviation
|
|
81
|
+
}
|
|
82
|
+
street
|
|
83
|
+
street2
|
|
84
|
+
timeZoneName
|
|
85
|
+
zipCode
|
|
86
|
+
}
|
|
87
|
+
shipperBol
|
|
88
|
+
shipperName
|
|
89
|
+
shipperReference
|
|
90
|
+
status
|
|
91
|
+
totalChargesCents
|
|
92
|
+
totalChargesCurrency
|
|
93
|
+
truckload
|
|
94
|
+
updatedAt
|
|
95
|
+
}
|
|
96
|
+
page
|
|
97
|
+
pageSize
|
|
98
|
+
pages
|
|
99
|
+
totalItems
|
|
100
|
+
updatedAt
|
|
101
|
+
version
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
GRAPHQL
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module FreightKit
|
|
2
|
+
module Platforms
|
|
3
|
+
class NextTMS
|
|
4
|
+
# Parses each `Query::*::DETAILS` document against the Next TMS schema when it loads, so a wrong field or argument
|
|
5
|
+
# fails at boot rather than at request time.
|
|
6
|
+
class GraphQLClient
|
|
7
|
+
# Sends documents to the tenant in `context[:platform]`, so one parsed document serves every tenant.
|
|
8
|
+
class HTTPAdapter
|
|
9
|
+
def execute(document:, context: {}, operation_name: nil, variables: {})
|
|
10
|
+
context.fetch(:platform).execute(document.to_query_string, operation_name:, variables:)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Introspection of the GraphQL API, which every tenant shares
|
|
15
|
+
SCHEMA_PATH = File.expand_path('../../../../schemas/next_tms.json', __dir__).freeze
|
|
16
|
+
|
|
17
|
+
Schema = GraphQL::Client.load_schema(JSON.parse(File.read(SCHEMA_PATH)))
|
|
18
|
+
Client = GraphQL::Client.new(schema: Schema, execute: HTTPAdapter.new)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module FreightKit
|
|
2
|
+
module Platforms
|
|
3
|
+
class NextTMS
|
|
4
|
+
module ORM
|
|
5
|
+
# Embedded in other records, so it has no query of its own and is always loaded.
|
|
6
|
+
class Address < Base
|
|
7
|
+
def city = @city ||= attribute(:city)
|
|
8
|
+
def country = @country ||= attribute(:country)&.dig(:alpha2_code)&.then { |code| ActiveUtils::Country.find(code) }
|
|
9
|
+
def loaded? = true
|
|
10
|
+
def region = @region ||= attribute(:region)&.dig(:abbreviation)
|
|
11
|
+
def street = @street ||= attribute(:street)
|
|
12
|
+
def street2 = @street2 ||= attribute(:street2)
|
|
13
|
+
def time_zone_name = @time_zone_name ||= attribute(:time_zone_name)
|
|
14
|
+
|
|
15
|
+
def to_location = Location.new(address1: street, address2: street2, city:, country:, postal_code: zip_code, province: region)
|
|
16
|
+
|
|
17
|
+
def zip_code = @zip_code ||= attribute(:zip_code)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
module FreightKit
|
|
2
|
+
module Platforms
|
|
3
|
+
class NextTMS
|
|
4
|
+
module ORM
|
|
5
|
+
# A Next TMS record, loaded lazily from the tenant's GraphQL API with the {GraphQLClient::Query} of the same name.
|
|
6
|
+
# Records from a collection are loaded. Records from an association hold only the fields that its query selects
|
|
7
|
+
# for it, and load the rest when one is read.
|
|
8
|
+
class Base
|
|
9
|
+
class << self
|
|
10
|
+
attr_accessor :orm
|
|
11
|
+
|
|
12
|
+
delegate :count,
|
|
13
|
+
:find,
|
|
14
|
+
:find_by,
|
|
15
|
+
:find_by!,
|
|
16
|
+
:first,
|
|
17
|
+
:first!,
|
|
18
|
+
:ids,
|
|
19
|
+
:last,
|
|
20
|
+
:limit,
|
|
21
|
+
:none,
|
|
22
|
+
:not,
|
|
23
|
+
:offset,
|
|
24
|
+
:order,
|
|
25
|
+
:pluck,
|
|
26
|
+
:where,
|
|
27
|
+
to: :all
|
|
28
|
+
|
|
29
|
+
def all = Collection.new(self)
|
|
30
|
+
def association_type_mapping = {}
|
|
31
|
+
|
|
32
|
+
def build_association(name, data)
|
|
33
|
+
return if data&.dig(:id).blank?
|
|
34
|
+
|
|
35
|
+
class_name = association_type_mapping[name.to_sym] || name.to_s.classify
|
|
36
|
+
orm.const_get(class_name).new(data[:id], **data.except(:id))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def build_associations(name, items) = Array(items).filter_map { |data| build_association(name.to_s.singularize, data) }
|
|
40
|
+
|
|
41
|
+
# @return [Symbol] The collection query, e.g. `:broker_carriers` for {GraphQLClient::Query::BrokerCarriers}.
|
|
42
|
+
def collection_operation = graphql_name.pluralize.underscore.to_sym
|
|
43
|
+
|
|
44
|
+
# @return [Symbol] The singular query, e.g. `:broker_carrier` for {GraphQLClient::Query::BrokerCarrier}.
|
|
45
|
+
def element_operation = graphql_name.underscore.to_sym
|
|
46
|
+
|
|
47
|
+
def graphql_name = superclass == Base ? name.demodulize : superclass.graphql_name
|
|
48
|
+
|
|
49
|
+
def platform
|
|
50
|
+
raise ArgumentError, "#{name} is not bound to a tenant, use e.g. FreightKit::Carriers::JFJTransportation::ORM" if orm.nil?
|
|
51
|
+
|
|
52
|
+
orm.platform
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Instance variables that {#reload} keeps, all others memoize attributes
|
|
57
|
+
STATE = %i[@attributes @id @loaded].freeze
|
|
58
|
+
|
|
59
|
+
attr_reader :id
|
|
60
|
+
|
|
61
|
+
delegate :orm, :platform, to: :class
|
|
62
|
+
|
|
63
|
+
def initialize(id, created_at: nil, loaded: false, updated_at: nil, **attributes)
|
|
64
|
+
raise ArgumentError, 'id cannot be blank' if id.blank?
|
|
65
|
+
|
|
66
|
+
@attributes = { created_at:, updated_at: }.compact.merge(attributes)
|
|
67
|
+
# The API returns IDs without leading zeros, so `'01'` and `'1'` are the same record
|
|
68
|
+
@id = id.is_a?(String) && id.match?(/\A\d+\z/) ? id.to_i.to_s : id
|
|
69
|
+
@loaded = loaded
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def ==(other) = other.instance_of?(self.class) && other.id.to_s == id.to_s
|
|
73
|
+
alias eql? ==
|
|
74
|
+
|
|
75
|
+
def attribute(key)
|
|
76
|
+
reload unless @attributes.key?(key) || loaded?
|
|
77
|
+
|
|
78
|
+
@attributes[key]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def attributes
|
|
82
|
+
reload unless loaded?
|
|
83
|
+
|
|
84
|
+
@attributes
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def hash = [self.class, id.to_s].hash
|
|
88
|
+
def inspect = "#<#{self.class.name} id=#{id}>"
|
|
89
|
+
def loaded? = @loaded
|
|
90
|
+
|
|
91
|
+
# Loads every attribute, from {FreightKit.cache} when present.
|
|
92
|
+
def reload = load(cache: true)
|
|
93
|
+
|
|
94
|
+
# Loads every attribute from the API.
|
|
95
|
+
def reload! = load(cache: false)
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def date(key) = attribute(key)&.then { |value| Date.iso8601(value) }
|
|
100
|
+
def fetch_association(name) = self.class.build_association(name, attribute(name))
|
|
101
|
+
def fetch_associations(name) = self.class.build_associations(name, attribute(name))
|
|
102
|
+
|
|
103
|
+
def load(cache:)
|
|
104
|
+
operation = self.class.element_operation
|
|
105
|
+
data = platform.query(operation, cache:, id:)[operation]
|
|
106
|
+
raise RecordNotFoundError, "Couldn't find #{self.class.name} with id #{id}" if data.nil?
|
|
107
|
+
|
|
108
|
+
(instance_variables - STATE).each { |variable| remove_instance_variable(variable) }
|
|
109
|
+
@attributes = data.except(:id)
|
|
110
|
+
@loaded = true
|
|
111
|
+
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def time(key) = attribute(key)&.then { |value| Time.iso8601(value) }
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module FreightKit
|
|
2
|
+
module Platforms
|
|
3
|
+
class NextTMS
|
|
4
|
+
module ORM
|
|
5
|
+
class BrokerCarrier < Base
|
|
6
|
+
def account = @account ||= attribute(:account)
|
|
7
|
+
def account_desc = @account_desc ||= attribute(:account_desc)
|
|
8
|
+
def broker_customer = @broker_customer ||= fetch_association(:broker_customer)
|
|
9
|
+
def carrier = @carrier ||= fetch_association(:carrier)
|
|
10
|
+
def create_invoice_on_pickup? = attribute(:create_invoice_on_pickup)
|
|
11
|
+
def days_to_pay_after_invoice_date = @days_to_pay_after_invoice_date ||= attribute(:days_to_pay_after_invoice_date)
|
|
12
|
+
def shipments = @shipments ||= orm::Shipment.where(broker_carrier: self)
|
|
13
|
+
def shipments_count = @shipments_count ||= attribute(:shipments_count)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module FreightKit
|
|
2
|
+
module Platforms
|
|
3
|
+
class NextTMS
|
|
4
|
+
module ORM
|
|
5
|
+
class BrokerCustomer < Base
|
|
6
|
+
class << self
|
|
7
|
+
def association_type_mapping = super.merge(office_address: 'Address', primary_pickup_address: 'Address')
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def accounting_customer_id = @accounting_customer_id ||= attribute(:accounting_customer_id)
|
|
11
|
+
def broker_carriers = @broker_carriers ||= orm::BrokerCarrier.where(broker_customer: self)
|
|
12
|
+
def can_quote? = attribute(:can_quote)
|
|
13
|
+
def customer = @customer ||= fetch_association(:customer)
|
|
14
|
+
def office_address = @office_address ||= fetch_association(:office_address)
|
|
15
|
+
def primary_pickup_address = @primary_pickup_address ||= fetch_association(:primary_pickup_address)
|
|
16
|
+
def shipments = @shipments ||= orm::Shipment.where(broker_customer: self)
|
|
17
|
+
def valid_freight_classes = @valid_freight_classes ||= attribute(:valid_freight_classes)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module FreightKit
|
|
2
|
+
module Platforms
|
|
3
|
+
class NextTMS
|
|
4
|
+
module ORM
|
|
5
|
+
class Carrier < Base
|
|
6
|
+
def broker_carriers = @broker_carriers ||= orm::BrokerCarrier.where(carrier: self)
|
|
7
|
+
def mc_number = @mc_number ||= attribute(:mc_number)
|
|
8
|
+
def name = @name ||= attribute(:name)
|
|
9
|
+
def scac = @scac ||= attribute(:scac)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
module FreightKit
|
|
2
|
+
module Platforms
|
|
3
|
+
class NextTMS
|
|
4
|
+
module ORM
|
|
5
|
+
# A lazy, chainable query of a collection field, e.g. `shipments(filter:, page:, pageSize:, sort:)`.
|
|
6
|
+
#
|
|
7
|
+
# Conditions become ransack predicates (`where(pro: '123')` => `proEq`), so only the filter fields the tenant
|
|
8
|
+
# generates from the model's ransackable attributes work. Without {#limit}, {#to_a} fetches every page.
|
|
9
|
+
class Collection
|
|
10
|
+
include Enumerable
|
|
11
|
+
|
|
12
|
+
# Pairs of opposite predicates, e.g. `delivered_at_gteq` is the opposite of `delivered_at_lt`
|
|
13
|
+
NEGATIONS = {
|
|
14
|
+
'cont' => 'not_cont', 'end' => 'not_end', 'eq' => 'not_eq', 'gt' => 'lteq', 'gteq' => 'lt', 'in' => 'not_in',
|
|
15
|
+
'start' => 'not_start'
|
|
16
|
+
}.freeze
|
|
17
|
+
PAGE_SIZE = 100 # the API's maximum
|
|
18
|
+
PREDICATE = /_(cont|end|eq|gt|gteq|in|lt|lteq|not_cont|not_end|not_eq|not_in|not_start|null|start)\z/
|
|
19
|
+
|
|
20
|
+
attr_reader :filter, :orm_class, :page, :page_size, :sort, :updated_at, :version
|
|
21
|
+
|
|
22
|
+
attr_writer :orm_objects
|
|
23
|
+
|
|
24
|
+
delegate :[], :each, :each_with_index, :map, to: :to_a
|
|
25
|
+
|
|
26
|
+
def initialize(orm_class, **options)
|
|
27
|
+
raise ArgumentError, 'orm_class must be descended from FreightKit::Platforms::NextTMS::ORM::Base' unless orm_class < Base
|
|
28
|
+
|
|
29
|
+
@filter = options[:filter] || {}
|
|
30
|
+
@none = options[:none] || false
|
|
31
|
+
@offset = options[:offset]
|
|
32
|
+
@orm_class = orm_class
|
|
33
|
+
@page = options[:page]
|
|
34
|
+
@page_size = options[:page_size]
|
|
35
|
+
@sort = Array(options[:sort]).compact.presence
|
|
36
|
+
@total_items = options[:total_items]
|
|
37
|
+
@updated_at = options[:updated_at]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Arguments and blocks need the records, the bare forms only the total
|
|
41
|
+
def any?(*args, &) = args.empty? && !block_given? ? total_items.positive? : super
|
|
42
|
+
def blank? = !any?
|
|
43
|
+
def count(*args, &) = args.empty? && !block_given? ? total_items : super
|
|
44
|
+
def empty? = total_items.zero?
|
|
45
|
+
|
|
46
|
+
# The singular query cannot apply the collection's filter, so a filtered collection filters by ID too, and a
|
|
47
|
+
# limited one searches its page
|
|
48
|
+
def find(id)
|
|
49
|
+
record = if @none then nil
|
|
50
|
+
elsif page_size.present? then to_a.find { |item| integer_id(item.id).to_s == integer_id(id).to_s }
|
|
51
|
+
elsif filter.blank? then orm_class.new(id).reload
|
|
52
|
+
else where(id:).limit(1).first
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
record || raise(RecordNotFoundError, "Couldn't find #{orm_class} with id #{id}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def find_by(**conditions)
|
|
59
|
+
find_by!(**conditions)
|
|
60
|
+
rescue RecordNotFoundError
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def find_by!(**conditions)
|
|
65
|
+
return find(conditions[:id]) if conditions.keys == [:id]
|
|
66
|
+
|
|
67
|
+
where(**conditions).limit(1).first || raise(RecordNotFoundError, "Couldn't find #{orm_class} with #{conditions}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def first = page_size.present? ? orm_objects.first : limit(1).orm_objects.first
|
|
71
|
+
def first! = first || raise(RecordNotFoundError, "Couldn't find #{orm_class}")
|
|
72
|
+
|
|
73
|
+
def ids = pluck(:id)
|
|
74
|
+
def inspect = "#<#{self.class.name} orm_class=#{orm_class}>"
|
|
75
|
+
|
|
76
|
+
def last
|
|
77
|
+
return orm_objects.last if page_size.present?
|
|
78
|
+
|
|
79
|
+
offset(total_items - 1).limit(1).orm_objects.first unless empty?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def limit(page_size)
|
|
83
|
+
raise ArgumentError, 'page_size must be 1..100' unless page_size.is_a?(Integer) && page_size.between?(1, PAGE_SIZE)
|
|
84
|
+
|
|
85
|
+
new_instance(page: @offset ? offset_page(@offset, page_size) : page || 1, page_size:)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def new_instance(**options)
|
|
89
|
+
self.class.new(
|
|
90
|
+
orm_class,
|
|
91
|
+
filter: options.fetch(:filter, filter),
|
|
92
|
+
none: options.fetch(:none, @none),
|
|
93
|
+
offset: options.fetch(:offset, @offset),
|
|
94
|
+
page: options.fetch(:page, page),
|
|
95
|
+
page_size: options.fetch(:page_size, page_size),
|
|
96
|
+
sort: options.fetch(:sort, sort),
|
|
97
|
+
total_items: options[:total_items],
|
|
98
|
+
updated_at: options.fetch(:updated_at, updated_at)
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def none = new_instance(none: true, total_items: 0, page: 1, page_size: 1).tap { |collection| collection.orm_objects = [] }
|
|
103
|
+
def not(**conditions) = merge(conditions.map { |key, value| build_negated_filter(key => value) })
|
|
104
|
+
|
|
105
|
+
def offset(value)
|
|
106
|
+
raise ArgumentError, 'Offset must be a non-negative integer' unless value.is_a?(Integer) && value >= 0
|
|
107
|
+
|
|
108
|
+
page_size.present? ? new_instance(offset: value, page: offset_page(value, page_size)) : new_instance(offset: value)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def order(*orders, **order_hash)
|
|
112
|
+
sort_strings = if order_hash.any?
|
|
113
|
+
order_hash.map { |attribute, direction| "#{attribute}:#{direction || :asc}" }
|
|
114
|
+
else
|
|
115
|
+
orders.map { |(attribute, direction)| "#{attribute}:#{direction || :asc}" }
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
new_instance(sort: sort_strings)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def orm_objects = @orm_objects ||= fetch_orm_objects
|
|
122
|
+
def pluck(attribute) = map(&attribute)
|
|
123
|
+
|
|
124
|
+
def present? = any?
|
|
125
|
+
|
|
126
|
+
def sample
|
|
127
|
+
collection = page_size.present? ? self : limit(10)
|
|
128
|
+
collection.to_a.sample
|
|
129
|
+
end
|
|
130
|
+
alias take sample
|
|
131
|
+
|
|
132
|
+
def to_a
|
|
133
|
+
@to_a ||= if @orm_objects || page_size.present?
|
|
134
|
+
orm_objects
|
|
135
|
+
else
|
|
136
|
+
raise ArgumentError, 'offset needs a limit, since the API pages' if @offset
|
|
137
|
+
|
|
138
|
+
first = new_instance(page: page || 1, page_size: PAGE_SIZE).tap(&:orm_objects)
|
|
139
|
+
pages = ((first.page + 1)..first.total_pages).map { |page| new_instance(page:, page_size: PAGE_SIZE).orm_objects }
|
|
140
|
+
@total_items = first.total_items
|
|
141
|
+
@updated_at = first.updated_at
|
|
142
|
+
@version = first.version
|
|
143
|
+
|
|
144
|
+
[first.orm_objects, *pages].flatten
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def total_items
|
|
149
|
+
return 0 if @none
|
|
150
|
+
return @total_items if @total_items.present?
|
|
151
|
+
|
|
152
|
+
@total_items = (page_size.present? ? self : new_instance(page: 1, page_size: 1)).tap(&:orm_objects).total_items
|
|
153
|
+
end
|
|
154
|
+
alias length total_items
|
|
155
|
+
alias size total_items
|
|
156
|
+
|
|
157
|
+
def total_pages
|
|
158
|
+
return if total_items.blank? || page_size.blank?
|
|
159
|
+
|
|
160
|
+
(total_items.to_f / page_size).ceil
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def where(**conditions)
|
|
164
|
+
conditions.blank? ? self : merge(conditions.map { |key, value| build_filter(key => value) })
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
protected
|
|
168
|
+
|
|
169
|
+
def fetch_orm_objects
|
|
170
|
+
return @orm_objects = [] if @none
|
|
171
|
+
|
|
172
|
+
operation = orm_class.collection_operation
|
|
173
|
+
variables = { filter: serialize(filter).presence, page:, page_size:, sort: }
|
|
174
|
+
data = orm_class.platform.query(operation, cache: true, **variables)[operation]
|
|
175
|
+
|
|
176
|
+
raise ResponseError, 'data[:page] is blank' if data&.dig(:page).blank?
|
|
177
|
+
raise ResponseError, 'data[:page_size] is blank' if data[:page_size].blank?
|
|
178
|
+
raise ResponseError, 'data[:total_items] is blank' if data[:total_items].blank?
|
|
179
|
+
|
|
180
|
+
@page = data[:page]
|
|
181
|
+
@page_size = data[:page_size]
|
|
182
|
+
@total_items = data[:total_items]
|
|
183
|
+
@updated_at = data[:updated_at]
|
|
184
|
+
@version = data[:version]
|
|
185
|
+
|
|
186
|
+
data[:items].map { |item| orm_class.new(item[:id], loaded: true, **item.except(:id)) }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
private
|
|
190
|
+
|
|
191
|
+
# Contradictory alternatives match nothing, so they drop out
|
|
192
|
+
def alternatives(conditions)
|
|
193
|
+
conditions.filter_map { |condition| combine({}, condition.map { |key, value| build_filter(key => value) }) }
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Whether the filter knows the key only by its foreign key, e.g. `broker_carrier_id_null` but no `broker_carrier_null`
|
|
197
|
+
def association?(key)
|
|
198
|
+
arguments = GraphQLClient::Schema.types["#{orm_class.graphql_name}Filter"].arguments
|
|
199
|
+
|
|
200
|
+
arguments.exclude?("#{key.to_s.camelize(:lower)}Null") && arguments.key?("#{key.to_s.camelize(:lower)}IdNull")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def build_filter(**conditions)
|
|
204
|
+
conditions.each_with_object({}) do |(key, value), filter|
|
|
205
|
+
next filter[:or] = alternatives(value) if key == :or
|
|
206
|
+
|
|
207
|
+
value = integer_ids(value) if id_attribute?(key)
|
|
208
|
+
next filter[key] = value if key.to_s.match?(PREDICATE)
|
|
209
|
+
|
|
210
|
+
case value
|
|
211
|
+
when Array
|
|
212
|
+
next filter[:"#{key}_id_in"] = value.map { |record| integer_id(record.id) } if value.any? && value.all?(Base)
|
|
213
|
+
|
|
214
|
+
filter[:"#{key}_in"] = value
|
|
215
|
+
when Base
|
|
216
|
+
filter[:"#{key}_id_eq"] = integer_id(value.id)
|
|
217
|
+
when Collection
|
|
218
|
+
filter[:"#{key}_id_in"] = value.ids.map { |id| integer_id(id) }
|
|
219
|
+
when nil
|
|
220
|
+
filter[:"#{association?(key) ? "#{key}_id" : key}_null"] = true
|
|
221
|
+
when Range
|
|
222
|
+
filter[:"#{key}_gteq"] = value.begin unless value.begin.nil?
|
|
223
|
+
filter[:"#{key}_#{value.exclude_end? ? 'lt' : 'lteq'}"] = value.end unless value.end.nil?
|
|
224
|
+
else
|
|
225
|
+
filter[:"#{key}_eq"] = value
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Filter fields are ANDed, so only a condition that becomes one predicate can be negated
|
|
231
|
+
def build_negated_filter(**conditions)
|
|
232
|
+
conditions.each_with_object({}) do |(key, value), filter|
|
|
233
|
+
predicates = build_filter(key => value)
|
|
234
|
+
raise ArgumentError, "Can't negate #{key}: #{value.inspect}, negate each condition instead" if key == :or || predicates.many?
|
|
235
|
+
|
|
236
|
+
predicate, value = predicates.first
|
|
237
|
+
match = predicate.to_s.match(PREDICATE)
|
|
238
|
+
next filter[predicate] = !value if match[1] == 'null'
|
|
239
|
+
|
|
240
|
+
filter[:"#{match.pre_match}_#{NEGATIONS[match[1]] || NEGATIONS.key(match[1])}"] = value
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# ANDs each condition's predicates with `base`, since a filter has one value per predicate, and conditions such as
|
|
245
|
+
# `id:` and `id_eq:` share one.
|
|
246
|
+
#
|
|
247
|
+
# @return [Hash, nil] Nil when the predicates contradict each other.
|
|
248
|
+
def combine(base, predicates_by_condition)
|
|
249
|
+
predicates_by_condition.flat_map(&:to_a).each_with_object(base.dup) do |(key, value), result|
|
|
250
|
+
old = result[key]
|
|
251
|
+
next result[key] = value if old.nil? || comparable(old) == comparable(value)
|
|
252
|
+
raise ArgumentError, 'A query takes one or:, combine the alternatives instead' if key == :or
|
|
253
|
+
|
|
254
|
+
match = key.to_s.match(PREDICATE)
|
|
255
|
+
case match[1]
|
|
256
|
+
when 'eq', 'null' then return nil
|
|
257
|
+
when 'gt', 'gteq' then result[key] = [old, value].max_by { |bound| comparable(bound) }
|
|
258
|
+
when 'in' then result[key] = old.select { |item| comparable(value).include?(comparable(item)) }
|
|
259
|
+
when 'lt', 'lteq' then result[key] = [old, value].min_by { |bound| comparable(bound) }
|
|
260
|
+
when 'not_eq'
|
|
261
|
+
not_in = :"#{match.pre_match}_not_in"
|
|
262
|
+
result.delete(key)
|
|
263
|
+
result[not_in] = [*result[not_in], old, value].uniq { |item| comparable(item) }
|
|
264
|
+
when 'not_in' then result[key] = (old + value).uniq { |item| comparable(item) }
|
|
265
|
+
else raise ArgumentError, "A query takes one #{key}"
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Values can be ISO 8601 strings, of dates or of times with different offsets, so they compare by date or instant
|
|
271
|
+
def comparable(value)
|
|
272
|
+
case value
|
|
273
|
+
when Array then value.map { |item| comparable(item) }
|
|
274
|
+
when Hash then value.transform_values { |item| comparable(item) }
|
|
275
|
+
when String then value.match?(/\A\d{4}-\d{2}-\d{2}\z/) ? Date.iso8601(value) : Time.iso8601(value)
|
|
276
|
+
when Symbol then value.to_s
|
|
277
|
+
else value
|
|
278
|
+
end
|
|
279
|
+
rescue ArgumentError
|
|
280
|
+
value
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# `id` and `*_id`, with or without a predicate
|
|
284
|
+
def id_attribute?(key) = key.to_s.sub(PREDICATE, '').then { |attribute| attribute == 'id' || attribute.end_with?('_id') }
|
|
285
|
+
|
|
286
|
+
# GraphQL serializes IDs as strings, but ID filters take the integer column
|
|
287
|
+
def integer_id(id) = Integer(id, 10, exception: false) || id
|
|
288
|
+
|
|
289
|
+
def integer_ids(value)
|
|
290
|
+
case value
|
|
291
|
+
when Array then value.map { |id| integer_id(id) }
|
|
292
|
+
when Range then Range.new(integer_id(value.begin), integer_id(value.end), value.exclude_end?)
|
|
293
|
+
else integer_id(value)
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def merge(predicates_by_condition)
|
|
298
|
+
merged = combine(filter, predicates_by_condition)&.then { |combined| satisfiable(combined) }
|
|
299
|
+
|
|
300
|
+
merged ? new_instance(filter: merged) : none
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# The API pages, so the offset must start a page
|
|
304
|
+
def offset_page(offset, page_size)
|
|
305
|
+
raise ArgumentError, "offset #{offset} must be a multiple of the limit #{page_size}" unless (offset % page_size).zero?
|
|
306
|
+
|
|
307
|
+
(offset / page_size) + 1
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# The API ignores empty lists, so an empty list matches nothing, and an empty exclusion excludes nothing
|
|
311
|
+
def satisfiable(conditions)
|
|
312
|
+
conditions = conditions.reject { |key, value| value == [] && key.end_with?('_not_in') }
|
|
313
|
+
return if conditions.any? { |_, value| value == [] }
|
|
314
|
+
return conditions unless conditions.key?(:or)
|
|
315
|
+
|
|
316
|
+
alternatives = conditions[:or].filter_map { |alternative| satisfiable(alternative) }
|
|
317
|
+
return if alternatives.empty?
|
|
318
|
+
|
|
319
|
+
alternatives.any?(&:empty?) ? conditions.except(:or) : conditions.merge(or: alternatives)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
def serialize(value)
|
|
323
|
+
case value
|
|
324
|
+
when Array then value.map { |item| serialize(item) }
|
|
325
|
+
when Date, Time then value.iso8601
|
|
326
|
+
when Hash then value.transform_values { |item| serialize(item) }
|
|
327
|
+
when Symbol then value.to_s
|
|
328
|
+
else value
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
end
|