deliveries 0.1.0

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