spree_core 1.1.3 → 1.1.4

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.
Files changed (68) hide show
  1. data/app/assets/images/noimage/large.png +0 -0
  2. data/app/assets/javascripts/admin/admin.js.erb +14 -14
  3. data/app/assets/javascripts/admin/checkouts/edit.js +1 -1
  4. data/app/assets/javascripts/admin/spree_core.js +0 -2
  5. data/app/assets/javascripts/store/checkout.js +2 -2
  6. data/app/assets/stylesheets/store/screen.css.scss +1 -1
  7. data/app/controllers/spree/admin/images_controller.rb +5 -13
  8. data/app/controllers/spree/admin/mail_methods_controller.rb +7 -0
  9. data/app/controllers/spree/admin/orders/customer_details_controller.rb +1 -1
  10. data/app/controllers/spree/admin/payment_methods_controller.rb +7 -1
  11. data/app/controllers/spree/admin/products_controller.rb +5 -13
  12. data/app/controllers/spree/admin/prototypes_controller.rb +2 -2
  13. data/app/controllers/spree/checkout_controller.rb +10 -0
  14. data/app/controllers/spree/orders_controller.rb +9 -5
  15. data/app/helpers/spree/base_helper.rb +17 -4
  16. data/app/helpers/spree/checkout_helper.rb +1 -9
  17. data/app/helpers/spree/products_helper.rb +0 -4
  18. data/app/models/spree/address.rb +1 -1
  19. data/app/models/spree/app_configuration.rb +1 -0
  20. data/app/models/spree/calculator/price_sack.rb +5 -3
  21. data/app/models/spree/image.rb +3 -1
  22. data/app/models/spree/inventory_unit.rb +8 -6
  23. data/app/models/spree/line_item.rb +2 -1
  24. data/app/models/spree/order.rb +23 -2
  25. data/app/models/spree/payment/processing.rb +22 -20
  26. data/app/models/spree/preferences/preferable_class_methods.rb +2 -0
  27. data/app/models/spree/preferences/store.rb +20 -2
  28. data/app/models/spree/product.rb +35 -29
  29. data/app/models/spree/product/scopes.rb +21 -6
  30. data/app/models/spree/return_authorization.rb +1 -0
  31. data/app/models/spree/shipping_method.rb +3 -0
  32. data/app/models/spree/variant.rb +19 -9
  33. data/app/views/spree/admin/images/_form.html.erb +4 -8
  34. data/app/views/spree/admin/return_authorizations/_form.html.erb +17 -33
  35. data/app/views/spree/admin/shared/_head.html.erb +2 -15
  36. data/app/views/spree/admin/shared/_order_details.html.erb +1 -1
  37. data/app/views/spree/admin/shared/_routes.html.erb +8 -0
  38. data/app/views/spree/admin/shared/_translations.html.erb +17 -0
  39. data/app/views/spree/admin/tax_rates/index.html.erb +2 -2
  40. data/app/views/spree/admin/variants/_form.html.erb +2 -2
  41. data/app/views/spree/checkout/payment/_gateway.html.erb +1 -1
  42. data/app/views/spree/order_mailer/cancel_email.text.erb +5 -5
  43. data/app/views/spree/order_mailer/confirm_email.text.erb +7 -6
  44. data/app/views/spree/products/_thumbnails.html.erb +1 -1
  45. data/app/views/spree/shared/_google_analytics.html.erb +17 -16
  46. data/app/views/spree/shared/_head.html.erb +0 -3
  47. data/app/views/spree/shared/_order_details.html.erb +2 -3
  48. data/app/views/spree/shipment_mailer/shipped_email.text.erb +7 -7
  49. data/config/initializers/check_for_orphaned_preferences.rb +1 -1
  50. data/config/initializers/rails_5868.rb +8 -0
  51. data/config/locales/en.yml +17 -0
  52. data/config/routes.rb +7 -0
  53. data/db/migrate/20121017010007_remove_not_null_constraint_from_products_on_hand.rb +11 -0
  54. data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
  55. data/lib/generators/spree/dummy/templates/rails/database.yml +1 -1
  56. data/lib/generators/spree/install/templates/app/assets/javascripts/admin/all.js +2 -0
  57. data/lib/spree/core/mail_settings.rb +2 -1
  58. data/lib/spree/core/search/base.rb +5 -2
  59. data/lib/spree/core/testing_support/factories/tax_category_factory.rb +5 -2
  60. data/lib/spree/core/version.rb +1 -1
  61. data/lib/spree/product_filters.rb +1 -1
  62. metadata +41 -42
  63. data/app/helpers/spree/account_helper.rb +0 -4
  64. data/app/helpers/spree/orders_helper.rb +0 -5
  65. data/app/helpers/spree/trackers_helper.rb +0 -4
  66. data/config/initializers/workarounds_for_ruby19.rb +0 -72
  67. data/lib/spree/core/relation_serialization.rb +0 -9
  68. data/lib/spree/core/testing_support/env.rb +0 -2
