spree_packeta 0.1.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 +7 -0
- data/.github/workflows/release.yml +196 -0
- data/.github/workflows/test.yml +61 -0
- data/.gitignore +63 -0
- data/CHANGELOG.md +43 -0
- data/COMMANDS.md +322 -0
- data/Gemfile +13 -0
- data/LICENSE +21 -0
- data/README.md +307 -0
- data/app/decorators/spree/order_decorator.rb +44 -0
- data/app/decorators/spree/shipment_decorator.rb +65 -0
- data/db/migrate/20241206000001_add_packeta_tracking_to_shipments.rb +14 -0
- data/docs/RELEASING.md +421 -0
- data/lib/generators/spree_packeta/install_generator.rb +68 -0
- data/lib/generators/spree_packeta/templates/initializer.rb +39 -0
- data/lib/spree_packeta/configuration.rb +55 -0
- data/lib/spree_packeta/engine.rb +48 -0
- data/lib/spree_packeta/models/shipping_calculator.rb +54 -0
- data/lib/spree_packeta/models/shipping_method.rb +59 -0
- data/lib/spree_packeta/services/packet_creator.rb +98 -0
- data/lib/spree_packeta/services/tracker.rb +96 -0
- data/lib/spree_packeta/soap/client.rb +117 -0
- data/lib/spree_packeta/soap/operations.rb +140 -0
- data/lib/spree_packeta/soap/packeta.wsdl +1819 -0
- data/lib/spree_packeta/version.rb +5 -0
- data/lib/spree_packeta.rb +18 -0
- data/lib/tasks/spree_packeta.rake +293 -0
- data/spree_packeta.gemspec +41 -0
- metadata +228 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreePacketa
|
|
4
|
+
module Models
|
|
5
|
+
class ShippingMethod < Spree::ShippingMethod
|
|
6
|
+
def carrier
|
|
7
|
+
'Packeta'
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Check if this shipping method is available for the given package
|
|
11
|
+
#
|
|
12
|
+
# @param package [Spree::Stock::Package] The package to check
|
|
13
|
+
# @return [Boolean] True if available
|
|
14
|
+
def available?(package, _display_on = nil)
|
|
15
|
+
super && packeta_available_for_package?(package)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Build tracking URL for a shipment
|
|
19
|
+
#
|
|
20
|
+
# @param tracking_number [String] The Packeta packet ID
|
|
21
|
+
# @return [String] Tracking URL
|
|
22
|
+
def build_tracking_url(tracking_number)
|
|
23
|
+
"https://tracking.packeta.com/#{tracking_number}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def packeta_available_for_package?(package)
|
|
29
|
+
# Check if package destination is supported
|
|
30
|
+
return false unless package_destination_supported?(package)
|
|
31
|
+
|
|
32
|
+
# Check if package weight is acceptable
|
|
33
|
+
return false unless package_weight_acceptable?(package)
|
|
34
|
+
|
|
35
|
+
true
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def package_destination_supported?(package)
|
|
39
|
+
# Get the destination country
|
|
40
|
+
country_code = package.order.ship_address&.country&.iso
|
|
41
|
+
|
|
42
|
+
# For now, support Czech Republic and EU countries
|
|
43
|
+
# This can be expanded based on Packeta's coverage
|
|
44
|
+
supported_countries = %w[CZ SK PL HU RO AT DE]
|
|
45
|
+
supported_countries.include?(country_code)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def package_weight_acceptable?(package)
|
|
49
|
+
# Calculate total weight
|
|
50
|
+
total_weight = package.contents.sum { |item| item.variant.weight.to_f * item.quantity }
|
|
51
|
+
|
|
52
|
+
# Packeta typically has a maximum weight limit (e.g., 30kg)
|
|
53
|
+
# Adjust this based on your needs
|
|
54
|
+
max_weight = 30.0 # kg
|
|
55
|
+
total_weight <= max_weight
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreePacketa
|
|
4
|
+
module Services
|
|
5
|
+
class PacketCreator
|
|
6
|
+
attr_reader :shipment, :operations
|
|
7
|
+
|
|
8
|
+
def initialize(shipment, operations = nil)
|
|
9
|
+
@shipment = shipment
|
|
10
|
+
@operations = operations || Soap::Operations.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Create a Packeta packet from a Spree shipment
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash] Packet details with :id, :barcode, :barcode_text
|
|
16
|
+
# @raise [ValidationError] If packet attributes are invalid
|
|
17
|
+
def create_packet
|
|
18
|
+
raise ValidationError, 'Shipment already has a Packeta packet' if shipment.packeta_packet_id.present?
|
|
19
|
+
|
|
20
|
+
# Extract attributes from shipment
|
|
21
|
+
attributes = build_packet_attributes
|
|
22
|
+
|
|
23
|
+
# Validate attributes first
|
|
24
|
+
operations.packet_attributes_valid?(attributes)
|
|
25
|
+
|
|
26
|
+
# Create the packet
|
|
27
|
+
result = operations.create_packet(attributes)
|
|
28
|
+
|
|
29
|
+
# Update shipment with packet details
|
|
30
|
+
update_shipment_with_packet(result)
|
|
31
|
+
|
|
32
|
+
result
|
|
33
|
+
rescue ValidationError => e
|
|
34
|
+
Rails.logger.error("Failed to create Packeta packet for shipment #{shipment.id}: #{e.message}")
|
|
35
|
+
raise
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def build_packet_attributes
|
|
41
|
+
order = shipment.order
|
|
42
|
+
address = order.ship_address
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
number: order.number,
|
|
46
|
+
name: address.firstname,
|
|
47
|
+
surname: address.lastname,
|
|
48
|
+
company: address.company,
|
|
49
|
+
email: order.email,
|
|
50
|
+
phone: address.phone,
|
|
51
|
+
address_id: packeta_pickup_point_id,
|
|
52
|
+
value: order.item_total.to_f,
|
|
53
|
+
weight: calculate_weight,
|
|
54
|
+
cod: calculate_cod,
|
|
55
|
+
currency: order.currency,
|
|
56
|
+
eshop: SpreePacketa.configuration.eshop
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def packeta_pickup_point_id
|
|
61
|
+
# Get pickup point from order metadata (set by frontend widget)
|
|
62
|
+
order.packeta_pickup_point_id ||
|
|
63
|
+
raise(ValidationError, 'No Packeta pickup point selected')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def calculate_weight
|
|
67
|
+
# Calculate total weight in kg
|
|
68
|
+
shipment.line_items.sum do |line_item|
|
|
69
|
+
(line_item.variant.weight || 0) * line_item.quantity
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def calculate_cod
|
|
74
|
+
# Cash on delivery amount
|
|
75
|
+
# Only if payment method is COD
|
|
76
|
+
payment_method = shipment.order.payments.last&.payment_method
|
|
77
|
+
if payment_method&.type == 'Spree::PaymentMethod::COD'
|
|
78
|
+
shipment.order.total.to_f
|
|
79
|
+
else
|
|
80
|
+
0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def update_shipment_with_packet(packet_details)
|
|
85
|
+
shipment.update!(
|
|
86
|
+
packeta_packet_id: packet_details[:id],
|
|
87
|
+
packeta_barcode: packet_details[:barcode],
|
|
88
|
+
packeta_tracking_url: build_tracking_url(packet_details[:id]),
|
|
89
|
+
tracking: packet_details[:id].to_s
|
|
90
|
+
)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_tracking_url(packet_id)
|
|
94
|
+
"https://tracking.packeta.com/#{packet_id}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreePacketa
|
|
4
|
+
module Services
|
|
5
|
+
class Tracker
|
|
6
|
+
attr_reader :shipment, :operations
|
|
7
|
+
|
|
8
|
+
def initialize(shipment, operations = nil)
|
|
9
|
+
@shipment = shipment
|
|
10
|
+
@operations = operations || Soap::Operations.new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Get current status of the packet
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash] Current status details
|
|
16
|
+
# @raise [NotFoundError] If packet ID is not found
|
|
17
|
+
def current_status
|
|
18
|
+
ensure_packet_exists!
|
|
19
|
+
|
|
20
|
+
status = operations.packet_status(shipment.packeta_packet_id)
|
|
21
|
+
update_shipment_status(status)
|
|
22
|
+
status
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Get full tracking history
|
|
26
|
+
#
|
|
27
|
+
# @return [Array<Hash>] Array of tracking records
|
|
28
|
+
# @raise [NotFoundError] If packet ID is not found
|
|
29
|
+
def tracking_history
|
|
30
|
+
ensure_packet_exists!
|
|
31
|
+
|
|
32
|
+
operations.packet_tracking(shipment.packeta_packet_id)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Sync status from Packeta and update shipment
|
|
36
|
+
#
|
|
37
|
+
# @return [Hash] Current status
|
|
38
|
+
def sync_status!
|
|
39
|
+
status = current_status
|
|
40
|
+
|
|
41
|
+
# Update last sync timestamp
|
|
42
|
+
shipment.update!(packeta_last_sync_at: Time.current)
|
|
43
|
+
|
|
44
|
+
# Update Spree shipment state based on Packeta status
|
|
45
|
+
update_spree_shipment_state(status)
|
|
46
|
+
|
|
47
|
+
status
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if shipment needs status sync
|
|
51
|
+
#
|
|
52
|
+
# @return [Boolean]
|
|
53
|
+
def needs_sync?
|
|
54
|
+
return true if shipment.packeta_last_sync_at.nil?
|
|
55
|
+
|
|
56
|
+
sync_interval = SpreePacketa.configuration.tracking_sync_interval
|
|
57
|
+
shipment.packeta_last_sync_at < sync_interval.seconds.ago
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def ensure_packet_exists!
|
|
63
|
+
unless shipment.packeta_packet_id
|
|
64
|
+
raise NotFoundError, "Shipment #{shipment.id} does not have a Packeta packet"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def update_shipment_status(status)
|
|
69
|
+
shipment.update!(
|
|
70
|
+
packeta_status_code: status[:status_code],
|
|
71
|
+
packeta_branch_id: status[:branch_id],
|
|
72
|
+
packeta_last_sync_at: Time.current
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def update_spree_shipment_state(status)
|
|
77
|
+
# Map Packeta status codes to Spree shipment states
|
|
78
|
+
# This is a simplified mapping - adjust based on your needs
|
|
79
|
+
case status[:status_code]
|
|
80
|
+
when 1, 2 # Packet created, In preparation
|
|
81
|
+
shipment.update!(state: 'pending') unless shipment.shipped?
|
|
82
|
+
when 3, 4 # Sent to carrier, In transit
|
|
83
|
+
shipment.ship! unless shipment.shipped?
|
|
84
|
+
when 5 # Delivered
|
|
85
|
+
shipment.ship! unless shipment.shipped?
|
|
86
|
+
when 6 # Returned
|
|
87
|
+
# Handle returns - could create a return authorization
|
|
88
|
+
Rails.logger.info("Packet #{shipment.packeta_packet_id} was returned")
|
|
89
|
+
end
|
|
90
|
+
rescue => e
|
|
91
|
+
Rails.logger.error("Failed to update shipment state: #{e.message}")
|
|
92
|
+
# Don't fail the sync if state transition fails
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'savon'
|
|
4
|
+
|
|
5
|
+
module SpreePacketa
|
|
6
|
+
module Soap
|
|
7
|
+
class Client
|
|
8
|
+
MAX_RETRIES = 3
|
|
9
|
+
RETRY_DELAY = 1 # second
|
|
10
|
+
|
|
11
|
+
attr_reader :client
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@client = build_client
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Call a SOAP operation with automatic error handling and retries
|
|
18
|
+
#
|
|
19
|
+
# @param operation [Symbol] The SOAP operation name
|
|
20
|
+
# @param message [Hash] The SOAP message body
|
|
21
|
+
# @return [Savon::Response] The SOAP response
|
|
22
|
+
# @raise [SpreePacketa::AuthenticationError] If API password is incorrect
|
|
23
|
+
# @raise [SpreePacketa::ValidationError] If packet attributes are invalid
|
|
24
|
+
# @raise [SpreePacketa::NotFoundError] If packet ID is not found
|
|
25
|
+
# @raise [SpreePacketa::NetworkError] If network error occurs
|
|
26
|
+
def call(operation, message = {})
|
|
27
|
+
retries = 0
|
|
28
|
+
|
|
29
|
+
begin
|
|
30
|
+
# Always include API password in the message
|
|
31
|
+
message_with_auth = message.merge(api_password: api_password)
|
|
32
|
+
|
|
33
|
+
response = client.call(operation, message: message_with_auth)
|
|
34
|
+
handle_response(response)
|
|
35
|
+
rescue Savon::SOAPFault => e
|
|
36
|
+
handle_soap_fault(e)
|
|
37
|
+
rescue Savon::HTTPError, Savon::InvalidResponseError => e
|
|
38
|
+
retries += 1
|
|
39
|
+
if retries <= MAX_RETRIES
|
|
40
|
+
sleep(RETRY_DELAY * retries)
|
|
41
|
+
retry
|
|
42
|
+
else
|
|
43
|
+
raise NetworkError, "Network error after #{MAX_RETRIES} retries: #{e.message}"
|
|
44
|
+
end
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
raise ApiError, "Unexpected error: #{e.message}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def build_client
|
|
53
|
+
Savon.client(
|
|
54
|
+
wsdl: wsdl_path,
|
|
55
|
+
endpoint: soap_endpoint,
|
|
56
|
+
namespace: 'http://www.zasilkovna.cz/api/soap.wsdl',
|
|
57
|
+
namespace_identifier: :tns,
|
|
58
|
+
env_namespace: :soap,
|
|
59
|
+
convert_request_keys_to: :none,
|
|
60
|
+
pretty_print_xml: Rails.env.development?,
|
|
61
|
+
log: Rails.env.development?,
|
|
62
|
+
log_level: :debug,
|
|
63
|
+
logger: Rails.logger,
|
|
64
|
+
open_timeout: 10,
|
|
65
|
+
read_timeout: 30,
|
|
66
|
+
raise_errors: true
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def handle_response(response)
|
|
71
|
+
# Log the response in development
|
|
72
|
+
Rails.logger.debug("Packeta SOAP Response: #{response.body}") if Rails.env.development?
|
|
73
|
+
response
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def handle_soap_fault(fault)
|
|
77
|
+
fault_code = fault.to_hash.dig(:fault, :faultcode)
|
|
78
|
+
fault_string = fault.to_hash.dig(:fault, :faultstring)
|
|
79
|
+
|
|
80
|
+
case fault_code
|
|
81
|
+
when /IncorrectApiPasswordFault/
|
|
82
|
+
raise AuthenticationError, "Incorrect API password: #{fault_string}"
|
|
83
|
+
when /PacketAttributesFault/
|
|
84
|
+
raise ValidationError, "Invalid packet attributes: #{fault_string}"
|
|
85
|
+
when /PacketIdFault/
|
|
86
|
+
raise NotFoundError, "Packet not found: #{fault_string}"
|
|
87
|
+
when /CancelNotAllowedFault/
|
|
88
|
+
raise ValidationError, "Cancel not allowed: #{fault_string}"
|
|
89
|
+
when /TooLateToUpdateCodFault/
|
|
90
|
+
raise ValidationError, "Too late to update COD: #{fault_string}"
|
|
91
|
+
when /ArgumentsFault/
|
|
92
|
+
raise ValidationError, "Invalid arguments: #{fault_string}"
|
|
93
|
+
else
|
|
94
|
+
raise ApiError, "SOAP fault: #{fault_code} - #{fault_string}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def api_password
|
|
99
|
+
SpreePacketa.configuration.api_password || raise(AuthenticationError, 'API password not configured')
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def soap_endpoint
|
|
103
|
+
SpreePacketa.configuration.soap_endpoint
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def wsdl_path
|
|
107
|
+
# Prefer configured path, then bundled local file, finally remote URL
|
|
108
|
+
SpreePacketa.configuration.wsdl_path.presence || local_wsdl_path || 'https://www.zasilkovna.cz/api/soap.wsdl'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def local_wsdl_path
|
|
112
|
+
path = File.expand_path('packeta.wsdl', __dir__)
|
|
113
|
+
File.exist?(path) ? path : nil
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpreePacketa
|
|
4
|
+
module Soap
|
|
5
|
+
class Operations
|
|
6
|
+
attr_reader :client
|
|
7
|
+
|
|
8
|
+
def initialize(client = nil)
|
|
9
|
+
@client = client || Client.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Validate packet attributes before creating a packet
|
|
13
|
+
#
|
|
14
|
+
# @param attributes [Hash] Packet attributes
|
|
15
|
+
# @return [Boolean] True if valid
|
|
16
|
+
# @raise [ValidationError] If attributes are invalid
|
|
17
|
+
def packet_attributes_valid?(attributes)
|
|
18
|
+
response = client.call(:packet_attributes_valid, attributes: packet_attributes_hash(attributes))
|
|
19
|
+
true
|
|
20
|
+
rescue ValidationError => e
|
|
21
|
+
Rails.logger.error("Packet attributes validation failed: #{e.message}")
|
|
22
|
+
raise
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Create a new packet
|
|
26
|
+
#
|
|
27
|
+
# @param attributes [Hash] Packet attributes
|
|
28
|
+
# @option attributes [String] :number Order number
|
|
29
|
+
# @option attributes [String] :name Customer first name
|
|
30
|
+
# @option attributes [String] :surname Customer last name
|
|
31
|
+
# @option attributes [String] :email Customer email
|
|
32
|
+
# @option attributes [String] :phone Customer phone
|
|
33
|
+
# @option attributes [Integer] :address_id Packeta pickup point ID
|
|
34
|
+
# @option attributes [BigDecimal] :value Package value
|
|
35
|
+
# @option attributes [BigDecimal] :weight Package weight in kg
|
|
36
|
+
# @option attributes [BigDecimal] :cod Cash on delivery amount (optional)
|
|
37
|
+
# @option attributes [String] :currency Currency code (CZK, EUR, etc.)
|
|
38
|
+
# @return [Hash] Created packet details with :id, :barcode, :barcode_text
|
|
39
|
+
def create_packet(attributes)
|
|
40
|
+
response = client.call(:create_packet, attributes: packet_attributes_hash(attributes))
|
|
41
|
+
|
|
42
|
+
result = response.body[:create_packet_response][:create_packet_result]
|
|
43
|
+
|
|
44
|
+
{
|
|
45
|
+
id: result[:id],
|
|
46
|
+
barcode: result[:barcode],
|
|
47
|
+
barcode_text: result[:barcode_text]
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get current status of a packet
|
|
52
|
+
#
|
|
53
|
+
# @param packet_id [Integer] Packeta packet ID
|
|
54
|
+
# @return [Hash] Current status with :date_time, :status_code, :code_text, :status_text, :branch_id
|
|
55
|
+
def packet_status(packet_id)
|
|
56
|
+
response = client.call(:packet_status, packet_id: packet_id)
|
|
57
|
+
|
|
58
|
+
result = response.body[:packet_status_response][:packet_status_result]
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
date_time: result[:date_time],
|
|
62
|
+
status_code: result[:status_code],
|
|
63
|
+
code_text: result[:code_text],
|
|
64
|
+
status_text: result[:status_text],
|
|
65
|
+
branch_id: result[:branch_id],
|
|
66
|
+
destination_branch_id: result[:destination_branch_id],
|
|
67
|
+
is_returning: result[:is_returning],
|
|
68
|
+
stored_until: result[:stored_until]
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Get full tracking history for a packet
|
|
73
|
+
#
|
|
74
|
+
# @param packet_id [Integer] Packeta packet ID
|
|
75
|
+
# @return [Array<Hash>] Array of tracking records
|
|
76
|
+
def packet_tracking(packet_id)
|
|
77
|
+
response = client.call(:packet_tracking, packet_id: packet_id)
|
|
78
|
+
|
|
79
|
+
records = response.body[:packet_tracking_response][:packet_tracking_result][:record]
|
|
80
|
+
records = [records] unless records.is_a?(Array) # Handle single record
|
|
81
|
+
|
|
82
|
+
records.map do |record|
|
|
83
|
+
{
|
|
84
|
+
date_time: record[:date_time],
|
|
85
|
+
status_code: record[:status_code],
|
|
86
|
+
code_text: record[:code_text],
|
|
87
|
+
status_text: record[:status_text],
|
|
88
|
+
branch_id: record[:branch_id],
|
|
89
|
+
destination_branch_id: record[:destination_branch_id]
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Cancel a packet (if allowed)
|
|
95
|
+
#
|
|
96
|
+
# @param packet_id [Integer] Packeta packet ID
|
|
97
|
+
# @return [Boolean] True if cancelled successfully
|
|
98
|
+
def cancel_packet(packet_id)
|
|
99
|
+
client.call(:cancel_packet, packet_id: packet_id)
|
|
100
|
+
true
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Get packet label as PDF
|
|
104
|
+
#
|
|
105
|
+
# @param packet_id [Integer] Packeta packet ID
|
|
106
|
+
# @param format [String] Label format ('A7 on A4', 'A6 on A4', etc.)
|
|
107
|
+
# @param offset [Integer] Offset for positioning on page (0-3)
|
|
108
|
+
# @return [String] Base64 encoded PDF
|
|
109
|
+
def packet_label_pdf(packet_id, format: 'A7 on A4', offset: 0)
|
|
110
|
+
response = client.call(
|
|
111
|
+
:packet_label_pdf,
|
|
112
|
+
packet_id: packet_id,
|
|
113
|
+
format: format,
|
|
114
|
+
offset: offset
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
response.body[:packet_label_pdf_response][:packet_label_pdf_result]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
# Convert Spree-friendly attributes to Packeta SOAP format
|
|
123
|
+
def packet_attributes_hash(attributes)
|
|
124
|
+
{
|
|
125
|
+
number: attributes[:number],
|
|
126
|
+
name: attributes[:name],
|
|
127
|
+
surname: attributes[:surname],
|
|
128
|
+
email: attributes[:email],
|
|
129
|
+
phone: attributes[:phone],
|
|
130
|
+
address_id: attributes[:address_id],
|
|
131
|
+
cod: attributes[:cod] || 0,
|
|
132
|
+
currency: attributes[:currency] || 'CZK',
|
|
133
|
+
value: attributes[:value],
|
|
134
|
+
weight: attributes[:weight],
|
|
135
|
+
eshop: attributes[:eshop] || SpreePacketa.configuration.eshop
|
|
136
|
+
}.compact
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|