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.
data/docs/RELEASING.md ADDED
@@ -0,0 +1,421 @@
1
+ # Release Guide for spree_packeta
2
+
3
+ This guide explains how to create a new release of the spree_packeta gem.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. **Maintainer access**: You must have push access to the repository
8
+ 2. **RubyGems account**: Configured as owner of the spree_packeta gem
9
+ 3. **GitHub secrets**: `RUBYGEMS_API_KEY` must be configured (maintainers only)
10
+
11
+ ## Dual Publishing
12
+
13
+ This gem is published to **two registries**:
14
+ - **RubyGems.org**: Public gem registry (primary)
15
+ - **GitHub Packages**: GitHub's gem registry (backup/alternative)
16
+
17
+ Benefits:
18
+ - **Redundancy**: If RubyGems.org is down, users can install from GitHub Packages
19
+ - **Private gems**: Can be extended for private versions
20
+ - **GitHub integration**: Better integration with GitHub ecosystem
21
+ - **No extra setup required**: `GITHUB_TOKEN` is automatically provided by GitHub Actions
22
+
23
+ ## Release Process
24
+
25
+ ### 1. Prepare the Release
26
+
27
+ #### Update Version Number
28
+
29
+ Edit `lib/spree_packeta/version.rb`:
30
+
31
+ ```ruby
32
+ module SpreePacketa
33
+ VERSION = '1.0.0' # Change to your new version
34
+ end
35
+ ```
36
+
37
+ Follow [Semantic Versioning](https://semver.org/):
38
+ - **MAJOR** (1.0.0): Breaking changes
39
+ - **MINOR** (0.2.0): New features, backward compatible
40
+ - **PATCH** (0.1.1): Bug fixes, backward compatible
41
+
42
+ #### Update CHANGELOG.md
43
+
44
+ Move items from `[Unreleased]` to a new version section:
45
+
46
+ ```markdown
47
+ ## [Unreleased]
48
+
49
+ ### Added
50
+ - (empty after release)
51
+
52
+ ## [1.0.0] - 2024-12-21
53
+
54
+ ### Added
55
+ - New feature X
56
+ - New feature Y
57
+
58
+ ### Fixed
59
+ - Bug fix Z
60
+
61
+ [Unreleased]: https://github.com/yourusername/spree_packeta/compare/v1.0.0...HEAD
62
+ [1.0.0]: https://github.com/yourusername/spree_packeta/compare/v0.1.0...v1.0.0
63
+ ```
64
+
65
+ #### Update Documentation
66
+
67
+ If needed, update:
68
+ - `README.md` - Installation version numbers
69
+ - Code examples with new features
70
+ - Roadmap section
71
+
72
+ ### 2. Pre-Release Checks
73
+
74
+ Run these commands locally:
75
+
76
+ ```bash
77
+ # Install dependencies
78
+ bundle install
79
+
80
+ # Run tests
81
+ bundle exec rspec
82
+
83
+ # Run linter (if configured)
84
+ bundle exec rubocop
85
+
86
+ # Build gem locally to verify
87
+ gem build spree_packeta.gemspec
88
+ # Should output: Successfully built RubyGem
89
+ # Should create: spree_packeta-1.0.0.gem
90
+
91
+ # Clean up test gem
92
+ rm spree_packeta-*.gem
93
+ ```
94
+
95
+ All checks must pass before proceeding.
96
+
97
+ ### 3. Commit and Tag
98
+
99
+ ```bash
100
+ # Stage changes
101
+ git add lib/spree_packeta/version.rb CHANGELOG.md README.md
102
+
103
+ # Commit with descriptive message
104
+ git commit -m "Prepare v1.0.0 release
105
+
106
+ - Update version to 1.0.0
107
+ - Update CHANGELOG with new features and fixes
108
+ - Update README documentation
109
+ "
110
+
111
+ # Push to main
112
+ git push origin main
113
+
114
+ # Create annotated tag
115
+ git tag -a v1.0.0 -m "Release v1.0.0"
116
+
117
+ # Push tag (this triggers the release workflow)
118
+ git push origin v1.0.0
119
+ ```
120
+
121
+ ### 4. Monitor Release Workflow
122
+
123
+ 1. Go to GitHub Actions: https://github.com/yourusername/spree_packeta/actions
124
+ 2. Find the "Release Gem to RubyGems and GitHub Packages" workflow
125
+ 3. Watch the progress through three stages:
126
+ - ✅ Validate Release (version check)
127
+ - ✅ Run Tests (RSpec)
128
+ - ✅ Build and Publish (RubyGems.org + GitHub Packages + GitHub Release)
129
+
130
+ Expected duration: 3-5 minutes
131
+
132
+ ### 5. Verify Release
133
+
134
+ After workflow completes:
135
+
136
+ #### Check RubyGems.org
137
+ ```bash
138
+ # Search for new version
139
+ gem search spree_packeta
140
+
141
+ # Install and verify
142
+ gem install spree_packeta -v 1.0.0
143
+ ```
144
+
145
+ Or visit: https://rubygems.org/gems/spree_packeta
146
+
147
+ #### Check GitHub Packages
148
+
149
+ Visit: https://github.com/yourusername/spree_packeta/packages
150
+
151
+ Should see:
152
+ - Package: spree_packeta
153
+ - Latest version: 1.0.0
154
+ - Published timestamp
155
+
156
+ Or verify via command line (requires GitHub authentication):
157
+ ```bash
158
+ # Configure bundler to use GitHub Packages
159
+ bundle config set --local https://rubygems.pkg.github.com/yourusername <YOUR_GITHUB_TOKEN>
160
+
161
+ # Then install
162
+ gem install spree_packeta --source https://rubygems.pkg.github.com/yourusername
163
+ ```
164
+
165
+ #### Check GitHub Release
166
+
167
+ Visit: https://github.com/yourusername/spree_packeta/releases
168
+
169
+ Should see:
170
+ - Release title: "Release v1.0.0"
171
+ - Auto-generated release notes with installation instructions for both registries
172
+ - Attached gem file: `spree_packeta-1.0.0.gem`
173
+
174
+ #### Test Installation
175
+
176
+ In a test Rails app:
177
+
178
+ ```ruby
179
+ # Gemfile
180
+ gem 'spree_packeta', '~> 1.0.0'
181
+ ```
182
+
183
+ ```bash
184
+ bundle install
185
+ bundle exec rails g spree_packeta:install
186
+ ```
187
+
188
+ ### 6. Announce Release (Optional)
189
+
190
+ Consider announcing:
191
+ - Twitter/X post
192
+ - Company blog post
193
+ - Spree Community forums
194
+ - Email to users/clients
195
+
196
+ ## Troubleshooting
197
+
198
+ ### Version Mismatch Error
199
+
200
+ ```
201
+ ❌ ERROR: Version mismatch!
202
+ Git tag: v1.0.0
203
+ version.rb: 0.9.0
204
+ ```
205
+
206
+ **Solution**:
207
+ ```bash
208
+ # Delete the tag
209
+ git tag -d v1.0.0
210
+ git push --delete origin v1.0.0
211
+
212
+ # Update version.rb to 1.0.0
213
+ # Commit and re-tag
214
+ ```
215
+
216
+ ### Test Failures
217
+
218
+ ```
219
+ ❌ Run Tests failed
220
+ ```
221
+
222
+ **Solution**:
223
+ ```bash
224
+ # Run tests locally to debug
225
+ bundle exec rspec --format documentation
226
+
227
+ # Fix failing tests
228
+ # Commit fixes
229
+ # Delete and recreate tag
230
+ ```
231
+
232
+ ### RubyGems Publish Failed
233
+
234
+ ```
235
+ ❌ ERROR: There was a problem saving your gem: Name already exists
236
+ ```
237
+
238
+ **Cause**: Version already published
239
+
240
+ **Solution**:
241
+ ```bash
242
+ # Increment patch version
243
+ # Update version.rb to 1.0.1
244
+ # Commit and create new tag v1.0.1
245
+ ```
246
+
247
+ ### GitHub Release Failed
248
+
249
+ The workflow continues even if GitHub Release creation fails.
250
+
251
+ **Solution**: Create release manually:
252
+ ```bash
253
+ gh release create v1.0.0 \
254
+ --title "Release v1.0.0" \
255
+ --notes "See CHANGELOG.md for details" \
256
+ spree_packeta-1.0.0.gem
257
+ ```
258
+
259
+ ### Missing RubyGems API Key
260
+
261
+ ```
262
+ ❌ ERROR: Please sign up for an account at http://rubygems.org
263
+ ```
264
+
265
+ **Cause**: `RUBYGEMS_API_KEY` secret not configured or invalid
266
+
267
+ **Solution**:
268
+ 1. Login to rubygems.org
269
+ 2. Go to Edit Profile > API Keys
270
+ 3. Create new API key with "Push rubygems" scope
271
+ 4. Copy the token (shown once)
272
+ 5. Add to GitHub: Settings > Secrets and variables > Actions > New repository secret
273
+ - Name: `RUBYGEMS_API_KEY`
274
+ - Value: [paste token]
275
+
276
+ ## Rollback Procedures
277
+
278
+ ### Yank a Published Gem
279
+
280
+ If you need to remove a published version:
281
+
282
+ ```bash
283
+ # Mark version as yanked (users can't install, but existing installs work)
284
+ gem yank spree_packeta -v 1.0.0
285
+
286
+ # To undo yank
287
+ gem yank spree_packeta -v 1.0.0 --undo
288
+ ```
289
+
290
+ **Note**: Yanking should be rare and only for critical security issues.
291
+
292
+ ### Delete a GitHub Release
293
+
294
+ ```bash
295
+ # Delete release (keeps tag)
296
+ gh release delete v1.0.0 --yes
297
+
298
+ # Delete tag
299
+ git push --delete origin v1.0.0
300
+ git tag -d v1.0.0
301
+ ```
302
+
303
+ ## Setting up RubyGems API Key
304
+
305
+ For first-time setup or rotation:
306
+
307
+ ### 1. Generate API Key on RubyGems
308
+
309
+ 1. Login to https://rubygems.org
310
+ 2. Click on your profile > Edit Profile
311
+ 3. Navigate to "API Keys" section
312
+ 4. Click "New API Key"
313
+ 5. Configure the key:
314
+ - **Name**: "GitHub Actions - spree_packeta"
315
+ - **Scopes**: Check "Push rubygems" only
316
+ - **Gem**: Select "spree_packeta" (or leave blank for all)
317
+ - **Expiration**: Set appropriate expiration (or leave blank)
318
+ 6. Click "Create"
319
+ 7. **COPY THE KEY NOW** - it's shown only once
320
+
321
+ ### 2. Add to GitHub Secrets
322
+
323
+ 1. Go to repository on GitHub
324
+ 2. Settings > Secrets and variables > Actions
325
+ 3. Click "New repository secret"
326
+ 4. Name: `RUBYGEMS_API_KEY`
327
+ 5. Value: Paste the API key from step 1
328
+ 6. Click "Add secret"
329
+
330
+ ### 3. Verify Setup
331
+
332
+ Test with a patch release:
333
+
334
+ ```bash
335
+ # Update version to 0.1.1
336
+ # Update CHANGELOG
337
+ # Commit and tag
338
+ git tag v0.1.1
339
+ git push origin v0.1.1
340
+
341
+ # Watch workflow to ensure it completes successfully
342
+ ```
343
+
344
+ ## Security Notes
345
+
346
+ - Never commit API keys or credentials
347
+ - Rotate `RUBYGEMS_API_KEY` periodically (every 6-12 months)
348
+ - Use 2FA on RubyGems account
349
+ - Review dependency updates before releases
350
+ - Run `bundle audit` to check for vulnerabilities
351
+ - Limit API key scope to push-only permissions
352
+ - Set expiration dates on API keys
353
+
354
+ ## Installing from GitHub Packages (for users)
355
+
356
+ If users want to install your gem from GitHub Packages instead of RubyGems.org:
357
+
358
+ ### Option 1: Using Gemfile source block
359
+
360
+ ```ruby
361
+ # Gemfile
362
+ source 'https://rubygems.pkg.github.com/yourusername' do
363
+ gem 'spree_packeta', '~> 1.0.0'
364
+ end
365
+ ```
366
+
367
+ Then authenticate:
368
+ ```bash
369
+ bundle config set --local https://rubygems.pkg.github.com/yourusername <GITHUB_TOKEN>
370
+ bundle install
371
+ ```
372
+
373
+ ### Option 2: Using gem command
374
+
375
+ ```bash
376
+ # Configure credentials
377
+ gem sources --add https://rubygems.pkg.github.com/yourusername
378
+
379
+ # Install
380
+ gem install spree_packeta --source https://rubygems.pkg.github.com/yourusername
381
+ ```
382
+
383
+ **Note**: GitHub Packages requires authentication. Users need a GitHub Personal Access Token with `read:packages` scope.
384
+
385
+ ## FAQ
386
+
387
+ **Q: Can I release from a branch other than main?**
388
+ A: The workflow triggers on any tag push, regardless of branch. However, it's recommended to always release from main after merging all changes.
389
+
390
+ **Q: What if I need to release a hotfix?**
391
+ A: Create a hotfix branch, make changes, update version to a patch increment (e.g., 1.0.1), merge to main, then tag and push.
392
+
393
+ **Q: How do I handle pre-releases (beta, rc)?**
394
+ A: Currently, the workflow only supports stable releases (v1.0.0). For pre-releases, you'd need to manually publish or extend the tag regex pattern.
395
+
396
+ **Q: What happens if the workflow fails midway?**
397
+ A: The workflow is designed to fail-fast:
398
+ - If validation fails: Nothing is published
399
+ - If tests fail: Nothing is published
400
+ - If RubyGems.org publish fails: GitHub Packages is not published
401
+ - If GitHub Packages publish fails: Workflow continues (RubyGems.org is primary)
402
+ - If GitHub Release fails: Gems are already published (manual release creation needed)
403
+
404
+ **Q: What if GitHub Packages publishing fails?**
405
+ A: GitHub Packages publishing is considered non-critical. If it fails, the workflow continues and the gem is still available on RubyGems.org. You can manually publish to GitHub Packages later if needed.
406
+
407
+ **Q: How do I test the workflow without publishing?**
408
+ A: You can test validation and build steps by:
409
+ 1. Running `gem build` locally
410
+ 2. Checking the workflow logs on a test tag
411
+ 3. Using a separate test gem name for initial setup
412
+
413
+ **Q: Can I skip the tests?**
414
+ A: Not recommended. Tests are a quality gate. If you absolutely must, you can modify the workflow to remove the test job dependency.
415
+
416
+ **Q: Do users need special setup to use my gem from GitHub Packages?**
417
+ A: Yes, they need a GitHub Personal Access Token with `read:packages` scope. For most users, installing from RubyGems.org is simpler and recommended.
418
+
419
+ ## Questions?
420
+
421
+ Contact the maintainers or open an issue on GitHub.
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpreePacketa
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('templates', __dir__)
7
+
8
+ desc 'Installs Spree Packeta and generates necessary files'
9
+
10
+ def copy_initializer
11
+ template 'initializer.rb', 'config/initializers/spree_packeta.rb'
12
+ say 'Initializer created at config/initializers/spree_packeta.rb', :green
13
+ end
14
+
15
+ def copy_migrations
16
+ rake 'railties:install:migrations FROM=spree_packeta'
17
+ say 'Migrations copied', :green
18
+ end
19
+
20
+ def run_migrations
21
+ if options[:auto_run_migrations] || yes?('Run migrations now? (yes/no)')
22
+ rake 'db:migrate'
23
+ say 'Migrations completed', :green
24
+ else
25
+ say 'Skipping migrations. Run `rails db:migrate` manually', :yellow
26
+ end
27
+ end
28
+
29
+ def install_shipping_method
30
+ if options[:skip_shipping_method]
31
+ say 'Skipping shipping method installation', :yellow
32
+ elsif yes?('Install Packeta shipping method now? (yes/no)')
33
+ rake 'spree_packeta:install'
34
+ else
35
+ say 'You can install the shipping method later with: rake spree_packeta:install', :yellow
36
+ end
37
+ end
38
+
39
+ def show_readme
40
+ say ''
41
+ say '=' * 80, :green
42
+ say 'Spree Packeta Installation Complete!', :green
43
+ say '=' * 80, :green
44
+ say ''
45
+ say 'Next steps:', :yellow
46
+ say ''
47
+ say ' 1. Add your Packeta credentials to .env:', :cyan
48
+ say ' PACKETA_API_PASSWORD=your_api_password'
49
+ say ' PACKETA_ESHOP=your_eshop_id'
50
+ say ' PACKETA_WSDL_PATH=file:///path/to/soap.wsdl'
51
+ say ' PACKETA_SENDER_EMAIL=shipping@yourstore.com'
52
+ say ''
53
+ say ' 2. Restart your Rails server', :cyan
54
+ say ''
55
+ say ' 3. Configure shipping method in Spree admin', :cyan
56
+ say ' or run: rake spree_packeta:install (if you skipped it)'
57
+ say ''
58
+ say ' 4. Frontend integration:', :cyan
59
+ say ' See FRONTEND_INTEGRATION.md for React/frontend setup'
60
+ say ''
61
+ say 'Documentation:', :yellow
62
+ say ' - README.md - General usage'
63
+ say ' - FRONTEND_INTEGRATION.md - Frontend setup guide'
64
+ say ''
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ SpreePacketa.configure do |config|
4
+ # API credentials
5
+ # Get your API password from Packeta dashboard
6
+ config.api_password = ENV['PACKETA_API_PASSWORD']
7
+
8
+ # SOAP endpoint (production vs sandbox)
9
+ # Default: http://www.zasilkovna.cz/api/soap
10
+ # Sandbox: http://www.zasilkovna.cz/api/soap-php-bugfix (if available)
11
+ config.soap_endpoint = ENV.fetch('PACKETA_SOAP_ENDPOINT',
12
+ 'http://www.zasilkovna.cz/api/soap')
13
+
14
+ # WSDL path (can be local file or remote URL)
15
+ # Use local file for better performance: file:///path/to/soap.wsdl
16
+ # Or use remote: http://www.zasilkovna.cz/api/soap.wsdl
17
+ config.wsdl_path = ENV.fetch('PACKETA_WSDL_PATH',
18
+ 'http://www.zasilkovna.cz/api/soap.wsdl')
19
+
20
+ # Your Eshop identifier from Packeta
21
+ config.eshop = ENV['PACKETA_ESHOP']
22
+
23
+ # Sender information (for return labels and contact)
24
+ config.sender_name = ENV.fetch('PACKETA_SENDER_NAME', 'Your Store Name')
25
+ config.sender_email = ENV.fetch('PACKETA_SENDER_EMAIL', 'shipping@yourstore.com')
26
+ config.sender_phone = ENV.fetch('PACKETA_SENDER_PHONE', '+420123456789')
27
+
28
+ # Rate caching duration (in seconds)
29
+ # How long to cache shipping rate calculations
30
+ config.rate_cache_duration = 3600 # 1 hour
31
+
32
+ # Auto-create packets on shipment
33
+ # Set to true to automatically create Packeta packets when order is shipped
34
+ config.auto_create_packets = true
35
+
36
+ # Tracking sync interval (in seconds)
37
+ # How often to sync tracking status from Packeta
38
+ config.tracking_sync_interval = 3600 # 1 hour
39
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpreePacketa
4
+ class Configuration
5
+ # API credentials
6
+ attr_accessor :api_password
7
+
8
+ # SOAP endpoint (production vs sandbox)
9
+ attr_accessor :soap_endpoint
10
+
11
+ # Default eshop identifier
12
+ attr_accessor :eshop
13
+
14
+ # Sender information for return labels
15
+ attr_accessor :sender_name
16
+ attr_accessor :sender_email
17
+ attr_accessor :sender_phone
18
+
19
+ # Rate caching duration (in seconds)
20
+ attr_accessor :rate_cache_duration
21
+
22
+ # Auto-create packets on shipment
23
+ attr_accessor :auto_create_packets
24
+
25
+ # Sync tracking status interval
26
+ attr_accessor :tracking_sync_interval
27
+
28
+ # WSDL file path (can be local or remote)
29
+ attr_accessor :wsdl_path
30
+
31
+ def initialize
32
+ @soap_endpoint = 'http://www.zasilkovna.cz/api/soap'
33
+ @rate_cache_duration = 3600 # 1 hour
34
+ @auto_create_packets = true
35
+ @tracking_sync_interval = 3600 # 1 hour in seconds
36
+ @wsdl_path = 'http://www.zasilkovna.cz/api/soap.wsdl'
37
+ end
38
+ end
39
+
40
+ class << self
41
+ attr_writer :configuration
42
+
43
+ def configuration
44
+ @configuration ||= Configuration.new
45
+ end
46
+
47
+ def configure
48
+ yield(configuration)
49
+ end
50
+
51
+ def reset_configuration!
52
+ @configuration = Configuration.new
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spree_core'
4
+
5
+ module SpreePacketa
6
+ class Engine < Rails::Engine
7
+ require 'spree/core'
8
+ isolate_namespace Spree
9
+ engine_name 'spree_packeta'
10
+
11
+ config.autoload_paths += %W[#{config.root}/lib]
12
+
13
+ # use rspec for tests
14
+ config.generators do |g|
15
+ g.test_framework :rspec
16
+ end
17
+
18
+ def self.activate
19
+ # Load decorators
20
+ Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
21
+ Rails.configuration.cache_classes ? require(c) : load(c)
22
+ end
23
+
24
+ # Load gem components after Spree has been initialized
25
+ require 'spree_packeta/soap/client'
26
+ require 'spree_packeta/soap/operations'
27
+ require 'spree_packeta/models/shipping_method'
28
+ require 'spree_packeta/models/shipping_calculator'
29
+ require 'spree_packeta/services/packet_creator'
30
+ require 'spree_packeta/services/tracker'
31
+ end
32
+
33
+ config.to_prepare(&method(:activate).to_proc)
34
+
35
+ initializer 'spree_packeta.environment', before: :load_config_initializers do |_app|
36
+ SpreePacketa.configure do |config|
37
+ # Load configuration from environment variables
38
+ config.api_password = ENV['PACKETA_API_PASSWORD'] if ENV['PACKETA_API_PASSWORD']
39
+ config.soap_endpoint = ENV['PACKETA_SOAP_ENDPOINT'] if ENV['PACKETA_SOAP_ENDPOINT']
40
+ config.eshop = ENV['PACKETA_ESHOP'] if ENV['PACKETA_ESHOP']
41
+ config.sender_name = ENV['PACKETA_SENDER_NAME'] if ENV['PACKETA_SENDER_NAME']
42
+ config.sender_email = ENV['PACKETA_SENDER_EMAIL'] if ENV['PACKETA_SENDER_EMAIL']
43
+ config.sender_phone = ENV['PACKETA_SENDER_PHONE'] if ENV['PACKETA_SENDER_PHONE']
44
+ config.wsdl_path = ENV['PACKETA_WSDL_PATH'] if ENV['PACKETA_WSDL_PATH']
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SpreePacketa
4
+ module Models
5
+ class ShippingCalculator < Spree::ShippingCalculator
6
+ # Preference for base shipping rate
7
+ preference :amount, :decimal, default: 99.0
8
+ preference :currency, :string, default: 'CZK'
9
+
10
+ # Calculate shipping rate for a package
11
+ #
12
+ # @param package [Spree::Stock::Package] The package to calculate for
13
+ # @return [BigDecimal] The shipping cost
14
+ def compute_package(package)
15
+ return 0 if package.contents.empty?
16
+
17
+ # For Phase 1, use fixed rate from preferences
18
+ # Phase 2 will implement dynamic rate calculation via Packeta API
19
+ base_rate = BigDecimal(preferred_amount.to_s)
20
+
21
+ # Apply weight-based multiplier if needed
22
+ total_weight = calculate_total_weight(package)
23
+ weight_multiplier = calculate_weight_multiplier(total_weight)
24
+
25
+ (base_rate * weight_multiplier).round(2)
26
+ end
27
+
28
+ private
29
+
30
+ def calculate_total_weight(package)
31
+ package.contents.sum do |item|
32
+ weight = item.variant.weight.to_f
33
+ quantity = item.quantity
34
+ weight * quantity
35
+ end
36
+ end
37
+
38
+ def calculate_weight_multiplier(weight)
39
+ # Simple weight-based pricing tiers
40
+ # Adjust these based on your business needs
41
+ case weight
42
+ when 0..5
43
+ 1.0
44
+ when 5..10
45
+ 1.2
46
+ when 10..20
47
+ 1.5
48
+ else
49
+ 2.0
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end