spree_core 1.0.7 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (209) hide show
  1. data/app/assets/javascripts/admin/admin.js.erb +9 -18
  2. data/app/assets/javascripts/admin/calculator.js +7 -6
  3. data/app/assets/javascripts/admin/checkouts/edit.js +2 -2
  4. data/app/assets/javascripts/admin/image_settings.js +49 -0
  5. data/app/assets/javascripts/admin/orders/edit_form.js +14 -14
  6. data/app/assets/stylesheets/admin/admin.css.erb +36 -54
  7. data/app/assets/stylesheets/store/screen.css.scss +25 -12
  8. data/app/controllers/spree/admin/base_controller.rb +3 -3
  9. data/app/controllers/spree/admin/image_settings_controller.rb +71 -0
  10. data/app/controllers/spree/admin/images_controller.rb +22 -21
  11. data/app/controllers/spree/admin/mail_methods_controller.rb +1 -1
  12. data/app/controllers/spree/admin/orders/customer_details_controller.rb +2 -2
  13. data/app/controllers/spree/admin/orders_controller.rb +13 -13
  14. data/app/controllers/spree/admin/payment_methods_controller.rb +1 -1
  15. data/app/controllers/spree/admin/payments_controller.rb +1 -1
  16. data/app/controllers/spree/admin/product_properties_controller.rb +3 -4
  17. data/app/controllers/spree/admin/products_controller.rb +49 -47
  18. data/app/controllers/spree/admin/properties_controller.rb +1 -1
  19. data/app/controllers/spree/admin/prototypes_controller.rb +12 -6
  20. data/app/controllers/spree/admin/reports_controller.rb +15 -16
  21. data/app/controllers/spree/admin/resource_controller.rb +1 -1
  22. data/app/controllers/spree/admin/return_authorizations_controller.rb +1 -1
  23. data/app/controllers/spree/admin/shipments_controller.rb +0 -3
  24. data/app/controllers/spree/admin/states_controller.rb +9 -9
  25. data/app/controllers/spree/admin/users_controller.rb +18 -42
  26. data/app/controllers/spree/admin/variants_controller.rb +22 -21
  27. data/app/controllers/spree/admin/zones_controller.rb +11 -11
  28. data/app/controllers/spree/content_controller.rb +1 -1
  29. data/app/controllers/spree/locale_controller.rb +2 -2
  30. data/app/controllers/spree/orders_controller.rb +4 -6
  31. data/app/controllers/spree/products_controller.rb +1 -1
  32. data/app/controllers/spree/states_controller.rb +4 -7
  33. data/app/helpers/spree/admin/base_helper.rb +4 -0
  34. data/app/helpers/spree/admin/navigation_helper.rb +4 -16
  35. data/app/helpers/spree/base_helper.rb +22 -3
  36. data/app/models/spree/activator.rb +4 -1
  37. data/app/models/spree/address.rb +45 -45
  38. data/app/models/spree/adjustment.rb +4 -4
  39. data/app/models/spree/app_configuration.rb +12 -0
  40. data/app/models/spree/calculator/default_tax.rb +1 -3
  41. data/app/models/spree/calculator/flat_percent_item_total.rb +0 -2
  42. data/app/models/spree/calculator/flat_rate.rb +0 -3
  43. data/app/models/spree/calculator/flexi_rate.rb +1 -3
  44. data/app/models/spree/calculator/per_item.rb +1 -6
  45. data/app/models/spree/calculator/price_sack.rb +0 -2
  46. data/app/models/spree/country.rb +1 -1
  47. data/app/models/spree/creditcard.rb +9 -10
  48. data/app/models/spree/gateway.rb +0 -2
  49. data/app/models/spree/image.rb +18 -2
  50. data/app/models/spree/inventory_unit.rb +4 -2
  51. data/app/models/spree/mail_method.rb +1 -6
  52. data/app/models/spree/option_type.rb +3 -1
  53. data/app/models/spree/order.rb +68 -62
  54. data/app/models/spree/payment.rb +1 -1
  55. data/app/models/spree/payment_method.rb +0 -6
  56. data/app/models/spree/product/scopes.rb +44 -44
  57. data/app/models/spree/product.rb +44 -26
  58. data/app/models/spree/product_property.rb +0 -2
  59. data/app/models/spree/property.rb +0 -16
  60. data/app/models/spree/prototype.rb +0 -1
  61. data/app/models/spree/return_authorization.rb +0 -2
  62. data/app/models/spree/shipment.rb +2 -3
  63. data/app/models/spree/shipping_category.rb +0 -2
  64. data/app/models/spree/shipping_method.rb +1 -2
  65. data/app/models/spree/shipping_rate.rb +9 -0
  66. data/app/models/spree/state.rb +10 -2
  67. data/app/models/spree/{state_event.rb → state_change.rb} +1 -4
  68. data/app/models/spree/tax_rate.rb +3 -3
  69. data/app/models/spree/taxon.rb +1 -6
  70. data/app/models/spree/taxonomy.rb +3 -6
  71. data/app/models/spree/user.rb +1 -0
  72. data/app/models/spree/variant.rb +3 -4
  73. data/app/models/spree/zone.rb +0 -10
  74. data/app/views/spree/admin/adjustments/_adjustments_table.html.erb +1 -1
  75. data/app/views/spree/admin/configurations/index.html.erb +4 -0
  76. data/app/views/spree/admin/image_settings/edit.html.erb +91 -0
  77. data/app/views/spree/admin/image_settings/show.html.erb +11 -0
  78. data/app/views/spree/admin/images/index.html.erb +3 -3
  79. data/app/views/spree/admin/mail_methods/_form.html.erb +1 -1
  80. data/app/views/spree/admin/mail_methods/index.html.erb +1 -1
  81. data/app/views/spree/admin/option_types/_option_value_fields.html.erb +1 -1
  82. data/app/views/spree/admin/option_types/index.html.erb +1 -1
  83. data/app/views/spree/admin/option_types/new.js.erb +1 -1
  84. data/app/views/spree/admin/orders/_form.html.erb +3 -5
  85. data/app/views/spree/admin/orders/_line_item.html.erb +1 -1
  86. data/app/views/spree/admin/orders/customer_details/_form.html.erb +1 -1
  87. data/app/views/spree/admin/orders/history.html.erb +2 -2
  88. data/app/views/spree/admin/orders/index.html.erb +19 -19
  89. data/app/views/spree/admin/payment_methods/_form.html.erb +1 -1
  90. data/app/views/spree/admin/payment_methods/index.html.erb +1 -3
  91. data/app/views/spree/admin/payments/_list.html.erb +1 -1
  92. data/app/views/spree/admin/product_properties/index.html.erb +1 -1
  93. data/app/views/spree/admin/products/_form.html.erb +30 -27
  94. data/app/views/spree/admin/products/index.html.erb +9 -9
  95. data/app/views/spree/admin/products/new.html.erb +28 -4
  96. data/app/views/spree/admin/products/new.js.erb +1 -1
  97. data/app/views/spree/admin/properties/index.html.erb +1 -1
  98. data/app/views/spree/admin/properties/new.js.erb +1 -1
  99. data/app/views/spree/admin/prototypes/index.html.erb +1 -1
  100. data/app/views/spree/admin/prototypes/new.js.erb +1 -1
  101. data/app/views/spree/admin/prototypes/show.html.erb +42 -0
  102. data/app/views/spree/admin/return_authorizations/index.html.erb +1 -1
  103. data/app/views/spree/admin/shared/_address_form.html.erb +1 -1
  104. data/app/views/spree/admin/shared/_calculator_fields.html.erb +2 -2
  105. data/app/views/spree/admin/shared/_head.html.erb +3 -3
  106. data/app/views/spree/admin/shared/_order_details.html.erb +1 -1
  107. data/app/views/spree/admin/shared/_order_tabs.html.erb +6 -6
  108. data/app/views/spree/admin/shared/_product_sub_menu.html.erb +0 -1
  109. data/app/views/spree/admin/shared/_report_criteria.html.erb +3 -3
  110. data/app/views/spree/admin/shared/_tabs.html.erb +1 -1
  111. data/app/views/spree/admin/shared/_update_order_state.js +5 -4
  112. data/app/views/spree/admin/shipments/index.html.erb +1 -1
  113. data/app/views/spree/admin/shipping_categories/index.html.erb +1 -1
  114. data/app/views/spree/admin/shipping_methods/index.html.erb +1 -1
  115. data/app/views/spree/admin/states/_state_list.html.erb +1 -1
  116. data/app/views/spree/admin/states/new.js.erb +1 -1
  117. data/app/views/spree/admin/tax_categories/index.html.erb +2 -2
  118. data/app/views/spree/admin/tax_rates/index.html.erb +2 -2
  119. data/app/views/spree/admin/taxonomies/_list.html.erb +1 -1
  120. data/app/views/spree/admin/trackers/_form.html.erb +1 -1
  121. data/app/views/spree/admin/trackers/index.html.erb +1 -1
  122. data/app/views/spree/admin/users/_form.html.erb +1 -16
  123. data/app/views/spree/admin/users/index.html.erb +3 -4
  124. data/app/views/spree/admin/variants/index.html.erb +2 -2
  125. data/app/views/spree/admin/variants/new.js.erb +1 -1
  126. data/app/views/spree/admin/zones/index.html.erb +1 -1
  127. data/app/views/spree/checkout/_address.html.erb +1 -1
  128. data/app/views/spree/checkout/edit.html.erb +1 -1
  129. data/app/views/spree/checkout/registration.html.erb +2 -2
  130. data/app/views/spree/order_mailer/confirm_email.text.erb +3 -3
  131. data/app/views/spree/orders/show.html.erb +2 -2
  132. data/app/views/spree/products/_cart_form.html.erb +1 -2
  133. data/app/views/spree/products/_image.html.erb +1 -1
  134. data/app/views/spree/products/show.html.erb +3 -3
  135. data/app/views/spree/shared/_filters.html.erb +26 -24
  136. data/app/views/spree/shared/_google_analytics.html.erb +26 -26
  137. data/app/views/spree/shared/_products.html.erb +2 -2
  138. data/app/views/spree/shared/_search.html.erb +1 -1
  139. data/app/views/spree/shared/_store_menu.html.erb +1 -1
  140. data/app/views/spree/states/index.js.erb +1 -1
  141. data/config/initializers/rails_3_1.rb +3 -3
  142. data/config/locales/en.yml +40 -35
  143. data/config/routes.rb +5 -13
  144. data/db/migrate/20101026184714_migrate_transactions_to_payment_state.rb +4 -4
  145. data/db/migrate/20111007143030_namespace_top_level_models.rb +0 -3
  146. data/db/migrate/20120203001428_rename_state_events_to_state_changes.rb +9 -0
  147. data/db/migrate/20120315064358_migrate_images_from_products_to_variants.rb +35 -0
  148. data/lib/generators/spree/dummy/templates/rails/database.yml +48 -20
  149. data/lib/generators/spree/install/install_generator.rb +17 -1
  150. data/lib/generators/spree/install/templates/app/assets/javascripts/admin/all.js +0 -1
  151. data/lib/generators/spree/install/templates/app/assets/javascripts/store/all.js +0 -1
  152. data/lib/generators/spree/install/templates/app/assets/stylesheets/admin/all.css +0 -1
  153. data/lib/generators/spree/install/templates/app/assets/stylesheets/store/all.css +0 -1
  154. data/lib/generators/spree/sandbox/sandbox_generator.rb +9 -2
  155. data/lib/spree/core/calculated_adjustments.rb +29 -34
  156. data/lib/spree/core/controller_helpers.rb +36 -34
  157. data/lib/spree/core/custom_fixtures.rb +1 -1
  158. data/lib/spree/core/delegate_belongs_to.rb +22 -24
  159. data/lib/spree/core/engine.rb +3 -3
  160. data/lib/spree/core/environment_extension.rb +12 -15
  161. data/lib/spree/core/mail_settings.rb +1 -1
  162. data/lib/spree/core/permalinks.rb +24 -16
  163. data/lib/spree/core/preference_rescue.rb +1 -1
  164. data/lib/spree/core/respond_with.rb +13 -8
  165. data/lib/spree/core/responder.rb +1 -2
  166. data/lib/spree/core/search/base.rb +36 -19
  167. data/lib/spree/core/ssl_requirement.rb +18 -10
  168. data/lib/spree/core/testing_support/common_rake.rb +1 -1
  169. data/lib/spree/core/testing_support/factories/product_factory.rb +9 -9
  170. data/lib/spree/core/testing_support/factories/role_factory.rb +1 -1
  171. data/lib/spree/core/testing_support/factories/shipping_category_factory.rb +1 -1
  172. data/lib/spree/core/testing_support/factories/shipping_method_factory.rb +3 -3
  173. data/lib/spree/core/testing_support/factories/user_factory.rb +1 -1
  174. data/lib/spree/core/testing_support/factories/zone_factory.rb +4 -2
  175. data/lib/spree/core/validators/email.rb +23 -0
  176. data/lib/spree/core/version.rb +1 -1
  177. data/lib/spree/core.rb +2 -2
  178. data/lib/spree/product_filters.rb +10 -19
  179. data/lib/tasks/core.rake +1 -1
  180. data/vendor/assets/javascripts/jquery.alerts/jquery.alerts.css.erb +5 -5
  181. data/vendor/assets/stylesheets/jquery-ui.datepicker.css.erb +1 -1
  182. metadata +68 -140
  183. data/app/assets/images/noimage/large.png +0 -0
  184. data/app/controllers/spree/admin/product_groups_controller.rb +0 -49
  185. data/app/controllers/spree/admin/product_scopes_controller.rb +0 -39
  186. data/app/helpers/spree/admin/product_groups_helper.rb +0 -14
  187. data/app/helpers/spree/admin/product_properties_helper.rb +0 -24
  188. data/app/models/spree/product_group.rb +0 -200
  189. data/app/models/spree/product_scope.rb +0 -79
  190. data/app/views/spree/admin/banners/_gateway.html.erb +0 -14
  191. data/app/views/spree/admin/product_groups/_preview.html.erb +0 -33
  192. data/app/views/spree/admin/product_groups/_product_scope.html.erb +0 -24
  193. data/app/views/spree/admin/product_groups/edit.html.erb +0 -59
  194. data/app/views/spree/admin/product_groups/index.html.erb +0 -37
  195. data/app/views/spree/admin/product_groups/new.html.erb +0 -12
  196. data/app/views/spree/admin/product_groups/show.html.erb +0 -32
  197. data/app/views/spree/admin/product_scopes/create.js.erb +0 -6
  198. data/app/views/spree/admin/product_scopes/destroy.js.erb +0 -3
  199. data/app/views/spree/admin/product_scopes/new.html.erb +0 -1
  200. data/app/views/spree/admin/shared/_group_from_products_form.html.erb +0 -12
  201. data/db/migrate/20091012120519_product_groups_and_scopes.rb +0 -18
  202. data/db/migrate/20100126103714_create_products_product_groups.rb +0 -8
  203. data/db/migrate/20100306153445_fix_by_popularity.rb +0 -9
  204. data/db/migrate/20120523061241_convert_sales_tax_to_default_tax.rb +0 -9
  205. data/lib/spree/core/testing_support/factories/product_group_factory.rb +0 -5
  206. data/lib/spree/core/testing_support/factories/product_scope_factory.rb +0 -7
  207. data/lib/spree/core/theme_support/hook_listener.rb +0 -145
  208. data/lib/spree/core/theme_support.rb +0 -1
  209. data/vendor/assets/javascripts/jquery.alerts/jquery.alerts.css +0 -57
