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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9573611d63a9e36e61ada02613b8e8764fb2a45125a17f5938f2521b0a80586d
4
- data.tar.gz: 1db61fe84045560aac6dc7e362aeb5698413851d23851c1b2601f84ca1ff6bc3
3
+ metadata.gz: 43a87cdb950f04f9508bd0facb45918b68d17fc2442f6dd6ef9a0698e350950d
4
+ data.tar.gz: 24fe3c8cbde4d7ddc4e5cd5d21f03a6f9001c5820fdad7fcea0dcb06013c5909
5
5
  SHA512:
6
- metadata.gz: 4ee356c1da9fad9db8b62a6d5bbe135c317b4c49cd818e856f8a81e70c8452dc98deeb03ffc59dd4d108efac8434c4ed1e9ce0ba5dcca41b23485f327255a26c
7
- data.tar.gz: 40826b94cf8e911fc7930c5dae67412f4688ccc68f43bed53d5d5317d3146c848fd0f95111e24706cca2a5907b76d54fadf3d53c93eaa6a91aef8dc11f7190f9
6
+ metadata.gz: 8a90de8d13d830d5eb68b0fbbfa9374495f699cd66932b4c2d6e422348a494e6f9f4b46d30b3e8444f50e7d73fc28175cc1fe78036927b878a14c95108131dd1
7
+ data.tar.gz: c9bedc78bd654b6624ee3f20b7e292e78c4a0de11ed957763c367d7eadcd3ca719a65f4f2a805b2b1a8951d9afac621a05f97a5864cfc805d2161193d00f5e31
data/CHANGELOG.md CHANGED
@@ -1,7 +1,31 @@
1
- # Changelog
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.0] - 2025-04-15
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
@@ -33,6 +33,6 @@ Gem::Specification.new do |spec|
33
33
  spec.require_paths = ["lib"]
34
34
 
35
35
  spec.add_dependency "dotenv"
36
- spec.add_dependency "dry-validation", "~> 1.0"
37
36
  spec.add_dependency "dry-schema", "~> 1.0"
37
+ spec.add_dependency "dry-validation", "~> 1.0"
38
38
  end
@@ -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
@@ -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
- .gsub(/\s*корпус\s*(\d+)\s*/i, '/\1')
169
- .gsub(/\s*корп\s*(\d+)\s*/i, '/\1')
170
- .gsub(/\s*кор\s*(\d+)\s*/i, '/\1')
171
- .gsub(/\s*к\s*(\d+)\s*/i, '/\1')
172
- .strip
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
@@ -39,4 +39,4 @@ module Belpost
39
39
  end
40
40
  end
41
41
  end
42
- end
42
+ end
@@ -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
- if @data.dig(:parcel, :arrival, :country) != "BY"
209
- if @data[:cp72].nil? || @data[:cp72].empty?
210
- raise ValidationError, "Customs declaration is required for international parcels"
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?: %w[
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Belpost
4
- VERSION = "0.9.0"
4
+ VERSION = "0.11.0"
5
5
  end
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/models/parcel"
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.9.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-validation
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-schema
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: