belpost 0.7.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: 5555f3f61fcb03030a1e5fd226abac810104277326db2ee42b505656ff0f3ce8
4
- data.tar.gz: 585c4832344b4cfa9a654e4605926bacd605bf854e77e21391910de03d3d22db
3
+ metadata.gz: 43a87cdb950f04f9508bd0facb45918b68d17fc2442f6dd6ef9a0698e350950d
4
+ data.tar.gz: 24fe3c8cbde4d7ddc4e5cd5d21f03a6f9001c5820fdad7fcea0dcb06013c5909
5
5
  SHA512:
6
- metadata.gz: eaf547611c62aa03df55771a57d4405ea1352b04cf9fe3fddd04f1120b3df24a100a38e4f449e7dcadffa1a7b1bd58101212425d263d7e168b448712af597d5c
7
- data.tar.gz: 4eb26318bd0e28467e50a31caa184880fab8276e331efcd7240723124ebf3795615d47eca35516f7f0d5048dbf3bb44663bb25182815d807fd4aef9d59313899
6
+ metadata.gz: 8a90de8d13d830d5eb68b0fbbfa9374495f699cd66932b4c2d6e422348a494e6f9f4b46d30b3e8444f50e7d73fc28175cc1fe78036927b878a14c95108131dd1
7
+ data.tar.gz: c9bedc78bd654b6624ee3f20b7e292e78c4a0de11ed957763c367d7eadcd3ca719a65f4f2a805b2b1a8951d9afac621a05f97a5864cfc805d2161193d00f5e31
data/CHANGELOG.md CHANGED
@@ -1,4 +1,52 @@
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
6
+
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
27
+
28
+ ## [0.9.0] - 2025-04-15
29
+ ### Changed
30
+ - Refactored API paths into a separate module `Belpost::ApiPaths` for better maintainability
31
+ - Moved all hardcoded API endpoints into constants in `api_paths.rb`
32
+ - Updated all client methods to use the new API path constants
33
+ - Updated tests to use the new API path constants
34
+
35
+ ### Added
36
+ - Added batch retrieval functionality via `find_batch_by_id` method
37
+ - Support for finding batch mailings by their ID with validation
38
+ - New module `Belpost::ApiPaths` with organized API endpoint constants:
39
+ - Batch mailing paths
40
+ - Postal deliveries paths
41
+ - Geo directory paths
42
+
43
+ ## [0.8.0] - 2025-04-14
44
+ ### Added
45
+ - Added batch mailing functionality via `create_batch` method
46
+ - Support for creating batch mailings with various delivery types
47
+ - Added comprehensive validation for batch mailing data
48
+ - Added new models and schemas for batch mailing
49
+ - Added tests for batch mailing functionality
2
50
 
3
51
  ## [0.7.0] - 2025-04-01
4
52
  ### Added
data/README.md CHANGED
@@ -308,6 +308,54 @@ 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
+
319
+ ### Finding a batch by ID
320
+
321
+ ```ruby
322
+ client = Belpost::Client.new
323
+ batch = client.find_batch_by_id(123)
324
+ puts batch
325
+ ```
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
+
311
359
  ## Error handling
312
360
 
313
361
  The client may throw the following exceptions:
data/belpost.gemspec CHANGED
@@ -33,5 +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-schema", "~> 1.0"
36
37
  spec.add_dependency "dry-validation", "~> 1.0"
37
38
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Belpost
4
+ # Constants for API paths
5
+ module ApiPaths
6
+ # Batch mailing paths
7
+ BATCH_MAILING_LIST = "/api/v1/business/batch-mailing/list"
8
+ BATCH_MAILING_DOCUMENTS = "/api/v1/business/batch-mailing/documents"
9
+ BATCH_MAILING_ITEM = "/api/v1/business/batch-mailing/item"
10
+ BATCH_MAILING_DUPLICATE = "/api/v1/business/batch-mailing/list/:id/duplicate"
11
+ BATCH_MAILING_DUPLICATE_FULL = "/api/v1/business/batch-mailing/list/:id/duplicate-full"
12
+ BATCH_MAILING_COMMIT = "/api/v1/business/batch-mailing/list/:id/commit"
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"
15
+
16
+ # Postal deliveries paths
17
+ POSTAL_DELIVERIES = "/api/v1/business/postal-deliveries"
18
+ POSTAL_DELIVERIES_HS_CODES = "/api/v1/business/postal-deliveries/hs-codes/list"
19
+ POSTAL_DELIVERIES_VALIDATION = "/api/v1/business/postal-deliveries/validation"
20
+ POSTAL_DELIVERIES_COUNTRIES = "/api/v1/business/postal-deliveries/countries"
21
+
22
+ # Geo directory paths
23
+ GEO_DIRECTORY_SEARCH_ADDRESS = "/api/v1/business/geo-directory/search-address"
24
+ GEO_DIRECTORY_POSTCODE = "/api/v1/business/geo-directory/postcode"
25
+ GEO_DIRECTORY_ADDRESSES = "/api/v1/business/geo-directory/addresses"
26
+ end
27
+ end
@@ -151,7 +151,6 @@ module Belpost
151
151
  http = Net::HTTP.new(uri.host, uri.port)