@@ -1,3 +1,5 @@
1
+ require 'spree/core/validators/email'
2
+
1
3
  module Spree
2
4
  class Order < ActiveRecord::Base
3
5
  attr_accessible :line_items, :bill_address_attributes, :ship_address_attributes, :payments_attributes,
@@ -5,11 +7,18 @@ module Spree
5
7
  :shipping_method_id, :email, :use_billing, :special_instructions
6
8
 
7
9
  belongs_to :user
10
+
8
11
  belongs_to :bill_address, :foreign_key => 'bill_address_id', :class_name => 'Spree::Address'
12
+ alias_method :billing_address, :bill_address
13
+ alias_method :billing_address=, :bill_address=
14
+
9
15
  belongs_to :ship_address, :foreign_key => 'ship_address_id', :class_name => 'Spree::Address'
16
+ alias_method :shipping_address, :ship_address
17
+ alias_method :shipping_address=, :ship_address=
18
+
10
19
  belongs_to :shipping_method
11
20
 
12
- has_many :state_events, :as => :stateful
21
+ has_many :state_changes, :as => :stateful
13
22
  has_many :line_items, :dependent => :destroy
14
23
  has_many :inventory_units
15
24
  has_many :payments, :dependent => :destroy
@@ -30,7 +39,7 @@ module Spree
30
39
  after_create :create_tax_charge!
