belpost 0.11.1 → 0.13.1

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: 9514e803064862ca1a9922d89527d3a97da9806105b3621d6c844405a8ee517d
4
- data.tar.gz: '0274388346a6da307d7d27ccf1420893e35deb74d860a35a9bffc2e630f8f7a1'
3
+ metadata.gz: e4adf946e8f59bd6f7819db47ee253b758f285c1bdf6fac6ca326dea521902e1
4
+ data.tar.gz: 8e9490b37a3518193182dc6db5cf1e65c5a7f7f13fafdd6935dfd1c91d4c9c3a
5
5
  SHA512:
6
- metadata.gz: 522b879eb66e52848f47c987195d801ee62b59ceb86d3cc54211287bf87b85452649413c4754623b5aba4ae1b463b3c6f17befef452c3b5720be4cc08083c550
7
- data.tar.gz: 74a86bddb8eed2b813ddc73f1d0f1fc2bd9c6b00737cdf680060af06e0b38415abbe9fc0b8e4b600f36f4a885c1dfd0552d9d53ad4317da647202d9b24602a99
6
+ metadata.gz: e32a32e6561612bee6e576851bad30ada292cce9b3a6096c8db9298ce5c30f892095b8687e0353417505732abb87664f19173eb352957993d52257d76d4b1209
7
+ data.tar.gz: d38a5cf9783d0d7cac6ddeee2f8ff5c2cd5762b9c6d3a96da7da8a89470d98d9c6f52a3706bb0033c607b67e40100ceafe12f727efed16700f20859bada631ba
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ ## [0.13.0] - 2025-04-25
2
+ ### Added
3
+ - Added support for committing batch mailings via `commit_batch` method
4
+ - Added ability to change batch status from "uncommitted" to "committed"
5
+
6
+ ## [0.12.1] - 2025-04-24
7
+ ### Added
8
+ - Added Russian translations for batch statuses:
9
+ - 'uncommitted' -> 'В обработке' (in processing)
10
+ - 'committed' -> 'Сформирована' (formed)
11
+ - New `BatchStatus` class for working with batch status values
12
+ - Added `translate_batch_status` method for translating batch status values
13
+
14
+ ## [0.12.0] - 2025-04-24
15
+ ### Added
16
+ - Added support for downloading batch mailing documents as ZIP archives via `download_batch_documents` method
17
+ - New `get_binary` method in ApiService to handle binary data downloads
18
+
19
+ ## [0.11.2] - 2025-04-24
20
+ ### Added
21
+ - Added support for generating address labels for batch mailings via `generate_batch_blanks` method
22
+ - Support for generating address labels for shipments within batches with "In processing" status
23
+ - Generation of PS112e form for batches with "is_partial_receipt" flag set to true that contain shipments with attachments
24
+
1
25
  ## [0.11.1] - 2025-04-24
2
26
  ### Changed
3
27
  - Enhanced validation for batch item schema to allow empty strings for `cash_on_delivery` and `declared_value` fields
data/README.md CHANGED
@@ -356,6 +356,70 @@ filtered_batches = client.list_batches(
356
356
  )
