solidus_fulfillment 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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