31
40
 
32
41
  # TODO: validate the format of the email as well (but we can't rely on authlogic anymore to help with validation)
33
- validates :email, :presence => true, :format => /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i, :if => :require_email
42
+ validates :email, :presence => true, :email => true, :if => :require_email
34
43
  validate :has_available_shipment
35
44
  validate :has_available_payment
36
45
 
@@ -51,41 +60,6 @@ module Spree
51
60
  class_attribute :update_hooks
52
61
  self.update_hooks = Set.new
53
62
 
54
- # Use this method in other gems that wish to register their own custom logic that should be called after Order#updat
55
- def self.register_update_hook(hook)
56
- self.update_hooks.add(hook)
57
- end
58
-
59
- # For compatiblity with Calculator::PriceSack
60
- def amount
61
- line_items.map(&:amount).sum
62
- end
63
-
64
- def to_param
65
- number.to_s.to_url.upcase
66
- end
67
-
68
- def completed?
69
- !! completed_at
70
- end
71
-
72
- # Indicates whether or not the user is allowed to proceed to checkout. Currently this is implemented as a
73
- # check for whether or not there is at least one LineItem in the Order. Feel free to override this logic
74
- # in your own application if you require additional steps before allowing a checkout.
75
- def checkout_allowed?
76
- line_items.count > 0
77
- end
78
-
79
- # Is this a free order in which case the payment step should be skipped
80
- def payment_required?
81
- total.to_f > 0.0
82
- end
83
-
84
- # Indicates the number of items in the order
85
- def item_count
86
- line_items.map(&:quantity).sum
87
- end
88
-
89
63
  # order state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
