solidus_fulfillment 2.0.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8cfed147ac17cacd2145fdcf48a2ceb3c3c227a8
4
+ data.tar.gz: 4f21644a4f060d33977c0e9a68ded129b77e366c
5
+ SHA512:
6
+ metadata.gz: d2bfcbcef93c29bd6f451cd80273447c449e4e35c3d8bc857da22adc32a43e15c22c3ace44a5cb771879ebac48dcac58380b8d3a824e1319af1252af074cd9e0
7
+ data.tar.gz: ce90938443ba6f680edf947ee85bbd8737e032d2b805d62ef5db1d6ac3604605acefcdea4bd8488124ef3042d331ce5fc8d1702e7113bebf89a9e8a300ed2ae6
@@ -0,0 +1,14 @@
1
+ \#*
2
+ *~
3
+ .#*
4
+ .DS_Store
5
+ .idea
6
+ .project
7
+ .sass-cache
8
+ coverage
9
+ Gemfile.lock
10
+ tmp
11
+ nbproject
12
+ pkg
13
+ *.swp
14
+ spec/dummy
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2017 Rémy Coutable, released under the New BSD License
2
+ Copyright (c) 2011 WIMM Labs, released under the New BSD License
3
+ All rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without modification,
6
+ are permitted provided that the following conditions are met:
7
+
8
+ * Redistributions of source code must retain the above copyright notice,
9
+ this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+ * Neither the name of the Rails Dog LLC nor the names of its
14
+ contributors may be used to endorse or promote products derived from this
15
+ software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
21
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
22
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,74 @@
1
+ SolidusFulfillment is a solidus extension to do fulfillment processing via
2
+ various fulfillment services when a shipment becomes ready.
3
+
4
+ The extension adds an additional state to the Shipment state machine called
5
+ `fulfilling` which acts as the transition between `ready` and `shipped`.
6
+
7
+ When a shipment becomes `ready` it is eligible for fulfillment:
8
+
9
+ 1. A `solidus_fulfillment:process` rake task intended to be called from a cron
10
+ job checks for `ready` shipments (by delegating to the
11
+ `solidus_fulfillment:process:ready` task) and initiates the fulfillment via
12
+ the merchant API.
13
+ 1. If the fulfillment transaction succeeds, the shipment enters the `fulfilling`
14
+ state.
15
+ 1. The `solidus_fulfillment:process:fulfilling` rake task then queries the
16
+ merchant's API for tracking numbers of any orders that are being fulfilled.
17
+ 1. If the tracking numbers are found, the shipment transitions into the
18
+ `shipped` state and an email is sent to the customer.
19
+
20
+ Stock levels can also be updated with the
21
+ `solidus_fulfillment:process:stock_levels` rake task which is intended to be
22
+ called from a cron job.
23
+
24
+ ## Installation
25
+
26
+ ### Add to your gemfile:
27
+
28
+ ```ruby
29
+ gem 'whenever', require: false # if you want whenever to manage the cron job
30
+ gem 'solidus_fulfillment'
31
+ ```
32
+
33
+ ### Create config/fulfillment.yml:
34
+
35
+ ```yml
36
+ development:
37
+ adapter: amazon
38
+ api_key: <YOUR AMAZON AWS API KEY>
39
+ secret_key: <YOUR AMAZON AWS SECRET KEY>
40
+ development_mode: true
41
+
42
+ test:
43
+ adapter: amazon
44
+ api_key: <YOUR AMAZON AWS API KEY>
45
+ secret_key: <YOUR AMAZON AWS SECRET KEY>
46
+
47
+ production:
48
+ adapter: amazon
49
+ api_key: <YOUR AMAZON AWS API KEY>
50
+ secret_key: <YOUR AMAZON AWS SECRET KEY>
51
+ ```
52
+
53
+ ### Create config/schedule.rb:
54
+
55
+ ```ruby
56
+ every :hour do
57
+ rake "solidus_fulfillment:process"
58
+ end
59
+ ```
60
+
61
+ ### Add to deploy.rb:
62
+
63
+ ```ruby
64
+ require 'whenever/capistrano' # if you want whenever to manage the cron job
65
+ ```
66
+
67
+ ### Configure the store
68
+
69
+ Set the SKU code for your products to be equal to the Amazon fulfillment SKU code.
70
+
71
+ ----
72
+
73
+ Copyright (c) 2017 Rémy Coutable, released under the New BSD License
74
+ Copyright (c) 2011 WIMM Labs, released under the New BSD License
@@ -0,0 +1,15 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ require 'spree/testing_support/common_rake'
6
+
7
+ RSpec::Core::RakeTask.new
8
+
9
+ task :default => [:spec]
10
+
11
+ desc 'Generates a dummy app for testing'
12
+ task :test_app do
13
+ ENV['LIB_NAME'] = 'spree_fulfillment'
14
+ Rake::Task['common:test_app'].invoke
15
+ end
@@ -0,0 +1,4 @@
1
+ # This file is used to designate compatibilty with different versions of Spree
2
+ # Please see http://spreecommerce.com/documentation/extensions.html#versionfile for details
3
+
4
+ '1.3.x' => { :branch => 'master' }
@@ -0,0 +1,134 @@
1
+ module Spree
2
+ class Fulfillment
3
+ CONFIG_FILE = Rails.root.join('config/fulfillment.yml')
4
+ CONFIG = HashWithIndifferentAccess.new(YAML.load_file(CONFIG_FILE)[Rails.env])
5
+
6
+ TrackingInfo = Struct.new(:carrier, :tracking_number, :ship_time) do
7
+ def to_hash
8
+ {
9
+ carrier: carrier,
10
+ tracking_number: tracking_number,
11
+ ship_time: ship_time
12
+ }
13
+ end
14
+ end
15
+
16
+ def self.service(shipment = nil)
17
+ ('Solidus::Fulfillment::' + "#{adapter}_fulfillment".camelize).constantize.new(shipment)
18
+ rescue NameError
19
+ require "solidus/fulfillment/#{adapter}_fulfillment"
20
+ retry
21
+ rescue LoadError
22
+ log "Spree::Fulfillment.service: cannot load #{'Solidus::Fulfillment::' + "#{adapter}_fulfillment".camelize}"
23
+ end
24
+
25
+ def self.adapter
26
+ return @adapter if defined?(@adapter)
27
+
28
+ @adapter = config[:adapter]
29
+
30
+ unless @adapter
31
+ raise "Missing adapter for #{Rails.env} -- Check config/fulfillment.yml"
32
+ end
33
+
34
+ @adapter
35
+ end
36
+
37
+ def self.fulfill(shipment)
38
+ service(shipment).fulfill
39
+ end
40
+
41
+ def self.config
42
+ CONFIG
43
+ end
44
+
45
+ def self.log(msg)
46
+ Rails.logger.info "**** solidus_fulfillment: #{msg}"
47
+ end
48
+
49
+ # Passes any shipments that are ready to the fulfillment service
50
+ def self.process_ready
51
+ log 'Spree::Fulfillment.process_ready start'
52
+
53
+ Spree::Shipment.ready.ids.each do |shipment_id|
54
+ shipment = Spree::Shipment.find(shipment_id)
55
+
56
+ next unless shipment && shipment.ready?
57
+
58
+ log "Request to ship shipment ##{shipment.id}"
59
+ begin
60
+ shipment.ship!
61
+ rescue => ex
62
+ log "Spree::Fulfillment.process_ready: Failed to ship id #{shipment.id} due to #{ex}"
63
+ Airbrake.notify(e) if defined?(Airbrake)
64
+ # continue on and try other shipments so that one bad shipment doesn't
65
+ # block an entire queue
66
+ end
67
+ end
68
+ end
69
+
70
+ # Gets tracking number and sends ship email when fulfillment house is done
71
+ def self.process_fulfilling
72
+ log 'Spree::Fulfillment.process_fulfilling start'
73
+
74
+ Spree::Shipment.fulfilling.each do |shipment|
75
+ next if shipment.shipped?
76
+
77
+ tracking_info = remote_tracking_info(shipment)
78
+ log "Spree::Fulfillment.process_fulfilling: tracking_info #{tracking_info}"
79
+ next unless tracking_info
80
+
81
+ if tracking_info == :error
82
+ log 'Spree::Fulfillment.process_fulfilling: Could not retrieve' \
83
+ "tracking information for shipment #{shipment.id} (order ID: "\
84
+ "#{shipment.number})"
85
+ shipment.cancel
86
+ else
87
+ log 'Spree::Fulfillment.process_fulfilling: Tracking information: ' \
88
+ "#{tracking_info.inspect}"
89
+ shipment.attributes = {
90
+ shipped_at: tracking_info.ship_time,
91
+ tracking: "#{tracking_info.carrier}::#{tracking_info.tracking_number}"
92
+ }
93
+ shipment.ship!
94
+ end
95
+ end
96
+ end
97
+
98
+ def self.process_stock_levels
99
+ log 'Spree::Fulfillment.process_stock_levels start'
100
+
101
+ skus = Spree::Variant.pluck(:sku)
102
+ default_stock_location = Spree::StockLocation.find_by(name: 'default')
103
+
104
+ response = service.fetch_stock_levels(skus)
105
+
106
+ response.params['stock_levels'].each do |sku, stock|
107
+ variant = Spree::Variant.find_by(sku: sku)
108
+ variant.stock_items.
109
+ find_by(stock_location_id: default_stock_location.id).
110
+ set_count_on_hand(stock)
111
+ log "Spree::Fulfillment.process_stock_levels: variant #{variant.inspect} has a new stock level of #{stock}"
112
+ end
113
+ end
114
+
115
+ def self.remote_tracking_info(shipment)
116
+ response = service(shipment).fetch_tracking_data
117
+ return unless response
118
+
119
+ tracking_info = TrackingInfo.new(
120
+ response.params.dig('tracking_companies', shipment.number.to_s)&.first,
121
+ response.params.dig('tracking_numbers', shipment.number.to_s)&.first,
122
+ response.params.dig('shipping_date_times', shipment.number.to_s)&.first
123
+ )
124
+
125
+ unless tracking_info.carrier &&
126
+ tracking_info.tracking_number &&
127
+ tracking_info.ship_time
128
+ return :error
129
+ end
130
+
131
+ tracking_info.to_hash
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,87 @@
1
+ Spree::Shipment.class_eval do
2
+ scope :fulfilling, -> { with_state('fulfilling') }
3
+ scope :fulfill_failed, -> { with_state('fulfill_failed') }
4
+
5
+ state_machines[:state] = nil # reset original state machine to start from scratch.
6
+
7
+ # This is a modified version of the original spree shipment state machine
8
+ # with the indicated changes.
9
+ state_machine initial: :pending, use_transactions: false do
10
+ event :ready do
11
+ transition from: :pending, to: :shipped, if: :can_transition_from_pending_to_shipped?
12
+ transition from: :pending, to: :ready, if: :can_transition_from_pending_to_ready?
13
+ end
14
+
15
+ event :pend do
16
+ transition from: :ready, to: :pending
17
+ end
18
+
19
+ event :ship do
20
+ transition from: [:ready, :canceled], to: :fulfilling # was to: :shipped
21
+ # new transition
22
+ transition from: :fulfilling, to: :shipped
23
+ end
24
+ after_transition to: :shipped, do: :after_ship
25
+
26
+ # new callback
27
+ before_transition to: :fulfilling, do: :before_fulfilling
28
+
29
+ event :cancel do
30
+ transition to: :canceled, from: [:pending, :ready]
31
+ # new transition
32
+ transition from: :fulfilling, to: :fulfill_failed
33
+ end
34
+ after_transition to: :canceled, do: :after_cancel
35
+
36
+ event :resume do
37
+ transition from: :canceled, to: :ready, if: :can_transition_from_canceled_to_ready?
38
+ transition from: :canceled, to: :pending
39
+ end
40
+ after_transition from: :canceled, to: [:pending, :ready, :shipped], do: :after_resume
41
+
42
+ after_transition do |shipment, transition|
43
+ shipment.state_changes.create!(
44
+ previous_state: transition.from,
45
+ next_state: transition.to,
46
+ name: 'shipment'
47
+ )
48
+ end
49
+ end
50
+
51
+ # If there's an error submitting to the fulfillment service, we should halt
52
+ # the transition to 'fulfill' and stay in 'ready'. That way transient errors
53
+ # will get rehandled. If there are persistent errors, that should be treated
54
+ # as a bug.
55
+ def before_fulfilling
56
+ response = Spree::Fulfillment.fulfill(self) # throws :halt on error, which aborts transition
57
+
58
+ # Stop the transition to shipped if there was an error.
59
+ unless response.success?
60
+ if Spree::Fulfillment.config[:development_mode] &&
61
+ response.params['faultstring'] =~ /the SellerSKU for Item Id: \S+ is invalid/
62
+ Spree::Fulfillment.log 'Ignoring missing catalog item (test / dev setting - should not see this on prod)'
63
+ else
64
+ Spree::Fulfillment.log 'Abort - response was in error'
65
+ throw :halt
66
+ end
67
+ end
68
+ # TODO: Narrow down the catched exception
69
+ rescue => ex
70
+ Spree::Fulfillment.log "Spree::Shipment#before_fulfilling failed: #{ex.message}" \
71
+ "\n#{ex.backtrace}"
72
+ throw :halt
73
+ end
74
+
75
+ alias_method :orig_determine_state, :determine_state
76
+ # Determines the appropriate +state+ according to the following logic:
77
+ #
78
+ # canceled if order is canceled
79
+ # pending unless order is complete and +order.payment_state+ is +paid+
80
+ # shipped if already shipped (ie. does not change the state)
81
+ # ready all other cases
82
+ def determine_state(order)
83
+ return state if ['fulfilling', 'fulfill_failed', 'shipped'].include?(state)
84
+
85
+ orig_determine_state(order)
86
+ end
87
+ end
@@ -0,0 +1,8 @@
1
+ module Solidus
2
+ module Fulfillment
3
+ module Generators
4
+ class InstallGenerator < Rails::Generators::Base
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,189 @@
1
+ require 'active_fulfillment'
2
+
3
+ ActiveFulfillment::Service.logger = Rails.logger
4
+ ActiveFulfillment::AmazonMarketplaceWebService.class_eval do
5
+ # Monkeypatch of the original parse_tracking_response to include the shipping date.
6
+ # Changed lines are marked.
7
+ def parse_tracking_response(document)
8
+ response = {
9
+ tracking_numbers: {},
10
+ tracking_companies: {},
11
+ tracking_urls: {},
12
+ shipping_date_times: {} # Additional key
13
+ }
14
+
15
+ tracking_numbers = document.css('FulfillmentShipmentPackage > member > TrackingNumber'.freeze)
16
+ if tracking_numbers.present?
17
+ order_id = document.at_css('FulfillmentOrder > SellerFulfillmentOrderId'.freeze).text.strip
18
+ response[:tracking_numbers][order_id] = tracking_numbers.map{ |t| t.text.strip }
19
+ end
20
+
21
+ tracking_companies = document.css('FulfillmentShipmentPackage > member > CarrierCode'.freeze)
22
+ if tracking_companies.present?
23
+ order_id = document.at_css('FulfillmentOrder > SellerFulfillmentOrderId'.freeze).text.strip
24
+ response[:tracking_companies][order_id] = tracking_companies.map{ |t| t.text.strip }
25
+ end
26
+
27
+ # Changes start here
28
+ shipping_date_times = document.css('FulfillmentShipment > member > ShippingDateTime'.freeze)
29
+ if shipping_date_times.present?
30
+ response[:shipping_date_times][order_id] = shipping_date_times.map { |t| t.text.strip }
31
+ end
32
+ # Changes end here
33
+
34
+ response[:response_status] = SUCCESS
35
+
36
+ Response.new(success?(response), message_from(response), response)
37
+ end
38
+ end
39
+
40
+ module Solidus
41
+ module Fulfillment
42
+ class AmazonFulfillment
43
+ def initialize(shipment = nil)
44
+ @shipment = shipment
45
+ end
46
+
47
+ # Runs inside a state_machine callback. So throwing :halt is how we abort things.
48
+ def fulfill
49
+ sleep 1 # avoid throttle from Amazon
50
+
51
+ response = remote.fulfill(order_id, address, line_items, options)
52
+ Spree::Fulfillment.log "Spree::AmazonFulfillment#fulfill: order_id: " \
53
+ "#{order_id}\naddress: #{address}\nline_items: #{line_items}\noptions: " \
54
+ "#{options}\nresponse: #{response.params}"
55
+
56
+ response
57
+ end
58
+
59
+ # Returns the tracking number if there is one, else :error if there's a
60
+ # problem with the shipment that will result in a permanent failure to
61
+ # fulfill, else nil.
62
+ def fetch_tracking_data
63
+ sleep 1 # avoid throttle from Amazon
64
+
65
+ response = begin
66
+ remote.fetch_tracking_data([order_id])
67
+ rescue => ex
68
+ Spree::Fulfillment.log 'Spree::AmazonFulfillment#fetch_tracking_data: Failed to get ' \
69
+ "tracking info for shipment #{@shipment.id} (order ID: #{order_id})"
70
+ Spree::Fulfillment.log "Spree::AmazonFulfillment#fetch_tracking_data: #{ex}"
71
+ Airbrake.notify(e) if defined?(Airbrake)
72
+
73
+ return nil
74
+ end
75
+
76
+ Spree::Fulfillment.log "Spree::AmazonFulfillment#fetch_tracking_data: #{response.params}"
77
+
78
+ response
79
+ end
80
+
81
+ # Returns the stock levels for the given skus
82
+ def fetch_stock_levels(skus)
83
+ sleep 1 # avoid throttle from Amazon
84
+
85
+ response = begin
86
+ remote.fetch_stock_levels(skus: skus)
87
+ rescue => ex
88
+ Spree::Fulfillment.log 'Spree::AmazonFulfillment#fetch_stock_levels: Failed to get ' \
89
+ "stock levels"
90
+ Spree::Fulfillment.log "Spree::AmazonFulfillment#fetch_stock_levels: #{ex}"
91
+ Airbrake.notify(e) if defined?(Airbrake)
92
+
93
+ return nil
94
+ end
95
+
96
+ Spree::Fulfillment.log "Spree::AmazonFulfillment#fetch_stock_levels: #{response.params}"
97
+
98
+ response
99
+ end
100
+
101
+ private
102
+
103
+ # For Amazon these are the API access key and secret.
104
+ def credentials
105
+ @credentials ||= {
106
+ login: Spree::Fulfillment.config[:api_key],
107
+ password: Spree::Fulfillment.config[:secret_key],
108
+ seller_id: Spree::Fulfillment.config[:seller_id]
109
+ }
110
+ end
111
+
112
+ def remote
113
+ @remote ||= ActiveFulfillment::Base.service('amazon_marketplace_web').new(credentials)
114
+ end
115
+
116
+ def order_id
117
+ @order_id ||= @shipment.number
118
+ end
119
+
120
+ def address
121
+ @address ||= begin
122
+ ship_address = @shipment.order.ship_address
123
+
124
+ {
125
+ name: "#{ship_address.firstname} #{ship_address.lastname}",
126
+ address1: ship_address.address1,
127
+ address2: ship_address.address2,
128
+ city: ship_address.city,
129
+ state: ship_address.state.abbr,
130
+ country: ship_address.state.country.iso,
131
+ zip: ship_address.zipcode
132
+ }
133
+ end
134
+ end
135
+
136
+ def max_quantity_failsafe(quantity)
137
+ return quantity unless Spree::Fulfillment.config[:max_quantity_failsafe]
138
+
139
+ [Spree::Fulfillment.config[:max_quantity_failsafe], quantity].min
140
+ end
141
+
142
+ def line_items
143
+ @line_items ||= begin
144
+ skus = @shipment.inventory_units.map do |inventory_unit|
145
+ sku = inventory_unit.variant.sku
146
+ raise "Missing sku for #{inventory_unit.variant}" if sku.blank?
147
+ sku
148
+ end.uniq
149
+
150
+ skus.map do |sku|
151
+ quantity = @shipment.inventory_units.select do |inventory_unit|
152
+ inventory_unit.variant.sku == sku
153
+ end.size
154
+
155
+ {
156
+ sku: sku,
157
+ quantity: max_quantity_failsafe(quantity)
158
+ }
159
+ end
160
+ end
161
+ end
162
+
163
+ def shipping_method
164
+ return @shipping_method if defined?(@shipping_method)
165
+
166
+ raw_shipping_method = @shipment.shipping_method
167
+ @shipping_method = 'Standard' unless raw_shipping_method
168
+ @shipping_method ||=
169
+ case raw_shipping_method.name.downcase
170
+ when /expedited/
171
+ 'Expedited'
172
+ when /priority/
173
+ 'Priority'
174
+ else
175
+ 'Standard'
176
+ end
177
+ end
178
+
179
+ def options
180
+ @options ||= {
181
+ shipping_method: shipping_method,
182
+ order_date: @shipment.order.created_at,
183
+ comment: 'Thank you for your order.',
184
+ email: @shipment.order.email
185
+ }
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,14 @@
1
+ module Solidus
2
+ module Fulfillment
3
+ class Engine < Rails::Engine
4
+ isolate_namespace Spree
5
+ engine_name 'solidus_fulfillment'
6
+
7
+ config.to_prepare do
8
+ Dir.glob(File.join(__dir__, '../../../app/**/*_decorator*.rb')) do |c|
9
+ require_dependency(c)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,2 @@
1
+ require 'solidus_core'
2
+ require 'solidus/fulfillment/engine'
@@ -0,0 +1,24 @@
1
+ namespace :solidus_fulfillment do
2
+ desc "Handles shipments that are ready for or have completed fulfillment"
3
+ task process: :environment do
4
+ Rake::Task['solidus_fulfillment:process:ready'].invoke
5
+ Rake::Task['solidus_fulfillment:process:fulfilling'].invoke
6
+ end
7
+
8
+ namespace :process do
9
+ desc "Passes any shipments that are ready to the fulfillment service"
10
+ task ready: :environment do
11
+ Spree::Fulfillment.process_ready
12
+ end
13
+
14
+ desc "Gets tracking number and sends ship email when fulfillment house is done"
15
+ task fulfilling: :environment do
16
+ Spree::Fulfillment.process_fulfilling
17
+ end
18
+
19
+ desc "Updates the stock levels"
20
+ task stock_levels: :environment do
21
+ Spree::Fulfillment.process_stock_levels
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,7 @@
1
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
2
+
3
+ ENGINE_ROOT = File.expand_path('../..', __FILE__)
4
+ ENGINE_PATH = File.expand_path('../../lib/spree_fulfillment/engine', __FILE__)
5
+
6
+ require 'rails/all'
7
+ require 'rails/engine/commands'
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+ Gem::Specification.new do |s|
3
+ s.platform = Gem::Platform::RUBY
4
+ s.name = 'solidus_fulfillment'
5
+ s.version = '2.0.0'
6
+ s.summary = 'Solidus extension to do fulfillment processing via various services when a shipment becomes ready'
7
+ s.description = 'Solidus extension to do fulfillment processing via various services when a shipment becomes ready'
8
+
9
+ s.required_ruby_version = '>= 2.2.7'
10
+
11
+ s.author = 'Rémy Coutable'
12
+ s.email = 'remy@rymai.me'
13
+ s.homepage = 'https://rubygems.org/gems/solidus_fulfillment'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.require_path = 'lib'
18
+ s.requirements << 'none'
19
+
20
+ s.add_dependency 'solidus_core', '~> 2.1'
21
+ s.add_dependency 'active_fulfillment'
22
+
23
+ # s.add_development_dependency 'capybara', '~> 1.1.2'
24
+ # s.add_development_dependency 'coffee-rails'
25
+ # s.add_development_dependency 'factory_girl', '~> 2.6.4'
26
+ # s.add_development_dependency 'ffaker'
27
+ # s.add_development_dependency 'rspec-rails', '~> 2.9'
28
+ # s.add_development_dependency 'sass-rails'
29
+ # s.add_development_dependency 'sqlite3'
30
+ end
@@ -0,0 +1,46 @@
1
+ # Configure Rails Environment
2
+ ENV['RAILS_ENV'] = 'test'
3
+
4
+ require File.expand_path('../dummy/config/environment.rb', __FILE__)
5
+
6
+ require 'rspec/rails'
7
+ require 'ffaker'
8
+
9
+ # Requires supporting ruby files with custom matchers and macros, etc,
10
+ # in spec/support/ and its subdirectories.
11
+ Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
12
+
13
+ # Requires factories defined in spree_core
14
+ require 'spree/core/testing_support/factories'
15
+ require 'spree/core/testing_support/controller_requests'
16
+ require 'spree/core/testing_support/authorization_helpers'
17
+ require 'spree/core/url_helpers'
18
+
19
+ RSpec.configure do |config|
20
+ config.include FactoryGirl::Syntax::Methods
21
+
22
+ # == URL Helpers
23
+ #
24
+ # Allows access to Spree's routes in specs:
25
+ #
26
+ # visit spree.admin_path
27
+ # current_path.should eql(spree.products_path)
28
+ config.include Spree::Core::UrlHelpers
29
+
30
+ # == Mock Framework
31
+ #
32
+ # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
33
+ #
34
+ # config.mock_with :mocha
35
+ # config.mock_with :flexmock
36
+ # config.mock_with :rr
37
+ config.mock_with :rspec
38
+
39
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
40
+ config.fixture_path = "#{::Rails.root}/spec/fixtures"
41
+
42
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
43
+ # examples within a transaction, remove the following line or assign false
44
+ # instead of true.
45
+ config.use_transactional_fixtures = true
46
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solidus_fulfillment
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Rémy Coutable
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-04-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: solidus_core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: active_fulfillment
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Solidus extension to do fulfillment processing via various services when
42
+ a shipment becomes ready
43
+ email: remy@rymai.me
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".rspec"
50
+ - Gemfile
51
+ - LICENSE
52
+ - README.md
53
+ - Rakefile
54
+ - Versionfile
55
+ - app/models/spree/fulfillment.rb
56
+ - app/models/spree/shipment_decorator.rb
57
+ - lib/generators/solidus/fulfillment/install/install_generator.rb
58
+ - lib/solidus/fulfillment/amazon_fulfillment.rb
59
+ - lib/solidus/fulfillment/engine.rb
60
+ - lib/solidus_fulfillment.rb
61
+ - lib/tasks/solidus_fulfillment.rake
62
+ - script/rails
63
+ - solidus_fulfillment.gemspec
64
+ - spec/spec_helper.rb
65
+ homepage: https://rubygems.org/gems/solidus_fulfillment
66
+ licenses: []
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 2.2.7
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements:
83
+ - none
84
+ rubyforge_project:
85
+ rubygems_version: 2.5.2
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Solidus extension to do fulfillment processing via various services when
89
+ a shipment becomes ready
90
+ test_files:
91
+ - spec/spec_helper.rb