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.
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpreePacketa
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'savon'
4
+
5
+ require 'spree_packeta/version'
6
+ require 'spree_packeta/configuration'
7
+
8
+ module SpreePacketa
9
+ # Custom exceptions
10
+ class Error < StandardError; end
11
+ class AuthenticationError < Error; end
12
+ class ValidationError < Error; end
13
+ class NotFoundError < Error; end
14
+ class NetworkError < Error; end
15
+ class ApiError < Error; end
16
+ end
17
+
18
+ require 'spree_packeta/engine'
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :spree_packeta do
4
+ desc 'Install Packeta shipping method'
5
+ task install: :environment do
6
+ puts 'Installing Packeta shipping method...'
7
+
8
+ # Check if Packeta method already exists
9
+ existing_method = Spree::ShippingMethod.find_by(
10
+ name: 'Packeta',
11
+ admin_name: 'Packeta (Zásilkovna)'
12
+ )
13
+
14
+ if existing_method
15
+ puts ' ⚠️ Packeta shipping method already exists (ID: #{existing_method.id})'
16
+ puts ' Run rake spree_packeta:uninstall to remove it first'
17
+ next
18
+ end
19
+
20
+ # Create shipping category if needed
21
+ shipping_category = Spree::ShippingCategory.find_or_create_by!(name: 'Default') do |sc|
22
+ puts ' ✓ Created default shipping category'
23
+ end
24
+
25
+ # Find or create zones
26
+ czech_zone = find_or_create_czech_zone
27
+ eu_zone = find_or_create_eu_zone
28
+
29
+ # Create calculator
30
+ calculator = SpreePacketa::Models::ShippingCalculator.new
31
+ calculator.preferences = {
32
+ amount: 99.0,
33
+ currency: 'CZK'
34
+ }
35
+
36
+ # Create shipping method
37
+ shipping_method = Spree::ShippingMethod.new(
38
+ name: 'Packeta',
39
+ admin_name: 'Packeta (Zásilkovna)',
40
+ code: 'packeta',
41
+ tracking_url: 'https://tracking.packeta.com/:tracking',
42
+ display_on: 'both',
43
+ calculator: calculator
44
+ )
45
+
46
+ shipping_method.shipping_categories << shipping_category
47
+ shipping_method.zones << czech_zone
48
+ shipping_method.zones << eu_zone if eu_zone
49
+
50
+ if shipping_method.save
51
+ puts " ✓ Created Packeta shipping method (ID: #{shipping_method.id})"
52
+ puts " ✓ Assigned to zones: #{shipping_method.zones.pluck(:name).join(', ')}"
53
+ puts " ✓ Base rate: #{calculator.preferences[:amount]} #{calculator.preferences[:currency]}"
54
+ puts ''
55
+ puts '✅ Packeta shipping method installed successfully!'
56
+ puts ''
57
+ puts 'Next steps:'
58
+ puts ' 1. Configure your API credentials in config/initializers/spree_packeta.rb'
59
+ puts ' 2. Add PACKETA_API_PASSWORD to your environment variables'
60
+ puts ' 3. Adjust shipping rates in Spree admin if needed'
61
+ else
62
+ puts " ❌ Failed to create shipping method: #{shipping_method.errors.full_messages.join(', ')}"
63
+ end
64
+ end
65
+
66
+ desc 'Uninstall Packeta shipping method'
67
+ task uninstall: :environment do
68
+ puts 'Uninstalling Packeta shipping method...'
69
+
70
+ methods = Spree::ShippingMethod.where(code: 'packeta')
71
+ count = methods.count
72
+
73
+ if count.zero?
74
+ puts ' ℹ️ No Packeta shipping methods found'
75
+ else
76
+ methods.destroy_all
77
+ puts " ✓ Removed #{count} Packeta shipping method(s)"
78
+ puts '✅ Packeta shipping method uninstalled successfully!'
79
+ end
80
+ end
81
+
82
+ desc 'Show Packeta shipping method status'
83
+ task status: :environment do
84
+ puts 'Packeta Shipping Method Status'
85
+ puts '=' * 50
86
+
87
+ methods = Spree::ShippingMethod.where(code: 'packeta')
88
+
89
+ if methods.empty?
90
+ puts '❌ Not installed'
91
+ puts ''
92
+ puts 'Run: rake spree_packeta:install'
93
+ else
94
+ methods.each do |method|
95
+ puts "✅ Installed (ID: #{method.id})"
96
+ puts ''
97
+ puts "Name: #{method.name}"
98
+ puts "Admin Name: #{method.admin_name}"
99
+ puts "Display On: #{method.display_on}"
100
+ puts "Available: #{method.available_to_users? ? 'Yes' : 'No'}"
101
+ puts "Calculator: #{method.calculator.class.name}"
102
+ puts "Zones: #{method.zones.pluck(:name).join(', ')}"
103
+ puts "Categories: #{method.shipping_categories.pluck(:name).join(', ')}"
104
+
105
+ if method.calculator.is_a?(SpreePacketa::Models::ShippingCalculator)
106
+ puts "Base Rate: #{method.calculator.preferences[:amount]} #{method.calculator.preferences[:currency]}"
107
+ end
108
+
109
+ puts ''
110
+ end
111
+ end
112
+
113
+ # Show configuration status
114
+ puts 'Configuration Status'
115
+ puts '=' * 50
116
+ config = SpreePacketa.configuration
117
+
118
+ check_config('API Password', config.api_password)
119
+ check_config('SOAP Endpoint', config.soap_endpoint)
120
+ check_config('WSDL Path', config.wsdl_path)
121
+ check_config('Eshop ID', config.eshop)
122
+ check_config('Sender Email', config.sender_email)
123
+ end
124
+
125
+ desc 'Sync tracking status for undelivered Packeta shipments'
126
+ task sync_tracking: :environment do
127
+ start_time = Time.current
128
+
129
+ # Initialize counters
130
+ total_count = 0
131
+ success_count = 0
132
+ skipped_count = 0
133
+ error_count = 0
134
+ errors = []
135
+
136
+ # Log start
137
+ Rails.logger.info('=' * 80)
138
+ Rails.logger.info("Packeta Tracking Sync Started at #{start_time}")
139
+ Rails.logger.info("Sync Interval: #{SpreePacketa.configuration.tracking_sync_interval} seconds")
140
+ Rails.logger.info('=' * 80)
141
+
142
+ # Find eligible shipments (has packet, not delivered)
143
+ eligible_shipments = Spree::Shipment
144
+ .where.not(packeta_packet_id: nil)
145
+ .where('packeta_status_code < ? OR packeta_status_code IS NULL', 5)
146
+ .order(:id)
147
+
148
+ # Check if there are any shipments to sync
149
+ if eligible_shipments.count.zero?
150
+ Rails.logger.info('No eligible Packeta shipments found for sync')
151
+ Rails.logger.info('=' * 80)
152
+ next
153
+ end
154
+
155
+ # Process each shipment
156
+ eligible_shipments.find_each do |shipment|
157
+ total_count += 1
158
+
159
+ begin
160
+ tracker = SpreePacketa::Services::Tracker.new(shipment)
161
+
162
+ # Check if sync is needed (respects interval)
163
+ unless tracker.needs_sync?
164
+ skipped_count += 1
165
+ next
166
+ end
167
+
168
+ # Perform sync
169
+ tracker.sync_status!
170
+ success_count += 1
171
+
172
+ rescue SpreePacketa::AuthenticationError => e
173
+ # Critical error - stop processing
174
+ Rails.logger.error("CRITICAL: Packeta API authentication failed: #{e.message}")
175
+ Rails.logger.error('Check PACKETA_API_PASSWORD environment variable')
176
+ raise
177
+
178
+ rescue SpreePacketa::NotFoundError => e
179
+ # Packet doesn't exist in Packeta system
180
+ error_count += 1
181
+ errors << {
182
+ shipment_id: shipment.id,
183
+ packet_id: shipment.packeta_packet_id,
184
+ error: "NotFound: #{e.message}"
185
+ }
186
+
187
+ rescue SpreePacketa::ApiError, SpreePacketa::NetworkError => e
188
+ # API/network issues
189
+ error_count += 1
190
+ errors << {
191
+ shipment_id: shipment.id,
192
+ packet_id: shipment.packeta_packet_id,
193
+ error: "API/Network: #{e.message}"
194
+ }
195
+
196
+ rescue StandardError => e
197
+ # Unexpected errors
198
+ error_count += 1
199
+ errors << {
200
+ shipment_id: shipment.id,
201
+ packet_id: shipment.packeta_packet_id,
202
+ error: "Unexpected: #{e.class} - #{e.message}",
203
+ backtrace: e.backtrace.first(3)
204
+ }
205
+ end
206
+ end
207
+
208
+ # Log error details
209
+ errors.each do |error_detail|
210
+ log_message = "Sync failed for shipment #{error_detail[:shipment_id]} " \
211
+ "(packet: #{error_detail[:packet_id]}): #{error_detail[:error]}"
212
+
213
+ if error_detail[:error].start_with?('NotFound')
214
+ Rails.logger.warn(log_message)
215
+ else
216
+ Rails.logger.error(log_message)
217
+ end
218
+
219
+ if error_detail[:backtrace]
220
+ Rails.logger.error(" Backtrace: #{error_detail[:backtrace].join("\n ")}")
221
+ end
222
+ end
223
+
224
+ # Log summary
225
+ end_time = Time.current
226
+ duration = end_time - start_time
227
+
228
+ Rails.logger.info('=' * 80)
229
+ Rails.logger.info("Packeta Tracking Sync Completed at #{end_time}")
230
+ Rails.logger.info("Total Eligible: #{total_count}")
231
+ Rails.logger.info("Successfully Synced: #{success_count}")
232
+ Rails.logger.info("Skipped (no sync needed): #{skipped_count}")
233
+ Rails.logger.info("Errors: #{error_count}")
234
+ Rails.logger.info("Duration: #{duration.round(2)} seconds")
235
+ Rails.logger.info('=' * 80)
236
+ end
237
+
238
+ # Helper methods
239
+ def find_or_create_czech_zone
240
+ zone = Spree::Zone.find_by(name: 'Czech Republic')
241
+
242
+ unless zone
243
+ zone = Spree::Zone.create!(
244
+ name: 'Czech Republic',
245
+ description: 'Czech Republic',
246
+ kind: 'country'
247
+ )
248
+
249
+ czech_country = Spree::Country.find_by(iso: 'CZ')
250
+ if czech_country
251
+ zone.zone_members.create!(zoneable: czech_country)
252
+ puts ' ✓ Created Czech Republic zone'
253
+ else
254
+ puts ' ⚠️ Czech Republic country not found in database'
255
+ end
256
+ end
257
+
258
+ zone
259
+ end
260
+
261
+ def find_or_create_eu_zone
262
+ zone = Spree::Zone.find_by(name: 'EU')
263
+
264
+ unless zone
265
+ eu_countries = %w[AT BE BG HR CY CZ DK EE FI FR DE GR HU IE IT LV LT LU MT NL PL PT RO SK SI ES SE]
266
+ countries = Spree::Country.where(iso: eu_countries)
267
+
268
+ if countries.any?
269
+ zone = Spree::Zone.create!(
270
+ name: 'EU',
271
+ description: 'European Union',
272
+ kind: 'country'
273
+ )
274
+
275
+ countries.each do |country|
276
+ zone.zone_members.create!(zoneable: country)
277
+ end
278
+
279
+ puts ' ✓ Created EU zone'
280
+ else
281
+ puts ' ⚠️ EU countries not found in database'
282
+ end
283
+ end
284
+
285
+ zone
286
+ end
287
+
288
+ def check_config(name, value)
289
+ status = value.present? ? '✅' : '❌'
290
+ display_value = value.present? ? (value.length > 50 ? "#{value[0..50]}..." : value) : 'Not set'
291
+ puts "#{status} #{name.ljust(20)}: #{display_value}"
292
+ end
293
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/spree_packeta/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'spree_packeta'
7
+ spec.version = SpreePacketa::VERSION
8
+ spec.authors = ['Your Name']
9
+ spec.email = ['your.email@example.com']
10
+
11
+ spec.summary = 'Packeta (Zasilkovna) shipping integration for Spree Commerce'
12
+ spec.description = 'Integrates Packeta shipping service with Spree Commerce using their SOAP API for packet creation, tracking, and label generation'
13
+ spec.homepage = 'https://github.com/yourusername/spree_packeta'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 2.7.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = spec.homepage
19
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
23
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
24
+ end
25
+ spec.require_paths = ['lib']
26
+
27
+ # Runtime dependencies
28
+ spec.add_dependency 'spree_core', '~> 5.0'
29
+ spec.add_dependency 'savon', '~> 2.14'
30
+ spec.add_dependency 'activesupport', '>= 6.0'
31
+
32
+ # Development dependencies
33
+ spec.add_development_dependency 'rspec-rails', '~> 6.0'
34
+ spec.add_development_dependency 'vcr', '~> 6.0'
35
+ spec.add_development_dependency 'webmock', '~> 3.18'
36
+ spec.add_development_dependency 'factory_bot', '~> 6.2'
37
+ spec.add_development_dependency 'database_cleaner', '~> 2.0'
38
+ spec.add_development_dependency 'sqlite3', '~> 1.6'
39
+ spec.add_development_dependency 'rubocop', '~> 1.50'
40
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.20'
41
+ end
metadata ADDED
@@ -0,0 +1,228 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spree_packeta
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Your Name
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: spree_core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: savon
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.14'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.14'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '6.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '6.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '6.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '6.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: vcr
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '6.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.18'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.18'
97
+ - !ruby/object:Gem::Dependency
98
+ name: factory_bot
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '6.2'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '6.2'
111
+ - !ruby/object:Gem::Dependency
112
+ name: database_cleaner
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: sqlite3
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.6'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.6'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '1.50'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '1.50'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rubocop-rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.20'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.20'
167
+ description: Integrates Packeta shipping service with Spree Commerce using their SOAP
168
+ API for packet creation, tracking, and label generation
169
+ email:
170
+ - your.email@example.com
171
+ executables: []
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - ".github/workflows/release.yml"
176
+ - ".github/workflows/test.yml"
177
+ - ".gitignore"
178
+ - CHANGELOG.md
179
+ - COMMANDS.md
180
+ - Gemfile
181
+ - LICENSE
182
+ - README.md
183
+ - app/decorators/spree/order_decorator.rb
184
+ - app/decorators/spree/shipment_decorator.rb
185
+ - db/migrate/20241206000001_add_packeta_tracking_to_shipments.rb
186
+ - docs/RELEASING.md
187
+ - lib/generators/spree_packeta/install_generator.rb
188
+ - lib/generators/spree_packeta/templates/initializer.rb
189
+ - lib/spree_packeta.rb
190
+ - lib/spree_packeta/configuration.rb
191
+ - lib/spree_packeta/engine.rb
192
+ - lib/spree_packeta/models/shipping_calculator.rb
193
+ - lib/spree_packeta/models/shipping_method.rb
194
+ - lib/spree_packeta/services/packet_creator.rb
195
+ - lib/spree_packeta/services/tracker.rb
196
+ - lib/spree_packeta/soap/client.rb
197
+ - lib/spree_packeta/soap/operations.rb
198
+ - lib/spree_packeta/soap/packeta.wsdl
199
+ - lib/spree_packeta/version.rb
200
+ - lib/tasks/spree_packeta.rake
201
+ - spree_packeta.gemspec
202
+ homepage: https://github.com/yourusername/spree_packeta
203
+ licenses:
204
+ - MIT
205
+ metadata:
206
+ homepage_uri: https://github.com/yourusername/spree_packeta
207
+ source_code_uri: https://github.com/yourusername/spree_packeta
208
+ changelog_uri: https://github.com/yourusername/spree_packeta/blob/main/CHANGELOG.md
209
+ post_install_message:
210
+ rdoc_options: []
211
+ require_paths:
212
+ - lib
213
+ required_ruby_version: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: 2.7.0
218
+ required_rubygems_version: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ">="
221
+ - !ruby/object:Gem::Version
222
+ version: '0'
223
+ requirements: []
224
+ rubygems_version: 3.3.27
225
+ signing_key:
226
+ specification_version: 4
227
+ summary: Packeta (Zasilkovna) shipping integration for Spree Commerce
228
+ test_files: []