90
64
  state_machine :initial => 'cart', :use_transactions => false do
91
65
 
@@ -120,11 +94,7 @@ module Spree
120
94
  begin
121
95
  order.process_payments!
122
96
  rescue Core::GatewayError
123
- if Spree::Config[:allow_checkout_on_gateway_error]
124
- true
125
- else
126
- false
127
- end
97
+ !!Spree::Config[:allow_checkout_on_gateway_error]
128
98
  end
129
99
  end
130
100
 
@@ -140,6 +110,41 @@ module Spree
140
110
 
141
111
  end
142
112
 
113
+ # Use this method in other gems that wish to register their own custom logic that should be called after Order#updat
114
+ def self.register_update_hook(hook)
115
+ self.update_hooks.add(hook)
116
+ end
117
+
118
+ # For compatiblity with Calculator::PriceSack
119
+ def amount
120
+ line_items.map(&:amount).sum
121
+ end
122
+
123
+ def to_param
124
+ number.to_s.to_url.upcase
125
+ end
126
+
127
+ def completed?
128
+ !! completed_at
129
+ end
130
+
131
+ # Indicates whether or not the user is allowed to proceed to checkout. Currently this is implemented as a
132
+ # check for whether or not there is at least one LineItem in the Order. Feel free to override this logic
133
+ # in your own application if you require additional steps before allowing a checkout.
134
+ def checkout_allowed?
135
+ line_items.count > 0
136
+ end
137
+
138
+ # Is this a free order in which case the payment step should be skipped
139
+ def payment_required?
140
+ total.to_f > 0.0
141
+ end
142
+
143
+ # Indicates the number of items in the order
144
+ def item_count
145
+ line_items.map(&:quantity).sum
146
+ end
147
+
143
148
  # Indicates whether there are any backordered InventoryUnits associated with the Order.
144
149
  def backordered?
145
150
  return false unless Spree::Config[:track_inventory_levels]
@@ -220,8 +225,8 @@ module Spree
220
225
 
221
226
  def restore_state
222
227
  # pop the resume event so we can see what the event before that was
223
- state_events.pop if state_events.last.name == 'resume'
224
- update_attribute('state', state_events.last.previous_state)
228
+ state_changes.pop if state_changes.last.name == 'resume'
229
+ update_attribute('state', state_changes.last.previous_state)
225
230
 
226
231
  if paid?
227
232
  raise 'do something with inventory'
@@ -250,7 +255,7 @@ module Spree
250
255
 
251
256
  def allow_resume?
252
257
  # we shouldn't allow resume for legacy orders b/c we lack the information necessary to restore to a previous state
253
- return false if state_events.empty? || state_events.last.previous_state.nil?
258
+ return false if state_changes.empty? || state_changes.last.previous_state.nil?
254
259
  true
255
260
  end
256
261
 
@@ -319,8 +324,8 @@ module Spree
319
324
  # include taxes then price adjustments are created instead.
320
325
  def create_tax_charge!
321
326
  # destroy any previous adjustments (eveything is recalculated from scratch)
322
- adjustments.tax.each { |e| e.destroy }
323
- price_adjustments.each { |p| p.destroy }
327
+ adjustments.tax.each(&:destroy)
328
+ price_adjustments.each(&:destroy)
324
329
 
325
330
  TaxRate.match(self).each { |rate| rate.adjust(self) }
326
331
  end
@@ -331,10 +336,10 @@ module Spree
331
336
  if shipment.present?
332
337
  shipment.update_attributes!(:shipping_method => shipping_method)
333
338
  else