152
152
  http.use_ssl = true if uri.scheme == "https"
153
153
  http.read_timeout = @timeout
154
-
155
154
  begin
156
155
  response = http.request(request)
157
156
  handle_response(response)
@@ -191,7 +190,7 @@ module Belpost
191
190
 
192
191
  def handle_response(response)
193
192
  case response.code
194
- when "200"
193
+ when "200", "201"
195
194
  response
196
195
  when "401", "403"
197
196
  raise AuthenticationError.new(
@@ -2,8 +2,14 @@
2
2
 
3
3
  require_relative "api_service"
4
4
  require_relative "models/parcel"
5
+ require_relative "models/batch"
6
+ require_relative "models/batch_item"
5
7
  require_relative "models/api_response"
6
8
  require_relative "validations/address_schema"
9
+ require_relative "validations/batch_schema"
10
+ require_relative "validations/batch_item_schema"
11
+ require_relative "validations/postcode_schema"
12
+ require_relative "api_paths"
7
13
 
8
14
  module Belpost
9
15
  # Main client class for interacting with the BelPost API.
@@ -23,6 +29,41 @@ module Belpost
23
29
  )
24
30
  end
25
31
 
32
+ # Creates a new batch mailing by sending a POST request to the API.
33
+ #
34
+ # @param batch_data [Hash] The data for the batch mailing.
35
+ # @return [Hash] The parsed JSON response from the API.
36
+ # @raise [Belpost::InvalidRequestError] If the request data is invalid.
37
+ # @raise [Belpost::ApiError] If the API returns an error response.
38
+ def create_batch(batch_data)
39
+ validation_result = Validation::BatchSchema.new.call(batch_data)
40
+ raise ValidationError, "Invalid batch data: #{validation_result.errors.to_h}" unless validation_result.success?
41
+
42
+ batch = Models::Batch.new(batch_data)
43
+ response = @api_service.post(ApiPaths::BATCH_MAILING_LIST, batch.to_h)
44
+ response.to_h
45
+ end
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
+
26
67
  # Creates a new postal parcel by sending a POST request to the API.
27
68
  #
28
69
  # @param parcel_data [Hash] The data for the postal parcel.
@@ -31,12 +72,10 @@ module Belpost
31
72
  # @raise [Belpost::ApiError] If the API returns an error response.
32
73
  def create_parcel(parcel_data)
33
74
  validation_result = Validation::ParcelSchema.call(parcel_data)
34
- unless validation_result.success?
35
- raise ValidationError, "Invalid parcel data: #{validation_result.errors.to_h}"
36
- end
75
+ raise ValidationError, "Invalid parcel data: #{validation_result.errors.to_h}" unless validation_result.success?
37
76
 
38
77
  parcel = Models::Parcel.new(parcel_data)
39
- response = @api_service.post("/api/v1/business/postal-deliveries", parcel.to_h)
78
+ response = @api_service.post(ApiPaths::POSTAL_DELIVERIES, parcel.to_h)
40
79
  response.to_h
41
80
  end
42
81
 
@@ -45,7 +84,7 @@ module Belpost
45
84
  # @return [Array<Hash>] The HS codes tree as an array of hashes.
46
85
  # @raise [Belpost::ApiError] If the API returns an error response.
47
86
  def fetch_hs_codes
48
- response = @api_service.get("/api/v1/business/postal-deliveries/hs-codes/list")
87
+ response = @api_service.get(ApiPaths::POSTAL_DELIVERIES_HS_CODES)
49
88
  response.to_h
50
89
  end
51
90
 
@@ -56,7 +95,7 @@ module Belpost
56
95
  # @raise [Belpost::ApiError] If the API returns an error response.
57
96
  def validate_postal_delivery(country_code)
58
97
  country_code = country_code.upcase
