we_ship_client 1.0.0
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 +7 -0
- data/.env.example +7 -0
- data/.github/workflows/gem-push.yml +33 -0
- data/.github/workflows/specs.yml +35 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +118 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/we_ship_client/client.rb +33 -0
- data/lib/we_ship_client/entities/address.rb +19 -0
- data/lib/we_ship_client/entities/base.rb +23 -0
- data/lib/we_ship_client/entities/order.rb +20 -0
- data/lib/we_ship_client/entities/order_item.rb +16 -0
- data/lib/we_ship_client/entities/order_items.rb +14 -0
- data/lib/we_ship_client/entities/process_orders_request.rb +24 -0
- data/lib/we_ship_client/entities/responses/order_accepted.rb +18 -0
- data/lib/we_ship_client/entities/responses/order_rejected.rb +17 -0
- data/lib/we_ship_client/entities/responses/process_orders.rb +17 -0
- data/lib/we_ship_client/entities/responses/proof_of_delivery.rb +18 -0
- data/lib/we_ship_client/entities/responses/rejected_orders.rb +17 -0
- data/lib/we_ship_client/entities/responses/track_order.rb +32 -0
- data/lib/we_ship_client/entities/responses/track_response.rb +16 -0
- data/lib/we_ship_client/entities/responses/tracking_item.rb +18 -0
- data/lib/we_ship_client/entities/track_request.rb +21 -0
- data/lib/we_ship_client/entities/types.rb +15 -0
- data/lib/we_ship_client/entities.rb +17 -0
- data/lib/we_ship_client/exceptions.rb +17 -0
- data/lib/we_ship_client/interactors/get_tracking.rb +119 -0
- data/lib/we_ship_client/interactors/process_orders.rb +63 -0
- data/lib/we_ship_client/token_client.rb +36 -0
- data/lib/we_ship_client/transforms/tracking_item.rb +136 -0
- data/lib/we_ship_client/version.rb +5 -0
- data/lib/we_ship_client.rb +15 -0
- data/we_ship_client.gemspec +33 -0
- metadata +186 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/entities/base'
|
4
|
+
require 'we_ship_client/entities/responses/order_accepted'
|
5
|
+
require 'we_ship_client/entities/responses/rejected_orders'
|
6
|
+
|
7
|
+
module WeShipClient
|
8
|
+
module Entities
|
9
|
+
module Responses
|
10
|
+
# The response returned by `Interactors::ProcessOrders`.
|
11
|
+
class ProcessOrders < Base
|
12
|
+
attribute? :ordersHeldInGateway, Types::Strict::Array.of(OrderAccepted).optional
|
13
|
+
attribute? :rejectedorders, RejectedOrders.optional
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/entities/base'
|
4
|
+
|
5
|
+
module WeShipClient
|
6
|
+
module Entities
|
7
|
+
module Responses
|
8
|
+
# The proof of delivery data of a single order.
|
9
|
+
class ProofOfDelivery < Base
|
10
|
+
attribute :additional_information, Types::Strict::String.optional.default(nil)
|
11
|
+
attribute :delivery_date, Types::Strict::String.optional.default(nil)
|
12
|
+
attribute :signature, Types::Strict::String.optional.default(nil)
|
13
|
+
attribute :signer_name, Types::Strict::String.optional.default(nil)
|
14
|
+
attribute :visual, Types::Strict::String.optional.default(nil)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/entities/base'
|
4
|
+
require 'we_ship_client/entities/responses/order_rejected'
|
5
|
+
|
6
|
+
module WeShipClient
|
7
|
+
module Entities
|
8
|
+
module Responses
|
9
|
+
# Just a container for the rejected orders list.
|
10
|
+
# Note the singular "order" name that may be confusing.
|
11
|
+
class RejectedOrders < Base
|
12
|
+
attribute? :count, Types::Strict::Integer.optional
|
13
|
+
attribute :order, Types::Strict::Array.of(OrderRejected).optional
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/entities/base'
|
4
|
+
require 'we_ship_client/entities/responses/proof_of_delivery'
|
5
|
+
require 'we_ship_client/entities/responses/tracking_item'
|
6
|
+
|
7
|
+
module WeShipClient
|
8
|
+
module Entities
|
9
|
+
module Responses
|
10
|
+
# The details of a single order returned by `Interactors::GetTracking`.
|
11
|
+
class TrackOrder < Base
|
12
|
+
attribute :address1, Types::Strict::String
|
13
|
+
attribute :address2, Types::Strict::String.optional
|
14
|
+
attribute :carrier, Types::Strict::String.optional
|
15
|
+
attribute :carrier_tracking_num, Types::Strict::String.optional
|
16
|
+
attribute :city, Types::Strict::String
|
17
|
+
attribute :client_ref1, Types::Strict::String
|
18
|
+
attribute :customer_code, Types::Strict::String
|
19
|
+
attribute :estimated_delivery_date, Types::Strict::String.optional
|
20
|
+
attribute :fgw_order_id, Types::Coercible::Integer.optional
|
21
|
+
attribute :internal_tracking_num, Types::Strict::String.optional
|
22
|
+
attribute :last_tracking_update, Types::Strict::String.optional
|
23
|
+
attribute :name, Types::Strict::String
|
24
|
+
attribute :postal_code, Types::Strict::String
|
25
|
+
attribute? :proof_of_delivery, ProofOfDelivery.optional
|
26
|
+
attribute :state, Types::Strict::String
|
27
|
+
attribute :tracking_items, Types::Strict::Array.of(TrackingItem)
|
28
|
+
attribute :upload_date, Types::Strict::String
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/entities/base'
|
4
|
+
require 'we_ship_client/entities/responses/track_order'
|
5
|
+
|
6
|
+
module WeShipClient
|
7
|
+
module Entities
|
8
|
+
module Responses
|
9
|
+
# The response returned by `Interactors::GetTracking`.
|
10
|
+
class TrackResponse < Base
|
11
|
+
attribute? :page_num, Types::Strict::Integer
|
12
|
+
attribute :results, Types::Strict::Array.of(TrackOrder)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/entities/base'
|
4
|
+
|
5
|
+
module WeShipClient
|
6
|
+
module Entities
|
7
|
+
module Responses
|
8
|
+
# A single tracking item returned by `Interactors::GetTracking`.
|
9
|
+
class TrackingItem < Base
|
10
|
+
attribute :location, Types::Strict::String.optional
|
11
|
+
attribute :message, Types::Strict::String
|
12
|
+
attribute :status_type, Types::Strict::String
|
13
|
+
attribute :tracking_item_date, Types::Strict::String.optional.default(nil)
|
14
|
+
attribute :tracking_item_id, Types::Coercible::Integer.optional.default(nil)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/entities/base'
|
4
|
+
|
5
|
+
module WeShipClient
|
6
|
+
module Entities
|
7
|
+
# The request payload used by `Interactors::GetTracking`.
|
8
|
+
class TrackRequest < Base
|
9
|
+
BATCH_SIZE = 500
|
10
|
+
|
11
|
+
attribute? :order_id, Types::Strict::Array.of(Types::Strict::String)
|
12
|
+
attribute? :client_ref1, Types::Strict::Array.of(Types::Strict::String)
|
13
|
+
attribute? :carrier_tracking_num, Types::Strict::Array.of(Types::Strict::String)
|
14
|
+
attribute? :internal_tracking_num, Types::Strict::Array.of(Types::Strict::String)
|
15
|
+
attribute :customer_code, Types::Strict::Array.of(Types::Strict::String)
|
16
|
+
attribute? :status, Types::Strict::String.enum('M', 'I', 'D', 'R', 'X', 'P', 'N', 'C')
|
17
|
+
attribute :page_num, Types::Strict::Integer.default(0)
|
18
|
+
attribute :num_records, Types::Strict::Integer.default(BATCH_SIZE)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/entities/base'
|
4
|
+
require 'we_ship_client/entities/address'
|
5
|
+
require 'we_ship_client/entities/order_item'
|
6
|
+
require 'we_ship_client/entities/order_items'
|
7
|
+
require 'we_ship_client/entities/order'
|
8
|
+
require 'we_ship_client/entities/process_orders_request'
|
9
|
+
require 'we_ship_client/entities/track_request'
|
10
|
+
require 'we_ship_client/entities/responses/order_accepted'
|
11
|
+
require 'we_ship_client/entities/responses/order_rejected'
|
12
|
+
require 'we_ship_client/entities/responses/process_orders'
|
13
|
+
require 'we_ship_client/entities/responses/proof_of_delivery'
|
14
|
+
require 'we_ship_client/entities/responses/rejected_orders'
|
15
|
+
require 'we_ship_client/entities/responses/track_order'
|
16
|
+
require 'we_ship_client/entities/responses/track_response'
|
17
|
+
require 'we_ship_client/entities/responses/tracking_item'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module WeShipClient
|
4
|
+
module Exceptions
|
5
|
+
class BaseError < RuntimeError
|
6
|
+
end
|
7
|
+
|
8
|
+
class AuthenticationError < BaseError
|
9
|
+
end
|
10
|
+
|
11
|
+
class NotFoundError < BaseError
|
12
|
+
end
|
13
|
+
|
14
|
+
class ServerError < BaseError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/client'
|
4
|
+
require 'we_ship_client/exceptions'
|
5
|
+
require 'we_ship_client/entities'
|
6
|
+
require 'we_ship_client/transforms/tracking_item'
|
7
|
+
|
8
|
+
module WeShipClient
|
9
|
+
module Interactors
|
10
|
+
# Class to interact with the /track API endpoint.
|
11
|
+
class GetTracking
|
12
|
+
ERRONEOUS_EXCEPTION_MSGS = [
|
13
|
+
'RQA Mapping',
|
14
|
+
'OQA Mapping',
|
15
|
+
'SML Mapping',
|
16
|
+
'Out for Delivery'
|
17
|
+
].freeze
|
18
|
+
|
19
|
+
# @param auth_token [String] A token generated by TokenClient
|
20
|
+
# @param track_request [Entities::TrackRequest] The request payload
|
21
|
+
# @param timeout [Integer,nil] The request timeout
|
22
|
+
def initialize(auth_token:, track_request:, timeout: nil)
|
23
|
+
@auth_token = auth_token
|
24
|
+
@request = track_request
|
25
|
+
@timeout = timeout
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get the tracking response.
|
29
|
+
#
|
30
|
+
# @raise [Exceptions::AuthenticationError] If the status code is 401
|
31
|
+
# @raise [Exceptions::ServerError] If the response is blank
|
32
|
+
# @return [Entities::Responses::TrackResponse] The tracking response
|
33
|
+
def call
|
34
|
+
response = client.http_client.post(
|
35
|
+
"#{client.base_url}/track",
|
36
|
+
data: request.to_hash,
|
37
|
+
headers: { Authorization: "JWT #{auth_token}" }
|
38
|
+
)
|
39
|
+
handle_exception(response) unless response.status == 200
|
40
|
+
json_response = JSON.parse(response.body, symbolize_names: true)
|
41
|
+
handle_exception(response) if json_response[:results].nil?
|
42
|
+
filter_results(json_response)
|
43
|
+
modified_response(response_class.new(json_response))
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_accessor :auth_token, :request, :timeout
|
49
|
+
|
50
|
+
def handle_exception(response)
|
51
|
+
WeShipClient.logger.info(
|
52
|
+
"[WeShipClient::Interactors::GetTracking] [EXCEPTION] #{response}"
|
53
|
+
)
|
54
|
+
|
55
|
+
http_errors = {
|
56
|
+
401 => WeShipClient::Exceptions::AuthenticationError
|
57
|
+
}
|
58
|
+
|
59
|
+
raise http_errors[response.status], response.body if http_errors.key?(response.status)
|
60
|
+
|
61
|
+
raise WeShipClient::Exceptions::ServerError, response.body
|
62
|
+
end
|
63
|
+
|
64
|
+
def response_class
|
65
|
+
WeShipClient::Entities::Responses::TrackResponse
|
66
|
+
end
|
67
|
+
|
68
|
+
def client
|
69
|
+
@client ||= WeShipClient::Client.new(timeout: timeout)
|
70
|
+
end
|
71
|
+
|
72
|
+
def filter_results(response) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
73
|
+
# filters out tracking items with empty message
|
74
|
+
# filters out tracking items with empty tracking_item_date and tracking_item_id
|
75
|
+
response[:results].each do |track_order|
|
76
|
+
next unless track_order[:tracking_items]
|
77
|
+
|
78
|
+
track_order[:tracking_items] = track_order[:tracking_items].select do |item|
|
79
|
+
valid_tracking_item?(item)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def valid_tracking_item?(item)
|
85
|
+
# either date or id must be present
|
86
|
+
(item[:tracking_item_date].present? || item[:tracking_item_id].present?) &&
|
87
|
+
# message must be present
|
88
|
+
item[:message].present? &&
|
89
|
+
# does not have a erroneous exception message
|
90
|
+
!(item[:status_type] == 'X' && erroneous_exception_msg?(item[:message]))
|
91
|
+
end
|
92
|
+
|
93
|
+
def erroneous_exception_msg?(item_message)
|
94
|
+
ERRONEOUS_EXCEPTION_MSGS.any? do |msg|
|
95
|
+
item_message.downcase.start_with?(msg.downcase)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def modified_response(response)
|
100
|
+
response.results.each do |track_order|
|
101
|
+
track_order&.tracking_items&.map do |item|
|
102
|
+
next if item.tracking_item_date.nil? && item.tracking_item_id.nil?
|
103
|
+
|
104
|
+
tracking_item_transform_class.new.call(
|
105
|
+
tracking_item: item,
|
106
|
+
state: track_order.state
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
response
|
112
|
+
end
|
113
|
+
|
114
|
+
def tracking_item_transform_class
|
115
|
+
WeShipClient::Transforms::TrackingItem
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/client'
|
4
|
+
require 'we_ship_client/entities'
|
5
|
+
require 'we_ship_client/exceptions'
|
6
|
+
|
7
|
+
module WeShipClient
|
8
|
+
module Interactors
|
9
|
+
# Class to interact with the /process_orders API endpoint.
|
10
|
+
class ProcessOrders
|
11
|
+
# @param auth_token [String] A token generated by TokenClient
|
12
|
+
# @param process_orders_request [Entities::ProcessOrdersRequest] The request payload
|
13
|
+
def initialize(auth_token:, process_orders_request:)
|
14
|
+
@auth_token = auth_token
|
15
|
+
@process_orders_request = process_orders_request
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sends orders to be processed.
|
19
|
+
#
|
20
|
+
# @raise [Exceptions::AuthenticationError] If the status code is 401
|
21
|
+
# @raise [Exceptions::ServerError] If the request is not successful
|
22
|
+
# @return [Entities::Responses::ProcessOrders] The response
|
23
|
+
def call
|
24
|
+
json_response = client.http_client.post(
|
25
|
+
"#{client.base_url}/process_orders",
|
26
|
+
data: process_orders_request.to_h,
|
27
|
+
headers: {
|
28
|
+
Authorization: "JWT #{auth_token}"
|
29
|
+
}
|
30
|
+
)
|
31
|
+
|
32
|
+
handle_exception(json_response) unless json_response.status == 200
|
33
|
+
response_hash = JSON.parse(json_response.body, symbolize_names: true)
|
34
|
+
response_class.new(response_hash[:response])
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_accessor :auth_token, :process_orders_request
|
40
|
+
|
41
|
+
def handle_exception(response)
|
42
|
+
WeShipClient.logger.info(
|
43
|
+
"[WeShipClient::Interactors::ProcessOrders] [EXCEPTION] #{response}"
|
44
|
+
)
|
45
|
+
|
46
|
+
case response.status
|
47
|
+
when 401
|
48
|
+
raise WeShipClient::Exceptions::AuthenticationError, response.body
|
49
|
+
else
|
50
|
+
raise WeShipClient::Exceptions::ServerError, response.body
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def client
|
55
|
+
@client ||= WeShipClient::Client.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def response_class
|
59
|
+
WeShipClient::Entities::Responses::ProcessOrders
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/client'
|
4
|
+
require 'we_ship_client/exceptions'
|
5
|
+
|
6
|
+
module WeShipClient
|
7
|
+
class TokenClient < Client
|
8
|
+
# @raise [Exceptions::AuthenticationError] If status is 401
|
9
|
+
# @raise [Exceptions::ServerError] If status is not 200
|
10
|
+
# @return [String] The access token
|
11
|
+
def generate_access_token
|
12
|
+
response = http_client.post(
|
13
|
+
"#{base_url}/token",
|
14
|
+
data: {
|
15
|
+
username: ENV['WE_SHIP_USERNAME'],
|
16
|
+
password: ENV['WE_SHIP_PASSWORD']
|
17
|
+
}
|
18
|
+
)
|
19
|
+
|
20
|
+
raise_token_generation_exception(response) unless response.status == 200
|
21
|
+
|
22
|
+
JSON.parse(response.body)['access_token']
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def raise_token_generation_exception(response)
|
28
|
+
case response.status
|
29
|
+
when 401
|
30
|
+
raise WeShipClient::Exceptions::AuthenticationError, response.body
|
31
|
+
else
|
32
|
+
raise WeShipClient::Exceptions::ServerError, response.body
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/time_with_zone'
|
4
|
+
|
5
|
+
module WeShipClient
|
6
|
+
module Transforms
|
7
|
+
# A class that modifies/converts/removes some tracking items data to match our needs.
|
8
|
+
# If you want to use your custom logic, create another class and set it on
|
9
|
+
# `Interactors::GetTracking#tracking_item_transform_class`
|
10
|
+
class TrackingItem
|
11
|
+
# Following array of messages mean the status is a delivery exception regardless
|
12
|
+
# of what the actual status_type is received from weship
|
13
|
+
OVERRIDE_DELIVERED = [
|
14
|
+
'Delivered, Signed by 85',
|
15
|
+
'Delivered, Signed by no answer at door',
|
16
|
+
'Delivered, Signed by LOST'
|
17
|
+
].freeze
|
18
|
+
OVERRIDE_DELIVERY_EXCEPTION = ['48'].freeze
|
19
|
+
OVERRIDE_STATUS_TO_DELIVERY_EXCEPTION = ['No One Avail Sig Required'].freeze
|
20
|
+
OVERRIDE_STATUS_TO_RETURNED = [
|
21
|
+
'Delivered, Signed by RETURN',
|
22
|
+
'Delivered, Signed by 3 att',
|
23
|
+
'Delivered, Signed by RTS'
|
24
|
+
].freeze
|
25
|
+
OVERRIDE_STATUS_TO_MANIFEST = ['Electronically Transmitted'].freeze
|
26
|
+
DUPLICATE_DELIMITER = '----'
|
27
|
+
DELIVERY_ATTEMPTED_MESSAGE = 'Delivery attempt was made'
|
28
|
+
RETURNED_MESSAGE = 'Return to Sender'
|
29
|
+
UNDELIVERABLE_MESSAGE = 'undeliverable'
|
30
|
+
OUT_FOR_DELIVERY_MESSAGE = 'Out for Delivery'
|
31
|
+
|
32
|
+
ET_STATES = %w[CT DE FL GA IN ME MD MA MI NH NJ NY NC OH PA RI SC VT VA WV].freeze
|
33
|
+
CT_STATES = %w[AL AR IL IA KS KY LA MN MS MO NE ND OK SD TN TX WI].freeze
|
34
|
+
MT_STATES = %w[AZ CO ID MT NM UT WY].freeze
|
35
|
+
PT_STATES = %w[CA NV OR WA].freeze
|
36
|
+
|
37
|
+
def call(tracking_item:, state:) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
38
|
+
if should_override_delivered_to_exception?(tracking_item)
|
39
|
+
tracking_item.status_type.replace('X')
|
40
|
+
tracking_item.message.replace(DELIVERY_ATTEMPTED_MESSAGE)
|
41
|
+
elsif should_override_delivered_to_returned?(tracking_item)
|
42
|
+
tracking_item.status_type.replace('R')
|
43
|
+
tracking_item.message.replace(RETURNED_MESSAGE)
|
44
|
+
elsif should_override_delivery_exception_message?(tracking_item)
|
45
|
+
tracking_item.message.replace(DELIVERY_ATTEMPTED_MESSAGE)
|
46
|
+
elsif should_override_status_to_exception?(tracking_item)
|
47
|
+
tracking_item.status_type.replace('X')
|
48
|
+
elsif should_override_out_for_delivery_status?(tracking_item)
|
49
|
+
tracking_item.status_type.replace('I')
|
50
|
+
elsif should_override_exception_to_manifest?(tracking_item)
|
51
|
+
tracking_item.status_type.replace('M')
|
52
|
+
end
|
53
|
+
|
54
|
+
# if there is a extraneous driver related message text, override to simpler message
|
55
|
+
tracking_item.message.replace('With driver') if should_override_driver_details?(tracking_item)
|
56
|
+
if should_remove_delimeters?(tracking_item)
|
57
|
+
# remove duplicates from message
|
58
|
+
tracking_item.message.replace(tracking_item.message.split(DUPLICATE_DELIMITER)[0])
|
59
|
+
end
|
60
|
+
# weship datetime is local carrier time, so convert it to UTC
|
61
|
+
convert_date_to_utc(tracking_item, state)
|
62
|
+
end
|
63
|
+
|
64
|
+
def should_override_exception_to_manifest?(tracking_item)
|
65
|
+
tracking_item.status_type == 'X' &&
|
66
|
+
OVERRIDE_STATUS_TO_MANIFEST.any? do |message|
|
67
|
+
tracking_item.message.downcase.start_with?(message.downcase)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def should_override_delivered_to_exception?(tracking_item)
|
72
|
+
tracking_item.status_type == 'D' &&
|
73
|
+
[
|
74
|
+
OVERRIDE_DELIVERED.any? { |message| tracking_item.message.downcase.start_with?(message.downcase) },
|
75
|
+
tracking_item.message.downcase.include?(UNDELIVERABLE_MESSAGE.downcase)
|
76
|
+
].any?
|
77
|
+
end
|
78
|
+
|
79
|
+
def should_override_delivered_to_returned?(tracking_item)
|
80
|
+
tracking_item.status_type == 'D' &&
|
81
|
+
OVERRIDE_STATUS_TO_RETURNED.any? do |message|
|
82
|
+
tracking_item.message.downcase.start_with?(message.downcase)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def should_override_delivery_exception_message?(tracking_item)
|
87
|
+
tracking_item.status_type == 'X' &&
|
88
|
+
OVERRIDE_DELIVERY_EXCEPTION.any? do |message|
|
89
|
+
tracking_item.message.downcase.start_with?(message.downcase)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def should_override_status_to_exception?(tracking_item)
|
94
|
+
OVERRIDE_STATUS_TO_DELIVERY_EXCEPTION.any? do |message|
|
95
|
+
tracking_item.message.downcase.start_with?(message.downcase)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def should_remove_delimeters?(tracking_item)
|
100
|
+
tracking_item.message.index(DUPLICATE_DELIMITER)&.positive?
|
101
|
+
end
|
102
|
+
|
103
|
+
def should_override_out_for_delivery_status?(tracking_item)
|
104
|
+
tracking_item.status_type == 'X' &&
|
105
|
+
tracking_item.message.downcase.include?(OUT_FOR_DELIVERY_MESSAGE.downcase)
|
106
|
+
end
|
107
|
+
|
108
|
+
def should_override_driver_details?(tracking_item)
|
109
|
+
tracking_item.message.downcase.index('driver:')
|
110
|
+
end
|
111
|
+
|
112
|
+
def convert_date_to_utc(tracking_item, state)
|
113
|
+
date = tracking_item.tracking_item_date
|
114
|
+
if date.present?
|
115
|
+
timezone = ActiveSupport::TimeZone.new(us_timezone_from(state))
|
116
|
+
utc_date = timezone.local_to_utc(DateTime.parse(date)).to_s
|
117
|
+
tracking_item.tracking_item_date.replace(utc_date)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def us_timezone_from(state)
|
122
|
+
if ET_STATES.include?(state)
|
123
|
+
'Eastern Time (US & Canada)'
|
124
|
+
elsif CT_STATES.include?(state)
|
125
|
+
'Central Time (US & Canada)'
|
126
|
+
elsif MT_STATES.include?(state)
|
127
|
+
'Mountain Time (US & Canada)'
|
128
|
+
elsif PT_STATES.include?(state)
|
129
|
+
'Pacific Time (US & Canada)'
|
130
|
+
else # default to est
|
131
|
+
'Eastern Time (US & Canada)'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'we_ship_client/client'
|
4
|
+
require 'we_ship_client/entities'
|
5
|
+
require 'we_ship_client/exceptions'
|
6
|
+
require 'we_ship_client/token_client'
|
7
|
+
require 'we_ship_client/interactors/get_tracking'
|
8
|
+
require 'we_ship_client/interactors/process_orders'
|
9
|
+
require 'we_ship_client/transforms/tracking_item'
|
10
|
+
|
11
|
+
module WeShipClient
|
12
|
+
def self.logger
|
13
|
+
defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative 'lib/we_ship_client/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.required_ruby_version = '>= 2.6'
|
5
|
+
spec.name = 'we_ship_client'
|
6
|
+
spec.version = WeShipClient::VERSION
|
7
|
+
spec.authors = ['Juul Labs, Inc.']
|
8
|
+
spec.email = ['opensource@juul.com']
|
9
|
+
|
10
|
+
spec.summary = "API client for We Ship Express V2."
|
11
|
+
spec.description = spec.summary
|
12
|
+
spec.homepage = 'https://github.com/JuulLabs-OSS/we_ship_client'
|
13
|
+
|
14
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.bindir = 'exe'
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.add_dependency 'activesupport'
|
26
|
+
spec.add_dependency 'dry-struct', '>= 0.6', '< 1.5'
|
27
|
+
spec.add_dependency 'dry-types'
|
28
|
+
spec.add_dependency 'loogi_http', '~> 1.0'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'stub_env'
|
31
|
+
spec.add_development_dependency 'vcr'
|
32
|
+
spec.add_development_dependency 'webmock'
|
33
|
+
end
|