belpost 0.9.0 → 0.11.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 +4 -4
- data/CHANGELOG.md +26 -2
- data/README.md +40 -0
- data/belpost.gemspec +1 -1
- data/lib/belpost/api_paths.rb +3 -1
- data/lib/belpost/client.rb +92 -11
- data/lib/belpost/configuration.rb +3 -3
- data/lib/belpost/models/batch.rb +1 -1
- data/lib/belpost/models/batch_item.rb +22 -0
- data/lib/belpost/models/customs_declaration.rb +2 -3
- data/lib/belpost/models/parcel_builder.rb +5 -9
- data/lib/belpost/postal_delivery_types.rb +158 -0
- data/lib/belpost/validations/batch_item_schema.rb +163 -0
- data/lib/belpost/validations/batch_schema.rb +11 -17
- data/lib/belpost/validations/postcode_schema.rb +18 -0
- data/lib/belpost/version.rb +1 -1
- data/lib/belpost.rb +15 -4
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 43a87cdb950f04f9508bd0facb45918b68d17fc2442f6dd6ef9a0698e350950d
|
4
|
+
data.tar.gz: 24fe3c8cbde4d7ddc4e5cd5d21f03a6f9001c5820fdad7fcea0dcb06013c5909
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a90de8d13d830d5eb68b0fbbfa9374495f699cd66932b4c2d6e422348a494e6f9f4b46d30b3e8444f50e7d73fc28175cc1fe78036927b878a14c95108131dd1
|
7
|
+
data.tar.gz: c9bedc78bd654b6624ee3f20b7e292e78c4a0de11ed957763c367d7eadcd3ca719a65f4f2a805b2b1a8951d9afac621a05f97a5864cfc805d2161193d00f5e31
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,31 @@
|
|
1
|
-
|
1
|
+
## [0.11.0] - 2025-04-23
|
2
|
+
### Added
|
3
|
+
- Added support for finding addresses by postal code via `find_addresses_by_postcode` method
|
4
|
+
- Added new validation schema for postal codes
|
5
|
+
- Added comprehensive tests for the new endpoint
|
2
6
|
|
3
|
-
## [0.9.
|
7
|
+
## [0.9.3] - 2025-04-18
|
8
|
+
### Added
|
9
|
+
- A new `list_batches` method in the Client class that:
|
10
|
+
- Supports pagination with the page parameter
|
11
|
+
- Allows filtering by batch status (committed or uncommitted)
|
12
|
+
- Enables limiting results per page with the per_page parameter
|
13
|
+
- Provides search functionality by batch number
|
14
|
+
|
15
|
+
## [0.9.2] - 2025-04-17
|
16
|
+
### Added
|
17
|
+
- Added support for ecommerce-specific parameters:
|
18
|
+
- `negotiated_rate`
|
19
|
+
- `is_declared_value`
|
20
|
+
- `is_partial_receipt`
|
21
|
+
- `postal_items_in_ops`
|
22
|
+
|
23
|
+
## [0.9.1] - 2025-04-16
|
24
|
+
### Added
|
25
|
+
- New `PostalDeliveryTypes` module for centralized management of postal delivery types
|
26
|
+
- Tests for the `PostalDeliveryTypes` module
|
4
27
|
|
28
|
+
## [0.9.0] - 2025-04-15
|
5
29
|
### Changed
|
6
30
|
- Refactored API paths into a separate module `Belpost::ApiPaths` for better maintainability
|
7
31
|
- Moved all hardcoded API endpoints into constants in `api_paths.rb`
|
data/README.md
CHANGED
@@ -308,6 +308,14 @@ postcodes = client.search_postcode(
|
|
308
308
|
puts postcodes
|
309
309
|
```
|
310
310
|
|
311
|
+
### Finding addresses by postal code
|
312
|
+
|
313
|
+
```ruby
|
314
|
+
client = Belpost::Client.new
|
315
|
+
addresses = client.find_addresses_by_postcode("210001")
|
316
|
+
puts addresses
|
317
|
+
```
|
318
|
+
|
311
319
|
### Finding a batch by ID
|
312
320
|
|
313
321
|
```ruby
|
@@ -316,6 +324,38 @@ batch = client.find_batch_by_id(123)
|
|
316
324
|
puts batch
|
317
325
|
```
|
318
326
|
|
327
|
+
### Listing and searching for batches
|
328
|
+
|
329
|
+
```ruby
|
330
|
+
client = Belpost::Client.new
|
331
|
+
|
332
|
+
# Get all batches with default pagination
|
333
|
+
batches = client.list_batches
|
334
|
+
puts "Total batches: #{batches["total"]}"
|
335
|
+
|
336
|
+
# Get a specific page of results
|
337
|
+
batches = client.list_batches(page: 2)
|
338
|
+
|
339
|
+
# Filter by status (committed or uncommitted)
|
340
|
+
committed_batches = client.list_batches(status: "committed")
|
341
|
+
uncommitted_batches = client.list_batches(status: "uncommitted")
|
342
|
+
|
343
|
+
# Set the number of batches per page
|
344
|
+
batches = client.list_batches(per_page: 50)
|
345
|
+
|
346
|
+
# Search for a specific batch by number
|
347
|
+
batch_number = 12345
|
348
|
+
search_results = client.list_batches(search: batch_number)
|
349
|
+
|
350
|
+
# Combine multiple parameters
|
351
|
+
filtered_batches = client.list_batches(
|
352
|
+
page: 2,
|
353
|
+
status: "committed",
|
354
|
+
per_page: 25,
|
355
|
+
search: 12345
|
356
|
+
)
|
357
|
+
```
|
358
|
+
|
319
359
|
## Error handling
|
320
360
|
|
321
361
|
The client may throw the following exceptions:
|
data/belpost.gemspec
CHANGED
data/lib/belpost/api_paths.rb
CHANGED
@@ -11,6 +11,7 @@ module Belpost
|
|
11
11
|
BATCH_MAILING_DUPLICATE_FULL = "/api/v1/business/batch-mailing/list/:id/duplicate-full"
|
12
12
|
BATCH_MAILING_COMMIT = "/api/v1/business/batch-mailing/list/:id/commit"
|
13
13
|
BATCH_MAILING_GENERATE_BLANK = "/api/v1/business/batch-mailing/list/:id/generate-blank"
|
14
|
+
BATCH_MAILING_LIST_ITEM = "/api/v1/business/batch-mailing/list/:id/item"
|
14
15
|
|
15
16
|
# Postal deliveries paths
|
16
17
|
POSTAL_DELIVERIES = "/api/v1/business/postal-deliveries"
|
@@ -21,5 +22,6 @@ module Belpost
|
|
21
22
|
# Geo directory paths
|
22
23
|
GEO_DIRECTORY_SEARCH_ADDRESS = "/api/v1/business/geo-directory/search-address"
|
23
24
|
GEO_DIRECTORY_POSTCODE = "/api/v1/business/geo-directory/postcode"
|
25
|
+
GEO_DIRECTORY_ADDRESSES = "/api/v1/business/geo-directory/addresses"
|
24
26
|
end
|
25
|
-
end
|
27
|
+
end
|
data/lib/belpost/client.rb
CHANGED
@@ -3,9 +3,12 @@
|
|
3
3
|
require_relative "api_service"
|
4
4
|
require_relative "models/parcel"
|
5
5
|
require_relative "models/batch"
|
6
|
+
require_relative "models/batch_item"
|
6
7
|
require_relative "models/api_response"
|
7
8
|
require_relative "validations/address_schema"
|
8
9
|
require_relative "validations/batch_schema"
|
10
|
+
require_relative "validations/batch_item_schema"
|
11
|
+
require_relative "validations/postcode_schema"
|
9
12
|
require_relative "api_paths"
|
10
13
|
|
11
14
|
module Belpost
|
@@ -34,15 +37,33 @@ module Belpost
|
|
34
37
|
# @raise [Belpost::ApiError] If the API returns an error response.
|
35
38
|
def create_batch(batch_data)
|
36
39
|
validation_result = Validation::BatchSchema.new.call(batch_data)
|
37
|
-
unless validation_result.success?
|
38
|
-
raise ValidationError, "Invalid batch data: #{validation_result.errors.to_h}"
|
39
|
-
end
|
40
|
+
raise ValidationError, "Invalid batch data: #{validation_result.errors.to_h}" unless validation_result.success?
|
40
41
|
|
41
42
|
batch = Models::Batch.new(batch_data)
|
42
43
|
response = @api_service.post(ApiPaths::BATCH_MAILING_LIST, batch.to_h)
|
43
44
|
response.to_h
|
44
45
|
end
|
45
46
|
|
47
|
+
# Creates new items in a batch by sending a POST request to the API.
|
48
|
+
#
|
49
|
+
# @param batch_id [Integer] The ID of the batch where items should be added.
|
50
|
+
# @param items_data [Hash] The data for the batch items to create.
|
51
|
+
# @return [Hash] The parsed JSON response from the API.
|
52
|
+
# @raise [Belpost::InvalidRequestError] If the request data is invalid.
|
53
|
+
# @raise [Belpost::ApiError] If the API returns an error response.
|
54
|
+
def create_batch_items(batch_id, items_data)
|
55
|
+
raise ValidationError, "Batch ID must be provided" if batch_id.nil?
|
56
|
+
raise ValidationError, "Batch ID must be a positive integer" unless batch_id.is_a?(Integer) && batch_id.positive?
|
57
|
+
|
58
|
+
validation_result = Validation::BatchItemSchema.call(items_data)
|
59
|
+
raise ValidationError, "Invalid batch item data: #{validation_result.errors.to_h}" unless validation_result.success?
|
60
|
+
|
61
|
+
batch_item = Models::BatchItem.new(items_data)
|
62
|
+
path = ApiPaths::BATCH_MAILING_LIST_ITEM.gsub(':id', batch_id.to_s)
|
63
|
+
response = @api_service.post(path, batch_item.to_h)
|
64
|
+
response.to_h
|
65
|
+
end
|
66
|
+
|
46
67
|
# Creates a new postal parcel by sending a POST request to the API.
|
47
68
|
#
|
48
69
|
# @param parcel_data [Hash] The data for the postal parcel.
|
@@ -51,9 +72,7 @@ module Belpost
|
|
51
72
|
# @raise [Belpost::ApiError] If the API returns an error response.
|
52
73
|
def create_parcel(parcel_data)
|
53
74
|
validation_result = Validation::ParcelSchema.call(parcel_data)
|
54
|
-
unless validation_result.success?
|
55
|
-
raise ValidationError, "Invalid parcel data: #{validation_result.errors.to_h}"
|
56
|
-
end
|
75
|
+
raise ValidationError, "Invalid parcel data: #{validation_result.errors.to_h}" unless validation_result.success?
|
57
76
|
|
58
77
|
parcel = Models::Parcel.new(parcel_data)
|
59
78
|
response = @api_service.post(ApiPaths::POSTAL_DELIVERIES, parcel.to_h)
|
@@ -123,6 +142,20 @@ module Belpost
|
|
123
142
|
response.to_h
|
124
143
|
end
|
125
144
|
|
145
|
+
# Searches for addresses belonging to a specific postal code (postcode).
|
146
|
+
#
|
147
|
+
# @param postcode [String] The postal code to search for (6 digits)
|
148
|
+
# @return [Array<Hash>] An array of addresses belonging to the specified postal code
|
149
|
+
# @raise [Belpost::ValidationError] If the postcode is invalid
|
150
|
+
# @raise [Belpost::ApiError] If the API returns an error response
|
151
|
+
def find_addresses_by_postcode(postcode)
|
152
|
+
validation_result = Validation::PostcodeSchema.new.call(postcode: postcode)
|
153
|
+
raise ValidationError, "Invalid postcode: #{validation_result.errors.to_h}" unless validation_result.success?
|
154
|
+
|
155
|
+
response = @api_service.get(ApiPaths::GEO_DIRECTORY_ADDRESSES, { postcode: postcode })
|
156
|
+
response.to_h
|
157
|
+
end
|
158
|
+
|
126
159
|
# Searches for postal codes by city, street, and building number.
|
127
160
|
#
|
128
161
|
# @param city [String] The city name (required)
|
@@ -150,6 +183,54 @@ module Belpost
|
|
150
183
|
response.to_h
|
151
184
|
end
|
152
185
|
|
186
|
+
# Lists all batches with optional filtering.
|
187
|
+
#
|
188
|
+
# @param page [Integer] The page number for pagination (optional, default: 1, must be > 0)
|
189
|
+
# @param status [String] Filter by status: 'committed' or 'uncommitted' (optional)
|
190
|
+
# @param per_page [Integer] Number of items per page (optional)
|
191
|
+
# @param search [Integer] Search by batch number (optional)
|
192
|
+
# @return [Hash] The parsed JSON response containing batch list with pagination details
|
193
|
+
# @raise [Belpost::ValidationError] If parameters are invalid
|
194
|
+
# @raise [Belpost::ApiError] If the API returns an error response
|
195
|
+
def list_batches(page: 1, status: nil, per_page: nil, search: nil)
|
196
|
+
params = {}
|
197
|
+
|
198
|
+
# Validate page parameter
|
199
|
+
if page
|
200
|
+
raise ValidationError, "Page must be an integer" unless page.is_a?(Integer)
|
201
|
+
raise ValidationError, "Page must be greater than 0" unless page.positive?
|
202
|
+
|
203
|
+
params[:page] = page
|
204
|
+
end
|
205
|
+
|
206
|
+
# Validate status parameter
|
207
|
+
if status
|
208
|
+
unless %w[committed uncommitted].include?(status)
|
209
|
+
raise ValidationError, "Status must be 'committed' or 'uncommitted'"
|
210
|
+
end
|
211
|
+
|
212
|
+
params[:status] = status
|
213
|
+
end
|
214
|
+
|
215
|
+
# Validate per_page parameter
|
216
|
+
if per_page
|
217
|
+
raise ValidationError, "Per page must be an integer" unless per_page.is_a?(Integer)
|
218
|
+
raise ValidationError, "Per page must be positive" unless per_page.positive?
|
219
|
+
|
220
|
+
params[:perPage] = per_page
|
221
|
+
end
|
222
|
+
|
223
|
+
# Validate search parameter
|
224
|
+
if search
|
225
|
+
raise ValidationError, "Search must be an integer" unless search.is_a?(Integer)
|
226
|
+
|
227
|
+
params[:search] = search
|
228
|
+
end
|
229
|
+
|
230
|
+
response = @api_service.get(ApiPaths::BATCH_MAILING_LIST, params)
|
231
|
+
response.to_h
|
232
|
+
end
|
233
|
+
|
153
234
|
private
|
154
235
|
|
155
236
|
def format_address(address)
|
@@ -165,11 +246,11 @@ module Belpost
|
|
165
246
|
return building unless building
|
166
247
|
|
167
248
|
building.gsub(/\s+/, " ")
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
249
|
+
.gsub(/\s*корпус\s*(\d+)\s*/i, '/\1')
|
250
|
+
.gsub(/\s*корп\s*(\d+)\s*/i, '/\1')
|
251
|
+
.gsub(/\s*кор\s*(\d+)\s*/i, '/\1')
|
252
|
+
.gsub(/\s*к\s*(\d+)\s*/i, '/\1')
|
253
|
+
.strip
|
173
254
|
end
|
174
255
|
end
|
175
256
|
end
|
@@ -9,7 +9,7 @@ module Belpost
|
|
9
9
|
def initialize
|
10
10
|
@base_url = ENV.fetch("BELPOST_API_URL", "https://api.belpost.by")
|
11
11
|
@jwt_token = ENV.fetch("BELPOST_JWT_TOKEN", nil)
|
12
|
-
|
12
|
+
|
13
13
|
# Convert timeout to integer with a fallback to default
|
14
14
|
begin
|
15
15
|
@timeout = Integer(ENV.fetch("BELPOST_TIMEOUT", 10))
|
@@ -17,14 +17,14 @@ module Belpost
|
|
17
17
|
@timeout = 10
|
18
18
|
end
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
# Validates that all required configuration is present
|
22
22
|
# @raise [Belpost::ConfigurationError] If required configuration is missing
|
23
23
|
def validate!
|
24
24
|
raise ConfigurationError, "Base URL is required" if base_url.nil?
|
25
25
|
raise ConfigurationError, "JWT token is required" if jwt_token.nil?
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
# Returns a hash representation of the configuration
|
29
29
|
# @return [Hash] The configuration as a hash
|
30
30
|
def to_h
|
data/lib/belpost/models/batch.rb
CHANGED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
module Models
|
5
|
+
# Model class for batch item
|
6
|
+
class BatchItem
|
7
|
+
attr_reader :data
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
@data = data
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
data
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_json(*_args)
|
18
|
+
data.to_json
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -50,8 +50,7 @@ module Belpost
|
|
50
50
|
def valid?
|
51
51
|
return false if @category && !VALID_CATEGORIES.include?(@category)
|
52
52
|
return false if @category == "other" && @explanation.nil?
|
53
|
-
|
54
|
-
# Проверка наличия обязательных полей для коммерческих отправлений
|
53
|
+
|
55
54
|
if %w[merchandise sample returned_goods].include?(@category)
|
56
55
|
return false if @items.empty?
|
57
56
|
return false if @price.empty?
|
@@ -61,4 +60,4 @@ module Belpost
|
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
64
|
-
end
|
63
|
+
end
|
@@ -194,23 +194,19 @@ module Belpost
|
|
194
194
|
private
|
195
195
|
|
196
196
|
def validate!
|
197
|
-
# Проверка обязательных полей
|
198
197
|
raise ValidationError, "Weight is required" unless @data.dig(:parcel, :measures, :weight)
|
199
198
|
raise ValidationError, "Sender type is required" unless @data.dig(:sender, :type)
|
200
199
|
raise ValidationError, "Recipient type is required" unless @data.dig(:recipient, :type)
|
201
200
|
|
202
|
-
# Проверка логики
|
203
201
|
if @data.dig(:addons, :cash_on_delivery) && !@data.dig(:addons, :declared_value)
|
204
202
|
raise ValidationError, "Declared value is required when cash on delivery is set"
|
205
203
|
end
|
206
204
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
end
|
212
|
-
end
|
205
|
+
return unless @data.dig(:parcel, :arrival, :country) != "BY"
|
206
|
+
return unless @data[:cp72].nil? || @data[:cp72].empty?
|
207
|
+
|
208
|
+
raise ValidationError, "Customs declaration is required for international parcels"
|
213
209
|
end
|
214
210
|
end
|
215
211
|
end
|
216
|
-
end
|
212
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Belpost
|
4
|
+
# Module for managing postal delivery types in the Belpost system.
|
5
|
+
# Provides a centralized way to handle and validate different types of postal deliveries.
|
6
|
+
#
|
7
|
+
# @example Validating a postal delivery type
|
8
|
+
# Belpost::PostalDeliveryTypes.valid?("ordered_small_package") # => true
|
9
|
+
#
|
10
|
+
# @example Getting all available types
|
11
|
+
# Belpost::PostalDeliveryTypes.all # => [:ordered_small_package, :letter_declare_value, ...]
|
12
|
+
#
|
13
|
+
# @example Getting a description for a type
|
14
|
+
# Belpost::PostalDeliveryTypes.description("ordered_small_package") # => "Заказной мелкий пакет республиканский"
|
15
|
+
#
|
16
|
+
# @note All types are frozen to prevent modification at runtime
|
17
|
+
module PostalDeliveryTypes
|
18
|
+
# Hash containing all valid postal delivery types with their Russian descriptions
|
19
|
+
# @return [Hash<Symbol, String>] frozen hash of postal delivery types and their descriptions
|
20
|
+
TYPES = {
|
21
|
+
ordered_small_package: "Заказной мелкий пакет республиканский",
|
22
|
+
letter_declare_value: "Письмо с объявленной ценностью республиканское",
|
23
|
+
package: "Простая посылка республиканская (без ОЦ)",
|
24
|
+
ems: "Республиканское отправление EMS",
|
25
|
+
ordered_parcel_post: "Заказная бандероль республиканская",
|
26
|
+
ordered_letter: "Заказное письмо республиканское",
|
27
|
+
ordered_postcard: "Заказная почтовая карточка республиканская",
|
28
|
+
small_package_declare_value: "Мелкий пакет с объявленной ценностью республиканский",
|
29
|
+
package_declare_value: "Посылка с объявленной ценностью республиканская",
|
30
|
+
ecommerce_economical: "Отправление E-commerce Эконом",
|
31
|
+
ecommerce_standard: "Отправление E-commerce Стандарт",
|
32
|
+
ecommerce_elite: "Отправление E-commerce Элит",
|
33
|
+
ecommerce_express: "Отправление E-commerce Экспресс",
|
34
|
+
ecommerce_light: "Отправление E-commerce Лайт",
|
35
|
+
ecommerce_optima: "Отправление E-commerce Оптима"
|
36
|
+
}.freeze
|
37
|
+
|
38
|
+
# Validation rules for each postal delivery type
|
39
|
+
# @return [Hash<Symbol, Hash>] frozen hash of validation rules for each type
|
40
|
+
VALIDATION_RULES = {
|
41
|
+
ecommerce_economical: {
|
42
|
+
negotiated_rate: false,
|
43
|
+
declared_value: [true, false],
|
44
|
+
partial_receipt: false,
|
45
|
+
postal_items_in_ops: true
|
46
|
+
},
|
47
|
+
ecommerce_standard: {
|
48
|
+
negotiated_rate: false,
|
49
|
+
declared_value: [true, false],
|
50
|
+
partial_receipt: false,
|
51
|
+
postal_items_in_ops: [true, false]
|
52
|
+
},
|
53
|
+
ecommerce_elite: {
|
54
|
+
negotiated_rate: false,
|
55
|
+
declared_value: [true, false],
|
56
|
+
partial_receipt: false,
|
57
|
+
postal_items_in_ops: [true, false]
|
58
|
+
},
|
59
|
+
ecommerce_express: {
|
60
|
+
negotiated_rate: false,
|
61
|
+
declared_value: [true, false],
|
62
|
+
partial_receipt: false,
|
63
|
+
postal_items_in_ops: [true, false]
|
64
|
+
},
|
65
|
+
ecommerce_light: {
|
66
|
+
negotiated_rate: false,
|
67
|
+
declared_value: [true, false],
|
68
|
+
partial_receipt: false,
|
69
|
+
postal_items_in_ops: true
|
70
|
+
},
|
71
|
+
ecommerce_optima: {
|
72
|
+
negotiated_rate: false,
|
73
|
+
declared_value: [true, false],
|
74
|
+
partial_receipt: false,
|
75
|
+
postal_items_in_ops: [true, false]
|
76
|
+
}
|
77
|
+
}.freeze
|
78
|
+
|
79
|
+
# Checks if the given type is a valid postal delivery type
|
80
|
+
# @param type [String, Symbol, nil] the type to validate
|
81
|
+
# @return [Boolean] true if the type is valid, false otherwise
|
82
|
+
def self.valid?(type)
|
83
|
+
return false if type.nil?
|
84
|
+
|
85
|
+
TYPES.key?(type.to_sym)
|
86
|
+
rescue NoMethodError
|
87
|
+
false
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns all valid postal delivery types
|
91
|
+
# @return [Array<Symbol>] array of all valid postal delivery type symbols
|
92
|
+
def self.all
|
93
|
+
TYPES.keys
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the Russian description for a given postal delivery type
|
97
|
+
# @param type [String, Symbol] the type to get the description for
|
98
|
+
# @return [String, nil] the description if the type is valid, nil otherwise
|
99
|
+
def self.description(type)
|
100
|
+
TYPES[type.to_sym]
|
101
|
+
rescue NoMethodError
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns validation rules for a given postal delivery type
|
106
|
+
# @param type [String, Symbol] the type to get rules for
|
107
|
+
# @return [Hash, nil] the validation rules if the type has them, nil otherwise
|
108
|
+
def self.validation_rules(type)
|
109
|
+
VALIDATION_RULES[type.to_sym]
|
110
|
+
rescue NoMethodError
|
111
|
+
nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# Validates if the given parameters match the rules for the given type
|
115
|
+
# @param type [String, Symbol] the postal delivery type
|
116
|
+
# @param params [Hash] the parameters to validate
|
117
|
+
# @return [Array<String>] array of error messages, empty if validation passes
|
118
|
+
def self.validate_params(type, params)
|
119
|
+
rules = validation_rules(type)
|
120
|
+
return [] unless rules
|
121
|
+
|
122
|
+
errors = []
|
123
|
+
type_sym = type.to_sym
|
124
|
+
|
125
|
+
# Check negotiated_rate
|
126
|
+
if rules[:negotiated_rate] != params[:negotiated_rate]
|
127
|
+
errors << "negotiated_rate must be #{rules[:negotiated_rate]} for #{type_sym}"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Check declared_value
|
131
|
+
if rules[:declared_value] && !rules[:declared_value].include?(params[:is_declared_value])
|
132
|
+
errors << "is_declared_value must be one of #{rules[:declared_value]} for #{type_sym}"
|
133
|
+
end
|
134
|
+
|
135
|
+
# Check partial_receipt
|
136
|
+
if rules[:partial_receipt] != params[:is_partial_receipt]
|
137
|
+
errors << "is_partial_receipt must be #{rules[:partial_receipt]} for #{type_sym}"
|
138
|
+
end
|
139
|
+
|
140
|
+
# Check postal_items_in_ops
|
141
|
+
if rules[:postal_items_in_ops]
|
142
|
+
if rules[:postal_items_in_ops].is_a?(Array)
|
143
|
+
unless rules[:postal_items_in_ops].include?(params[:postal_items_in_ops])
|
144
|
+
errors << "postal_items_in_ops must be one of #{rules[:postal_items_in_ops]} for #{type_sym}"
|
145
|
+
end
|
146
|
+
elsif rules[:postal_items_in_ops] != params[:postal_items_in_ops]
|
147
|
+
errors << if rules[:postal_items_in_ops] == true
|
148
|
+
"postal_items_in_ops must be one of [true] for #{type_sym}"
|
149
|
+
else
|
150
|
+
"postal_items_in_ops must be #{rules[:postal_items_in_ops]} for #{type_sym}"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
errors
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-validation"
|
4
|
+
|
5
|
+
module Belpost
|
6
|
+
module Validation
|
7
|
+
# Schema for validating batch item data
|
8
|
+
class BatchItemSchema < Dry::Validation::Contract
|
9
|
+
params do
|
10
|
+
required(:items).array(:hash) do
|
11
|
+
optional(:recipient_id).maybe(:integer)
|
12
|
+
optional(:recipient_foreign_id).maybe(:string)
|
13
|
+
optional(:recipient_object).maybe(:hash) do
|
14
|
+
optional(:foreign_id).maybe(:string)
|
15
|
+
required(:type).filled(:string, included_in?: %w[legal individual individual_entrepreneur])
|
16
|
+
|
17
|
+
optional(:first_name).maybe(:string)
|
18
|
+
optional(:last_name).maybe(:string)
|
19
|
+
optional(:second_name).maybe(:string)
|
20
|
+
|
21
|
+
optional(:company_name).maybe(:string)
|
22
|
+
|
23
|
+
required(:address).hash do
|
24
|
+
required(:address_type).filled(:string, included_in?: %w[address on_demand subscriber_box])
|
25
|
+
required(:postcode).filled(:string)
|
26
|
+
optional(:ops_id).maybe(:string)
|
27
|
+
required(:country_code).filled(:string)
|
28
|
+
optional(:region).maybe(:string)
|
29
|
+
optional(:district).maybe(:string)
|
30
|
+
required(:city).filled(:string)
|
31
|
+
optional(:building).maybe(:string)
|
32
|
+
optional(:housing).maybe(:string)
|
33
|
+
optional(:apartment).maybe(:string)
|
34
|
+
optional(:cell_number).maybe(:string)
|
35
|
+
end
|
36
|
+
|
37
|
+
optional(:phone).maybe(:string)
|
38
|
+
end
|
39
|
+
|
40
|
+
optional(:s10code).maybe(:string)
|
41
|
+
optional(:notification_s10code).maybe(:string)
|
42
|
+
required(:notification).filled(:integer, included_in?: [0, 1, 2, 5, 7])
|
43
|
+
required(:category).filled(:integer, included_in?: [0, 1, 2])
|
44
|
+
required(:weight).filled(:integer, gt?: 0)
|
45
|
+
|
46
|
+
optional(:addons).maybe(:hash) do
|
47
|
+
optional(:declared_value).maybe(:float, gt?: 0)
|
48
|
+
optional(:cash_on_delivery).maybe(:float, gt?: 0)
|
49
|
+
optional(:careful_fragile).maybe(:bool)
|
50
|
+
optional(:hand_over_personally).maybe(:bool)
|
51
|
+
optional(:subpoena).maybe(:bool)
|
52
|
+
optional(:description).maybe(:bool)
|
53
|
+
optional(:bulky).maybe(:bool)
|
54
|
+
optional(:recipient_payment).maybe(:bool)
|
55
|
+
optional(:documents_return).maybe(:bool)
|
56
|
+
optional(:deliver_to_work).maybe(:bool)
|
57
|
+
optional(:open_upon_delivery).maybe(:bool)
|
58
|
+
optional(:time_of_delivery).maybe(:hash) do
|
59
|
+
required(:type).filled(:string, included_in?: %w[level1 level2 level3 level4])
|
60
|
+
optional(:time_interval).hash do
|
61
|
+
required(:from).filled(:string)
|
62
|
+
required(:to).filled(:string)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
optional(:free_return).maybe(:bool)
|
66
|
+
optional(:partial_return).maybe(:bool)
|
67
|
+
optional(:government).maybe(:bool)
|
68
|
+
optional(:military).maybe(:bool)
|
69
|
+
optional(:service).maybe(:bool)
|
70
|
+
optional(:shelf_life).maybe(:integer, included_in?: (10..30))
|
71
|
+
optional(:email).maybe(:string)
|
72
|
+
optional(:phone).maybe(:string)
|
73
|
+
optional(:coordinate_delivery_interval).maybe(:bool)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
rule('items[].recipient_object.first_name') do
|
79
|
+
if key? && value.is_a?(Array)
|
80
|
+
value.each_with_index do |item, idx|
|
81
|
+
if item[:recipient_object] && item[:recipient_object][:type] == 'individual' && item[:recipient_object][:first_name].nil?
|
82
|
+
key(:"items.#{idx}.recipient_object.first_name").failure('must be filled for individual type')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
rule('items[].recipient_object.last_name') do
|
89
|
+
if key? && value.is_a?(Array)
|
90
|
+
value.each_with_index do |item, idx|
|
91
|
+
if item[:recipient_object] && item[:recipient_object][:type] == 'individual' && item[:recipient_object][:last_name].nil?
|
92
|
+
key(:"items.#{idx}.recipient_object.last_name").failure('must be filled for individual type')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
rule('items[].recipient_object.company_name') do
|
99
|
+
if key? && value.is_a?(Array)
|
100
|
+
value.each_with_index do |item, idx|
|
101
|
+
if item[:recipient_object] &&
|
102
|
+
(item[:recipient_object][:type] == 'legal' || item[:recipient_object][:type] == 'individual_entrepreneur') &&
|
103
|
+
item[:recipient_object][:company_name].nil?
|
104
|
+
key(:"items.#{idx}.recipient_object.company_name").failure('must be filled for legal or individual_entrepreneur type')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
rule('items[].recipient_object.address.ops_id') do
|
111
|
+
if key? && value.is_a?(Array)
|
112
|
+
value.each_with_index do |item, idx|
|
113
|
+
if item[:recipient_object] &&
|
114
|
+
item[:recipient_object][:address] &&
|
115
|
+
item[:recipient_object][:address][:address_type] == 'address' &&
|
116
|
+
item[:recipient_object][:address][:ops_id].nil?
|
117
|
+
key(:"items.#{idx}.recipient_object.address.ops_id").failure('must be filled for address type')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
rule('items[].recipient_object.address.cell_number') do
|
124
|
+
if key? && value.is_a?(Array)
|
125
|
+
value.each_with_index do |item, idx|
|
126
|
+
if item[:recipient_object] &&
|
127
|
+
item[:recipient_object][:address] &&
|
128
|
+
item[:recipient_object][:address][:address_type] == 'subscriber_box' &&
|
129
|
+
item[:recipient_object][:address][:cell_number].nil?
|
130
|
+
key(:"items.#{idx}.recipient_object.address.cell_number").failure('must be filled for subscriber_box type')
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
rule('items[].recipient_object') do
|
137
|
+
if key? && value.is_a?(Array)
|
138
|
+
value.each_with_index do |item, idx|
|
139
|
+
if item[:recipient_id].nil? && item[:recipient_foreign_id].nil? && item[:recipient_object].nil?
|
140
|
+
key(:"items.#{idx}.recipient_object").failure('must be provided if recipient_id and recipient_foreign_id are null')
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Add validation to ensure e-commerce has the proper notifications
|
147
|
+
rule('items[].notification') do
|
148
|
+
if key? && value.is_a?(Array)
|
149
|
+
value.each_with_index do |item, idx|
|
150
|
+
if item[:notification] == 5 && (!item[:addons] || !item[:addons][:email])
|
151
|
+
key(:"items.#{idx}.addons.email").failure('email is required for electronic notification (5)')
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Add class method to make it callable directly
|
158
|
+
def self.call(params)
|
159
|
+
new.call(params)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -8,23 +8,7 @@ module Belpost
|
|
8
8
|
class BatchSchema < Dry::Validation::Contract
|
9
9
|
params do
|
10
10
|
required(:postal_delivery_type).filled(:str?).value(
|
11
|
-
included_in?:
|
12
|
-
ordered_small_package
|
13
|
-
letter_declare_value
|
14
|
-
package
|
15
|
-
ems
|
16
|
-
ordered_parcel_post
|
17
|
-
ordered_letter
|
18
|
-
ordered_postcard
|
19
|
-
small_package_declare_value
|
20
|
-
package_declare_value
|
21
|
-
ecommerce_economical
|
22
|
-
ecommerce_standard
|
23
|
-
ecommerce_elite
|
24
|
-
ecommerce_express
|
25
|
-
ecommerce_light
|
26
|
-
ecommerce_optima
|
27
|
-
]
|
11
|
+
included_in?: Belpost::PostalDeliveryTypes.all.map(&:to_s)
|
28
12
|
)
|
29
13
|
|
30
14
|
required(:direction).filled(:str?).value(
|
@@ -53,6 +37,16 @@ module Belpost
|
|
53
37
|
optional(:is_declared_value).maybe(:bool?)
|
54
38
|
optional(:is_partial_receipt).maybe(:bool?)
|
55
39
|
end
|
40
|
+
|
41
|
+
rule(:postal_delivery_type) do
|
42
|
+
if key? && value
|
43
|
+
errors = Belpost::PostalDeliveryTypes.validate_params(
|
44
|
+
value,
|
45
|
+
values
|
46
|
+
)
|
47
|
+
errors.each { |error| key.failure(error) }
|
48
|
+
end
|
49
|
+
end
|
56
50
|
end
|
57
51
|
end
|
58
52
|
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry-validation"
|
4
|
+
|
5
|
+
module Belpost
|
6
|
+
module Validation
|
7
|
+
# Schema for validating postcode parameters
|
8
|
+
class PostcodeSchema < Dry::Validation::Contract
|
9
|
+
params do
|
10
|
+
required(:postcode).filled(:string)
|
11
|
+
end
|
12
|
+
|
13
|
+
rule(:postcode) do
|
14
|
+
key.failure("must be 6 digits") unless value.match?(/^\d{6}$/)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/belpost/version.rb
CHANGED
data/lib/belpost.rb
CHANGED
@@ -1,17 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "dotenv/load"
|
4
|
-
|
5
3
|
require_relative "belpost/version"
|
6
4
|
require_relative "belpost/configuration"
|
7
|
-
require_relative "belpost/client"
|
8
5
|
require_relative "belpost/errors"
|
9
6
|
require_relative "belpost/retry"
|
10
|
-
require_relative "belpost/
|
7
|
+
require_relative "belpost/api_paths"
|
8
|
+
require_relative "belpost/api_service"
|
9
|
+
|
10
|
+
# Load models
|
11
11
|
require_relative "belpost/models/api_response"
|
12
|
+
require_relative "belpost/models/batch"
|
12
13
|
require_relative "belpost/models/customs_declaration"
|
14
|
+
require_relative "belpost/models/parcel"
|
13
15
|
require_relative "belpost/models/parcel_builder"
|
16
|
+
|
17
|
+
# Load types and validations
|
18
|
+
require_relative "belpost/postal_delivery_types"
|
19
|
+
require_relative "belpost/validations/batch_schema"
|
14
20
|
require_relative "belpost/validations/parcel_schema"
|
21
|
+
require_relative "belpost/validations/address_schema"
|
22
|
+
require_relative "belpost/validations/postcode_schema"
|
23
|
+
|
24
|
+
# Load client last as it depends on other files
|
25
|
+
require_relative "belpost/client"
|
15
26
|
|
16
27
|
# Module for working with Belpochta API.
|
17
28
|
# Provides an interface for configuring and interacting with the API.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: belpost
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- KuberLite
|
@@ -24,7 +24,7 @@ dependencies:
|
|
24
24
|
- !ruby/object:Gem::Version
|
25
25
|
version: '0'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
|
-
name: dry-
|
27
|
+
name: dry-schema
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
29
29
|
requirements:
|
30
30
|
- - "~>"
|
@@ -38,7 +38,7 @@ dependencies:
|
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '1.0'
|
40
40
|
- !ruby/object:Gem::Dependency
|
41
|
-
name: dry-
|
41
|
+
name: dry-validation
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
43
43
|
requirements:
|
44
44
|
- - "~>"
|
@@ -76,13 +76,17 @@ files:
|
|
76
76
|
- lib/belpost/errors.rb
|
77
77
|
- lib/belpost/models/api_response.rb
|
78
78
|
- lib/belpost/models/batch.rb
|
79
|
+
- lib/belpost/models/batch_item.rb
|
79
80
|
- lib/belpost/models/customs_declaration.rb
|
80
81
|
- lib/belpost/models/parcel.rb
|
81
82
|
- lib/belpost/models/parcel_builder.rb
|
83
|
+
- lib/belpost/postal_delivery_types.rb
|
82
84
|
- lib/belpost/retry.rb
|
83
85
|
- lib/belpost/validations/address_schema.rb
|
86
|
+
- lib/belpost/validations/batch_item_schema.rb
|
84
87
|
- lib/belpost/validations/batch_schema.rb
|
85
88
|
- lib/belpost/validations/parcel_schema.rb
|
89
|
+
- lib/belpost/validations/postcode_schema.rb
|
86
90
|
- lib/belpost/version.rb
|
87
91
|
homepage: https://github.com/KuberLite/belpost
|
88
92
|
licenses:
|