59
- response = @api_service.get("/api/v1/business/postal-deliveries/validation/#{country_code}")
98
+ response = @api_service.get("#{ApiPaths::POSTAL_DELIVERIES_VALIDATION}/#{country_code}")
60
99
  response.to_h
61
100
  end
62
101
 
@@ -65,7 +104,20 @@ module Belpost
65
104
  # @return [Hash] The parsed JSON response containing available countries.
66
105
  # @raise [Belpost::ApiError] If the API returns an error response.
67
106
  def fetch_available_countries
68
- response = @api_service.get("/api/v1/business/postal-deliveries/countries")
107
+ response = @api_service.get(ApiPaths::POSTAL_DELIVERIES_COUNTRIES)
108
+ response.to_h
109
+ end
110
+
111
+ # Finds a batch by its ID.
112
+ #
113
+ # @param id [Integer] The ID of the batch to find.
114
+ # @return [Hash] The batch data if found.
115
+ # @raise [Belpost::ApiError] If the API returns an error response.
116
+ def find_batch_by_id(id)
117
+ raise ValidationError, "ID must be provided" if id.nil?
118
+ raise ValidationError, "ID must be a positive integer" unless id.is_a?(Integer) && id.positive?
119
+
120
+ response = @api_service.get("#{ApiPaths::BATCH_MAILING_LIST}/#{id}")
69
121
  response.to_h
70
122
  end
71
123
 
@@ -86,7 +138,21 @@ module Belpost
86
138
  raise ValidationError, "Address must be filled" if address.empty?
87
139
 
88
140
  formatted_address = format_address(address)
89
- response = @api_service.get("/api/v1/business/geo-directory/search-address", { search: formatted_address })
141
+ response = @api_service.get(ApiPaths::GEO_DIRECTORY_SEARCH_ADDRESS, { search: formatted_address })
142
+ response.to_h
143
+ end
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 })
90
156
  response.to_h
91
157
  end
92
158
 
@@ -113,7 +179,55 @@ module Belpost
113
179
  params[:building] = format_building_number(building) if building
114
180
  params[:limit] = limit
115
181
 
116
- response = @api_service.get("/api/v1/business/geo-directory/postcode", params)
182
+ response = @api_service.get(ApiPaths::GEO_DIRECTORY_POSTCODE, params)
183
+ response.to_h
184
+ end
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)
117
231
  response.to_h
118
232
  end
119
233
 
@@ -132,11 +246,11 @@ module Belpost
132
246
  return building unless building
133
247
 
134
248
  building.gsub(/\s+/, " ")
135
- .gsub(/\s*корпус\s*(\d+)\s*/i, '/\1')
136
- .gsub(/\s*корп\s*(\d+)\s*/i, '/\1')
137
- .gsub(/\s*кор\s*(\d+)\s*/i, '/\1')
138
- .gsub(/\s*к\s*(\d+)\s*/i, '/\1')
139
- .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
140
254
  end
141
255
  end
142
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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Belpost
4
+ module Models
5
+ # Model class for batch mailing
6
+ class Batch
7
+ attr_reader :postal_delivery_type, :direction, :payment_type, :negotiated_rate,
8
+ :name, :card_number, :postal_items_in_ops, :category, :is_document,
9
+ :is_declared_value, :is_partial_receipt
10
+
11
+ def initialize(data)
12
+ @postal_delivery_type = data[:postal_delivery_type]
13
+ @direction = data[:direction]
14
+ @payment_type = data[:payment_type]
15
+ @negotiated_rate = data[:negotiated_rate]
16
+ @name = data[:name]
17
+ @card_number = data[:card_number]
18
+ @postal_items_in_ops = data[:postal_items_in_ops]
19
+ @category = data[:category]
20
+ @is_document = data[:is_document]
21
+ @is_declared_value = data[:is_declared_value]
22
+ @is_partial_receipt = data[:is_partial_receipt]
23
+ end
24
+
25
+ def to_h
26
+ {
27
+ postal_delivery_type: @postal_delivery_type,
28
+ direction: @direction,
29
+ payment_type: @payment_type,
30
+ negotiated_rate: @negotiated_rate,
31
+ name: @name,
32
+ card_number: @card_number,
33
+ postal_items_in_ops: @postal_items_in_ops,
34
+ category: @category,
35
+ is_document: @is_document,
36
+ is_declared_value: @is_declared_value,
37
+ is_partial_receipt: @is_partial_receipt
38
+ }.compact
39
+ end
40
+ end
41
+ 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