@@ -80,6 +80,7 @@ module Spree
80
80
 
81
81
  def update_order
82
82
  # update the order totals, etc.
83
+ order.create_tax_charge!
83
84
  order.update!
84
85
  end
85
86
 
@@ -97,7 +98,7 @@ module Spree
97
98
  end
98
99
 
99
100
  def quantity_no_less_than_shipped
100
- already_shipped = order.shipments.reduce(0) { |acc,s| acc + s.inventory_units.count { |i| i.variant == variant } }
101
+ already_shipped = order.shipments.reduce(0) { |acc,s| acc + s.inventory_units.select { |i| i.variant == variant }.count }
101
102
  unless quantity >= already_shipped
102
103
  errors.add(:quantity, I18n.t('validation.cannot_be_less_than_shipped_units'))
103
104
  end
@@ -19,7 +19,7 @@ module Spree
19
19
  belongs_to :shipping_method, :class_name => "Spree::ShippingMethod"
20
20
 
21
21
  has_many :state_changes, :as => :stateful
22
- has_many :line_items, :dependent => :destroy
22
+ has_many :line_items, :dependent => :destroy, :order => "created_at ASC"
23
23
  has_many :inventory_units
24
24
  has_many :payments, :dependent => :destroy
25
25
  has_many :shipments, :dependent => :destroy
@@ -69,7 +69,7 @@ module Spree
69
69
  transition :to => 'canceled', :if => :allow_cancel?
70
70
  end
71
71
  event :return do
72
- transition :to => 'returned', :from => 'awaiting_return'
72
+ transition :to => 'returned', :from => 'awaiting_return', :unless=>:awaiting_returns?
73
73
  end
74
74
  event :resume do
75
75
  transition :to => 'resumed', :from => 'canceled', :if => :allow_resume?
@@ -127,6 +127,14 @@ module Spree
127
127
  self.update_hooks.add(hook)
128
128
  end
129
129
 
130
+ def checkout_steps
131
+ if payment and payment.payment_method.payment_profiles_supported?
132
+ %w(address delivery payment confirm complete)
133
+ else
134
+ %w(address delivery payment complete)
135
+ end
136
+ end
137
+
130
138
  # For compatiblity with Calculator::PriceSack
131
139
  def amount
132
140
  line_items.map(&:amount).sum
@@ -271,6 +279,10 @@ module Spree
271
279
  true
272
280
  end
273
281
 
282
+ def awaiting_returns?
283
+ return_authorizations.any? { |return_authorization| return_authorization.authorized? }
284
+ end
285
+
274
286
  def add_variant(variant, quantity = 1)
275
287
  current_item = contains?(variant)
276
288
  if current_item
@@ -466,6 +478,11 @@ module Spree
466
478
  order.destroy
467
479
  end
468
480
 
481
+ def empty!
482
+ line_items.destroy_all
483
+ adjustments.destroy_all
484
+ end
485
+
469
486
  private
470
487
  def create_user
471
488
  self.email = user.email if self.user and not user.anonymous?
@@ -581,6 +598,10 @@ module Spree
581
598
  end
582
599
 
583
600
  def after_cancel