334
- self.shipments << Shipment.create!(:order => self,
339
+ self.shipments << Shipment.create!({ :order => self,
335
340
  :shipping_method => shipping_method,
336
341
  :address => self.ship_address,
337
- :inventory_units => self.inventory_units)
342
+ :inventory_units => self.inventory_units}, :without_protection => true)
338
343
  end
339
344
 
340
345
  end
@@ -371,12 +376,12 @@ module Spree
371
376
  adjustments.optional.each { |adjustment| adjustment.update_attribute('locked', true) }
372
377
  OrderMailer.confirm_email(self).deliver
373
378
 
374
- self.state_events.create({
379
+ self.state_changes.create({
375
380
  :previous_state => 'cart',
376
381
  :next_state => 'complete',
377
382
  :name => 'order' ,
378
383
  :user_id => (User.respond_to?(:current) && User.current.try(:id)) || self.user_id
379
- })
384
+ }, :without_protection => true)
380
385
  end
381
386
 
382
387
  # Helper methods for checkout steps
@@ -393,12 +398,11 @@ module Spree
393
398
  def rate_hash
394
399
  @rate_hash ||= available_shipping_methods(:front_end).collect do |ship_method|
395
400
  next unless cost = ship_method.calculator.compute(self)
396
- { :id => ship_method.id,
397
- :shipping_method => ship_method,
398
- :name => ship_method.name,
399
- :cost => cost
400
- }
401
- end.compact.sort_by { |r| r[:cost] }
401
+ ShippingRate.new( :id => ship_method.id,
402
+ :shipping_method => ship_method,
403
+ :name => ship_method.name,
404
+ :cost => cost)
405
+ end.compact.sort_by { |r| r.cost }
402
406
  end
403
407
 
404
408
  def payment
@@ -466,12 +470,12 @@ module Spree
466
470
  self.shipment_state = 'backorder' if backordered?
467
471
 
468
472
  if old_shipment_state = self.changed_attributes['shipment_state']
469
- self.state_events.create({
473
+ self.state_changes.create({
470
474
  :previous_state => old_shipment_state,
471
475
  :next_state => self.shipment_state,
472
476
  :name => 'shipment',
473
477
  :user_id => (User.respond_to?(:current) && User.current && User.current.id) || self.user_id
474
- })
478
+ }, :without_protection => true)
475
479
  end
476
480
  end
477
481
 
@@ -484,7 +488,9 @@ module Spree
484
488
  #
485
489
  # The +payment_state+ value helps with reporting, etc. since it provides a quick and easy way to locate Orders needing attention.
486
490
  def update_payment_state
487
- if round_money(payment_total) < round_money(total)
491
+
492
+ #line_item are empty when user empties cart
493
+ if self.line_items.empty? || round_money(payment_total) < round_money(total)
488
494
  self.payment_state = 'balance_due'
489
495
  self.payment_state = 'failed' if payments.present? and payments.last.state == 'failed'
490
496
  elsif round_money(payment_total) > round_money(total)
@@ -494,12 +500,12 @@ module Spree
494
500
  end
495
501
 
496
502
  if old_payment_state = self.changed_attributes['payment_state']
497
- self.state_events.create({
503
+ self.state_changes.create({
498
504
  :previous_state => old_payment_state,
499
505
  :next_state => self.payment_state,
500
506
  :name => 'payment',
501
507
  :user_id => (User.respond_to?(:current) && User.current && User.current.id) || self.user_id
502
- })
508
+ }, :without_protection => true)
503
509
  end
504
510
  end
505
511
 
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class Payment < ActiveRecord::Base
3
3
  belongs_to :order
4
- belongs_to :source, :polymorphic => true
4
+ belongs_to :source, :polymorphic => true, :validate => true
5
5
  belongs_to :payment_method
6
6
 
7
7
  has_many :offsets, :class_name => 'Spree::Payment', :foreign_key => 'source_id', :conditions => "source_type = 'Spree::Payment' AND amount < 0 AND state = 'completed'"
@@ -30,12 +30,6 @@ module Spree
30
30
  self.count(:conditions => { :type => self.to_s, :environment => Rails.env, :active => true }) > 0
31
31
  end
32
32
 
33
- # TODO: Remove this method by 1.0
34
- def self.current
35
- ActiveSupport::Deprecation.warn "Gateway.current is deprecated and will be removed in Spree > 1.0. Use current_order.payment_method instead."
36
- first(:conditions => { :active => true, :environment => Rails.env })
37
- end
38
-
39
33
  def method_type
40
34
  type.demodulize.downcase
41
35
  end
@@ -1,25 +1,11 @@
1
1
  module Spree
2
2
  class Product < ActiveRecord::Base
3
- cattr_accessor :search_scopes do
4
- []
5
- end
6
-
7
- def self.add_search_scope(name, &block)
8
- self.singleton_class.send(:define_method, name.to_sym, &block)
9
- search_scopes << name.to_sym
10
- end
11
-
12
3
  def self.simple_scopes
13
4
  [
14
5
  :ascend_by_updated_at,
15
6
  :descend_by_updated_at,
16
7
  :ascend_by_name,
17
- :descend_by_name,
18
- # Need to have master price scopes here
19
- # This makes them appear in admin/product_groups/edit
20
- :ascend_by_master_price,
21
- :descend_by_master_price,
22
- :descend_by_popularity
8
+ :descend_by_name
23
9
  ]
24
10
  end
25
11
 
@@ -31,23 +17,37 @@ module Spree
31
17
  self.scope(name.to_s, relation.order(order_text))
32
18
  end
33
19
 
34
- add_search_scope :ascend_by_master_price do
20
+ def self.ascend_by_master_price
35
21
  joins(:variants_with_only_master).order("#{variant_table_name}.price ASC")
36
22
  end
37
23
 
38
- add_search_scope :descend_by_master_price do
24
+ def self.descend_by_master_price
39
25
  joins(:variants_with_only_master).order("#{variant_table_name}.price DESC")
40
26
  end
41
27
 
42
- add_search_scope :price_between do |low, high|
28
+ # Ryan Bates - http://railscasts.com/episodes/112
29
+ # general merging of conditions, names following the searchlogic pattern
30
+ scope :conditions, lambda { |*args| { :conditions => args } }
31
+
32
+ # conditions_all is a more descriptively named enhancement of the above
33
+ scope :conditions_all, lambda { |*args| { :conditions => [args].flatten } }
34
+
35
+ # forming the disjunction of a list of conditions (as strings)
36
+ scope :conditions_any, lambda { |*args|
37
+ args = [args].flatten
38
+ raise "non-strings in conditions_any" unless args.all? { |s| s.is_a? String }
39
+ { :conditions => args.map { |c| "(#{c})"}.join(" OR ") }
40
+ }
41
+
42
+ def self.price_between(low, high)
43
43
  joins(:master).where(Variant.table_name => { :price => low..high })
44
44
  end
45
45
 
46
- add_search_scope :master_price_lte do |price|
46
+ def self.master_price_lte(price)
47
47
  joins(:master).where("#{variant_table_name}.price <= ?", price)
48
48
  end
49
49
 
50
- add_search_scope :master_price_gte do |price|
50
+ def self.master_price_gte(price)
51
51
  joins(:master).where("#{variant_table_name}.price >= ?", price)
52
52
  end
53
53
 
@@ -55,7 +55,7 @@ module Spree
55
55
  # If you need products only within one taxon use
56
56
  #
57
57
  # Spree::Product.taxons_id_eq(x)
58
- add_search_scope :in_taxon do |taxon|
58
+ def self.in_taxon(taxon)
59
59
  joins(:taxons).where(Taxon.table_name => { :id => taxon.self_and_descendants.map(&:id) })
60
60
  end
61
61
 
@@ -63,17 +63,18 @@ module Spree
63
63
  # If you need products only within one taxon use
64
64
  #
65
65
  # Spree::Product.taxons_id_eq([x,y])
66
- add_search_scope :in_taxons do |*taxons|
66
+ #
67
+ def self.in_taxons(*taxons)
67
68
  taxons = get_taxons(taxons)
68
69
  taxons.first ? prepare_taxon_conditions(taxons) : scoped
69
70
  end
70
71
 
71
- def self.in_cached_group(product_group)
72
- joins(:product_groups).where('spree_product_groups_products.product_group_id' => product_group)
73
- end
72
+ # def self.in_cached_group(product_group)
73
+ # joins(:product_groups).where('spree_product_groups_products.product_group_id' => product_group)
74
+ # end
74
75
 
75
76
  # a scope that finds all products having property specified by name, object or id
76
- add_search_scope :with_property do |property|
77
+ def self.with_property(property)
77
78
  properties = Property.table_name
78
79
  conditions = case property
79
80
  when String then { "#{properties}.name" => property }
@@ -86,7 +87,7 @@ module Spree
86
87
 
87
88
  # a simple test for product with a certain property-value pairing
88
89
  # note that it can test for properties with NULL values, but not for absent values
89
- add_search_scope :with_property_value do |property, value|
90
+ def self.with_property_value(property, value)
90
91
  properties = Spree::Property.table_name
91
92
  conditions = case property
92
93
  when String then ["#{properties}.name = ?", property]
@@ -98,7 +99,8 @@ module Spree
98
99
  joins(:properties).where(conditions)
99
100
  end
100
101
 
101
- add_search_scope :with_option do |option|
102
+ # a scope that finds all products having an option_type specified by name, object or id
103
+ def self.with_option(option)
102
104
  option_types = OptionType.table_name
103
105
  conditions = case option
104
106
  when String then { "#{option_types}.name" => option }
@@ -109,7 +111,8 @@ module Spree
109
111
  joins(:option_types).where(conditions)
110
112
  end
111
113
 
112
- add_search_scope :with_option_value do |option, value|
114
+ # a scope that finds all products having an option value specified by name, object or id
115
+ def self.with_option_value(option, value)
113
116
  option_values = OptionValue.table_name
114
117
  option_type_id = case option
115
118
  when String then OptionType.find_by_name(option) || option.to_i
@@ -124,30 +127,30 @@ module Spree
124
127
  # Finds all products which have either:
125
128
  # 1) have an option value with the name matching the one given
126
129
  # 2) have a product property with a value matching the one given
127
- add_search_scope :with do |value|
130
+ def self.with(value)
128
131
  includes(:variants_including_master => :option_values).
129
132
  includes(:product_properties).
130
133
  where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value)
131
134
  end
132
135
 
133
136
  # Finds all products that have a name containing the given words.
134
- add_search_scope :in_name do |words|
137
+ def self.in_name(words)
135
138
  like_any([:name], prepare_words(words))
136
139
  end
137
140
 
138
141
  # Finds all products that have a name or meta_keywords containing the given words.
139
- add_search_scope :in_name_or_keywords do |words|
142
+ def self.in_name_or_keywords(words)
140
143
  like_any([:name, :meta_keywords], prepare_words(words))
141
144
  end
142
145
 
143
146
  # Finds all products that have a name, description, meta_description or meta_keywords containing the given keywords.
144
- add_search_scope :in_name_or_description do |words|
147
+ def self.in_name_or_description(words)
145
148
  like_any([:name, :description, :meta_description, :meta_keywords], prepare_words(words))