357
357
  ```
358
358
 
359
+ ### Generating address labels for a batch
360
+
361
+ ```ruby
362
+ client = Belpost::Client.new
363
+
364
+ # Generate address labels for a batch with ID 17292
365
+ documents = client.generate_batch_blanks(17292)
366
+ puts documents
367
+
368
+ # The response will contain document information with the following structure:
369
+ # {
370
+ # "documents" => {
371
+ # "id" => 8660,
372
+ # "list_id" => 17292,
373
+ # "status" => "processing",
374
+ # "path" => nil,
375
+ # "expire_at" => nil,
376
+ # "created_at" => "2024-11-06 13:18:59",
377
+ # "updated_at" => "2024-11-06 16:03:36",
378
+ # "name" => "Партия (заказ) №91"
379
+ # }
380
+ # }
381
+ ```
382
+
383
+ Note: Address labels can only be generated for batches with the "In processing" status. When you make this request, all address labels for shipments within the batch will be regenerated. Label generation is available if the batch has the "has_documents_label" flag set to false.
384
+
385
+ If the batch has the "is_partial_receipt" flag set to true and contains shipments with attachments, a PS112e form with attachment descriptions will be included in the response ZIP archive.
386
+
387
+ ### Committing a batch
388
+
389
+ After a batch has been created and filled with items, you can commit it to change its status from "uncommitted" (or "В обработке") to "committed" (or "Сформирована").
390
+
391
+ ```ruby
392
+ # Commit a batch with ID 19217
393
+ result = client.commit_batch(19217)
394
+ puts "New status: #{result["status"]}" # => "committed"
395
+ ```
396
+
397
+ Note: You can only commit a batch that is currently uncommitted, has items, and includes contents if the batch has the "is_partial_receipt" flag set to true.
398
+
399
+ ### Downloading batch documents
400
+
401
+ ```ruby
402
+ client = Belpost::Client.new
403
+
404
+ # Generate address labels for a batch with ID 17292
405
+ documents = client.generate_batch_blanks(17292)
406
+ puts documents
407
+
408
+ # The response will contain document information with the following structure:
409
+ # {
410
+ # "documents" => {
411
+ # "id" => 8660,
412
+ # "list_id" => 17292,
413
+ # "status" => "processing",
414
+ # "path" => nil,
415
+ # "expire_at" => nil,
416
+ # "created_at" => "2024-11-06 13:18:59",
417
+ # "updated_at" => "2024-11-06 16:03:36",
418
+ # "name" => "Партия (заказ) №91"
419
+ # }
420
+ # }
421
+ ```
422
+
359
423
  ## Error handling
360
424
 
361
425
  The client may throw the following exceptions:
@@ -5,13 +5,14 @@ module Belpost
5
5
  module ApiPaths
6
6
  # Batch mailing paths
7
7
  BATCH_MAILING_LIST = "/api/v1/business/batch-mailing/list"
8
+ BATCH_MAILING_LIST_BY_ID = "/api/v1/business/batch-mailing/list/:id"
8
9
  BATCH_MAILING_DOCUMENTS = "/api/v1/business/batch-mailing/documents"
9
- BATCH_MAILING_ITEM = "/api/v1/business/batch-mailing/item"
10
10
  BATCH_MAILING_DUPLICATE = "/api/v1/business/batch-mailing/list/:id/duplicate"
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
14
  BATCH_MAILING_LIST_ITEM = "/api/v1/business/batch-mailing/list/:id/item"
15
+ BATCH_MAILING_DOCUMENTS_DOWNLOAD = "/api/v1/batch-mailing/documents/:id/download"
15
16
 
16
17
  # Postal deliveries paths
17
18
  POSTAL_DELIVERIES = "/api/v1/business/postal-deliveries"
@@ -47,6 +47,31 @@ module Belpost
47
47
  end
48
48
  end
49
49
  end
50
+
51
+ # Performs a GET request to the specified path and returns binary data.
52
+ #
53
+ # @param path [String] The API endpoint path.
54
+ # @param params [Hash] The query parameters (default: {}).
55
+ # @return [Hash] Hash containing binary data, status code and headers
56
+ def get_binary(path, params = {})
57
+ Retry.with_retry do
58
+ uri = URI("#{@base_url}#{path}")
59
+ uri.query = URI.encode_www_form(params) unless params.empty?
60
+ request = Net::HTTP::Get.new(uri)
61
+ add_headers(request)
62
+ request["Accept"] = "*/*" # Override Accept header to receive any content type
63
+
64
+ log_request(request)
65
+ response = execute_request(uri, request)
66
+ log_response(response, binary: true)
67
+
68
+ {
69
+ data: response.body,
70
+ status_code: response.code.to_i,
71
+ headers: response.to_hash
72
+ }
73
+ end
74
+ end
50
75
 
51
76
  # Performs a POST request to the specified path with the given body.
52
77
  #
@@ -188,23 +213,58 @@ module Belpost
188
213
  end
189
214
  end
190
215
 
216
+ # Logs the HTTP request details.
217
+ #
218
+ # @param request [Net::HTTP::Request] The HTTP request object.
219
+ def log_request(request)
220
+ @logger.info("API Request: #{request.method} #{request.uri}")
221
+ @logger.debug("Request Headers: #{request.each_header.to_h}") if request.respond_to?(:each_header)
222
+ @logger.debug("Request Body: #{request.body}") if request.body
223
+ end
224
+
225
+ # Logs the HTTP response details.
226
+ #
227
+ # @param response [Net::HTTP::Response] The HTTP response object.
228
+ # @param binary [Boolean] If this is a binary response (default: false).
229
+ def log_response(response, binary: false)
230
+ status_message = response.respond_to?(:message) ? " #{response.message}" : ""
231
+ @logger.info("API Response: #{response.code}#{status_message}")
232
+
233
+ if response.respond_to?(:each_header)
234
+ @logger.debug("Response Headers: #{response.each_header.to_h}")
235
+ elsif response.respond_to?(:to_hash)
236
+ @logger.debug("Response Headers: #{response.to_hash}")
237
+ end
238
+
239
+ if binary
240
+ @logger.debug("Response Body: [BINARY DATA]")
241
+ else
242
+ @logger.debug("Response Body: #{response.body}")
243
+ end
244
+ end
245
+
246
+ # Handles the HTTP response.
247
+ #
248
+ # @param response [Net::HTTP::Response] The HTTP response object.
249
+ # @return [Net::HTTP::Response] The HTTP response object if successful.
250
+ # @raise [Belpost::ApiError] If the API returns an error response.
191
251
  def handle_response(response)
192
- case response.code
193
- when "200", "201"
252
+ case response.code.to_i
253
+ when 200..299
194
254
  response
195
- when "401", "403"
255
+ when 401, 403
196
256
  raise AuthenticationError.new(
197
257
  "Authentication failed",
198
258
  status_code: response.code.to_i,
199
259
  response_body: response.body
200
260
  )
201
- when "429"
261
+ when 429
202
262
  raise RateLimitError.new(
203
263
  "Rate limit exceeded",
204
264
  status_code: response.code.to_i,
205
265
  response_body: response.body
206
266
  )
207
- when "400"
267
+ when 400
208
268
  raise InvalidRequestError.new(
209
269
  "Invalid request",
210
270
  status_code: response.code.to_i,
@@ -218,17 +278,5 @@ module Belpost
218
278
  )
219
279
  end
220
280
  end
221
-
222
- def log_request(request)
223
- @logger.info("Making #{request.method} request to #{request.uri}")
224
- @logger.debug("Request headers: #{request.to_hash}")
225
- @logger.debug("Request body: #{request.body}") if request.body
226
- end
227
-
228
- def log_response(response)
229
- @logger.info("Received response with status #{response.code}")
230
- @logger.debug("Response headers: #{response.to_hash}")
231
- @logger.debug("Response body: #{response.body}")
232
- end
233
281
  end
234
282
  end
@@ -4,6 +4,7 @@ require_relative "api_service"
4
4
  require_relative "models/parcel"
5
5
  require_relative "models/batch"
6
6
  require_relative "models/batch_item"
7
+ require_relative "models/batch_status"
7
8
  require_relative "models/api_response"
8
9
  require_relative "validations/address_schema"
9
10
  require_relative "validations/batch_schema"
@@ -112,12 +113,14 @@ module Belpost
112
113
  #
113
114
  # @param id [Integer] The ID of the batch to find.
114
115
  # @return [Hash] The batch data if found.
116
+ # @raise [Belpost::ValidationError] If the ID parameter is invalid
115
117
  # @raise [Belpost::ApiError] If the API returns an error response.
116
118
  def find_batch_by_id(id)
117
119
  raise ValidationError, "ID must be provided" if id.nil?
118
120
  raise ValidationError, "ID must be a positive integer" unless id.is_a?(Integer) && id.positive?
119
121
 
120
- response = @api_service.get("#{ApiPaths::BATCH_MAILING_LIST}/#{id}")
122
+ path = ApiPaths::BATCH_MAILING_LIST_BY_ID.gsub(':id', id.to_s)
123
+ response = @api_service.get(path)
121
124
  response.to_h
122
125
  end
123
126
 
@@ -205,7 +208,7 @@ module Belpost
205
208
 
206
209
  # Validate status parameter
207
210
  if status
208
- unless %w[committed uncommitted].include?(status)
211
+ unless Models::BatchStatus.valid?(status)
209
212
  raise ValidationError, "Status must be 'committed' or 'uncommitted'"
210
213
  end
211
214
 
@@ -231,6 +234,84 @@ module Belpost
231
234
  response.to_h
232
235
  end
233
236
 
237
+ # Generates address labels for a batch mailing.
238
+ #
239
+ # This method allows generating address labels for shipments within a batch.
240
+ # Labels can only be generated for batches with the "In processing" status.
241
+ # When this request is made, all address labels for shipments within the batch will be regenerated.
242
+ # Label generation is available if the batch has the "has_documents_label" flag set to false.
243
+ #
244
+ # If the batch has the "is_partial_receipt" flag set to true and contains shipments with attachments,
245
+ # a PS112e form with attachment descriptions will be included in the response ZIP archive.
246
+ #
247
+ # If the batch has the "is_partial_receipt" flag set to true and contains shipments without attachments,
248
+ # address labels will not be generated for those shipments. Adding attachments to a shipment is mandatory
249
+ # for generating address labels in this case.
250
+ #
251
+ # @param batch_id [Integer] The ID of the batch to generate labels for
252
+ # @return [Hash] The parsed JSON response containing document information
253
+ # @raise [Belpost::ValidationError] If the batch_id parameter is invalid
254
+ # @raise [Belpost::ApiError] If the API returns an error response
255
+ def generate_batch_blanks(batch_id)
256
+ raise ValidationError, "Batch ID must be provided" if batch_id.nil?
257
+ raise ValidationError, "Batch ID must be a positive integer" unless batch_id.is_a?(Integer) && batch_id.positive?
258
+
259
+ path = ApiPaths::BATCH_MAILING_GENERATE_BLANK.gsub(':id', batch_id.to_s)
260
+ response = @api_service.post(path, {})
261
+ response.to_h
262
+ end
263
+
264
+ # Commits a batch mailing by its ID, changing status from "uncommitted" to "committed".
265
+ #
266
+ # This method can only commit a batch that is currently uncommitted, has items and
267
+ # includes contents if the batch has "is_partial_receipt" flag set to true.
268
+ #
269
+ # @param batch_id [Integer] The ID of the batch to commit
270
+ # @return [Hash] The committed batch data with updated status
271
+ # @raise [Belpost::ValidationError] If the batch_id parameter is invalid
272
+ # @raise [Belpost::ApiError] If the API returns an error response
273
+ def commit_batch(batch_id)
274
+ raise ValidationError, "Batch ID must be provided" if batch_id.nil?
275
+ raise ValidationError, "Batch ID must be a positive integer" unless batch_id.is_a?(Integer) && batch_id.positive?
276
+
277
+ path = ApiPaths::BATCH_MAILING_COMMIT.gsub(':id', batch_id.to_s)
278
+ response = @api_service.post(path, {})
279
+ response.to_h
280
+ end
281
+
282
+ # Downloads batch mailing documents as a ZIP archive.
283
+ #
284
+ # This method retrieves a ZIP archive containing all documents related to a batch mailing.
285
+ # The response contains binary data that can be saved as a ZIP file.
286
+ #
287
+ # @param document_id [Integer] The ID of the document to download
288
+ # @return [Hash] Hash containing binary data, status code and headers
289
+ # @raise [Belpost::ValidationError] If the document_id parameter is invalid
290
+ # @raise [Belpost::ApiError] If the API returns an error response
291
+ def download_batch_documents(document_id)
292
+ raise ValidationError, "Document ID must be provided" if document_id.nil?
293
+ raise ValidationError, "Document ID must be a positive integer" unless document_id.is_a?(Integer) && document_id.positive?
294
+
295
+ path = ApiPaths::BATCH_MAILING_DOCUMENTS_DOWNLOAD.gsub(':id', document_id.to_s)
296
+ response = @api_service.get_binary(path)
297
+
298
+ # Ensure we have the correct content type for a ZIP file
299
+ content_type = response[:headers]["content-type"]&.first
300
+ unless content_type && content_type.include?("application/zip")
301
+ @logger.warn("Expected ZIP file but got content type: #{content_type}")
302
+ end
303
+
304
+ response
305
+ end
306
+
307
+ # Translates a batch status code to its Russian translation
308
+ #
309
+ # @param status [String] The status code ('uncommitted' or 'committed')
310
+ # @return [String] The Russian translation or the original status if not found
311
+ def translate_batch_status(status)
312
+ Models::BatchStatus.translate(status)
313
+ end
314
+
234
315
  private
235
316
 
236
317
  def format_address(address)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Belpost
4
+ module Models
5
+ # Helper class for batch status translations and utilities
6
+ class BatchStatus
7
+ # Mapping of status codes to their Russian translations
8
+ TRANSLATIONS = {
9
+ "uncommitted" => "В обработке",
10
+ "committed" => "Сформирована"
11
+ }.freeze
12
+
13
+ # Get the Russian translation for a status
14
+ #
15
+ # @param status [String] The status code ('uncommitted' or 'committed')
16
+ # @return [String] The Russian translation or the original status if not found
17
+ def self.translate(status)
18
+ TRANSLATIONS[status] || status
19
+ end
20
+
21
+ # Get all possible statuses
22
+ #
23
+ # @return [Array<String>] Array of all possible status values
24
+ def self.all
25
+ %w[uncommitted committed]
26
+ end
27
+
28
+ # Check if a status is valid
29
+ #
30
+ # @param status [String] The status to check
31
+ # @return [Boolean] True if valid, false otherwise
32
+ def self.valid?(status)
33
+ all.include?(status)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -71,8 +71,7 @@ module Belpost
71
71
  ecommerce_optima: {
72
72
  negotiated_rate: false,
73
73
  declared_value: [true, false],
74
- partial_receipt: false,
75
- postal_items_in_ops: [true, false]
74
+ partial_receipt: false
76
75
  }
77
76
  }.freeze
78
77
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Belpost
4
- VERSION = "0.11.1"
4
+ VERSION = "0.13.1"
5
5
  end
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.11.1
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - KuberLite
@@ -77,6 +77,7 @@ files:
77
77
  - lib/belpost/models/api_response.rb
78
78
  - lib/belpost/models/batch.rb
79
79
  - lib/belpost/models/batch_item.rb
80
+ - lib/belpost/models/batch_status.rb
80
81
  - lib/belpost/models/customs_declaration.rb
81
82
  - lib/belpost/models/parcel.rb
82
83
  - lib/belpost/models/parcel_builder.rb