601
+ if shipment_state == 'pending'
602
+ self.update_attribute(:payment_state, 'credit_owed')
603
+ end
604
+
584
605
  restock_items!
585
606
 
586
607
  #TODO: make_shipments_pending
@@ -47,6 +47,7 @@ module Spree
47
47
  end
48
48
 
49
49
  def void_transaction!
50
+ return true if void?
50
51
  protect_from_connection_error do
51
52
  check_environment
52
53
 
@@ -104,8 +105,27 @@ module Spree
104
105
  credit!(amount)
105
106
  end
106
107
 
107
- # This needs to be public so that gateway subclasses can use
108
- # this to report errors
108
+ def gateway_options
109
+ options = { :email => order.email,
110
+ :customer => order.email,
111
+ :ip => '192.168.1.100', # TODO: Use an actual IP
112
+ :order_id => order.number }
113
+
114
+ options.merge!({ :shipping => order.ship_total * 100,
115
+ :tax => order.tax_total * 100,
116
+ :subtotal => order.item_total * 100 })
117
+
118
+ options.merge!({ :currency => payment_method.preferences[:currency_code] }) if payment_method && payment_method.preferences[:currency_code]
119
+
120
+ options.merge!({ :billing_address => order.bill_address.try(:active_merchant_hash),
121
+ :shipping_address => order.ship_address.try(:active_merchant_hash) })
122
+
123
+ options.merge!(:discount => promo_total) if respond_to?(:promo_total)
124
+ options
125
+ end
126
+
127
+ private
128
+
109
129
  def gateway_error(error)
110
130
  if error.is_a? ActiveMerchant::Billing::Response
111
131
  text = error.params['message'] || error.params['response_reason_text'] || error.message
@@ -119,8 +139,6 @@ module Spree
119
139
  raise Core::GatewayError.new(text)
120
140
  end
121
141
 
122
- private
123
-
124
142
  def gateway_action(source, action, success_state)
125
143
  protect_from_connection_error do
126
144
  check_environment
@@ -151,22 +169,6 @@ module Spree
151
169
  log_entries.create({:details => response.to_yaml}, :without_protection => true)
152
170
  end
153
171
 
154
- def gateway_options
155
- options = { :email => order.email,
156
- :customer => order.email,
157
- :ip => '192.168.1.100', # TODO: Use real IP address
158
- :order_id => order.number }
159
-
160
- options.merge!({ :shipping => order.ship_total * 100,
161
- :tax => order.tax_total * 100,
162
- :subtotal => order.item_total * 100 })
163
-
164
- options.merge!({ :currency => payment_method.preferences[:currency_code] }) if payment_method && payment_method.preferences[:currency_code]
165
-
166
- options.merge({ :billing_address => order.bill_address.try(:active_merchant_hash),
167
- :shipping_address => order.ship_address.try(:active_merchant_hash) })
168
- end
169
-
170
172
  def protect_from_connection_error
171
173
  begin
172
174
  yield
@@ -15,6 +15,8 @@ module Spree::Preferences
15
15
  else
16
16
  if get_pending_preference(name)
17
17
  get_pending_preference(name)
18
+ elsif Spree::Preference.table_exists? && preference = Spree::Preference.find_by_name(name)
19
+ preference.value
18
20
  else
19
21
  send self.class.preference_default_getter_method(name)
20
22
  end
@@ -23,11 +23,29 @@ module Spree::Preferences
23
23
  end
24
24
 
25
25
  def exist?(key)
26
- @cache.exist? key
26
+ @cache.exist?(key) ||
27
+ should_persist? && Spree::Preference.where(:key => key).exists?
27
28
  end
28
29
 
29
30
  def get(key)
30
- @cache.read(key)
31
+ # return the retrieved value, if it's in the cache
32
+ if (val = @cache.read(key)).present?
33
+ return val
34
+ end
35
+
36
+ return nil unless should_persist?
37
+
38
+ # If it's not in the cache, maybe it's in the database, but
39
+ # has been cleared from the cache
40
+
41
+ # does it exist in the database?
42
+ if preference = Spree::Preference.find_by_key(key)
43
+ # it does exist, so let's put it back into the cache
44
+ @cache.write(preference.key, preference.value)
45
+
46
+ # and return the value
47
+ preference.value
48
+ end
31
49
  end