146
149
  end
147
150
 
148
151
  # Finds all products that have the ids matching the given collection of ids.
149
152
  # Alternatively, you could use find(collection_of_ids), but that would raise an exception if one product couldn't be found
150
- add_search_scope :with_ids do |*ids|
153
+ def self.with_ids(*ids)
151
154
  where(:id => ids)
152
155
  end
153
156
 
@@ -159,7 +162,7 @@ module Spree
159
162
  #
160
163
  # :joins => "LEFT OUTER JOIN (SELECT line_items.variant_id as vid, COUNT(*) as cnt FROM line_items GROUP BY line_items.variant_id) AS popularity_count ON variants.id = vid",
161
164
  # :order => 'COALESCE(cnt, 0) DESC'
162
- add_search_scope :descend_by_popularity do
165
+ def self.descend_by_popularity
163
166
  joins(:master).
164
167
  order(%Q{
165
168
  COALESCE((
@@ -177,26 +180,25 @@ module Spree
177
180
  })
178
181
  end
179
182
 
180
- add_search_scope :not_deleted do
183
+ def self.not_deleted
181
184
  where(:deleted_at => nil)
182
185
  end
183
186
 
184
- # Can't use add_search_scope for this as it needs a default argument
185
187
  def self.available(available_on = nil)
186
188
  where('available_on <= ?', available_on || Time.now)
187
189
  end
188
- search_scopes << :available
189
190
 
190
- add_search_scope :active do
191
+ #RAILS 3 TODO - this scope doesn't match the original 2.3.x version, needs attention (but it works)
192
+ def self.active
191
193
  not_deleted.available
192
194
  end
193
195
 
194
- add_search_scope :on_hand do
196
+ def self.on_hand
195
197
  variants_table = Variant.table_name
196
- 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)")
198
+ 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)")
197
199
  end
198
200
 
199
- add_search_scope :taxons_name_eq do |name|
201
+ def self.taxons_name_eq(name)
200
202
  joins(:taxons).where(Taxon.arel_table[:name].eq(name))
201
203
  end
202
204
 
@@ -207,8 +209,6 @@ module Spree
207
209
  else
208
210
  scope :group_by_products_id, { :group => "#{self.quoted_table_name}.id" }
209
211
  end
210
- search_methods :group_by_products_id
211
- search_scopes << :group_by_products_id
212
212
 
213
213
  private
214
214
  def self.variant_table_name
@@ -23,26 +23,24 @@ 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
- has_many :images, :as => :viewable, :order => :position, :dependent => :destroy
27
- has_and_belongs_to_many :product_groups, :join_table => 'spree_product_groups_products'
28
26
  belongs_to :tax_category
29
27
  has_and_belongs_to_many :taxons, :join_table => 'spree_products_taxons'
30
28
  belongs_to :shipping_category
31
29
 
32
30
  has_one :master,
33
- :class_name => "Spree::Variant",
34
- :conditions => { :is_master => true }
31
+ :class_name => 'Spree::Variant',
32
+ :conditions => ["#{Variant.quoted_table_name}.is_master = ? AND #{Variant.quoted_table_name}.deleted_at IS NULL", true]
35
33
 
36
34
  delegate_belongs_to :master, :sku, :price, :weight, :height, :width, :depth, :is_master
37
35
  delegate_belongs_to :master, :cost_price if Variant.table_exists? && Variant.column_names.include?('cost_price')
38
36
 
39
37
  after_create :set_master_variant_defaults
40
38
  after_create :add_properties_and_option_types_from_prototype
39
+ after_create :build_variants_from_option_values_hash, :if => :option_values_hash
41
40
  before_save :recalculate_count_on_hand
42
- after_save :update_memberships if ProductGroup.table_exists?
43
41
  after_save :save_master
44
42
  after_save :set_master_on_hand_to_zero_when_product_has_variants
45
-
43
+
46
44
  has_many :variants,
47
45
  :class_name => 'Spree::Variant',
48
46
  :conditions => ["#{::Spree::Variant.quoted_table_name}.is_master = ? AND #{::Spree::Variant.quoted_table_name}.deleted_at IS NULL", false],
@@ -58,23 +56,29 @@ module Spree
58
56
  :conditions => ["#{::Spree::Variant.quoted_table_name}.deleted_at IS NULL AND #{::Spree::Variant.quoted_table_name}.is_master = ?", true],
59
57
  :dependent => :destroy
60
58
 
59
+ accepts_nested_attributes_for :variants, :allow_destroy => true
60
+
61
61
  def variant_images
62
62
  Image.find_by_sql("SELECT #{Asset.quoted_table_name}.* FROM #{Asset.quoted_table_name} LEFT JOIN #{Variant.quoted_table_name} ON (#{Variant.quoted_table_name}.id = #{Asset.quoted_table_name}.viewable_id) WHERE (#{Variant.quoted_table_name}.product_id = #{self.id})")
63
63
  end
64
64
 
65
+ alias_method :images, :variant_images
66
+
65
67
  validates :name, :price, :permalink, :presence => true
66
68
 
69
+ attr_accessor :option_values_hash
70
+
67
71
  attr_accessible :name, :description, :available_on, :permalink, :meta_description,
68
72
  :meta_keywords, :price, :sku, :deleted_at, :prototype_id,
69
73
  :option_values_hash, :on_hand, :weight, :height, :width, :depth,
70
- :shipping_category_id, :tax_category_id, :product_properties_attributes,
71
- :variants_attributes, :taxon_ids
74
+ :shipping_category_id, :tax_category_id
72
75
 
