From cfc1fab54cc8a1e6c58588b9e4a4436464baab6c Mon Sep 17 00:00:00 2001 From: Brody Date: Sat, 23 Mar 2024 17:40:22 -0700 Subject: [PATCH 1/4] init v.1.0.0.pre1 --- .github/dependabot.yml | 10 +- .github/workflows/checks.yml | 15 - .github/workflows/main.yml | 23 + .gitignore | 28 +- .rspec | 2 + .travis.yml | 33 -- .yardopts | 13 - Gemfile | 1 + Gemfile.lock | 114 +++++ MIT-LICENSE => LICENSE.txt | 13 - Rakefile | 12 + VERSION | 2 +- accessorial_symbols.txt | 97 ---- freight_kit.gemspec | 60 +-- lib/freight_kit.rb | 17 +- lib/freight_kit/carrier.rb | 473 ------------------ lib/freight_kit/carriers.rb | 24 - lib/freight_kit/error.rb | 5 - .../errors/document_not_found_error.rb | 5 - .../errors/expired_credentials_error.rb | 5 - lib/freight_kit/errors/http_error.rb | 25 - .../errors/invalid_credentials_error.rb | 5 - lib/freight_kit/errors/response_error.rb | 16 - .../errors/shipment_not_found_error.rb | 5 - .../unserviceable_accessorials_error.rb | 19 - lib/freight_kit/errors/unserviceable_error.rb | 5 - lib/freight_kit/model.rb | 17 - lib/freight_kit/models/contact.rb | 17 - lib/freight_kit/models/credential.rb | 117 ----- lib/freight_kit/models/date_time.rb | 37 -- lib/freight_kit/models/document_response.rb | 17 - lib/freight_kit/models/label.rb | 13 - lib/freight_kit/models/location.rb | 108 ---- lib/freight_kit/models/pickup_response.rb | 19 - lib/freight_kit/models/price.rb | 38 -- lib/freight_kit/models/rate.rb | 81 --- lib/freight_kit/models/rate_response.rb | 15 - lib/freight_kit/models/response.rb | 21 - lib/freight_kit/models/shipment.rb | 66 --- lib/freight_kit/models/shipment_event.rb | 38 -- lib/freight_kit/models/tracking_response.rb | 75 --- lib/freight_kit/package.rb | 313 ------------ lib/freight_kit/package_item.rb | 65 --- lib/freight_kit/packaging.rb | 52 -- lib/freight_kit/platform.rb | 36 -- lib/freight_kit/shipment_packer.rb | 116 ----- lib/freight_kit/tariff.rb | 29 -- service_type_symbols.txt | 4 - shipment_event_symbols.txt | 17 - sig/freight_kit.rbs | 4 + spec/freight_kit_spec.rb | 7 + spec/spec_helper.rb | 15 +- 52 files changed, 215 insertions(+), 2149 deletions(-) delete mode 100644 .github/workflows/checks.yml create mode 100644 .github/workflows/main.yml delete mode 100644 .travis.yml delete mode 100644 .yardopts create mode 100644 Gemfile.lock rename MIT-LICENSE => LICENSE.txt (70%) create mode 100644 Rakefile delete mode 100644 accessorial_symbols.txt delete mode 100644 lib/freight_kit/carrier.rb delete mode 100644 lib/freight_kit/carriers.rb delete mode 100644 lib/freight_kit/error.rb delete mode 100644 lib/freight_kit/errors/document_not_found_error.rb delete mode 100644 lib/freight_kit/errors/expired_credentials_error.rb delete mode 100644 lib/freight_kit/errors/http_error.rb delete mode 100644 lib/freight_kit/errors/invalid_credentials_error.rb delete mode 100644 lib/freight_kit/errors/response_error.rb delete mode 100644 lib/freight_kit/errors/shipment_not_found_error.rb delete mode 100644 lib/freight_kit/errors/unserviceable_accessorials_error.rb delete mode 100644 lib/freight_kit/errors/unserviceable_error.rb delete mode 100644 lib/freight_kit/model.rb delete mode 100644 lib/freight_kit/models/contact.rb delete mode 100644 lib/freight_kit/models/credential.rb delete mode 100644 lib/freight_kit/models/date_time.rb delete mode 100644 lib/freight_kit/models/document_response.rb delete mode 100644 lib/freight_kit/models/label.rb delete mode 100644 lib/freight_kit/models/location.rb delete mode 100644 lib/freight_kit/models/pickup_response.rb delete mode 100644 lib/freight_kit/models/price.rb delete mode 100644 lib/freight_kit/models/rate.rb delete mode 100644 lib/freight_kit/models/rate_response.rb delete mode 100644 lib/freight_kit/models/response.rb delete mode 100644 lib/freight_kit/models/shipment.rb delete mode 100644 lib/freight_kit/models/shipment_event.rb delete mode 100644 lib/freight_kit/models/tracking_response.rb delete mode 100644 lib/freight_kit/package.rb delete mode 100644 lib/freight_kit/package_item.rb delete mode 100644 lib/freight_kit/packaging.rb delete mode 100644 lib/freight_kit/platform.rb delete mode 100644 lib/freight_kit/shipment_packer.rb delete mode 100644 lib/freight_kit/tariff.rb delete mode 100644 service_type_symbols.txt delete mode 100644 shipment_event_symbols.txt create mode 100644 sig/freight_kit.rbs create mode 100644 spec/freight_kit_spec.rb diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 452ebb34..75a4e3d6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,7 @@ version: 2 updates: -- package-ecosystem: bundler - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 + - package-ecosystem: bundler + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 10 diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml deleted file mode 100644 index fcba338c..00000000 --- a/.github/workflows/checks.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Checks -on: [push] -jobs: - syntax: - name: Syntax - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.2.2 - - run: | - gem install bundler - bundle install --jobs 4 --retry 3 - - run: bundle exec rubocop diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..c1aab0d2 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,23 @@ +name: Ruby +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + name: Ruby ${{ matrix.ruby-version }} + strategy: + matrix: + ruby-version: + - '3.2' + - '3.1' + - '3.0' + steps: + - uses: actions/checkout@v4 + - name: Set up Ruby ${{ matrix.ruby-version }} + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + ruby-version: ${{ matrix.ruby-version }} + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + - run: bundle exec rake diff --git a/.gitignore b/.gitignore index 20b38239..7a6f5dfe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,13 @@ -.DS_Store -test.xml -sample.rb -*.orig -pkg -.dotest -Gemfile*.lock -.rvmrc -test/fixtures/live_credentials.yml -.rbenv-version +/.bundle/ +/.yardoc +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ + +# rspec failure tracking +.rspec_status + .ruby-version -.yardoc/ -doc/ -.idea -.bundle -.byebug_history -*.gem \ No newline at end of file diff --git a/.rspec b/.rspec index c99d2e73..34c5164d 100644 --- a/.rspec +++ b/.rspec @@ -1 +1,3 @@ +--format documentation +--color --require spec_helper diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4d835047..00000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -# travis.yml - -language: ruby -script: bundle exec rake test -sudo: false - -rvm: -- "2.2.2" -- "2.3.1" -- "2.4.1" - -gemfile: -- gemfiles/activesupport42.gemfile -- gemfiles/activesupport50.gemfile -- gemfiles/activesupport51.gemfile -- gemfiles/activesupport52.gemfile - -matrix: - include: - - rvm: "2.4.1" - gemfile: gemfiles/activesupport_master.gemfile - exclude: - - rvm: "2.4.1" - gemfile: gemfiles/activesupport42.gemfile - -env: - global: - - ACTIVESHIPPING_NEW_ZEALAND_POST_KEY=4d9dc0f0-dda0-012e-066f-000c29b44ac0 - - ACTIVESHIPPING_CANADA_POST_LOGIN=CPC_DEMO_XML - - ACTIVESHIPPING_CANADA_POST_PWS_CUSTOMER_NUMBER=2004381 - - ACTIVESHIPPING_CANADA_POST_PWS_API_KEY=6e93d53968881714 - - ACTIVESHIPPING_CANADA_POST_PWS_SECRET=0bfa9fcb9853d1f51ee57a - - ACTIVESHIPPING_USPS_LOGIN=677JADED7283 diff --git a/.yardopts b/.yardopts deleted file mode 100644 index f60f8cbc..00000000 --- a/.yardopts +++ /dev/null @@ -1,13 +0,0 @@ ---readme README.md ---title 'ActiveShipping API documentation' ---charset utf-8 ---hide-void-return ---protected ---no-private ---markup markdown ---markup-provider redcarpet -- -README.md -CHANGELOG.md -CONTRIBUTING.md -MIT-LICENSE diff --git a/Gemfile b/Gemfile index 7f4f5e95..d06ff575 100644 --- a/Gemfile +++ b/Gemfile @@ -2,4 +2,5 @@ source 'https://rubygems.org' +# Specify your gem's dependencies in freight_kit.gemspec gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..6600f127 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,114 @@ +PATH + remote: . + specs: + freight_kit (1.0.0.pre1) + activesupport (>= 4.2, < 7.1.4) + zeitwerk (>= 2.6.0, < 2.6.13) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (7.1.3.2) + base64 + bigdecimal + concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + ast (2.4.2) + base64 (0.1.1) + bigdecimal (3.1.7) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + diff-lcs (1.5.1) + drb (2.2.1) + i18n (1.14.4) + concurrent-ruby (~> 1.0) + json (2.7.1) + language_server-protocol (3.17.0.3) + minitest (5.22.3) + mutex_m (0.2.0) + parallel (1.24.0) + parser (3.3.0.5) + ast (~> 2.4.1) + racc + racc (1.7.3) + rack (3.0.10) + rainbow (3.1.1) + rake (13.1.0) + regexp_parser (2.9.0) + rexml (3.2.6) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.0) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) + rubocop (1.56.4) + base64 (~> 0.1.1) + json (~> 2.3) + language_server-protocol (>= 3.17.0) + parallel (~> 1.10) + parser (>= 3.2.2.3) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.28.1, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.2) + parser (>= 3.3.0.4) + rubocop-capybara (2.20.0) + rubocop (~> 1.41) + rubocop-factory_bot (2.25.1) + rubocop (~> 1.41) + rubocop-graphql (1.4.0) + rubocop (>= 0.90, < 2) + rubocop-next (1.0.6) + rubocop (~> 1.56.0) + rubocop-graphql (~> 1.4.0) + rubocop-rails (~> 2.20.2) + rubocop-rake (~> 0.6.0) + rubocop-rspec (~> 2.23.2) + rubocop-shopify (~> 2.14.0) + rubocop-rails (2.20.2) + activesupport (>= 4.2.0) + rack (>= 1.1) + rubocop (>= 1.33.0, < 2.0) + rubocop-rake (0.6.0) + rubocop (~> 1.0) + rubocop-rspec (2.23.2) + rubocop (~> 1.33) + rubocop-capybara (~> 2.17) + rubocop-factory_bot (~> 2.22) + rubocop-shopify (2.14.0) + rubocop (~> 1.51) + ruby-progressbar (1.13.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + unicode-display_width (2.5.0) + zeitwerk (2.6.12) + +PLATFORMS + arm64-darwin-23 + ruby + +DEPENDENCIES + freight_kit! + rake (~> 13.0) + rspec (~> 3.0) + rubocop (~> 1.21) + rubocop-next (~> 1.0.3) + +BUNDLED WITH + 2.5.6 diff --git a/MIT-LICENSE b/LICENSE.txt similarity index 70% rename from MIT-LICENSE rename to LICENSE.txt index 8c79a7d7..c9b44cb8 100644 --- a/MIT-LICENSE +++ b/LICENSE.txt @@ -1,16 +1,3 @@ -Portions of this project are © 2016 Shopify from project -ActiveShipping. - -Portions of this project are © 2018 Sub Pop Records from project -RectiveShipping. - -Portions of this project are © 2020 Brody Hoskins from project ReactiveFreight. - -Portions of this project are © 2021 Brody Hoskins from project HyperCarrier. - -Copyright for all later portions this project are held by Third Party Logistics -Management Systems LLC. - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..49647511 --- /dev/null +++ b/Rakefile @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'bundler/gem_tasks' +require 'rspec/core/rake_task' + +RSpec::Core::RakeTask.new(:spec) + +require 'rubocop/rake_task' + +RuboCop::RakeTask.new + +task default: %i[spec rubocop] diff --git a/VERSION b/VERSION index 845639ee..b06ea577 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.4 +1.0.0.pre1 diff --git a/accessorial_symbols.txt b/accessorial_symbols.txt deleted file mode 100644 index d0a2ba4b..00000000 --- a/accessorial_symbols.txt +++ /dev/null @@ -1,97 +0,0 @@ -:afterhours_delivery -:afterhours_pickup -:airport_delivery -:airport_pickup -:amusement_park_delivery -:amusement_park_pickup -:appointment_delivery -:appointment_pickup -:athletic_facility_delivery -:athletic_facility_pickup -:brewery_delivery -:brewery_pickup -:cemetery_delivery -:cemetery_pickup -:church_delivery -:church_pickup -:construction_delivery -:construction_pickup -:convention_delivery -:convention_pickup -:customs_brokerage -:dock_dropoff -:dock_pickup -:driver_assist -:early_morning_delivery -:early_morning_pickup -:fair_delivery -:fair_pickup -:farm_delivery -:farm_pickup -:fitness_center_delivery -:fitness_center_pickup -:flatbed_delivery -:flatbed_pickup -:gated_community_delivery -:gated_community_pickup -:golf_course_delivery -:golf_course_pickup -:government_delivery -:government_pickup -:grocery_store_delivery -:grocery_store_pickup -:grocery_warehouse_delivery -:grocery_warehouse_pickup -:guaranteed_delivery -:guaranteed_delivery_am -:guaranteed_delivery_pm -:hospital_delivery -:hospital_pickup -:hotel_delivery -:hotel_pickup -:inside_delivery -:inside_pickup -:inspection_site_delivery -:inspection_site_pickup -:jobsite_delivery -:jobsite_pickup -:liftgate_delivery -:liftgate_pickup -:mall_delivery -:mall_pickup -:marina_delivery -:marina_pickup -:military_site_delivery -:military_site_pickup -:mine_site_delivery -:mine_site_pickup -:motel_delivery -:motel_pickup -:nursing_home_delivery -:nursing_home_pickup -:park_delivery -:park_pickup -:prison_delivery -:prison_pickup -:ranch_delivery -:ranch_pickup -:reservation_delivery -:reservation_pickup -:residential_delivery -:residential_pickup -:resort_delivery -:resort_pickup -:restaurant_delivery -:restaurant_pickup -:school_delivery -:school_pickup -:steel_mill_delivery -:steel_mill_pickup -:storage_facility_delivery -:storage_facility_pickup -:university_delivery -:university_pickup -:utility_site_delivery -:utility_site_pickup -:winery_delivery -:winery_pickup \ No newline at end of file diff --git a/freight_kit.gemspec b/freight_kit.gemspec index b27a236e..2de8e139 100644 --- a/freight_kit.gemspec +++ b/freight_kit.gemspec @@ -1,53 +1,37 @@ # frozen_string_literal: true -version = File.read(File.expand_path('VERSION', __dir__)).strip.freeze +require_relative 'lib/freight_kit' Gem::Specification.new do |spec| spec.name = 'freight_kit' - spec.version = version - - spec.authors = [ - 'Third Party Transportation Systems LLC', - 'Brody Hoskins', - 'Sub Pop Records', - 'Shopify', - ] - spec.email = [ - 'hello@next-tms.com', - 'brody@brody.digital', - 'webmaster@subpop.com', - 'integrations-team@shopify.com', - ] + spec.version = FreightKit::VERSION + spec.authors = ['Third Party Transportation Systems LLC'] + spec.email = ['hello@next-tms.com'] spec.description = 'Freight carrier API and website abstraction library for transportation management systems (TMS)' - spec.homepage = 'https://github.com/next-tms/freight_kit' spec.summary = spec.description - - spec.files = Dir['lib/**/*'] + - Dir['[A-Z]*'] + - Dir['test/**/*'] + spec.homepage = 'https://github.com/next-tms/freight_kit' + spec.license = 'MIT' + spec.required_ruby_version = '>= 3.0.0' + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = spec.homepage + spec.metadata['changelog_uri'] = 'https://github.com/next-tms/freight_kit/blob/main/CHANGELOG.md' + + spec.files = Dir.chdir(__dir__) do + %x(git ls-files -z).split("\x0").reject do |f| + (File.expand_path(f) == __FILE__) || + f.start_with?('bin/', 'test/', 'spec/', 'features/', '.git', '.github', 'appveyor', 'Gemfile') + end + end + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] - spec.add_development_dependency('business_time', '~> 0.13.0') - spec.add_development_dependency('faker', '~> 3.2.1') - spec.add_development_dependency('rake', '~> 13.1.0') - spec.add_development_dependency('redcarpet', '~> 3.6.0') # for yard - spec.add_development_dependency('rspec', '~> 3.12') + spec.add_development_dependency('rake', '~> 13.0') + spec.add_development_dependency('rspec', '~> 3.0') + spec.add_development_dependency('rubocop', '~> 1.21') spec.add_development_dependency('rubocop-next', '~> 1.0.3') - spec.add_development_dependency('yard', '~> 0.9.28') - spec.add_dependency('activemodel', '>= 4.2', '< 7.1.4') spec.add_dependency('activesupport', '>= 4.2', '< 7.1.4') - spec.add_dependency('active_utils', '>= 3.3.1', '< 3.5.0') - spec.add_dependency('httparty', '~> 0.10') - spec.add_dependency('measured', '>= 2.0', '< 2.8.3') - spec.add_dependency('mimemagic', '~> 0.4.3') - spec.add_dependency('nokogiri', '>= 1.6', '< 1.17') - spec.add_dependency('place_kit', '~> 0.0.2') - spec.add_dependency('savon', '>= 2.0', '< 2.15') - spec.add_dependency('tzinfo-data', '~> 1.2023', '>= 1.2023.3') - spec.add_dependency('watir', '>= 7.0', '< 7.4') spec.add_dependency('zeitwerk', '>= 2.6.0', '< 2.6.13') - - spec.required_ruby_version = '>= 3.2.0' end diff --git a/lib/freight_kit.rb b/lib/freight_kit.rb index 5a111271..8b7af76a 100644 --- a/lib/freight_kit.rb +++ b/lib/freight_kit.rb @@ -1,22 +1,10 @@ # frozen_string_literal: true -require 'active_model' require 'active_support/all' -require 'active_utils' -require 'cgi' -require 'httparty' -require 'measured' -require 'mimemagic' -require 'nokogiri' -require 'open-uri' -require 'place_kit' -require 'savon' -require 'watir' -require 'yaml' require 'zeitwerk' module FreightKit - VERSION = File.read(File.expand_path('../VERSION', __dir__)).strip.freeze + VERSION = File.read(File.expand_path('../../VERSION', __FILE__)).strip.freeze class Inflector < Zeitwerk::Inflector def camelize(basename, abspath) @@ -31,9 +19,6 @@ def camelize(basename, abspath) loader = Zeitwerk::Loader.for_gem -loader.collapse("#{__dir__}/freight_kit/errors") -loader.collapse("#{__dir__}/freight_kit/models") - loader.inflector = FreightKit::Inflector.new loader.setup diff --git a/lib/freight_kit/carrier.rb b/lib/freight_kit/carrier.rb deleted file mode 100644 index cd79bf08..00000000 --- a/lib/freight_kit/carrier.rb +++ /dev/null @@ -1,473 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Carrier is the abstract base class for all supported carriers. - # - # To implement support for a carrier, you should subclass this class and - # implement all the methods that the carrier supports. - # - # @see #create_pickup - # @see #cancel_shipment - # @see #find_tracking_info - # @see #find_rates - # - # @!attribute last_request - # The last request performed against the carrier's API. - # @see #save_request - class Carrier - class << self - # Whether bill of lading (BOL) requires tracking number at time of pickup. - # @return [Boolean] - def bol_requires_tracking_number? - false - end - - # The default location to use for {#valid_credentials?}. - # @return [FreightKit::Location] - def default_location - Location.new( - address1: '455 N. Rexford Dr.', - address2: '3rd Floor', - city: 'Beverly Hills', - country: 'US', - fax: '1-310-275-8159', - phone: '1-310-285-1013', - state: 'CA', - zip: '90210', - ) - end - - # Whether finding rates with declared value (thus insurance) is implemented. - # @return [Boolean] - def find_rates_with_declared_value? - false - end - - # Whether instance method is supported - # @return [Boolean] - def implemented?(instance_method) - instance_method(instance_method).owner != FreightKit::Carrier - rescue NameError - false - end - - # The address field maximum length accepted by the carrier - # @return [Integer] - def maximum_address_field_length - 255 - end - - # The maximum height the carrier will accept. - # @return [Measured::Length] - def maximum_height - Measured::Length.new(105, :inches) - end - - # The maximum weight the carrier will accept. - # @return [Measured::Weight] - def maximum_weight - Measured::Weight.new(10_000, :pounds) - end - - # What length overlength fees the carrier begins charging at. - # @return [Measured::Length] - def minimum_length_for_overlength_fees - Measured::Length.new(48, :inches) - end - - # Whether or not the carrier quotes overlength fees via API. - # @note Should the API not calculate these fees, they should be calculated some other way outside of FreightKit. - # @return [Boolean] - def overlength_fees_require_tariff? - true - end - - # Whether carrier considers pickup number the same as the tracking number. - def pickup_number_is_tracking_number? - false - end - - # Returns the keywords passed to `#initialize` that cannot be blank. - # @return [Array] - def requirements - [] - end - - # Returns the `Credential` methods (passed to `#initialize`) that cannot respond with blank values. - # @return [Array] - def required_credential_types - %i[api] - end - - # The template URL for tracking one of carrier's `NUMBERS` publicly. A template URL is a URL containing the token - # "%s" that can be replaced to generate a valid URL. - # - # This method is designed to provide a template URL for a specific tracking-related key, allowing applications to - # generate valid tracking URLs without involving FreightKit each time. The `key` parameter must be one of the - # predefined carrier number types specified in the `NUMBERS` constant. - # - # @see `NUMBERS` - # @param [Symbol] key The symbol representing the type of tracking information for which the template URL is - # needed. - # @raise [ArgumentError] - # @return [String, nil] The template URL or nil if no template is available for the given key. - def tracking_url_template(key) - raise ArgumentError, "key must be one of: #{NUMBERS.join(", ")}" if NUMBERS.exclude?(key) - - "#{self}::#{key.to_s.upcase}_TRACKING_URL_TEMPLATE".constantize - end - - # Regular expression to use for validating carrier's `NUMBERS`. - # - # Returns the regular expression used for validating the carrier's number based on the provided `key`. - # The `key` parameter must be one of the predefined carrier number types specified in the `NUMBERS` constant. - # - # @see `NUMBERS` - # @param [Symbol] key The symbol representing the carrier number type for which the regular expression is needed. - # @raise [ArgumentError] Raised if the provided key is not one of the specified carrier number types. - # @return [Regexp] The regular expression for validating the specified carrier number type. - def valid_number_regex(key) - raise ArgumentError, "key must be one of: #{NUMBERS.join(", ")}" if NUMBERS.exclude?(key) - - "#{self}::VALID_#{key.to_s.upcase}_REGEX".constantize - end - end - - BOL_NUMBER_TRACKING_URL_TEMPLATE = nil - ORDER_NUMBER_TRACKING_URL_TEMPLATE = nil - PICKUP_NUMBER_TRACKING_URL_TEMPLATE = nil - PO_NUMBER_TRACKING_URL_TEMPLATE = nil - TRACKING_NUMBER_TRACKING_URL_TEMPLATE = nil - VALID_BOL_NUMBER_REGEX = nil - VALID_ORDER_NUMBER_REGEX = nil - VALID_PICKUP_NUMBER_REGEX = nil - VALID_PO_NUMBER_REGEX = nil - VALID_TRACKING_NUMBER_REGEX = nil - - NUMBERS = %i[bol_number order_number pickup_number po_number tracking_number] - - attr_accessor :conf, :rates_with_excessive_length_fees, :tmpdir - attr_reader :credentials, :customer_location, :last_request, :tariff - - # @param credentials [Array] - # @param customer_location [Location] - # @param tariff [Tariff] - def initialize(credentials, customer_location: nil, tariff: nil) - credentials = [credentials] if credentials.is_a?(Credential) - - if credentials.map(&:class).uniq != [Credential] - message = "#{self.class.name}#new: `credentials` must be a Credential or Array of Credential" - raise ArgumentError, message - end - - missing_credential_types = self.class.required_credential_types.uniq - credentials.map(&:type).uniq - - if missing_credential_types.any? - message = "#{self.class.name}#new: `Credential` of type(s) missing: #{missing_credential_types.join(", ")}" - raise ArgumentError, message - end - - @credentials = credentials - - if customer_location.present? - unless customer_location.is_a?(Location) - message = "#{self.class.name}#new: `customer_location` must be a Location" - raise ArgumentError, message - end - - @customer_location = customer_location - end - - if tariff.present? - raise ArgumentError, "#{self.class.name}#new: `tariff` must be a Tariff" unless tariff.is_a?(Tariff) - - @tariff = tariff - end - - conf_path = File - .join( - File.expand_path( - '../../../../configuration/carriers', - self.class.const_source_location(:REACTIVE_FREIGHT_CARRIER).first, - ), - "#{self.class.to_s.split("::")[1].underscore}.yml", - ) - @conf = YAML.safe_load(File.read(conf_path), permitted_classes: [Symbol]) - - @rates_with_excessive_length_fees = @conf.dig(:attributes, :rates, :with_excessive_length_fees) - end - - # Asks the carrier for the scanned proof of delivery that the carrier would provide after delivery. - # - # @param [String] tracking_number Tracking number. - # @return [DocumentResponse] - def pod(tracking_number) - raise NotImplementedError, "#{self.class.name}: #pod not supported" - end - - # Asks the carrier for the bill of lading that the carrier would provide before shipping. - # - # @see #scanned_bol - # - # @param [String] tracking_number Tracking number. - # @return [DocumentResponse] - def bol(tracking_number) - raise NotImplementedError, "#{self.class.name}: #bol not supported" - end - - # Asks the carrier for the scanned bill of lading that the carrier would provide after shipping. - # - # @see #bol - # - # @param [String] tracking_number Tracking number. - # @return [DocumentResponse] - def scanned_bol(tracking_number) - raise NotImplementedError, "#{self.class.name}: #scanned_bol not supported" - end - - def find_estimate(*) - raise NotImplementedError, "#{self.class.name}: #find_estimate not supported" - end - - # Asks the carrier for a list of locations (terminals) for a given country - # - # @param [ActiveUtils::Country] country - # @return [Array] - def find_locations(country) - raise NotImplementedError, "#{self.class.name}: #find_locations not supported" - end - - def find_tracking_number_from_pickup_number(pickup_number, date) - raise NotImplementedError, "#{self.class.name}: #find_tracking_number_from_pickup_number not supported" - end - - # Asks the carrier for rate estimates for a given shipment. - # - # @note Override with whatever you need to get the rates from the carrier. - # - # @param shipment [FreightKit::Shipment] Shipment details. - # @return [FreightKit::RateResponse] The response from the carrier, which - # includes 0 or more rate estimates for different shipping products - def find_rates(shipment:) - raise NotImplementedError, "#find_rates is not supported by #{self.class.name}." - end - - # Registers a new pickup with the carrier, to get a tracking number and - # potentially shipping labels - # - # @note Override with whatever you need to register a shipment, and obtain - # shipping labels if supported by the carrier. - # - # @param delivery_from [ActiveSupport::TimeWithZone] Local date, time and time zone that - # delivery hours begin. - # @param delivery_to [ActiveSupport::TimeWithZone] Local date, time and time zone that - # delivery hours end. - # @param dispatcher [FreightKit::Contact] The dispatcher. - # @param pickup_from [ActiveSupport::TimeWithZone] Local date, time and time zone that - # pickup hours begin. - # @param pickup_to [ActiveSupport::TimeWithZone] Local date, time and time zone that - # pickup hours end. - # @param scac [String] The carrier SCAC code (can be `nil`; only used for brokers). - # @param service [Symbol] The service type. - # @param shipment [FreightKit::Shipment] The shipment including `#destination.contact`, `#origin.contact`. - # @return [FreightKit::ShipmentResponse] The response from the carrier. This - # response should include a shipment identifier or tracking_number if successful, - # and potentially shipping labels. - def create_pickup( - delivery_from:, - delivery_to:, - dispatcher:, - pickup_from:, - pickup_to:, - scac:, - service:, - shipment: - ) - raise NotImplementedError, "#create_pickup is not supported by #{self.class.name}." - end - - # Cancels a shipment with a carrier. - # - # @note Override with whatever you need to cancel a shipping label - # - # @param tracking_number [String] The tracking number of the shipment to cancel. - # @return [FreightKit::Response] The response from the carrier. This - # response in most cases has a cancellation id. - def cancel_shipment(tracking_number) - raise NotImplementedError, "#cancel_shipment is not supported by #{self.class.name}." - end - - # Retrieves tracking information for a previous shipment - # - # @note Override with whatever you need to get a shipping label - # - # @param tracking_number [String] The tracking number of the shipment to track. - # @return [FreightKit::TrackingResponse] The response from the carrier. This - # response should a list of shipment tracking events if successful. - def find_tracking_info(tracking_number) - raise NotImplementedError, "#find_tracking_info is not supported by #{self.class.name}." - end - - # Get a list of services available for the a specific route. - # - # @param origin [Location] The origin location. - # @param destination [Location] The destination location. - # @return [Array] A list of service type symbols for the available services. - # - def available_services(origin, destination) - raise NotImplementedError, "#available_services is not supported by #{self.class.name}." - end - - # Fetch credential of given type. - # - # @param type [Symbol] Type of credential to find, should be one of: `:api`, `:selenoid`, `:website`. - # @return [FreightKit::Credential|NilClass] - def fetch_credential(type) - @fetch_credentials ||= {} - return @fetch_credentials[type] if @fetch_credentials[type].present? - - @fetch_credentials[type] ||= credentials.find { |credential| credential.type == type } - end - - # Validate credentials with a call to the API. - # - # By default this just does a `find_rates` call with the origin and destination both as - # the carrier's default_location. Override to provide alternate functionality. - # - # @return [Boolean] Should return `true` if the provided credentials proved to work, - # `false` otherswise. - def valid_credentials? - location = self.class.default_location - find_rates(location, location, Package.new(100, [5, 15, 30])) - rescue FreightKit::ResponseError - false - else - true - end - - # Validate the tracking number (may call API). - # - # @param [String] tracking_number Tracking number. - # @return [Boolean] Should return `true` if the provided pro is valid. - def valid_tracking_number?(tracking_number) - raise NotImplementedError, "#valid_pro is not supported by #{self.class.name}." - end - - def overlength_fee(tarrif, package) - max_dimension_inches = [package.length(:inches), package.width(:inches)].max - - return 0 if max_dimension_inches < self.class.minimum_length_for_overlength_fees.convert_to(:inches).value - - tarrif.overlength_rules.each do |overlength_rule| - next if max_dimension_inches < overlength_rule[:min_length].convert_to(:inches).value - - if overlength_rule[:max_length].blank? || - max_dimension_inches <= overlength_rule[:max_length].convert_to(:inches).value - return (package.quantity * overlength_rule[:fee_cents]) - end - end - - 0 - end - - # Determine whether the carrier will accept the packages based on credentials and/or tariff. - # @param packages [Array] - # @param tariff [FreightKit::Tariff] - # @return [Boolean] - def validate_packages(packages, tariff = nil) - return false if packages.blank? - - message = [] - - max_height_inches = self.class.maximum_height.convert_to(:inches).value - if packages.map { |p| p.height(:inches) }.max > max_height_inches - message << "items must be #{max_height_inches.to_f} inches tall or less" - end - - max_weight_pounds = self.class.maximum_weight.convert_to(:pounds).value - if packages.sum { |p| p.pounds(:total) } > max_weight_pounds - message << "items must weigh #{max_weight_pounds.to_f} lbs or less" - end - - if self.class.overlength_fees_require_tariff? && (tariff.blank? || !tariff.is_a?(FreightKit::Tariff)) - missing_dimensions = packages.map do |p| - [p.height(:inches), p.length(:inches), p.width(:inches)].any?(&:zero?) - end.any?(true) - - if missing_dimensions - message << 'item dimensions are required' - else - max_length_inches = self.class.minimum_length_for_overlength_fees.convert_to(:inches).value - - if packages.map { |p| [p.width(:inches), p.length(:inches)].max }.max >= max_length_inches - message << 'tariff must be defined to calculate overlength fees' - end - end - end - - raise UnserviceableError, message.join(', ').capitalize if message.present? - - true - end - - def serviceable_accessorials?(accessorials) - return true if accessorials.blank? - - unless self.class::REACTIVE_FREIGHT_CARRIER - raise NotImplementedError, "#{self.class.name}: #serviceable_accessorials? not supported" - end - - return false if @conf.dig(:accessorials, :mappable).blank? - - conf_mappable_accessorials = @conf.dig(:accessorials, :mappable) - conf_unquotable_accessorials = @conf.dig(:accessorials, :unquotable) - conf_unserviceable_accessorials = @conf.dig(:accessorials, :unserviceable) - - unserviceable_accessorials = [] - - accessorials.each do |accessorial| - if conf_unserviceable_accessorials.present? && conf_unserviceable_accessorials.any?(accessorial) - unserviceable_accessorials << accessorial - next - end - - next if conf_mappable_accessorials.present? && conf_mappable_accessorials.keys.any?(accessorial) - next if conf_unquotable_accessorials.present? && conf_unquotable_accessorials.any?(accessorial) - - unserviceable_accessorials << accessorial - end - - if unserviceable_accessorials.present? - raise FreightKit::UnserviceableAccessorialsError.new(accessorials: unserviceable_accessorials) - end - - true - end - - protected - - include ActiveUtils::RequiresParameters - include ActiveUtils::PostsData - - # Use after building the request to save for later inspection. - # @return [void] - def save_request(request) - @last_request = request - end - - # Calculates a timestamp that corresponds a given number of business days in the future - # - # @param days [Integer] The number of business days from now. - # @return [Time] A timestamp, the provided number of business days in the future. - def timestamp_from_business_day(days) - raise ArgumentError, 'days must be an Integer' unless days.is_a?(Integer) - - date = Time.current.utc + days.days - - date += 2.days if date.saturday? - date += 1.day if date.sunday? - - date - end - end -end diff --git a/lib/freight_kit/carriers.rb b/lib/freight_kit/carriers.rb deleted file mode 100644 index ea711fe0..00000000 --- a/lib/freight_kit/carriers.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - module Carriers - extend self - - attr_reader :registered - - @registered = [] - - def register(class_name, autoload_require) - FreightKit.autoload(class_name, autoload_require) - registered << class_name - end - - def all - FreightKit::Carriers.registered.map { |name| FreightKit.const_get(name) } - end - - def find(name) - all.find { |c| c.name.downcase == name.to_s.downcase } or raise NameError, "unknown carrier #{name}" - end - end -end diff --git a/lib/freight_kit/error.rb b/lib/freight_kit/error.rb deleted file mode 100644 index fb5f5b67..00000000 --- a/lib/freight_kit/error.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class Error < StandardError; end -end diff --git a/lib/freight_kit/errors/document_not_found_error.rb b/lib/freight_kit/errors/document_not_found_error.rb deleted file mode 100644 index 9612f336..00000000 --- a/lib/freight_kit/errors/document_not_found_error.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class DocumentNotFoundError < Error; end -end diff --git a/lib/freight_kit/errors/expired_credentials_error.rb b/lib/freight_kit/errors/expired_credentials_error.rb deleted file mode 100644 index 80f6644a..00000000 --- a/lib/freight_kit/errors/expired_credentials_error.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class ExpiredCredentialsError < InvalidCredentialsError; end -end diff --git a/lib/freight_kit/errors/http_error.rb b/lib/freight_kit/errors/http_error.rb deleted file mode 100644 index 5ce8eed4..00000000 --- a/lib/freight_kit/errors/http_error.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class HTTPError < Error - attr_reader :body, :code - - def initialize(body:, code:) - @body = body - @code = code - - super(message) - end - - def message - @message ||= ''.tap do |builder| - builder << "HTTP #{@code}" - builder << ":\n#{@body}" if @body.present? - end - end - - def to_hash - @to_hash ||= { code: @http.code, headers: @http.headers, body: @http.body } - end - end -end diff --git a/lib/freight_kit/errors/invalid_credentials_error.rb b/lib/freight_kit/errors/invalid_credentials_error.rb deleted file mode 100644 index be96aa98..00000000 --- a/lib/freight_kit/errors/invalid_credentials_error.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class InvalidCredentialsError < Error; end -end diff --git a/lib/freight_kit/errors/response_error.rb b/lib/freight_kit/errors/response_error.rb deleted file mode 100644 index df79f6f8..00000000 --- a/lib/freight_kit/errors/response_error.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class ResponseError < Error - attr_reader :response - - def initialize(response = nil) - if response.is_a?(Response) - super(response.message) - @response = response - else - super(response) - end - end - end -end diff --git a/lib/freight_kit/errors/shipment_not_found_error.rb b/lib/freight_kit/errors/shipment_not_found_error.rb deleted file mode 100644 index e12dd67a..00000000 --- a/lib/freight_kit/errors/shipment_not_found_error.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class ShipmentNotFoundError < Error; end -end diff --git a/lib/freight_kit/errors/unserviceable_accessorials_error.rb b/lib/freight_kit/errors/unserviceable_accessorials_error.rb deleted file mode 100644 index 1f5ba707..00000000 --- a/lib/freight_kit/errors/unserviceable_accessorials_error.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class UnserviceableAccessorialsError < UnserviceableError - attr_reader :accessorials - - def initialize(accessorials:) - @accessorials = accessorials - - super(message) - end - - def message - @message ||= "Unable to service #{@accessorials.map do |accessorial| - accessorial.to_s.gsub("_", " ") - end.join(", ")}" - end - end -end diff --git a/lib/freight_kit/errors/unserviceable_error.rb b/lib/freight_kit/errors/unserviceable_error.rb deleted file mode 100644 index ba771217..00000000 --- a/lib/freight_kit/errors/unserviceable_error.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class UnserviceableError < Error; end -end diff --git a/lib/freight_kit/model.rb b/lib/freight_kit/model.rb deleted file mode 100644 index 6669a7fa..00000000 --- a/lib/freight_kit/model.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class Model - include ActiveModel::AttributeAssignment - include ActiveModel::Validations - - def initialize(attributes = {}) - assign_attributes(attributes) - end - - def attributes - instance_values.with_indifferent_access - end - alias_method :to_hash, :attributes - end -end diff --git a/lib/freight_kit/models/contact.rb b/lib/freight_kit/models/contact.rb deleted file mode 100644 index 556c714e..00000000 --- a/lib/freight_kit/models/contact.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Contact is the abstract base class for all contacts. - # - # @!attribute company_name [String] Company name. - # @!attribute department_name [String] Department name (like "Shipping Dept"). - # @!attribute email [String] Email. - # @!attribute fax [String] E164 formatted fax number. - # @!attribute name [String] Name of person. - # @!attribute phone [String] E164 formatted phone number. - class Contact < Model - attr_accessor :company_name, :department, :email, :fax, :name, :phone - - alias_method :company, :company_name - end -end diff --git a/lib/freight_kit/models/credential.rb b/lib/freight_kit/models/credential.rb deleted file mode 100644 index 044880b7..00000000 --- a/lib/freight_kit/models/credential.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Class representing credentials. - # - # @!attribute kind - # What kind? - # @return [Symbol] One of `:api`, `:api_key`, `:oauth2`, `:website` - # - # @!attribute access_token - # Access token when type is `:oauth2` - # @return [String] Access token - # - # @!attribute expires_at - # Token expiration date/time when type is `:oauth2` - # @return [DateTime] Token expiration date/time - # - # @!attribute scope - # Scope when type is `:oauth2` - # @return [String] Scope - # - # @!attribute username - # Username when type is one of `:api`, `:website` - # @return [String] Username - # - # @!attribute password - # Password when type is one of `:api`, `:website` - # @return [String] Username - # - class Credential < Model - attr_accessor :type - - # Returns a new instance of Credential. - # - # Other than the following, instance `:attr_reader`s are generated dynamically based on keys. - # - # @param [Symbol] type One of `:api`, `:website` - # @param [String] base_url Required when type is `:selenoid` - # @param [String] username Required when type is one of `:api`, `:website` - # @param [String] password Required when type is one of `:api`, `:website` - # @param [String] access_token Required when type is `:oauth2` - # @param [DateTime] expires_at Required when type is `:oauth2` - # @param [String] scope Required when type is `:oauth2` - # @param [String] proxy_url Required when type is `:api_proxy` - def initialize(hash) - raise ArgumentError, 'Credential#new: `type` cannot be blank' if hash[:type].blank? - - type = hash[:type] - - requirements = case type - when :api_key - { api_key: String } - when :api, :website - { password: String } - when :api_proxy - { api_key: String, proxy_url: String } - when :oauth2 - { access_token: String, expires_at: ::DateTime, scope: String } - when :selenoid - { base_url: URI, browser: Symbol } - else - {} - end - - requirements.each_key do |k| - raise ArgumentError, "Credential#new: `#{k}` cannot be blank" if hash[k].blank? - - unless hash[k].is_a?(requirements[k]) - raise ArgumentError, "Credential#new: `#{k}` must be a #{requirements[k]}, got #{hash[k].class}" - end - end - - hash.each do |k, _v| - next if k == :type - - singleton_class.class_eval { attr_accessor(k.to_s) } unless singleton_class.respond_to?(k) - end - - super - end - - def selenoid_options - return unless type == :selenoid - return @selenoid_options if @selenoid_options.present? - - download_url = base_url.dup - download_url.path = '/download' - download_url = download_url.to_s - - @selenoid_options = { download_url: } - end - - def watir_args - return unless type == :selenoid - return @watir_args if @watir_args.present? - - url = base_url.dup - url.path = '/wd/hub/' - url = url.to_s - - @watir_args = [ - browser, - { - options: { - prefs: { - download: { - directory_upgrade: true, - prompt_for_download: false - } - } - }, - url: - }, - ] - end - end -end diff --git a/lib/freight_kit/models/date_time.rb b/lib/freight_kit/models/date_time.rb deleted file mode 100644 index 08524c08..00000000 --- a/lib/freight_kit/models/date_time.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Represent dates and times in varous formats. - # - # @attribute date_time_with_zone - # Date and time with time zone. - # @return [ActiveSupport::TimeWithZone] - # - # @attribute local_date - # Local date. - # @return [Date] - # - # @attribute local_date_time - # Local date and time in the format `"YYYY-MM-DD HH:MM:SS"` - # (zero-padded 24 hour clock) aka `DateTime#to_fs(:db)` format. - # @return [String] - # - class DateTime < Model - attr_accessor :local_date, :local_date_time, :location, :date_time_with_zone - - def initialize(*) - super - - attempt_upgrade_using_location(location) if location.present? - end - - private - - def attempt_upgrade_using_location(location) - return if @date_time_with_zone.present? || @local_date_time.blank? || location.time_zone.blank? - - @date_time_with_zone = location.time_zone.parse(@local_date_time) - @local_date_time = nil - end - end -end diff --git a/lib/freight_kit/models/document_response.rb b/lib/freight_kit/models/document_response.rb deleted file mode 100644 index 8ac1a4c7..00000000 --- a/lib/freight_kit/models/document_response.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Represents the response to calls: - # - {FreightKit::Carrier#bol} - # - {FreightKit::Carrier#pod} - # - {FreightKit::Carrier#scanned_bol} - # - # @attribute content_type - # @return [String] The HTTP `Content-Type` - # - # @attribute data - # @return [String] Raw document data. - class DocumentResponse < Response - attr_accessor :content_type, :data - end -end diff --git a/lib/freight_kit/models/label.rb b/lib/freight_kit/models/label.rb deleted file mode 100644 index d62baeaf..00000000 --- a/lib/freight_kit/models/label.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Class representing a shipping option with estimated price. - # - # @!attribute data - # The label image data. - # @return [String] - # - class Label < Model - attr_accessor :data - end -end diff --git a/lib/freight_kit/models/location.rb b/lib/freight_kit/models/location.rb deleted file mode 100644 index 9327d5d7..00000000 --- a/lib/freight_kit/models/location.rb +++ /dev/null @@ -1,108 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Class representing a location. - # - # @attribute address1 - # The first street line - # @return [String, nil] - # - # @attribute address2 - # The second street line - # @return [String, nil] - # - # @attribute address3 - # The third street line - # @return [String, nil] - # - # @attribute city - # The city name. - # @return [String, nil] - # - # @attribute contact - # The contact at the location. - # @return [String, nil] - # - # @attribute country - # The country of the location. - # @return [ActiveUtils::Country, nil] - # - # @attribute lat - # The latitude of the location. - # @return [BigNum, nil] - # - # @attribute lng - # The longitude of the location. - # @return [BigNum, nil] - # - # @attribute postal_code - # The postal code (or ZIP® code) of the location. - # @return [String, nil] - # - # @attribute province - # The province (or state/territory) abbreviation of the location. - # @return [String, nil] - # - # @attribute type - # The type of the location. - # - # It should be one of: :commercial, :po_box, :residential - # - # @return [Symbol, nil] - # - class Location < Model - TYPES = %i[commercial po_box residential].freeze - - attr_accessor :address1, :address2, :address3, :city, :postal_code, :province - attr_reader :contact, :country, :lat, :lng, :type - - def contact=(contact) - return @contact = nil if contact.blank? - - raise ArgumentError, 'contact must be a Contact' unless contact.is_a?(FreightKit::Contact) - - @contact = contact - end - - def country=(country) - return country = nil if country.blank? - - if country.is_a?(ActiveUtils::Country) - @country = country - return country - end - - raise ArgumentError, 'country must be an ActiveUtils::Country' - end - - def lat=(num) - return @lat = nil if num.blank? - - return @lat = num if num.is_a?(BigNum) - - raise ArgumentError, 'lat must be a BigNum' - end - - def lng=(num) - return @lng = nil if num.blank? - - return @lng = num if num.is_a?(BigNum) - - raise ArgumentError, 'lng must be a BigNum' - end - - def time_zone - return if country&.code(:alpha2)&.blank? || province.blank? || city.blank? - - PlaceKit.lookup(country.code(:alpha2).to_s, province, city) - end - - def type=(value) - return @type = nil if value.blank? - - raise ArgumentError, "type must be one of :#{TYPES.join(", :")}" unless TYPES.include?(value) - - @type = value - end - end -end diff --git a/lib/freight_kit/models/pickup_response.rb b/lib/freight_kit/models/pickup_response.rb deleted file mode 100644 index cbaa17a0..00000000 --- a/lib/freight_kit/models/pickup_response.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # The `PickupResponse` object is returned by the {FreightKit::Carrier#create_pickup} - # call. The most important method is {#pickup_number}, which will return the pickup reference - # number. - # - # @!attribute labels - # Shipping labels. - # @return [Array] - # - # @!attribute pickup_number - # Pickup reference number. - # @return [String] - # - class PickupResponse < Response - attr_accessor :labels, :pickup_number - end -end diff --git a/lib/freight_kit/models/price.rb b/lib/freight_kit/models/price.rb deleted file mode 100644 index f0c3faa1..00000000 --- a/lib/freight_kit/models/price.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Class representing a price. - # - # @!attribute blame - # Where did the cost come from? - # @return [Symbol] One of :api, :library, :tariff - # - # @!attribute description - # Description. - # @return [String] - # - # @!attribute objects - # Array of objects that the price applies to. - # @return [Array] - # - # @!attribute cents - # The price in cents. - # @return [Integer] - # - class Price < Model - attr_accessor :description, :objects - attr_writer :blame, :cents - - def blame - return @blame if %i[api library tariff].include?(@blame) - - raise 'blame must be one of :api, :library, :tariff' - end - - def cents - return @cents if @cents.is_a?(Integer) - - raise 'cents must be an `Integer`' - end - end -end diff --git a/lib/freight_kit/models/rate.rb b/lib/freight_kit/models/rate.rb deleted file mode 100644 index 622d38c3..00000000 --- a/lib/freight_kit/models/rate.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Class representing a shipping option with estimated price. - # - # @!attribute carrier - # The carrier. - # @return [FreightKit::Carrier] - # @see FreightKit::Carrier - # - # @!attribute carrier_name - # Name of the carrier. It may differ from the `Carrier` providing the rate quote - # when the `Carrier` is acting as a broker. - # @return [String] - # - # @!attribute currency - # ISO4217 currency code of the quoted rate estimates (e.g. `CAD`, `EUR`, or `USD`) - # @return [String] - # @see http://en.wikipedia.org/wiki/ISO_4217 - # - # @!attribute estimate_reference - # Quote number. - # @return [String] - # - # @!attribute expires_at - # When the rate estimate will expire. - # @return [DateTime] - # - # @!attribute prices - # Breakdown of a rate estimate's prices with amounts in cents. - # @return [Array] - # @see FreightKit::Price - # - # @!attribute scac - # SCAC code of the carrier. It may differ from the `Carrier` providing the rate - # estimate when the `Carrier` is acting as a broker. - # @return [String] - # - # @!attribute service_name - # The name of the shipping service (e.g. 'First Class Ground') - # @return [String] - # - # @!attribute shipment - # The shipment. - # @return [FreightKit::Shipment] - # - # @!attribute transit_days - # Estimated transit days after date of pickup. - # @return [Integer] - # - # @!attribute with_excessive_length_fees - # When the rate estimate `Price`s include applicable excessive length fees. - # @return [Integer] - # - class Rate < Model - attr_accessor :carrier, - :carrier_name, - :estimate_reference, - :expires_at, - :prices, - :scac, - :service_name, - :shipment, - :transit_days, - :with_excessive_length_fees - - attr_writer :currency - - def currency - ActiveUtils::CurrencyCode.standardize(@currency) - end - - # The total price of the shipment in cents. - # @return [Integer] - def total_cents - return 0 if @prices.blank? - - @prices.sum(&:cents) - end - end -end diff --git a/lib/freight_kit/models/rate_response.rb b/lib/freight_kit/models/rate_response.rb deleted file mode 100644 index 76ce0e0c..00000000 --- a/lib/freight_kit/models/rate_response.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # The `RateResponse` object is returned by the {FreightKit::Carrier#find_rates} - # call. The most important method is {#rates}, which will return a list of possible - # shipping options with an estimated price. - # - # @!attribute rates - # The available rate options for the shipment, with an estimated price. - # @return [Array] - # - class RateResponse < Response - attr_accessor :rates - end -end diff --git a/lib/freight_kit/models/response.rb b/lib/freight_kit/models/response.rb deleted file mode 100644 index 5c0afd7e..00000000 --- a/lib/freight_kit/models/response.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Basic Response class for requests against a carrier's API. - # - # @!attribute error - # The error object. - # @return [FreightKit::Error, NilClass] - # - # @!attribute request - # The raw request. - # @return [String] - # - # @!attribute response - # The raw response. - # @return [String] - # - class Response < Model - attr_accessor :error, :request, :response - end -end diff --git a/lib/freight_kit/models/shipment.rb b/lib/freight_kit/models/shipment.rb deleted file mode 100644 index 4fee3b64..00000000 --- a/lib/freight_kit/models/shipment.rb +++ /dev/null @@ -1,66 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Shipment is the abstract base class for all rate requests. - # - # @!attribute accessorials [Hash] Acceessorials requested. - # @!attribute declared_value_cents [Integer] Declared value in cents. - # @!attribute destination [FreightKit::Location] Where the package will go. - # @!attribute origin [FreightKit::Location] Where the shipment will originate from. - # @!attribute order_number [String] Order number (also known as shipper number, SO #). - # @!attribute packages [Array] The list of packages that will - # be in the shipment. - # @!attribute po_number [String] Purchase order number (also known as PO #). - # @!attribute pickup_at [FreightKit::DateTime] Pickup date/time. - class Shipment < Model - attr_accessor :accessorials, - :declared_value_cents, - :destination, - :origin, - :order_number, - :packages, - :po_number, - :pro - attr_reader :pickup_at - - def loose? - return false if @packages.blank? - - packages.map(&:packaging).map(&:pallet?).none?(true) - end - - def hazmat? - packages.map(&:hazmat?).any?(true) - end - - def loose_and_palletized? - !loose? && !palletized? - end - - def palletized? - return false if @packages.blank? - - packages.map(&:packaging).map(&:pallet?).none?(false) - end - - def pickup_at=(date_time) - if date_time.is_a?(ActiveSupport::TimeWithZone) - @pickup_at = FreightKit::DateTime.new(date_time_with_zone: date_time) - return - end - - raise ArgumentError, 'date_time must be an FreightKit::DateTime' unless date_time.is_a?(DateTime) - - @pickup_at = date_time - end - - def valid? - return false if @accessorials.nil? - return false unless @destination.is_a?(Location) - return false unless @packages.is_a?(Array) - return false if @packages.any? { |p| p.class != Package } - - true - end - end -end diff --git a/lib/freight_kit/models/shipment_event.rb b/lib/freight_kit/models/shipment_event.rb deleted file mode 100644 index cf6a7df2..00000000 --- a/lib/freight_kit/models/shipment_event.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # `ShipmentEvent` is the abstract base class for all shipment events (usually - # attached to `TrackingEresponse`). - # - # @attribute date_time - # @return [DateTime] Date and time the event occurred. - # - # @attribute location - # @return [Location] Location the event occurred. - # - # @attribute type_code - # @return [Symbol] One of: - # ``` - # :arrived_at_terminal - # :delayed_due_to_weather - # :delivered - # :delivery_appointment_scheduled - # :departed - # :found - # :located - # :lost - # :out_for_delivery - # :pending_delivery_appointment - # :picked_up - # :pickup_driver_assigned - # :pickup_information_received_by_carrier - # :pickup_information_sent_to_carrier - # :sailed - # :trailer_closed - # :trailer_unloaded - # ``` - # - class ShipmentEvent < Model - attr_accessor :date_time, :location, :type_code - end -end diff --git a/lib/freight_kit/models/tracking_response.rb b/lib/freight_kit/models/tracking_response.rb deleted file mode 100644 index 6dd7c395..00000000 --- a/lib/freight_kit/models/tracking_response.rb +++ /dev/null @@ -1,75 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - # Represents the response to a {FreightKit::Carrier#find_tracking_info} call. - # - # @note Some carriers provide more information than others, so not all attributes - # will be set, depending on what carrier you are using. - # - # @!attribute actual_delivery_date - # @return [DateTime] - # - # @!attribute attempted_delivery_date - # @return [DateTime] - # - # @!attribute carrier - # @return [Symbol] - # - # @!attribute carrier_name - # @return [String] - # - # @!attribute delivery_signature - # @return [String] - # - # @!attribute destination - # @return [FreightKit::Location] - # - # @!attribute estimated_delivery_date - # @return [FreightKit::DateTime] - # - # @!attribute origin - # @return [FreightKit::Location] - # - # @!attribute scheduled_delivery_date - # @return [DateTime] - # - # @!attribute ship_time - # @return [Date, Time] - # - # @!attribute shipment_events - # @return [Array] - # - # @!attribute shipper_address - # @return [FreightKit::Location] - # - # @!attribute status - # @return [Symbol] - # - # @!attribute status_code - # @return [string] - # - # @!attribute status_description - # @return [String] - # - # @!attribute tracking_number - # @return [String] - # - class TrackingResponse < Response - attr_accessor :actual_delivery_date, - :attempted_delivery_date, - :carrier, - :carrier_name, - :delivery_signature, - :destination, - :estimated_delivery_date, - :origin, - :scheduled_delivery_date, - :ship_time, - :shipment_events, - :shipper_address, - :status, - :status_code, - :status_description, - :tracking_number - end -end diff --git a/lib/freight_kit/package.rb b/lib/freight_kit/package.rb deleted file mode 100644 index 739a4a1a..00000000 --- a/lib/freight_kit/package.rb +++ /dev/null @@ -1,313 +0,0 @@ -# frozen_string_literal: true - -module FreightKit # :nodoc: - class Package - class << self - def cents_from(money) - return if money.nil? - - if money.respond_to?(:cents) - money.cents - else - case money - when Float - (money * 100).round - when String - money =~ /\./ ? (money.to_f * 100).round : money.to_i - else - money.to_i - end - end - end - end - - VALID_FREIGHT_CLASSES = [55, 60, 65, 70, 77.5, 85, 92.5, 100, 110, 125, 150, 175, 200, 250, 300, 400].freeze - - cattr_accessor :default_options - attr_accessor :description, :hazmat, :nmfc, :quantity - attr_reader :currency, :options, :packaging, :value - attr_writer :declared_freight_class - - # Package.new(100, [10, 20, 30], 'pallet', :units => :metric) - # Package.new(Measured::Weight.new(100, :g), 'box', [10, 20, 30].map {|m| Length.new(m, :centimetres)}) - # Package.new(100.grams, [10, 20, 30].map(&:centimetres)) - def initialize(total_grams_or_ounces, dimensions, packaging_type, options = {}) - options = @@default_options.update(options) if @@default_options - options.symbolize_keys! - @options = options - - raise ArgumentError, 'Package#new: packaging_type is required' unless packaging_type - raise ArgumentError, 'Package#new: quantity is required' unless options[:quantity] - - # For backward compatibility - if dimensions.is_a?(Array) - @dimensions = [dimensions].flatten.reject(&:nil?) - else - @dimensions = [dimensions.dig(:height), dimensions.dig(:width), dimensions.dig(:length)] - @dimensions = [@dimensions].flatten.reject(&:nil?) - end - - @description = options[:description] - @hazmat = options[:hazmat] == true - @nmfc = options[:nmfc].presence - - imperial = (options[:units] == :imperial) - - weight_imperial = dimensions_imperial = imperial if options.include?(:units) - - weight_imperial = (options[:weight_units] == :imperial) if options.include?(:weight_units) - - dimensions_imperial = (options[:dim_units] == :imperial) if options.include?(:dim_units) - - @weight_unit_system = weight_imperial ? :imperial : :metric - @dimensions_unit_system = dimensions_imperial ? :imperial : :metric - - @quantity = options[:quantity] || 1 - - @total_weight = attribute_from_metric_or_imperial( - total_grams_or_ounces, - Measured::Weight, - @weight_unit_system, - :grams, - :ounces, - ) - - @each_weight = attribute_from_metric_or_imperial( - total_grams_or_ounces / @quantity.to_f, - Measured::Weight, - @weight_unit_system, - :grams, - :ounces, - ) - - if @dimensions.blank? - zero_length = Measured::Length.new(0, (dimensions_imperial ? :inches : :centimetres)) - @dimensions = [zero_length] * 3 - else - # Overriding ReactiveShipping's protected process_dimensions which sorts - # them making it confusing for ReactiveFreight carrier API's that expect - # the H x W x L order. Since H x W x L is nonstandard in the freight - # industry ReactiveFreight introduces explicit functions for each - @dimensions = @dimensions.map do |l| - attribute_from_metric_or_imperial(l, Measured::Length, @dimensions_unit_system, :centimetres, :inches) - end - 2.downto(@dimensions.length) do |_n| - @dimensions.unshift(@dimensions[0]) - end - end - - @value = Package.cents_from(options[:value]) - @currency = options[:currency] || (options[:value].currency if options[:value].respond_to?(:currency)) - @cylinder = options[:cylinder] || options[:tube] ? true : false - @gift = options[:gift] ? true : false - @oversized = options[:oversized] ? true : false - @unpackaged = options[:unpackaged] ? true : false - @packaging = Packaging.new(packaging_type) - end - - def cubic_ft(each_or_total) - q = case each_or_total - when :each then 1 - when :total then @quantity - else - raise ArgumentError, 'each_or_total must be one of :each, :total' - end - - return unless inches[..2].all?(&:present?) - - cubic_ft = (inches[0] * inches[1] * inches[2]).to_f / 1728 - cubic_ft *= q - - format('%0.2f', cubic_ft).to_f - end - - def density - return unless inches[..2].all?(&:present?) && pounds(:each) - - density = pounds(:each).to_f / cubic_ft(:each) - format('%0.2f', density).to_f - end - - def calculated_freight_class - sanitized_freight_class(density_to_freight_class(density)) - end - - def declared_freight_class - @declared_freight_class || @options[:declared_freight_class] - end - - def freight_class - (declared_freight_class.presence || calculated_freight_class) - end - - def length(unit) - @dimensions[2].convert_to(unit).value.to_f - end - - def width(unit) - @dimensions[1].convert_to(unit).value.to_f - end - - def height(unit) - @dimensions[0].convert_to(unit).value.to_f - end - - def cylinder? - @cylinder - end - - def oversized? - @oversized - end - - def unpackaged? - @unpackaged - end - - alias_method :tube?, :cylinder? - - def gift? - @gift - end - - def hazmat? - @hazmat - end - - def ounces(options = {}) - weight(options).convert_to(:oz).value.to_f - end - alias_method :oz, :ounces - - def grams(options = {}) - weight(options).convert_to(:g).value.to_f - end - alias_method :g, :grams - - def pounds(args) - weight(*args).convert_to(:lb).value.to_f - end - alias_method :lb, :pounds - alias_method :lbs, :pounds - - def kilograms(options = {}) - weight(options).convert_to(:kg).value.to_f - end - alias_method :kg, :kilograms - alias_method :kgs, :kilograms - - def inches(measurement = nil) - @inches ||= @dimensions.map { |m| m.convert_to(:in).value.to_f } - measurement.nil? ? @inches : measure(measurement, @inches) - end - alias_method :in, :inches - - def centimetres(measurement = nil) - @centimetres ||= @dimensions.map { |m| m.convert_to(:cm).value.to_f } - measurement.nil? ? @centimetres : measure(measurement, @centimetres) - end - alias_method :cm, :centimetres - - def dim_weight - return if inches(:length).blank? || inches(:width).blank? || inches(:height).blank? || pounds(:each).blank? - - @dim_weight ||= (inches(:length).ceil * inches(:width).ceil * inches(:height).ceil).to_f / 139 - end - - def each_weight(options = {}) - weight(@each_weight, options) - end - - def total_weight(options = {}) - weight(@total_weight, options) - end - - private - - def attribute_from_metric_or_imperial(obj, klass, unit_system, metric_unit, imperial_unit) - if obj.is_a?(klass) - obj - else - klass.new(obj, (unit_system == :imperial ? imperial_unit : metric_unit)) - end - end - - def density_to_freight_class(density) - return unless density - return 400 if density < 1 - return 60 if density > 30 - - density_table = [ - [1, 2, 300], - [2, 4, 250], - [4, 6, 175], - [6, 8, 125], - [8, 10, 100], - [10, 12, 92.5], - [12, 15, 85], - [15, 22.5, 70], - [22.5, 30, 65], - [30, 35, 60], - ] - density_table.each do |density_row| - return density_row[2] if (density >= density_row[0]) && (density < density_row[1]) - end - end - - def sanitized_freight_class(freight_class) - return if freight_class.blank? - - if VALID_FREIGHT_CLASSES.include?(freight_class) - return freight_class.to_i == freight_class ? freight_class.to_i : freight_class - end - - nil - end - - def measure(measurement, ary) - case measurement - when Integer then ary[measurement] - when :x, :max, :length, :long then ary[2] - when :y, :mid, :width, :wide then ary[1] - when :z, :min, :height, :depth, :high, :deep then ary[0] - when :girth, :around, :circumference - cylinder? ? (Math::PI * (ary[0] + ary[1]) / 2) : (2 * ary[0]) + (2 * ary[1]) - when :volume then cylinder? ? (Math::PI * (ary[0] + ary[1]) / 4)**2 * ary[2] : measure(:box_volume, ary) - when :box_volume then ary[0] * ary[1] * ary[2] - end - end - - def process_dimensions - @dimensions = @dimensions.map do |l| - attribute_from_metric_or_imperial(l, Measured::Length, @dimensions_unit_system, :centimetres, :inches) - end.sort - # [1,2] => [1,1,2] - # [5] => [5,5,5] - # etc.. - 2.downto(@dimensions.length) do |_n| - @dimensions.unshift(@dimensions[0]) - end - end - - def weight(which_weight, options = {}) - weight = case which_weight - when :each then @each_weight - when :total then @total_weight - else - raise ArgumentError, 'which_weight must be one of :each, :total' - end - - case options[:type] - when nil, :actual - weight - when :volumetric, :dimensional - @volumetric_weight ||= begin - m = Measured::Weight.new((centimetres(:box_volume) / 6.0), :grams) - @weight_unit_system == :imperial ? m.convert_to(:oz) : m - end - when :billable - [weight, weight(weight, type: :volumetric)].max - end - end - end -end diff --git a/lib/freight_kit/package_item.rb b/lib/freight_kit/package_item.rb deleted file mode 100644 index 4c20c2ba..00000000 --- a/lib/freight_kit/package_item.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -module FreightKit # :nodoc: - class PackageItem - attr_reader :sku, :hs_code, :value, :name, :quantity, :options - - def initialize(name, grams_or_ounces, value, quantity, options = {}) - @name = name - - imperial = (options[:units] == :imperial) - - @unit_system = imperial ? :imperial : :metric - - @weight = grams_or_ounces - @weight = Measured::Weight.new( - grams_or_ounces, - (@unit_system == :imperial ? :oz : :g), - ) unless @weight.is_a?(Measured::Weight) - - @value = Package.cents_from(value) - @quantity = quantity > 0 ? quantity : 1 - - @sku = options[:sku] - @hs_code = options[:hs_code] - @options = options - end - - def weight(options = {}) - case options[:type] - when nil, :actual - @weight - when :volumetric, :dimensional - @volumetric_weight ||= begin - m = Measured::Weight.new((centimetres(:box_volume) / 6.0), :grams) - @unit_system == :imperial ? m.in_ounces : m - end - when :billable - [weight, weight(type: :volumetric)].max - end - end - alias_method :mass, :weight - - def ounces(options = {}) - weight(options).convert_to(:oz).value - end - alias_method :oz, :ounces - - def grams(options = {}) - weight(options).convert_to(:g).value - end - alias_method :g, :grams - - def pounds(options = {}) - weight(options).convert_to(:lb).value - end - alias_method :lb, :pounds - alias_method :lbs, :pounds - - def kilograms(options = {}) - weight(options).convert_to(:kg).value - end - alias_method :kg, :kilograms - alias_method :kgs, :kilograms - end -end diff --git a/lib/freight_kit/packaging.rb b/lib/freight_kit/packaging.rb deleted file mode 100644 index 5a505667..00000000 --- a/lib/freight_kit/packaging.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class Packaging - VALID_TYPES = %i[ - box - bundle - container - crate - cylinder - drum - luggage - pail - pallet - piece - roll - tote - truckload - tote - ].freeze - - PALLET_TYPES = %i[crate drum pallet tote].freeze - - attr_accessor :type - - # Packaging.new(:pallet) - def initialize(type, options = {}) - options.symbolize_keys! - @options = options - - unless VALID_TYPES.include?(type) - raise ArgumentError, "Package#new: `type` should be one of #{VALID_TYPES.join(", ")}" - end - - @type = type - end - - def box? - @box ||= BOX_TYPES.include?(@type) - end - - def pallet? - @pallet ||= PALLET_TYPES.include?(@type) - end - - def box_or_pallet_type - return :pallet if pallet? - - box? ? :box : nil - end - end -end diff --git a/lib/freight_kit/platform.rb b/lib/freight_kit/platform.rb deleted file mode 100644 index a077aa9d..00000000 --- a/lib/freight_kit/platform.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class Platform < Carrier - # Credentials should be a `Credential` or `Array` of `Credential` - def initialize(credentials, customer_location: nil, tariff: nil) - super - - # Use #superclass instead of using #ancestors to fetch the parent class which the carrier class is inheriting from - # (#ancestors returns an array including the parent class and all the modules that were included) - parent_class_name = self.class.superclass.name.demodulize.underscore - - conf_path = File - .join( - File.expand_path( - '../../../../configuration/platforms', - self.class.const_source_location(:REACTIVE_FREIGHT_PLATFORM).first, - ), - "#{parent_class_name}.yml", - ) - @conf = YAML.safe_load(File.read(conf_path), permitted_classes: [Symbol]) - - conf_path = File - .join( - File.expand_path( - '../../../../configuration/carriers', - self.class.const_source_location(:REACTIVE_FREIGHT_CARRIER).first, - ), - "#{self.class.to_s.demodulize.underscore}.yml", - ) - @conf = @conf.deep_merge(YAML.safe_load(File.read(conf_path), permitted_classes: [Symbol])) - - @rates_with_excessive_length_fees = @conf.dig(:attributes, :rates, :with_excessive_length_fees) - end - end -end diff --git a/lib/freight_kit/shipment_packer.rb b/lib/freight_kit/shipment_packer.rb deleted file mode 100644 index 6c789bb9..00000000 --- a/lib/freight_kit/shipment_packer.rb +++ /dev/null @@ -1,116 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class ShipmentPacker - class OverweightItem < StandardError - end - - EXCESS_PACKAGE_QUANTITY_THRESHOLD = 10_000 - class ExcessPackageQuantity < StandardError; end - - # items - array of hashes containing quantity, grams and price. - # ex. `[{:quantity => 2, :price => 1.0, :grams => 50}]` - # dimensions - `[5.0, 15.0, 30.0]` - # maximum_weight - maximum weight in grams - # currency - ISO currency code - - class << self - def pack(items, dimensions, maximum_weight, currency) - return [] if items.empty? - - packages = [] - items.map!(&:symbolize_keys) - - # Naive in that it assumes weight is equally distributed across all items - # Should raise early enough in most cases - validate_total_weight(items, maximum_weight) - items_to_pack = items.map(&:dup).sort_by! { |i| i[:grams].to_i } - - state = :package_empty - while state != :packing_finished - case state - when :package_empty - package_weight = 0 - package_value = 0 - state = :filling_package - when :filling_package - validate_package_quantity(packages.count) - - items_to_pack.each do |item| - quantity = determine_fillable_quantity_for_package(item, maximum_weight, package_weight) - package_weight += item_weight(quantity, item[:grams]) - package_value += item_value(quantity, item[:price]) - item[:quantity] = item[:quantity].to_i - quantity - end - - items_to_pack.reject! { |i| i[:quantity].to_i == 0 } - state = :package_full - when :package_full - packages << FreightKit::Package.new(package_weight, dimensions, value: package_value, currency:) - state = items_to_pack.any? ? :package_empty : :packing_finished - end - end - - packages - end - - private - - def validate_total_weight(items, maximum_weight) - total_weight = 0 - items.each do |item| - total_weight += item[:quantity].to_i * item[:grams].to_i - - if overweight_item?(item[:grams], maximum_weight) - message = <<~MESSAGE.squish - The item with weight of #{item[:grams]}g is heavier than the allowable package weight of - #{maximum_weight}g - MESSAGE - raise OverweightItem, message - end - - raise_excess_quantity_error if maybe_excess_package_quantity?(total_weight, maximum_weight) - end - end - - def validate_package_quantity(number_of_packages) - raise_excess_quantity_error if number_of_packages >= EXCESS_PACKAGE_QUANTITY_THRESHOLD - end - - def raise_excess_quantity_error - raise ExcessPackageQuantity, "Unable to pack more than #{EXCESS_PACKAGE_QUANTITY_THRESHOLD} packages" - end - - def overweight_item?(grams, maximum_weight) - grams.to_i > maximum_weight - end - - def maybe_excess_package_quantity?(total_weight, maximum_weight) - total_weight > (maximum_weight * EXCESS_PACKAGE_QUANTITY_THRESHOLD) - end - - def determine_fillable_quantity_for_package(item, maximum_weight, package_weight) - item_grams = item[:grams].to_i - item_quantity = item[:quantity].to_i - - if item_grams <= 0 - item_quantity - else - # Grab the max amount of this item we can fit into this package - # Or, if there are fewer than the max for this item, put - # what is left into this package - available_grams = (maximum_weight - package_weight).to_i - [available_grams / item_grams, item_quantity].min - end - end - - def item_weight(quantity, grams) - quantity * grams.to_i - end - - def item_value(quantity, price) - quantity * Package.cents_from(price) - end - end - end -end diff --git a/lib/freight_kit/tariff.rb b/lib/freight_kit/tariff.rb deleted file mode 100644 index 9c357130..00000000 --- a/lib/freight_kit/tariff.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -module FreightKit - class Tariff - attr_accessor :overlength_rules - - def initialize(options = {}) - options.symbolize_keys! - @options = options - - @options[:overlength_rules] = (@options[:overlength_rules].presence || []) - raise ArgumentError, 'overlength_rules must be an Array' unless @options[:overlength_rules].is_a?(Array) - - @options[:overlength_rules].each do |overlength_rule| - if !overlength_rule[:min_length].is_a?(Measured::Length) - raise ArgumentError, 'overlength_rule[:min_length] must be a Measured::Length' - elsif ![Measured::Length, NilClass].include?(overlength_rule[:max_length].class) - raise ArgumentError, 'overlength_rule[:max_length] must be one of Measured::Length, NilClass' - end - - unless overlength_rule[:fee_cents].is_a?(Integer) - raise ArgumentError, 'overlength_rule[:fee_cents] must be an Integer' - end - end - - @overlength_rules = @options[:overlength_rules] - end - end -end diff --git a/service_type_symbols.txt b/service_type_symbols.txt deleted file mode 100644 index fa856762..00000000 --- a/service_type_symbols.txt +++ /dev/null @@ -1,4 +0,0 @@ -:standard -:guaranteed_delivery -:guaranteed_delivery_am -:guaranteed_delivery_pm \ No newline at end of file diff --git a/shipment_event_symbols.txt b/shipment_event_symbols.txt deleted file mode 100644 index b5acea46..00000000 --- a/shipment_event_symbols.txt +++ /dev/null @@ -1,17 +0,0 @@ -:arrived_at_terminal -:delayed_due_to_weather -:delivered -:delivery_appointment_scheduled -:departed -:found -:located -:lost -:out_for_delivery -:pending_delivery_appointment -:picked_up -:pickup_driver_assigned -:pickup_information_received_by_carrier -:pickup_information_sent_to_carrier -:sailed -:trailer_closed -:trailer_unloaded diff --git a/sig/freight_kit.rbs b/sig/freight_kit.rbs new file mode 100644 index 00000000..c228ebcf --- /dev/null +++ b/sig/freight_kit.rbs @@ -0,0 +1,4 @@ +module FreightKit + VERSION: String + # See the writing guide of rbs: https://github.com/ruby/rbs#guides +end diff --git a/spec/freight_kit_spec.rb b/spec/freight_kit_spec.rb new file mode 100644 index 00000000..204f9d06 --- /dev/null +++ b/spec/freight_kit_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe(FreightKit) do + describe 'VERSION' do + it { expect(FreightKit::VERSION).to(be_present) } + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ef1847d6..c0c7ccc8 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,8 +1,15 @@ # frozen_string_literal: true -require 'bundler/setup' -Bundler.setup +require 'freight_kit' -require 'faker' +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = '.rspec_status' -require 'freight_kit' + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with(:rspec) do |c| + c.syntax = :expect + end +end From 66ce3e42515faabd5111f21a8eab7f268f68ec6d Mon Sep 17 00:00:00 2001 From: Brody Date: Sat, 23 Mar 2024 17:42:30 -0700 Subject: [PATCH 2/4] Remove Gemfile.lock --- .gitignore | 2 + Gemfile.lock | 114 --------------------------------------------------- 2 files changed, 2 insertions(+), 114 deletions(-) delete mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index 7a6f5dfe..193ed08f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ .rspec_status .ruby-version + +Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 6600f127..00000000 --- a/Gemfile.lock +++ /dev/null @@ -1,114 +0,0 @@ -PATH - remote: . - specs: - freight_kit (1.0.0.pre1) - activesupport (>= 4.2, < 7.1.4) - zeitwerk (>= 2.6.0, < 2.6.13) - -GEM - remote: https://rubygems.org/ - specs: - activesupport (7.1.3.2) - base64 - bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) - ast (2.4.2) - base64 (0.1.1) - bigdecimal (3.1.7) - concurrent-ruby (1.2.3) - connection_pool (2.4.1) - diff-lcs (1.5.1) - drb (2.2.1) - i18n (1.14.4) - concurrent-ruby (~> 1.0) - json (2.7.1) - language_server-protocol (3.17.0.3) - minitest (5.22.3) - mutex_m (0.2.0) - parallel (1.24.0) - parser (3.3.0.5) - ast (~> 2.4.1) - racc - racc (1.7.3) - rack (3.0.10) - rainbow (3.1.1) - rake (13.1.0) - regexp_parser (2.9.0) - rexml (3.2.6) - rspec (3.13.0) - rspec-core (~> 3.13.0) - rspec-expectations (~> 3.13.0) - rspec-mocks (~> 3.13.0) - rspec-core (3.13.0) - rspec-support (~> 3.13.0) - rspec-expectations (3.13.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-mocks (3.13.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.13.0) - rspec-support (3.13.1) - rubocop (1.56.4) - base64 (~> 0.1.1) - json (~> 2.3) - language_server-protocol (>= 3.17.0) - parallel (~> 1.10) - parser (>= 3.2.2.3) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.1, < 2.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.31.2) - parser (>= 3.3.0.4) - rubocop-capybara (2.20.0) - rubocop (~> 1.41) - rubocop-factory_bot (2.25.1) - rubocop (~> 1.41) - rubocop-graphql (1.4.0) - rubocop (>= 0.90, < 2) - rubocop-next (1.0.6) - rubocop (~> 1.56.0) - rubocop-graphql (~> 1.4.0) - rubocop-rails (~> 2.20.2) - rubocop-rake (~> 0.6.0) - rubocop-rspec (~> 2.23.2) - rubocop-shopify (~> 2.14.0) - rubocop-rails (2.20.2) - activesupport (>= 4.2.0) - rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) - rubocop-rake (0.6.0) - rubocop (~> 1.0) - rubocop-rspec (2.23.2) - rubocop (~> 1.33) - rubocop-capybara (~> 2.17) - rubocop-factory_bot (~> 2.22) - rubocop-shopify (2.14.0) - rubocop (~> 1.51) - ruby-progressbar (1.13.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) - zeitwerk (2.6.12) - -PLATFORMS - arm64-darwin-23 - ruby - -DEPENDENCIES - freight_kit! - rake (~> 13.0) - rspec (~> 3.0) - rubocop (~> 1.21) - rubocop-next (~> 1.0.3) - -BUNDLED WITH - 2.5.6 From ed2ec289c2cd0567b5ed64df629039ab99267e29 Mon Sep 17 00:00:00 2001 From: Brody Date: Sat, 23 Mar 2024 17:45:40 -0700 Subject: [PATCH 3/4] Read VERSION --- freight_kit.gemspec | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freight_kit.gemspec b/freight_kit.gemspec index 2de8e139..62d452fd 100644 --- a/freight_kit.gemspec +++ b/freight_kit.gemspec @@ -1,10 +1,8 @@ # frozen_string_literal: true -require_relative 'lib/freight_kit' - Gem::Specification.new do |spec| spec.name = 'freight_kit' - spec.version = FreightKit::VERSION + spec.version = File.read(File.expand_path('VERSION', __dir__)).strip.freeze spec.authors = ['Third Party Transportation Systems LLC'] spec.email = ['hello@next-tms.com'] From 0246efabf85a41194e7debf8e7f9d9542bb89142 Mon Sep 17 00:00:00 2001 From: Brody Date: Sat, 23 Mar 2024 17:47:13 -0700 Subject: [PATCH 4/4] Require Ruby >= 3.2.0 --- .github/workflows/main.yml | 2 -- freight_kit.gemspec | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c1aab0d2..1ca16324 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,8 +9,6 @@ jobs: matrix: ruby-version: - '3.2' - - '3.1' - - '3.0' steps: - uses: actions/checkout@v4 - name: Set up Ruby ${{ matrix.ruby-version }} diff --git a/freight_kit.gemspec b/freight_kit.gemspec index 62d452fd..a4380b97 100644 --- a/freight_kit.gemspec +++ b/freight_kit.gemspec @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| spec.summary = spec.description spec.homepage = 'https://github.com/next-tms/freight_kit' spec.license = 'MIT' - spec.required_ruby_version = '>= 3.0.0' + spec.required_ruby_version = '>= 3.2.0' spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage