spree_batch_capture 0.1.0 → 0.1.1

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
+ 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: []