73
76
  attr_accessible :cost_price if Variant.table_exists? && Variant.column_names.include?('cost_price')
74
77
 
78
+
75
79
  accepts_nested_attributes_for :product_properties, :allow_destroy => true, :reject_if => lambda { |pp| pp[:property_name].blank? }
76
80
 
77
- make_permalink
81
+ make_permalink :order => :name
78
82
 
79
83
  alias :options :product_option_types
80
84
 
@@ -124,12 +128,12 @@ module Spree
124
128
  @prototype_id = value.to_i
125
129
  end
126
130
 
127
- def add_properties_and_option_types_from_prototype
128
- if prototype_id && prototype = Spree::Prototype.find_by_id(prototype_id)
129
- prototype.properties.each do |property|
130
- product_properties.create({:property => property}, :without_protection => true)
131
- end
132
- self.option_types = prototype.option_types
131
+ # Ensures option_types and product_option_types exist for keys in option_values_hash
132
+ def ensure_option_types_exist_for_values_hash
133
+ return if option_values_hash.nil?
134
+ option_values_hash.keys.map(&:to_i).each do |id|
135
+ self.option_type_ids << id unless self.option_type_ids.include?(id)
136
+ self.product_option_types.create({:option_type_id => id}, :without_protection => true) unless product_option_types.map(&:option_type_id).include?(id)
133
137
  end
134
138
  end
135
139
 
@@ -145,20 +149,16 @@ module Spree
145
149
  p.product_properties = self.product_properties.map { |q| r = q.dup; r.created_at = r.updated_at = nil; r }
146
150
 
147
151
  image_dup = lambda { |i| j = i.dup; j.attachment = i.attachment.clone; j }
148
- p.images = self.images.map { |i| image_dup.call i }
149
152
 
150
- master = Spree::Variant.find_by_product_id_and_is_master(self.id, true)
151
153
  variant = master.dup
152
154
  variant.sku = 'COPY OF ' + master.sku
153
155
  variant.deleted_at = nil
154
156
  variant.images = master.images.map { |i| image_dup.call i }
155
157
  p.master = variant
156
158
 
157
- if self.has_variants?
158
- # don't dup the actual variants, just the characterising types
159
- p.option_types = self.option_types
160
- else
161
- end
159
+ # don't dup the actual variants, just the characterising types
160
+ p.option_types = self.option_types if self.has_variants?
161
+
162
162
  # allow site to do some customization
163
163
  p.send(:duplicate_extra, self) if p.respond_to?(:duplicate_extra)
164
164
  p.save!
@@ -199,6 +199,28 @@ module Spree
199
199
  end
200
200
 
201
201
  private
202
+
203
+ # Builds variants from a hash of option types & values
204
+ def build_variants_from_option_values_hash
205
+ ensure_option_types_exist_for_values_hash
206
+ values = option_values_hash.values
207
+ values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
208
+
209
+ values.each do |ids|
210
+ variant = self.variants.create({:option_value_ids => ids, :price => self.master.price}, :without_protection => true)
211
+ end
212
+ save
213
+ end
214
+
215
+ def add_properties_and_option_types_from_prototype
216
+ if prototype_id && prototype = Spree::Prototype.find_by_id(prototype_id)
217
+ prototype.properties.each do |property|
218
+ product_properties.create({:property => property}, :without_protection => true)
219
+ end
220
+ self.option_types = prototype.option_types
221
+ end
222
+ end
223
+
202
224
  def recalculate_count_on_hand
203
225
  product_count_on_hand = has_variants? ?
204
226
  variants.sum(:count_on_hand) : (master ? master.count_on_hand : 0)
@@ -221,10 +243,6 @@ module Spree
221
243
  def save_master
222
244
  master.save if master && (master.changed? || master.new_record?)
223
245
  end
224
-
225
- def update_memberships
226
- self.product_groups = ProductGroup.all.select { |pg| pg.include?(self) }
227
- end
228
246
  end
229
247
  end
230
248
 
@@ -6,8 +6,6 @@ module Spree
6
6
  validates :property, :presence => true
7
7
  validates_length_of :value, :maximum => 255
8
8
 
9
- attr_accessible :property_name, :value
10
-
11
9
  # virtual attributes for use with AJAX completion stuff
12
10
  def property_name
13
11
  property.name if property
@@ -5,8 +5,6 @@ module Spree
5
5
  has_many :product_properties, :dependent => :destroy
6
6
  has_many :products, :through => :product_properties
7
7
 
8
- after_destroy :recalculate_product_group_products
9
-
10
8
  attr_accessible :name, :presentation
11
9
 
12
10
  validates :name, :presentation, :presence => true
@@ -20,19 +18,5 @@ module Spree
20
18
  end
21
19
  joins("LEFT JOIN properties_prototypes ON property_id = #{self.table_name}.id").where(:prototype_id => id)
22
20
  end
23
-
24
- private
25
-
26
- # A fix for #774
27
- # What was happening was that when a property was deleted, any product group
28
- # that used the property to calculate included properties was not recalculated
29
- #
30
- # Recalculates product group products after the property has been deleted
31
- def recalculate_product_group_products
32
- ProductScope.where(:name => "with_property", :arguments => [self.name].to_yaml).each do |scope|
33
- # Triggers ProductGroup#update_memberships callback to recalculate products
34
- scope.product_group.save!
35
- end
36
- end
37
21
  end
38
22
  end
@@ -6,6 +6,5 @@ module Spree
6
6
  attr_accessible :name
7
7
 
8
8
  validates :name, :presence => true
9
-
10
9
  end
11
10
  end