spree_batch_capture 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ require 'carrierwave/orm/activerecord'
2
+
3
+ class OrderExport < ActiveRecord::Base
4
+
5
+ mount_uploader :exported_orders, ExportUploader
6
+
7
+ end
@@ -0,0 +1,5 @@
1
+ require 'carrierwave'
2
+
3
+ class ExportUploader < CarrierWave::Uploader::Base
4
+ #storage :fog
5
+ end
@@ -0,0 +1,8 @@
1
+ require 'carrierwave'
2
+ # Set carrierwave to file storage for testing.
3
+ if Rails.env.test? || Rails.env.cucumber?
4
+ CarrierWave.configure do |config|
5
+ config.storage = :file
6
+ config.enable_processing = false
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ class CreateOrderExports < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :order_exports do |t|
4
+ t.integer :user_id
5
+ t.datetime :requested_at
6
+ t.datetime :completed_at
7
+ t.integer :count_of_orders
8
+ t.string :exported_orders
9
+ t.string :export_errors
10
+
11
+ t.timestamps
12
+ end
13
+ end
14
+
15
+ def self.down
16
+ drop_table :order_exports
17
+ end
18
+ end
@@ -0,0 +1,127 @@
1
+ module SpreeBatchCapture::Formatter
2
+
3
+ class SimpleCsv
4
+
5
+ # Takes an order and returns 1 or more csv rows
6
+ def self.transform(order)
7
+ return "" if order.nil? || order.id.nil?
8
+
9
+ line_items = LineItem.includes(:order => [:ship_address, :bill_address], :variant => :product).where(:order_id => order.id)
10
+
11
+ content = ""
12
+ if line_items && line_items.count > 0
13
+ content = CSV.generate do |csv|
14
+
15
+ line_items.each do |line_item|
16
+ csv << values(line_item)
17
+ end
18
+ end
19
+ end
20
+ return content
21
+ end
22
+
23
+ def self.header(line_item)
24
+ head = []
25
+ sections.each do |section|
26
+ head << extract_field_names(section, line_item)
27
+ end
28
+ Rails.logger.debug "Exporting Header: #{head.flatten}:: #{head.flatten.to_csv}"
29
+ return head.flatten.to_csv
30
+ end
31
+
32
+
33
+ private
34
+
35
+ def self.order_fields(line_item)
36
+ return [
37
+ { "OrderNumber" => line_item.order.number },
38
+ { "OrderTotal" => line_item.order.total },
39
+ { "OrderState" => line_item.order.state },
40
+ { "ShipmentState" => line_item.order.shipment_state },
41
+ { "PaymentState" => line_item.order.payment_state },
42
+ { "EmailAddress" => line_item.order.email }
43
+ ]
44
+ end
45
+
46
+ def self.shipping_address_fields(line_item)
47
+ return address_for("Shipping", line_item.order.ship_address)
48
+ end
49
+
50
+ def self.billing_address_fields(line_item)
51
+ return address_for("Billing", line_item.order.bill_address)
52
+ end
53
+
54
+ def self.address_for(prefix, address)
55
+ # In case an address doesn't exist, return nil values
56
+ address = Address.default if address.nil?
57
+ return [
58
+ { "#{prefix}FirstName" => address.firstname },
59
+ { "#{prefix}LastName" => address.lastname },
60
+ { "#{prefix}Address1" => address.address1 },
61
+ { "#{prefix}Address2" => address.address2 },
62
+ { "#{prefix}City" => address.city },
63
+ { "#{prefix}State" => address.state_text },
64
+ { "#{prefix}ZipCode" => address.zipcode },
65
+ { "#{prefix}Country" => address.country.name }
66
+ ]
67
+ end
68
+
69
+ def self.product_fields(line_item)
70
+ return [{ "ProductName" => line_item.variant.product.name }]
71
+ end
72
+
73
+ def self.variant_fields(line_item)
74
+ return [
75
+ { "SKU" => line_item.variant.sku },
76
+ { "VariantWeight" => line_item.variant.weight },
77
+ { "VariantHeight" => line_item.variant.height },
78
+ { "VariantWidth" => line_item.variant.width },
79
+ { "VariantDepth" => line_item.variant.depth }
80
+ ]
81
+ end
82
+
83
+ def self.line_item_fields(line_item)
84
+ return [
85
+ { "Quantity" => line_item.quantity },
86
+ { "LineItemPrice" => line_item.price }
87
+ ]
88
+
89
+ end
90
+
91
+ def self.sections
92
+ return [
93
+ :order_fields,
94
+ :shipping_address_fields,
95
+ :billing_address_fields,
96
+ :product_fields,
97
+ :variant_fields,
98
+ :line_item_fields
99
+ ]
100
+ end
101
+
102
+ def self.values(line_item)
103
+ values = []
104
+ sections.each do |section|
105
+ values << extract_field_values(section, line_item)
106
+ end
107
+ return values.flatten
108
+ end
109
+
110
+ def self.extract_field_names(section, line_item)
111
+ self.send(section, line_item).map{ |h| h.keys.first }
112
+ end
113
+
114
+ def self.extract_field_values(section, line_item)
115
+ self.send(section, line_item).map{ |h| h.values.first }
116
+ end
117
+
118
+ def self.value(section, key, line_item)
119
+ section_arr = self.send(section, line_item)
120
+ hash = section_arr.find{|h| h[key] != nil}
121
+
122
+ return hash.nil? ? nil : hash[key]
123
+ end
124
+
125
+ end
126
+
127
+ end
@@ -0,0 +1,36 @@
1
+ module SpreeBatchCapture
2
+ class ScopeSerializer
3
+
4
+ def self.from_scope(relation)
5
+ raise "Invalid scope" unless relation.kind_of? ActiveRecord::Relation
6
+ {
7
+ :where => relation.where_values,
8
+ :joins => relation.joins_values,
9
+ :order => relation.order_values,
10
+ :group => relation.group_values,
11
+ :limit => relation.limit_value,
12
+ :from => relation.from_value,
13
+ :select => relation.select_values,
14
+ :includes => relation.includes_values,
15
+ :having => relation.having_values,
16
+ :offset_value => relation.offset_value,
17
+ }
18
+ end
19
+
20
+ def self.to_scope(scope_hash)
21
+ scope_hash.symbolize_keys!
22
+ return Order
23
+ .where(scope_hash[:where])
24
+ .joins(scope_hash[:joins])
25
+ .order(scope_hash[:order])
26
+ .group(scope_hash[:group])
27
+ .limit(scope_hash[:limit])
28
+ .from(scope_hash[:from])
29
+ .select(scope_hash[:select])
30
+ .includes(scope_hash[:includes])
31
+ .having(scope_hash[:having])
32
+ .offset(scope_hash[:offset])
33
+ end
34
+
35
+ end
36
+ end
@@ -7,7 +7,7 @@ module SpreeBatchCapture
7
7
  return true unless order.payment_state == "balance_due"
