spree_core 1.0.7 → 1.1.0.rc1

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 (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