32
50
 
33
51
  def delete(key)
@@ -23,13 +23,26 @@ module Spree
23
23
  has_many :option_types, :through => :product_option_types
24
24
  has_many :product_properties, :dependent => :destroy
25
25
  has_many :properties, :through => :product_properties
26
- belongs_to :tax_category, :class_name => "Spree::TaxCategory"
26
+
27
27
  has_and_belongs_to_many :taxons, :join_table => 'spree_products_taxons'
28
- belongs_to :shipping_category, :class_name => "Spree::ShippingCategory"
28
+
29
+ belongs_to :tax_category
30
+ belongs_to :shipping_category
29
31
 
30
32
  has_one :master,
31
33
  :class_name => 'Spree::Variant',
32
- :conditions => ["#{Variant.quoted_table_name}.is_master = ?", true]
34
+ :conditions => { :is_master => true },
35
+ :dependent => :destroy
36
+
37
+ has_many :variants,
38
+ :class_name => 'Spree::Variant',
39
+ :conditions => { :is_master => false, :deleted_at => nil },
40
+ :order => :position
41
+
42
+ has_many :variants_including_master,
43
+ :class_name => 'Spree::Variant',
44
+ :conditions => { :deleted_at => nil },
45
+ :dependent => :destroy
33
46
 
34
47
  delegate_belongs_to :master, :sku, :price, :weight, :height, :width, :depth, :is_master
35
48
  delegate_belongs_to :master, :cost_price if Variant.table_exists? && Variant.column_names.include?('cost_price')
@@ -41,33 +54,13 @@ module Spree
41
54
  after_save :save_master
42
55
  after_save :set_master_on_hand_to_zero_when_product_has_variants
43
56
 
44
- has_many :variants,
45
- :class_name => 'Spree::Variant',
46
- :conditions => ["#{::Spree::Variant.quoted_table_name}.is_master = ? AND #{::Spree::Variant.quoted_table_name}.deleted_at IS NULL", false],
47
- :order => "#{::Spree::Variant.quoted_table_name}.position ASC"
48
-
49
- has_many :variants_including_master,
50
- :class_name => 'Spree::Variant',
51
- :conditions => ["#{::Spree::Variant.quoted_table_name}.deleted_at IS NULL"],
52
- :dependent => :destroy
57
+ delegate :images, :to => :master, :prefix => true
58
+ alias_method :images, :master_images
53
59
 
54
- has_many :variants_with_only_master,
55
- :class_name => 'Spree::Variant',
56
- :conditions => ["#{::Spree::Variant.quoted_table_name}.deleted_at IS NULL AND #{::Spree::Variant.quoted_table_name}.is_master = ?", true],
57
- :dependent => :destroy
60
+ has_many :variant_images, :source => :images, :through => :variants_including_master, :order => :position
58
61
 
59
62
  accepts_nested_attributes_for :variants, :allow_destroy => true
60
63
 