8
8
 
9
9
  if order.payments.nil? || ( order.payments && order.payments.empty? )
10
- raise UnexpectedWorkerError.new "Attempted to capture an order without any payments."
10
+ raise Error::UnexpectedWorkerError.new "Attempted to capture an order without any payments."
11
11
  else
12
12
 
13
13
  order.payments.each do |payment|
@@ -0,0 +1,182 @@
1
+ require 'digest/sha1'
2
+ require 'zip/zip'
3
+
4
+ module SpreeBatchCapture
5
+ class Worker::Export < Worker::Base
6
+
7
+ # expected options:
8
+ # :orders
9
+ # :user_id
10
+ # :requested_at
11
+ # :mark_as_shipped (optional, defaults to false)
12
+ #
13
+ # :orders is a hash that can be any of the following
14
+ # {:scope => {:joins => [], :where => [], ...} ## There is a helper for this (see self.from_scope and self.to_scope)
15
+ # {:orders => []} ## Array of ids
16
+ # {:where => []} ## Conditional where clause
17
+ # {:sql => "select * from orders"} ## Any valid sql
18
+ # If more than one key is specified, only the first will be used in the order above.
19
+ def self.run(options)
20
+ Rails.logger.debug "Starting Export Worker..."
21
+
22
+ orders = get_orders(options[:orders])
23
+
24
+ default_attrs = {
25
+ :requested_at => options[:requested_at],
26
+ :user_id => options[:user_id]
27
+ }
28
+
29
+ if orders && orders.count > 0
30
+ export_file = export(orders, Formatter::SimpleCsv)
31
+ Rails.logger.debug "Exported orders to #{export_file}"
32
+ begin
33
+ oe = OrderExport.create(default_attrs.merge({
34
+ :count_of_orders => orders.count
35
+ }))
36
+
37
+ if export_file
38
+ oe.exported_orders = File.open(export_file)
39
+ else
40
+ oe.export_errors = I18n.t(:unable_to_export)
41
+ oe.count_of_orders = 0
42
+ end
43
+
44
+ oe.completed_at = Time.now
45
+ oe.save!
46
+
47
+ options[:successful_orders] = orders.map(&:id) if options[:mark_as_shipped]
48
+
49
+ rescue => e
50
+ if oe
51
+ oe.count_of_orders = 0
52
+ oe.export_errors = I18n.t(:unable_to_export)
53
+ oe.completed_at = Time.now
54
+ oe.save!
55
+ end
56
+ ensure
57
+ Rails.logger.debug "DELETING FILE: #{export_file}"
58
+ FileUtils.rm(export_file) if export_file && File.exist?(export_file)
59
+ end
60
+
61
+ else
62
+ OrderExport.create(default_attrs.merge({
63
+ :count_of_orders => 0,
64
+ :export_errors => I18n.t(:no_orders_for_export)
65
+ }))
66
+
67
+ end
68
+
69
+ return true
70
+ end
71
+
72
+
73
+ def self.after_run(success, options)
74
+ if success && options[:mark_as_shipped]
75
+ options[:successful_orders].each do |order_id|
76
+ Worker::Ship.enqueue({ :order_id => order_id })
77
+ end
78
+ end
79
+ return true
80
+ end
81
+
82
+ # Gets the orders based on a set of params.
83
+ # see run for more information
84
+ def self.get_orders(order_criteria)
85
+ orders = []
86
+ order_criteria.symbolize_keys!
87
+ if order_criteria[:scope]
88
+ orders = ScopeSerializer.to_scope(order_criteria[:scope])
89
+ elsif order_criteria[:orders]
90
+ orders = Order.where("id in (?)", order_criteria[:orders])
91
+ elsif order_criteria[:where]
92
+ orders = Order.where(order_criteria[:where])
93
+ elsif order_criteria[:sql]
94
+ orders = Order.find_by_sql(order_criteria[:sql])
95
+ else
96
+ return false
97
+ end
98
+
99
+ return orders
100
+
101
+ end
102
+
103
+ def self.worker_klass_name
104
+ return "Export"
105
+ end
106
+
107
+ def self.default_queue_name
108
+ return "export"
109
+ end
110
+
111
+
112
+ # Allows overriding of the default
113
+ # filename prefix. All filenames are as follows:
114
+ # prefix_sha1.csv[.gz]
115
+ def self.filename_prefix
116
+ "export"
117
+ end
118
+
119
+ # Temporary directory used for creating the export files.
120
+ # Can be overridden to provide a custom path
121
+ def self.temp_directory
122
+ File.join(Rails.root.to_s, 'tmp', 'export')
123
+ end
124
+
125
+ private
126
+
127
+ # Exports the orders and returns a file on the local filesystem.
128
+ # Requires a formatter such as SpreeBatchCapture::Formatter::SimpleCsv
129
+ # Accepts a zip flag to zip the file.
130
+ def self.export(orders, formatter, zip=true)
131
+ begin
132
+
133
+ Rails.logger.debug "Exporting file..."
134
+
135
+ export_file = open_file(zip) do |f|
136
+ f.print formatter.header(orders.first.line_items.first)
137
+ orders.each do |order|
138
+ Rails.logger.debug "Exporting order: #{order.number}"
139
+ f.print formatter.transform(order)
140
+ end
141
+ end
142
+
143
+ Rails.logger.debug "Export completed successfully."
144
+ return export_file
145
+
146
+ rescue => e
147
+ Rails.logger.debug ("Export failed: #{e.message}")
148
+ return false
149
+ end
150
+
151
+ end
152
+
153
+ def self.open_file(zip)
154
+ base_dir = temp_directory
155
+ FileUtils.mkdir_p(base_dir)
156
+ base_filename = generate_file_name
157
+ filename = File.join(base_dir, base_filename)
158
+
159
+ if zip
160
+ zip_filename = filename.gsub("csv", "zip")
161
+ Zip::ZipFile.open(zip_filename, Zip::ZipFile::CREATE) do |zipfile|
162
+ zipfile.get_output_stream(base_filename) { |f| yield(f) }
163
+ end
164
+ else
165
+ File.open(filename, "w") do |f|
166
+ yield(f)
167
+ end
168
+ end
169
+
170
+ return zip ? zip_filename : filename
171
+ end
172
+
173
+ # Generates a unique filename which includes
174
+ # the full path.
175
+ def self.generate_file_name
176
+ sha1 = Digest::SHA1.hexdigest "#{filename_prefix}::#{Time.now.to_i}::#{rand(100)}"
177
+ return "#{filename_prefix}_#{sha1[1..10]}.csv"
178
+ end
179
+
180
+
181
+ end
182
+ end
@@ -0,0 +1,52 @@
1
+ class SpreeBatchCapture::Worker::Ship < SpreeBatchCapture::Worker::Base
2
+
3
+ # expecting an order id like this:
4
+ # {:order => order_obj}
5
+ # where order_obj is set in before_run from
6
+ # {:order_id => 1}
7
+ def self.run(options)
8
+
9
+ order = options[:order]
10
+
11
+ result = false
12
+
13
+ if order && order.respond_to?(:fulfill)
14
+ result = order.fulfill
15
+ elsif order && order.shipments.count > 0
16
+ order.shipments.each do |shipment|
17
+ unless shipment.shipped?
18
+ result = shipment.ship!
19
+ if result
20
+ shipment.shipped_at = Time.now
21
+ shipment.save
22
+ end
23
+
24
+ break unless result # if something went wrong, don't try to continue
25
+ else
26
+ # already shipped so we'll just say we were successful.
27
+ result = true
28
+ end
29
+ end
30
+
31
+ order.update!
32
+
33
+ else
34
+ # Nothing to do so we'll just proceed as if everything was ok
35
+ result = true
36
+ end
37
+
38
+ return result
39
+ end
40
+
41
+ def self.before_run(options)
42
+ options[:order] = Order.find_by_id options[:order_id]
43
+ end
44
+
45
+ def self.worker_klass_name
46
+ return "Ship"
47
+ end
48
+
49
+ def self.default_queue_name
50
+ return "ship"
51
+ end
52
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_batch_capture
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,22 +9,22 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-05 00:00:00.000000000 Z
12
+ date: 2011-12-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: spree_core
16
- requirement: &16020680 !ruby/object:Gem::Requirement
16
+ requirement: &6467000 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ! '>='
19
+ - - ~>
20
20
  - !ruby/object:Gem::Version
21
21
  version: 0.60.4
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *16020680
24
+ version_requirements: *6467000
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: resque
27
- requirement: &16020220 !ruby/object:Gem::Requirement
27
+ requirement: &6466200 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 1.19.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *16020220
35
+ version_requirements: *6466200
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: resque-lock
38
- requirement: &16019760 !ruby/object:Gem::Requirement
38
+ requirement: &6464520 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,25 +43,66 @@ dependencies:
43
43
  version: 1.0.0
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *16019760
46
+ version_requirements: *6464520
47
+ - !ruby/object:Gem::Dependency
48
+ name: carrierwave
49
+ requirement: &6462680 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.8
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *6462680
58
+ - !ruby/object:Gem::Dependency
59
+ name: fog
60
+ requirement: &6461280 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 1.1.1
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *6461280
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubyzip
71
+ requirement: &6459040 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 0.9.0
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *6459040
47
80
  description: Adds batch processing to Spree. Includes batch capture and batch export
48
81
  of orders.
49
- email: ''
82
+ email: mike.farmer@gmail.com
50
83
  executables: []
51
84
  extensions: []
52
85
  extra_rdoc_files: []
53
86
  files:
87
+ - app/uploaders/export_uploader.rb
88
+ - app/models/order_export.rb
54
89
  - config/routes.rb
90
+ - config/initializers/carrierwave.rb
55
91
  - lib/spree_batch_capture_hooks.rb
56
92
  - lib/tasks/install.rake
57
93
  - lib/tasks/spree_batch_capture.rake
58
94
  - lib/spree_batch_capture/worker/bogus.rb
95
+ - lib/spree_batch_capture/worker/export.rb
59
96
  - lib/spree_batch_capture/worker/capture.rb
60
97
  - lib/spree_batch_capture/worker/base.rb
98
+ - lib/spree_batch_capture/worker/ship.rb
99
+ - lib/spree_batch_capture/formatter/simple_csv.rb
61
100
  - lib/spree_batch_capture/error.rb
101
+ - lib/spree_batch_capture/scope_serializer.rb
62
102
  - lib/spree_batch_capture.rb
63
103
  - db/migrate/20111129223646_add_last_processing_error_to_payments.rb
64
- homepage: http://vitrue.com
104
+ - db/migrate/20111207214102_create_order_exports.rb
105
+ homepage: http://github.com/vitrue/spree_batch_capture
65
106
  licenses: []
66
107
  post_install_message:
67
108
  rdoc_options: []