deliveries 0.1.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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +19 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +34 -0
  6. data/CHANGELOG.md +13 -0
  7. data/Gemfile +12 -0
  8. data/Gemfile.lock +127 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +207 -0
  11. data/Rakefile +12 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/deliveries.gemspec +36 -0
  15. data/lib/deliveries/address.rb +71 -0
  16. data/lib/deliveries/checkpoint.rb +12 -0
  17. data/lib/deliveries/collection_point.rb +45 -0
  18. data/lib/deliveries/courier.rb +69 -0
  19. data/lib/deliveries/couriers/correos_express/address.rb +31 -0
  20. data/lib/deliveries/couriers/correos_express/collection_points/search/format_response.rb +82 -0
  21. data/lib/deliveries/couriers/correos_express/collection_points/search.rb +56 -0
  22. data/lib/deliveries/couriers/correos_express/labels/generate.rb +68 -0
  23. data/lib/deliveries/couriers/correos_express/pickups/create/defaults.rb +60 -0
  24. data/lib/deliveries/couriers/correos_express/pickups/create/format_params.rb +95 -0
  25. data/lib/deliveries/couriers/correos_express/pickups/create.rb +66 -0
  26. data/lib/deliveries/couriers/correos_express/pickups/cutoff_time/format_params.rb +40 -0
  27. data/lib/deliveries/couriers/correos_express/pickups/cutoff_time.rb +61 -0
  28. data/lib/deliveries/couriers/correos_express/pickups/trace/correos.test.wsdl +163 -0
  29. data/lib/deliveries/couriers/correos_express/pickups/trace/correos.wsdl +163 -0
  30. data/lib/deliveries/couriers/correos_express/pickups/trace/format_response.rb +71 -0
  31. data/lib/deliveries/couriers/correos_express/pickups/trace.rb +49 -0
  32. data/lib/deliveries/couriers/correos_express/shipments/create/defaults.rb +80 -0
  33. data/lib/deliveries/couriers/correos_express/shipments/create/format_params.rb +99 -0
  34. data/lib/deliveries/couriers/correos_express/shipments/create.rb +101 -0
  35. data/lib/deliveries/couriers/correos_express/shipments/trace/format_response.rb +67 -0
  36. data/lib/deliveries/couriers/correos_express/shipments/trace.rb +65 -0
  37. data/lib/deliveries/couriers/correos_express.rb +162 -0
  38. data/lib/deliveries/couriers/dummy.rb +106 -0
  39. data/lib/deliveries/couriers/mondial_relay/address.rb +31 -0
  40. data/lib/deliveries/couriers/mondial_relay/collection_points/search/format_response.rb +103 -0
  41. data/lib/deliveries/couriers/mondial_relay/labels/generate.rb +40 -0
  42. data/lib/deliveries/couriers/mondial_relay/pickups/create/format_params.rb +57 -0
  43. data/lib/deliveries/couriers/mondial_relay/shipments/create/defaults.rb +96 -0
  44. data/lib/deliveries/couriers/mondial_relay/shipments/create/format_params.rb +68 -0
  45. data/lib/deliveries/couriers/mondial_relay/shipments/create.rb +36 -0
  46. data/lib/deliveries/couriers/mondial_relay/shipments/trace/format_response.rb +94 -0
  47. data/lib/deliveries/couriers/mondial_relay/shipments/trace.rb +47 -0
  48. data/lib/deliveries/couriers/mondial_relay/status_codes.rb +105 -0
  49. data/lib/deliveries/couriers/mondial_relay.rb +189 -0
  50. data/lib/deliveries/couriers/mondial_relay_dual/address.rb +31 -0
  51. data/lib/deliveries/couriers/mondial_relay_dual/pickups/create/format_params.rb +39 -0
  52. data/lib/deliveries/couriers/mondial_relay_dual/shipments/create/format_params.rb +101 -0
  53. data/lib/deliveries/couriers/mondial_relay_dual/shipments/create.rb +133 -0
  54. data/lib/deliveries/couriers/mondial_relay_dual.rb +105 -0
  55. data/lib/deliveries/couriers/spring/address.rb +23 -0
  56. data/lib/deliveries/couriers/spring/labels/generate.rb +33 -0
  57. data/lib/deliveries/couriers/spring/request.rb +43 -0
  58. data/lib/deliveries/couriers/spring/shipments/create/defaults.rb +73 -0
  59. data/lib/deliveries/couriers/spring/shipments/create/format_params.rb +77 -0
  60. data/lib/deliveries/couriers/spring/shipments/create.rb +45 -0
  61. data/lib/deliveries/couriers/spring/shipments/trace/format_response.rb +79 -0
  62. data/lib/deliveries/couriers/spring/shipments/trace.rb +29 -0
  63. data/lib/deliveries/couriers/spring.rb +84 -0
  64. data/lib/deliveries/couriers/ups/collection_points/search.rb +142 -0
  65. data/lib/deliveries/couriers/ups/json_request.rb +40 -0
  66. data/lib/deliveries/couriers/ups/labels/generate.rb +72 -0
  67. data/lib/deliveries/couriers/ups/shipments/create.rb +210 -0
  68. data/lib/deliveries/couriers/ups/shipments/trace.rb +79 -0
  69. data/lib/deliveries/couriers/ups.rb +122 -0
  70. data/lib/deliveries/couriers.rb +14 -0
  71. data/lib/deliveries/delivery.rb +20 -0
  72. data/lib/deliveries/errors.rb +22 -0
  73. data/lib/deliveries/label.rb +20 -0
  74. data/lib/deliveries/label_utils.rb +67 -0
  75. data/lib/deliveries/labels.rb +40 -0
  76. data/lib/deliveries/pickup.rb +15 -0
  77. data/lib/deliveries/shipment.rb +15 -0
  78. data/lib/deliveries/tracking_info.rb +29 -0
  79. data/lib/deliveries/version.rb +5 -0
  80. data/lib/deliveries.rb +63 -0
  81. metadata +240 -0
@@ -0,0 +1,210 @@
1
+ module Deliveries
2
+ module Couriers
3
+ module Ups
4
+ module Shipments
5
+ class Create
6
+ include JsonRequest
7
+ include LabelUtils
8
+
9
+ API_VERSION = 'v1807'.freeze
10
+ TYPES = %i[forward return].freeze
11
+
12
+ attr_accessor :shipper, :consignee, :parcels, :reference_code, :type, :collection_point, :language
13
+
14
+ def initialize(shipper:, consignee:, parcels:, reference_code:, type: :forward, collection_point: nil, language: nil)
15
+ raise ArgumentError, 'Invalid value for type' unless TYPES.include?(type)
16
+
17
+ self.shipper = shipper
18
+ self.consignee = consignee
19
+ self.parcels = parcels
20
+ self.reference_code = reference_code
21
+ self.collection_point = collection_point
22
+ self.type = type
23
+ self.language = language&.to_sym&.downcase || :en
24
+ end
25
+
26
+ def execute
27
+ request = {
28
+ ShipmentRequest: {
29
+ Request: {
30
+ RequestOption: 'nonvalidate'
31
+ },
32
+ Shipment: {
33
+ Description: Ups.config('default_product.description', default: 'International Goods'),
34
+
35
+ Shipper: shipper_address(with_number: true),
36
+
37
+ PaymentInformation: {
38
+ ShipmentCharge: {
39
+ Type: '01',
40
+ BillShipper: {
41
+ AccountNumber: account_number
42
+ }
43
+ }
44
+ },
45
+
46
+ ReferenceNumber: {
47
+ Code: 'ON',
48
+ Value: reference_code
49
+ },
50
+
51
+ Service: {
52
+ Code: '11',
53
+ Description: 'Standard'
54
+ },
55
+
56
+ Package: package_data,
57
+
58
+ LabelSpecification: {
59
+ LabelImageFormat: {
60
+ Code: 'GIF'
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+
67
+ if type == :return
68
+ request[:ShipmentRequest][:Shipment][:ReturnService] = { Code: '9' }
69
+ request[:ShipmentRequest][:Shipment][:ShipFrom] = consignee_address
70
+ request[:ShipmentRequest][:Shipment][:ShipTo] = shipper_address
71
+ else # forward
72
+ request[:ShipmentRequest][:Shipment][:ShipFrom] = shipper_address
73
+ request[:ShipmentRequest][:Shipment][:ShipTo] = consignee_address
74
+
75
+ if collection_point?
76
+ request[:ShipmentRequest][:Shipment][:ShipmentIndicationType] = {
77
+ Code: '01',
78
+ Description: 'DirectToRetail'
79
+ }
80
+ request[:ShipmentRequest][:Shipment][:AlternateDeliveryAddress] = collection_point_address
81
+ request[:ShipmentRequest][:Shipment][:ShipmentServiceOptions] = {
82
+ Notification: {
83
+ NotificationCode: '012',
84
+ EMail: {
85
+ EMailAddress: consignee.email
86
+ },
87
+ Locale: {
88
+ Language: locale_language,
89
+ Dialect: locale_dialect
90
+ }
91
+ }
92
+ }
93
+ end
94
+ end
95
+
96
+ response = call request
97
+
98
+ tracking_code = response.dig(:ShipmentResponse, :ShipmentResults, :ShipmentIdentificationNumber)
99
+ labels = [response.dig(:ShipmentResponse, :ShipmentResults, :PackageResults)].flatten.map do |p|
100
+ encoded_gif = p.dig(:ShippingLabel, :GraphicImage)
101
+ image2pdf Base64.decode64(encoded_gif).force_encoding('binary'), height: 4
102
+ end
103
+
104
+ {
105
+ tracking_code: tracking_code,
106
+ label: labels.one? ? labels.first : merge_pdfs(labels)
107
+ }
108
+ end
109
+
110
+ private
111
+
112
+ def api_endpoint
113
+ if Ups.live?
114
+ "https://onlinetools.ups.com/ship/#{API_VERSION}/shipments"
115
+ else
116
+ "https://wwwcie.ups.com/ship/#{API_VERSION}/shipments"
117
+ end
118
+ end
119
+
120
+ def collection_point?
121
+ !@collection_point.nil?
122
+ end
123
+
124
+ def account_number
125
+ if collection_point?
126
+ Ups.config(:point_account_number)
127
+ else
128
+ Ups.config(:home_account_number)
129
+ end
130
+ end
131
+
132
+ def locale_language
133
+ case language
134
+ when :es then 'SPA'
135
+ when :fr then 'FRA'
136
+ when :pt then 'POR'
137
+ when :it then 'ITA'
138
+ when :de then 'DEU'
139
+ when :pl then 'POL'
140
+ else 'ENG'
141
+ end
142
+ end
143
+
144
+ def locale_dialect
145
+ if language == :en
146
+ 'GB'
147
+ else
148
+ '97'
149
+ end
150
+ end
151
+
152
+ def package_data
153
+ parcels.times.map do
154
+ {
155
+ Description: Ups.config('default_product.description', default: 'International Goods'),
156
+ Packaging: {
157
+ Code: '02',
158
+ Description: 'Customer Supplied'
159
+ },
160
+ PackageWeight: {
161
+ UnitOfMeasurement: {
162
+ Code: 'KGS'
163
+ },
164
+ Weight: Ups.config('default_product.weight', default: '1')
165
+ }
166
+ }
167
+ end
168
+ end
169
+
170
+ def shipper_address(with_number: false)
171
+ address = format_address shipper
172
+ address[:ShipperNumber] = account_number if with_number
173
+
174
+ address
175
+ end
176
+
177
+ def consignee_address
178
+ format_address consignee
179
+ end
180
+
181
+ def collection_point_address
182
+ address = format_address collection_point
183
+ address[:UPSAccessPointID] = collection_point.point_id
184
+ address.delete :Phone
185
+ address.delete :EMailAddress
186
+
187
+ address
188
+ end
189
+
190
+ def format_address(address)
191
+ {
192
+ Name: I18n.transliterate(address.name)[0, 35],
193
+ AttentionName: I18n.transliterate(address.name)[0, 35],
194
+ Phone: {
195
+ Number: address.phone
196
+ },
197
+ EMailAddress: address.email,
198
+ Address: {
199
+ AddressLine: I18n.transliterate(address.street).scan(/.{1,35}/)[0, 3],
200
+ City: I18n.transliterate(address.city)[0, 30],
201
+ PostalCode: address.postcode.to_s[0, 9],
202
+ CountryCode: address.country.to_s.downcase[0, 2]
203
+ }
204
+ }
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,79 @@
1
+ module Deliveries
2
+ module Couriers
3
+ module Ups
4
+ module Shipments
5
+ class Trace
6
+ include JsonRequest
7
+
8
+ attr_accessor :tracking_code, :language
9
+
10
+ def initialize(tracking_code:, language: nil)
11
+ self.tracking_code = tracking_code
12
+ self.language = language
13
+ end
14
+
15
+ def execute
16
+ response = call nil, method: :get, url_params: { inquiry_number: tracking_code },
17
+ query_params: { locale: locale }
18
+
19
+ package = response.dig(:trackResponse, :shipment, 0, :package, 0, :activity)
20
+ raise Error, 'Cannot obtain package activity data' if package.blank?
21
+
22
+ activities = package.map do |activity|
23
+ {
24
+ status: status(activity.dig(:status, :type), activity.dig(:status, :code)),
25
+ location: activity.dig(:location, :address, :city),
26
+ tracked_at: DateTime.parse("#{activity[:date]}T#{activity[:time]}"),
27
+ description: activity.dig(:status, :description)
28
+ }
29
+ end
30
+
31
+ activities.sort_by { |activity| activity[:tracked_at] }
32
+ end
33
+
34
+ private
35
+
36
+ def api_endpoint
37
+ if Ups.live?
38
+ 'https://onlinetools.ups.com/track/v1/details/%<inquiry_number>s'
39
+ else
40
+ 'https://wwwcie.ups.com/track/v1/details/%<inquiry_number>s'
41
+ end
42
+ end
43
+
44
+ def locale
45
+ case language&.to_sym&.downcase
46
+ when :es then 'es_ES'
47
+ when :fr then 'fr_FR'
48
+ when :pt then 'pt_PT'
49
+ when :it then 'it_IT'
50
+ when :de then 'de_DE'
51
+ when :en then 'en_GB'
52
+ when :pl then 'pl_PL'
53
+ else 'en_US'
54
+ end
55
+ end
56
+
57
+ def status(type, code)
58
+ case type&.to_s&.upcase
59
+ when 'M'
60
+ :registered
61
+ when 'I', 'P', 'O'
62
+ :in_transit
63
+ when 'D'
64
+ if %w[2Q ZP].include?(code&.to_s&.upcase)
65
+ :in_collection_point
66
+ else
67
+ :delivered
68
+ end
69
+ when 'MV'
70
+ :canceled
71
+ else
72
+ :unknown_status
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,122 @@
1
+ require_relative 'ups/json_request'
2
+ require_relative 'ups/collection_points/search'
3
+ require_relative 'ups/shipments/create'
4
+ require_relative 'ups/shipments/trace'
5
+ require_relative 'ups/labels/generate'
6
+
7
+ module Deliveries
8
+ module Couriers
9
+ module Ups
10
+ extend Courier
11
+
12
+ COURIER_ID = :ups
13
+
14
+ Config = Struct.new(
15
+ :license_number,
16
+ :username,
17
+ :password,
18
+ :point_account_number,
19
+ :home_account_number,
20
+ :default_product
21
+ )
22
+
23
+ module_function
24
+
25
+ def get_collection_point(global_point_id:)
26
+ global_point = CollectionPoint.parse_global_point_id(global_point_id: global_point_id)
27
+ points = CollectionPoints::Search.new(
28
+ country: global_point.country,
29
+ point_id: global_point.point_id
30
+ ).execute
31
+
32
+ raise Error, 'No collection point found by that point ID' if points.empty?
33
+
34
+ CollectionPoint.new(**points.first)
35
+ end
36
+
37
+ def get_collection_points(country:, postcode:)
38
+ points = CollectionPoints::Search.new(
39
+ country: country,
40
+ postcode: postcode
41
+ ).execute
42
+
43
+ points.map do |arguments|
44
+ CollectionPoint.new(**arguments)
45
+ end
46
+ end
47
+
48
+ def create_shipment(sender:, receiver:, parcels:, reference_code:, collection_point:, shipment_date: nil, language: nil, **)
49
+ tracking_code, label = Shipments::Create.new(
50
+ shipper: sender,
51
+ consignee: receiver,
52
+ parcels: parcels,
53
+ reference_code: reference_code,
54
+ type: :forward,
55
+ collection_point: collection_point,
56
+ language: language
57
+ ).execute.values_at(:tracking_code, :label)
58
+
59
+ Deliveries::Shipment.new(
60
+ courier_id: COURIER_ID,
61
+ sender: sender,
62
+ receiver: receiver,
63
+ parcels: parcels,
64
+ reference_code: reference_code,
65
+ tracking_code: tracking_code,
66
+ shipment_date: shipment_date,
67
+ label: Label.new(raw: label)
68
+ )
69
+ end
70
+
71
+ def create_pickup(sender:, receiver:, parcels:, reference_code:, pickup_date: nil, language: nil, **)
72
+ tracking_code, label = Shipments::Create.new(
73
+ shipper: receiver,
74
+ consignee: sender,
75
+ parcels: parcels,
76
+ reference_code: reference_code,
77
+ type: :return,
78
+ language: language
79
+ ).execute.values_at(:tracking_code, :label)
80
+
81
+ Deliveries::Pickup.new(
82
+ courier_id: COURIER_ID,
83
+ sender: sender,
84
+ receiver: receiver,
85
+ parcels: parcels,
86
+ reference_code: reference_code,
87
+ tracking_code: tracking_code,
88
+ pickup_date: pickup_date,
89
+ label: Label.new(raw: label)
90
+ )
91
+ end
92
+
93
+ def get_label(tracking_code:, **)
94
+ arguments = Labels::Generate.new(tracking_code: tracking_code).execute
95
+ Label.new(**arguments)
96
+ end
97
+
98
+ def get_labels(tracking_codes:, **)
99
+ labels = Deliveries::Labels.new
100
+
101
+ tracking_codes.each do |tracking_code|
102
+ labels << get_label(tracking_code: tracking_code)
103
+ end
104
+
105
+ labels
106
+ end
107
+
108
+ def shipment_info(tracking_code:, language: nil)
109
+ checkpoints = Shipments::Trace.new(tracking_code: tracking_code, language: language).execute.map do |arguments|
110
+ Checkpoint.new(**arguments)
111
+ end
112
+
113
+ TrackingInfo.new(courier_id: COURIER_ID, tracking_code: tracking_code, checkpoints: checkpoints,
114
+ status: checkpoints.last&.status)
115
+ end
116
+
117
+ def pickup_info(tracking_code:, language: nil)
118
+ shipment_info(tracking_code: tracking_code, language: language)
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,14 @@
1
+ module Deliveries
2
+ module Couriers
3
+ Dir["#{File.dirname(__FILE__)}/couriers/*.rb"].each do |f|
4
+ # Get camelized class name
5
+ filename = File.basename(f, '.rb')
6
+
7
+ # Camelize the string to get the class name
8
+ courier_class = filename.split('_').map(&:capitalize).join.to_sym
9
+
10
+ # Register for autoloading
11
+ autoload courier_class, f
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ module Deliveries
2
+ class Delivery
3
+ ATTRIBUTES = %i[courier_id sender receiver parcels reference_code tracking_code label].freeze
4
+ attr_accessor(*ATTRIBUTES)
5
+
6
+ def initialize(**attributes)
7
+ self.courier_id = attributes[:courier_id]
8
+ self.sender = attributes[:sender]
9
+ self.receiver = attributes[:receiver]
10
+ self.parcels = attributes[:parcels]
11
+ self.reference_code = attributes[:reference_code]
12
+ self.tracking_code = attributes[:tracking_code]
13
+ self.label = attributes[:label]
14
+ end
15
+
16
+ def attributes
17
+ ATTRIBUTES.map { |attr| { attr => send(attr) } }.inject(&:merge)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Deliveries
2
+ # Generic Deliveries exception class.
3
+ class Error < StandardError; end
4
+
5
+ # For errors returned by the client library, like HTTP errors
6
+ class ClientError < Error; end
7
+
8
+ # For errors returned by the API
9
+ class APIError < Error
10
+ attr_reader :code
11
+
12
+ def initialize(message = nil, code = nil)
13
+ @code = code
14
+ super(message)
15
+ end
16
+ end
17
+
18
+ # Custom API errors.
19
+ class InvalidDateError < APIError; end
20
+
21
+ class InvalidTimeIntervalError < APIError; end
22
+ end
@@ -0,0 +1,20 @@
1
+ module Deliveries
2
+ class Label
3
+ attr_reader :url
4
+
5
+ def initialize(raw: nil, url: nil)
6
+ raise ArgumentError, 'Both raw and url cannot be nil' if raw.nil? && url.nil?
7
+
8
+ @raw = raw
9
+ @url = url
10
+ end
11
+
12
+ def raw
13
+ if @raw
14
+ @raw
15
+ elsif @url
16
+ @raw = URI.parse(@url).read.force_encoding('binary')
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,67 @@
1
+ require 'mini_magick'
2
+ require 'hexapdf'
3
+
4
+ module Deliveries
5
+ module LabelUtils
6
+ module_function
7
+
8
+ def image2pdf(bindata, width: nil, height: nil)
9
+ Tempfile.create do |f|
10
+ f.binmode
11
+
12
+ f << bindata
13
+
14
+ image = MiniMagick::Image.open(f.path)
15
+ image.format 'png' unless image.data['format']&.casecmp?('png')
16
+
17
+ doc = HexaPDF::Document.new
18
+ doc_image = doc.images.add(image.path)
19
+ iw = doc_image.info.width.to_f
20
+ ih = doc_image.info.height.to_f
21
+ media_box =
22
+ if width.nil? && height.nil?
23
+ [0, 0, iw, ih]
24
+ elsif width.nil?
25
+ width = (height * iw) / ih
26
+ [0, 0, width * 72, height * 72]
27
+ elsif height.nil?
28
+ height = (width * ih) / iw
29
+ [0, 0, width * 72, height * 72]
30
+ else
31
+ [0, 0, iw, ih]
32
+ end
33
+
34
+ if (ih > iw) != (media_box[3] > media_box[2]) && (iw > media_box[2] || ih > media_box[3])
35
+ media_box[2], media_box[3] = media_box[3], media_box[2]
36
+ end
37
+
38
+ page = doc.pages.add(media_box)
39
+ pw = page.box(:media).width.to_f
40
+ ph = page.box(:media).height.to_f
41
+ ratio = [pw / iw, ph / ih].min
42
+ iw *= ratio
43
+ ih *= ratio
44
+ x = (pw - iw) / 2
45
+ y = (ph - ih) / 2
46
+ page.canvas.image(doc_image, at: [x, y], width: iw, height: ih)
47
+
48
+ output = StringIO.new
49
+ doc.write(output)
50
+ output.string.force_encoding('binary')
51
+ end
52
+ end
53
+
54
+ def merge_pdfs(*pdfs)
55
+ doc = HexaPDF::Document.new
56
+ pdfs.flatten.each do |pdf|
57
+ HexaPDF::Document.new(io: StringIO.new(pdf)).pages.each do |page|
58
+ doc.pages << doc.import(page)
59
+ end
60
+ end
61
+
62
+ output = StringIO.new
63
+ doc.write(output)
64
+ output.string.force_encoding('binary')
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ module Deliveries
2
+ class Labels
3
+ include LabelUtils
4
+
5
+ attr_reader :url
6
+
7
+ def initialize(raw: nil, url: nil)
8
+ @raw = raw
9
+ @url = url
10
+ @labels = []
11
+ end
12
+
13
+ def raw
14
+ if @raw
15
+ @raw
16
+ elsif @url
17
+ @raw = URI.parse(@url).read.force_encoding('binary')
18
+ elsif !@labels.empty?
19
+ merge_pdfs @labels.map(&:raw)
20
+ end
21
+ end
22
+
23
+ def <<(label)
24
+ raise 'Cannot add labels when raw or url are already set' unless @raw.nil? && @url.nil?
25
+
26
+ case label
27
+ when Label
28
+ @labels << label
29
+ when %r{\Ahttps?://}
30
+ @labels << Label.new(url: label)
31
+ when String
32
+ @labels << Label.new(raw: label)
33
+ else
34
+ raise "Cannot cast #{label.class.name} to Label"
35
+ end
36
+
37
+ self
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ module Deliveries
2
+ class Pickup < Delivery
3
+ attr_accessor :pickup_date
4
+
5
+ def initialize(delivery: nil, **attributes)
6
+ if delivery.is_a? Deliveries::Delivery
7
+ super(**delivery.attributes)
8
+ else
9
+ super(**attributes)
10
+ end
11
+
12
+ self.pickup_date = attributes[:pickup_date]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Deliveries
2
+ class Shipment < Delivery
3
+ attr_accessor :shipment_date
4
+
5
+ def initialize(delivery: nil, **attributes)
6
+ if delivery.is_a? Deliveries::Delivery
7
+ super(**delivery.attributes)
8
+ else
9
+ super(**attributes)
10
+ end
11
+
12
+ self.shipment_date = attributes[:shipment_date]
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,29 @@
1
+ module Deliveries
2
+ class TrackingInfo
3
+ attr_accessor :courier_id, :tracking_code, :status, :checkpoints, :url
4
+
5
+ def initialize(courier_id:, tracking_code:, status: nil, checkpoints: nil, url: nil)
6
+ self.courier_id = courier_id
7
+ self.tracking_code = tracking_code
8
+ self.status = status
9
+ self.checkpoints = checkpoints
10
+ self.url = url
11
+ end
12
+
13
+ def registered?
14
+ status == :registered
15
+ end
16
+
17
+ def in_transit?
18
+ status == :in_transit
19
+ end
20
+
21
+ def in_collection_point?
22
+ status == :in_collection_point
23
+ end
24
+
25
+ def delivered?
26
+ status == :delivered
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Deliveries
4
+ VERSION = '0.1.0'
5
+ end