spree_core 1.1.3 → 1.1.4

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