61
- def variant_images
62
- Image.joins("LEFT JOIN #{Variant.quoted_table_name} ON #{Variant.quoted_table_name}.id = #{Asset.quoted_table_name}.viewable_id").
63
- where("(#{Asset.quoted_table_name}.viewable_type = ? AND #{Asset.quoted_table_name}.viewable_id = ?) OR
64
- (#{Asset.quoted_table_name}.viewable_type = ? AND #{Asset.quoted_table_name}.viewable_id = ?)", Variant.name, self.master.id, Product.name, self.id).
65
- order("#{Asset.quoted_table_name}.position").
66
- extend(Spree::Core::RelationSerialization)
67
- end
68
-
69
- alias_method :images, :variant_images
70
-
71
64
  validates :name, :price, :permalink, :presence => true
72
65
 
73
66
  attr_accessor :option_values_hash
@@ -89,11 +82,12 @@ module Spree
89
82
 
90
83
  after_initialize :ensure_master
91
84
 
92
- def ensure_master
93
- return unless new_record?
94
- self.master ||= Variant.new
85
+ def variants_with_only_master
86
+ ActiveSupport::Deprecation.warn("[SPREE] Spree::Product#variants_with_only_master will be deprecated in Spree 1.3. Please use Spree::Product#master instead.")
87
+ master
95
88
  end
96
89
 
90
+
97
91
  def to_param
98
92
  permalink.present? ? permalink : (permalink_was || name.to_s.to_url)
99
93
  end
@@ -127,6 +121,13 @@ module Spree
127
121
  end
128
122
  end
129
123
 
124
+ # override the delete method to set deleted_at value
125
+ # instead of actually deleting the product.
126
+ def delete
127
+ self.update_column(:deleted_at, Time.now)
128
+ variants_including_master.update_all(:deleted_at => Time.now)
129
+ end
130
+
130
131
  # Adding properties and option types on creation based on a chosen prototype
131
132
  attr_reader :prototype_id
132
133
  def prototype_id=(value)
@@ -256,6 +257,11 @@ module Spree
256
257
  def save_master
257
258
  master.save if master && (master.changed? || master.new_record?)
258
259
  end
260
+
261
+ def ensure_master
262
+ return unless new_record?
263
+ self.master ||= Variant.new
264
+ end
259
265
  end
260
266
  end
261
267
 
@@ -27,11 +27,11 @@ module Spree
27
27
  end
28
28
 
29
29
  add_search_scope :ascend_by_master_price do
30
- joins(:variants_with_only_master).order("#{variant_table_name}.price ASC")
30
+ joins(:master).order("#{variant_table_name}.price ASC")
31
31
  end
32
32
 
33
33
  add_search_scope :descend_by_master_price do
34
- joins(:variants_with_only_master).order("#{variant_table_name}.price DESC")
34
+ joins(:master).order("#{variant_table_name}.price DESC")
35
35
  end
36
36
 
37
37
  add_search_scope :price_between do |low, high|
@@ -50,8 +50,23 @@ module Spree
50
50
  # If you need products only within one taxon use
51
51
  #
52
52
  # Spree::Product.taxons_id_eq(x)
53
+ #
54
+ # If you're using count on the result of this scope, you must use the
55
+ # `:distinct` option as well:
56
+ #
57
+ # Spree::Product.in_taxon(taxon).count(:distinct => true)
58
+ #
59
+ # This is so that the count query is distinct'd:
60
+ #
61
+ # SELECT COUNT(DISTINCT "spree_products"."id") ...
62
+ #
63
+ # vs.
64
+ #
65
+ # SELECT COUNT(*) ...
53
66
  add_search_scope :in_taxon do |taxon|
54
- joins(:taxons).where(Taxon.table_name => { :id => taxon.self_and_descendants.map(&:id) })
67
+ select("DISTINCT(spree_products.id), spree_products.*").
68
+ joins(:taxons).
69
+ where(Taxon.table_name => { :id => taxon.self_and_descendants.map(&:id) })
55
70
  end
56
71
 
57
72
  # This scope selects products in all taxons AND all its descendants
@@ -113,7 +128,7 @@ module Spree
113
128
  end
114
129
 
115
130
  conditions = "#{option_values}.name = ? AND #{option_values}.option_type_id = ?", value, option_type_id
116
- joins(:variants_including_master => :option_values).where(conditions)
131
+ group("spree_products.id").joins(:variants_including_master => :option_values).where(conditions)
117
132
  end
118
133
 
119
134
  # Finds all products which have either:
@@ -188,11 +203,11 @@ module Spree
188
203
 
189
204
  add_search_scope :on_hand do
190
205
  variants_table = Variant.table_name
191
- where("#{table_name}.id in (select product_id from #{variants_table} where product_id = #{table_name}.id group by product_id having sum(count_on_hand) > 0)")
206
+ where("#{table_name}.id in (select product_id from #{variants_table} where product_id = #{table_name}.id and #{variants_table}.deleted_at IS NULL group by product_id having sum(count_on_hand) > 0)")
192
207
  end
193
208
 
194
209
  add_search_scope :taxons_name_eq do |name|
195
- joins(:taxons).where(Taxon.arel_table[:name].eq(name))
210
+ group("spree_products.id").joins(:taxons).where(Taxon.arel_table[:name].eq(name))
196
211
  end
197
212
 
198
213
  if (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL')
@@ -71,6 +71,7 @@ module Spree
71
71
  credit.source = self
72
72
  credit.adjustable = order
73
73
  credit.save
74
+ order.return if inventory_units.all?{ |inventory_unit| inventory_unit.returned? }
74
75
  end
75
76
 
76
77
  def allow_receive?
@@ -1,6 +1,9 @@
1
1
  module Spree
2
2
  class ShippingMethod < ActiveRecord::Base
3
3
  DISPLAY = [:both, :front_end, :back_end]
4
+
5
+ default_scope where(:deleted_at => nil)
6
+
4
7
  has_many :shipments
5
8
  validates :name, :zone, :presence => true
6
9
 
@@ -58,18 +58,12 @@ module Spree
58
58
  end
59
59
  end
60
60
 
61
- # strips all non-price-like characters from the price.
62
61
  def price=(price)
63
- if price.present?
64
- self[:price] = price.to_s.gsub(/[^0-9\.-]/, '').to_f
65
- end
62
+ self[:price] = parse_price(price) if price.present?
66
63
  end
67
64
 
68
- # and cost_price
69
65
  def cost_price=(price)
70
- if price.present?
71
- self[:cost_price] = price.to_s.gsub(/[^0-9\.-]/, '').to_f
72
- end
66
+ self[:cost_price] = parse_price(price) if price.present?
73
67
  end
74
68
 
75
69
  # returns number of units currently on backorder for this variant.
@@ -156,6 +150,19 @@ module Spree
156
150
  end
157
151
 
158
152
  private
153
+
154
+ # strips all non-price-like characters from the price, taking into account locale settings
155
+ def parse_price(price)
156
+ return price unless price.is_a?(String)
157
+
158
+ separator, delimiter = I18n.t([:'number.currency.format.separator', :'number.currency.format.delimiter'])
159
+ non_price_characters = /[^0-9\-#{separator}]/
160
+ price.gsub!(non_price_characters, '') # strip everything else first
161
+ price.gsub!(separator, '.') unless separator == '.' # then replace the locale-specific decimal separator with the standard separator if necessary
162
+
163
+ price.to_d
164
+ end
165
+
159
166
  # Ensures a new variant takes the product master price when price is not supplied
160
167
  def check_price
161
168
  if price.nil?
@@ -169,7 +176,10 @@ module Spree
169
176
  end
170
177
 
171
178
  def recalculate_product_on_hand
172
- product.update_column(:count_on_hand, product.on_hand)
179
+ on_hand = product.on_hand
180
+ if Spree::Config[:track_inventory_levels] && on_hand != (1.0 / 0) # Infinity
181
+ product.update_column(:count_on_hand, on_hand)
182
+ end
173
183
  end
174
184
  end
175
185
  end
@@ -3,14 +3,10 @@
3
3
  <td><%= t(:filename) %>:</td>
4
4
  <td><%= f.file_field :attachment %></td>
5
5
  </tr>
6
- <% if @product.has_variants? %>
7
- <tr data-hook="variant">
8
- <td><%= Spree::Variant.model_name.human %>:</td>
9
- <td><%= f.select :viewable_id, @variants %></td>
10
- </tr>
11
- <% else %>
12
- <%= hidden_field_tag :product_id, @product.id %>
13
- <% end %>
6
+ <tr data-hook="variant">
7
+ <td><%= Spree::Variant.model_name.human %>:</td>
8
+ <td><%= f.select :viewable_id, @variants %></td>
9
+ </tr>
14
10
  <tr data-hook="alt_text">
15
11
  <td><%= t(:alt_text) %>:</td>
16
12
  <td><%= f.text_area :alt %></td>