we_ship_client 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|