spree_core 0.40.4 → 0.50.0

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 (251) hide show
  1. data/README.md +6 -1
  2. data/app/controllers/admin/base_controller.rb +0 -57
  3. data/app/controllers/admin/configurations_controller.rb +1 -1
  4. data/app/controllers/admin/general_settings_controller.rb +1 -1
  5. data/app/controllers/admin/images_controller.rb +18 -8
  6. data/app/controllers/admin/inventory_settings_controller.rb +1 -1
  7. data/app/controllers/admin/mail_settings_controller.rb +1 -1
  8. data/app/controllers/admin/option_types_controller.rb +11 -0
  9. data/app/controllers/admin/orders_controller.rb +16 -20
  10. data/app/controllers/admin/overview_controller.rb +1 -1
  11. data/app/controllers/admin/payment_methods_controller.rb +6 -6
  12. data/app/controllers/admin/product_groups_controller.rb +10 -11
  13. data/app/controllers/admin/product_properties_controller.rb +5 -5
  14. data/app/controllers/admin/product_scopes_controller.rb +12 -4
  15. data/app/controllers/admin/products_controller.rb +17 -12
  16. data/app/controllers/admin/properties_controller.rb +7 -7
  17. data/app/controllers/admin/prototypes_controller.rb +15 -15
  18. data/app/controllers/admin/reports_controller.rb +12 -11
  19. data/app/controllers/admin/shipments_controller.rb +0 -5
  20. data/app/controllers/admin/shipping_categories_controller.rb +6 -6
  21. data/app/controllers/admin/shipping_methods_controller.rb +1 -2
  22. data/app/controllers/admin/states_controller.rb +12 -12
  23. data/app/controllers/admin/tax_categories_controller.rb +2 -2
  24. data/app/controllers/admin/tax_rates_controller.rb +8 -8
  25. data/app/controllers/admin/tax_settings_controller.rb +1 -1
  26. data/app/controllers/admin/taxonomies_controller.rb +3 -3
  27. data/app/controllers/admin/taxons_controller.rb +1 -8
  28. data/app/controllers/admin/trackers_controller.rb +1 -1
  29. data/app/controllers/admin/users_controller.rb +6 -9
  30. data/app/controllers/admin/variants_controller.rb +13 -2
  31. data/app/controllers/admin/zones_controller.rb +9 -9
  32. data/app/controllers/checkout_controller.rb +5 -5
  33. data/app/controllers/products_controller.rb +1 -1
  34. data/app/controllers/taxons_controller.rb +3 -1
  35. data/app/helpers/admin/navigation_helper.rb +36 -19
  36. data/app/helpers/admin/payments_helper.rb +1 -1
  37. data/app/helpers/admin/product_groups_helper.rb +1 -1
  38. data/app/helpers/admin/product_properties_helper.rb +5 -5
  39. data/app/helpers/admin/products_helper.rb +2 -2
  40. data/app/helpers/admin/users_helper.rb +5 -2
  41. data/app/helpers/hook_helper.rb +3 -3
  42. data/app/helpers/products_helper.rb +0 -19
  43. data/app/helpers/spree/base_helper.rb +33 -2
  44. data/app/helpers/taxons_helper.rb +3 -3
  45. data/app/mailers/order_mailer.rb +2 -2
  46. data/app/mailers/shipment_mailer.rb +2 -2
  47. data/app/models/address.rb +42 -9
  48. data/app/models/adjustment.rb +8 -8
  49. data/app/models/app_configuration.rb +0 -6
  50. data/app/models/billing_integration.rb +1 -1
  51. data/app/models/calculator/sales_tax.rb +3 -3
  52. data/app/models/configuration.rb +1 -1
  53. data/app/models/country.rb +4 -5
  54. data/app/models/creditcard.rb +38 -31
  55. data/app/models/gateway.rb +14 -14
  56. data/app/models/gateway/beanstream.rb +4 -4
  57. data/app/models/gateway/bogus.rb +6 -6
  58. data/app/models/gateway/braintree.rb +88 -0
  59. data/app/models/gateway/eway.rb +3 -3
  60. data/app/models/image.rb +5 -4
  61. data/app/models/option_type.rb +1 -0
  62. data/app/models/order.rb +60 -21
  63. data/app/models/payment.rb +5 -32
  64. data/app/models/preference.rb +7 -7
  65. data/app/models/product.rb +22 -7
  66. data/app/models/product_group.rb +22 -26
  67. data/app/models/product_property.rb +5 -5
  68. data/app/models/product_scope.rb +26 -6
  69. data/app/models/property.rb +1 -1
  70. data/app/models/state.rb +2 -3
  71. data/app/models/tax_category.rb +1 -0
  72. data/app/models/tax_rate.rb +1 -2
  73. data/app/models/taxon.rb +12 -10
  74. data/app/models/taxonomy.rb +7 -4
  75. data/app/models/tracker.rb +1 -1
  76. data/app/models/user.rb +4 -0
  77. data/app/models/variant.rb +1 -1
  78. data/app/models/zone.rb +1 -1
  79. data/app/models/zone_member.rb +3 -3
  80. data/app/views/admin/{shared → adjustments}/_adjustments_table.html.erb +7 -4
  81. data/app/views/admin/adjustments/edit.html.erb +1 -1
  82. data/app/views/admin/adjustments/index.html.erb +2 -2
  83. data/app/views/admin/adjustments/new.html.erb +2 -1
  84. data/app/views/admin/general_settings/edit.html.erb +4 -12
  85. data/app/views/admin/general_settings/show.html.erb +0 -5
  86. data/app/views/admin/images/index.html.erb +8 -5
  87. data/app/views/admin/inventory_settings/show.html.erb +1 -1
  88. data/app/views/admin/mail_methods/index.html.erb +4 -4
  89. data/app/views/admin/option_types/_form.html.erb +4 -4
  90. data/app/views/admin/option_types/_option_value_fields.html.erb +2 -2
  91. data/app/views/admin/option_types/edit.html.erb +4 -2
  92. data/app/views/admin/option_types/index.html.erb +5 -5
  93. data/app/views/admin/orders/_line_item.html.erb +2 -1
  94. data/app/views/admin/orders/history.html.erb +6 -2
  95. data/app/views/admin/orders/index.html.erb +22 -19
  96. data/app/views/admin/orders/show.html.erb +1 -1
  97. data/app/views/admin/orders/user.html.erb +1 -1
  98. data/app/views/admin/payment_methods/index.html.erb +7 -5
  99. data/app/views/admin/payments/_list.html.erb +3 -3
  100. data/app/views/admin/payments/index.html.erb +1 -1
  101. data/app/views/admin/payments/show.html.erb +2 -2
  102. data/app/views/admin/product_groups/edit.html.erb +7 -7
  103. data/app/views/admin/product_groups/index.html.erb +5 -3
  104. data/app/views/admin/product_groups/update.js.erb +4 -3
  105. data/app/views/admin/product_properties/_product_property_fields.html.erb +3 -3
  106. data/app/views/admin/product_properties/index.html.erb +10 -5
  107. data/app/views/admin/product_scopes/destroy.js.erb +1 -0
  108. data/app/views/admin/products/index.html.erb +32 -33
  109. data/app/views/admin/properties/_form.html.erb +2 -2
  110. data/app/views/admin/properties/index.html.erb +4 -4
  111. data/app/views/admin/prototypes/index.html.erb +4 -4
  112. data/app/views/admin/shared/_address_form.html.erb +1 -1
  113. data/app/views/admin/shared/_calculator_fields.html.erb +1 -1
  114. data/app/views/admin/shared/_destroy.js.erb +15 -2
  115. data/app/views/admin/shared/_order_tabs.html.erb +1 -1
  116. data/app/views/admin/shared/_report_criteria.html.erb +1 -1
  117. data/app/views/admin/shipments/_form.html.erb +6 -2
  118. data/app/views/admin/shipments/edit.html.erb +1 -1
  119. data/app/views/admin/shipments/index.html.erb +4 -2
  120. data/app/views/admin/shipping_methods/_form.html.erb +2 -0
  121. data/app/views/admin/shipping_methods/index.html.erb +3 -2
  122. data/app/views/admin/states/_state_list.html.erb +11 -5
  123. data/app/views/admin/tax_categories/index.html.erb +9 -4
  124. data/app/views/admin/tax_settings/show.html.erb +2 -2
  125. data/app/views/admin/taxonomies/_list.html.erb +4 -2
  126. data/app/views/admin/taxonomies/index.html.erb +2 -2
  127. data/app/views/admin/taxons/_form.html.erb +1 -1
  128. data/app/views/admin/trackers/index.html.erb +5 -5
  129. data/app/views/admin/users/_form.html.erb +3 -4
  130. data/app/views/admin/users/index.html.erb +7 -6
  131. data/app/views/admin/users/show.html.erb +3 -3
  132. data/app/views/admin/variants/index.html.erb +21 -6
  133. data/app/views/admin/zones/_form.html.erb +9 -9
  134. data/app/views/admin/zones/_member_type.html.erb +5 -5
  135. data/app/views/admin/zones/index.html.erb +7 -5
  136. data/app/views/checkout/_address.html.erb +2 -2
  137. data/app/views/checkout/_payment.html.erb +3 -6
  138. data/app/views/layouts/admin.html.erb +3 -9
  139. data/app/views/layouts/spree_application.html.erb +2 -1
  140. data/app/views/orders/_line_item.html.erb +1 -1
  141. data/app/views/orders/edit.html.erb +17 -16
  142. data/app/views/orders/show.html.erb +1 -1
  143. data/app/views/shared/_admin_head.html.erb +1 -1
  144. data/app/views/shared/_error_messages.html.erb +2 -2
  145. data/app/views/shared/_filters.html.erb +4 -4
  146. data/app/views/shared/_head.html.erb +2 -2
  147. data/app/views/shared/_nav_bar.html.erb +2 -2
  148. data/app/views/shared/_products.html.erb +4 -2
  149. data/app/views/shared/_taxonomies.html.erb +15 -8
  150. data/app/views/shipment_mailer/shipped_email.text.erb +2 -2
  151. data/config/cucumber.yml +10 -0
  152. data/config/initializers/form_builder.rb +1 -5
  153. data/config/initializers/workarounds_for_ruby19.rb +5 -5
  154. data/config/locales/en.yml +33 -6
  155. data/config/routes.rb +18 -13
  156. data/db/migrate/20090923100315_add_count_on_hand_to_variants_and_products.rb +5 -5
  157. data/db/migrate/20091213222815_creditcard_last_four_digits.rb +5 -5
  158. data/db/migrate/20100105132138_shipment_id_for_inventory_units.rb +2 -2
  159. data/db/migrate/20100209025806_create_payment_methods.rb +3 -3
  160. data/db/migrate/20100209144531_polymorphic_payments.rb +1 -1
  161. data/db/migrate/20100214212536_assign_creditcard_txns_to_payment.rb +2 -2
  162. data/db/migrate/20100224153127_deleted_at_for_payment_methods.rb +1 -1
  163. data/db/migrate/20100506185838_add_description_to_taxons.rb +1 -1
  164. data/db/migrate/20100816212146_shipping_method_id_for_orders.rb +1 -1
  165. data/db/migrate/20101026184808_migrate_checkout_to_orders.rb +2 -2
  166. data/db/migrate/20101223215658_add_position_to_variants.rb +9 -0
  167. data/db/migrate/20110110130847_add_next_state_to_state_events.rb +9 -0
  168. data/db/migrate/20110111122537_add_position_to_option_types.rb +9 -0
  169. data/db/migrate/20110314192118_remove_trailing_slashes_in_taxon_permalinks.rb +17 -0
  170. data/lib/custom_fixtures.rb +1 -1
  171. data/lib/{seo_assist.rb → middleware/seo_assist.rb} +14 -8
  172. data/lib/product_filters.rb +49 -43
  173. data/lib/redirect_legacy_product_url.rb +5 -5
  174. data/lib/scopes.rb +2 -2
  175. data/lib/scopes/dynamic.rb +9 -16
  176. data/lib/scopes/product.rb +33 -16
  177. data/lib/scopes/variant.rb +4 -3
  178. data/lib/spree/calculated_adjustments.rb +5 -2
  179. data/lib/spree/config.rb +2 -0
  180. data/lib/spree/current_order.rb +4 -4
  181. data/lib/spree/mail_settings.rb +3 -2
  182. data/lib/spree/search/base.rb +9 -10
  183. data/lib/spree_base.rb +22 -23
  184. data/lib/spree_core.rb +10 -69
  185. data/lib/spree_core/authorize_net_cim_hack.rb +1 -1
  186. data/lib/spree_core/delegate_belongs_to.rb +18 -24
  187. data/lib/spree_core/enumerable_constants.rb +38 -38
  188. data/lib/spree_core/find_by_param.rb +8 -6
  189. data/lib/spree_core/preferences/preference_definition.rb +7 -7
  190. data/lib/spree_core/railtie.rb +58 -0
  191. data/lib/spree_core/ssl_requirement.rb +4 -3
  192. data/lib/spree_core/testing_support/factories.rb +13 -0
  193. data/lib/spree_core/testing_support/factories/address_factory.rb +20 -0
  194. data/lib/spree_core/testing_support/factories/adjustment_factory.rb +6 -0
  195. data/lib/spree_core/testing_support/factories/calculator_factory.rb +5 -0
  196. data/lib/spree_core/testing_support/factories/configuraion_factory.rb +4 -0
  197. data/lib/spree_core/testing_support/factories/country_factory.rb +7 -0
  198. data/lib/spree_core/testing_support/factories/creditcard_factory.rb +11 -0
  199. data/lib/spree_core/testing_support/factories/inventory_unit_factory.rb +7 -0
  200. data/lib/spree_core/testing_support/factories/line_item_factory.rb +8 -0
  201. data/lib/spree_core/testing_support/factories/mail_method_factory.rb +4 -0
  202. data/lib/spree_core/testing_support/factories/options_factory.rb +10 -0
  203. data/lib/spree_core/testing_support/factories/order_factory.rb +18 -0
  204. data/lib/spree_core/testing_support/factories/payment_factory.rb +26 -0
  205. data/lib/spree_core/testing_support/factories/payment_method_factory.rb +17 -0
  206. data/lib/spree_core/testing_support/factories/product_factory.rb +16 -0
  207. data/lib/spree_core/testing_support/factories/product_group_factory.rb +3 -0
  208. data/lib/spree_core/testing_support/factories/product_option_type_factory.rb +4 -0
  209. data/lib/spree_core/testing_support/factories/product_property_factory.rb +4 -0
  210. data/lib/spree_core/testing_support/factories/product_scope_factory.rb +6 -0
  211. data/lib/spree_core/testing_support/factories/property_factory.rb +4 -0
  212. data/lib/spree_core/testing_support/factories/prototype_factory.rb +4 -0
  213. data/lib/spree_core/testing_support/factories/return_authorization_factory.rb +8 -0
  214. data/lib/spree_core/testing_support/factories/role_factory.rb +9 -0
  215. data/lib/spree_core/testing_support/factories/shipment_factory.rb +9 -0
  216. data/lib/spree_core/testing_support/factories/shipping_category_factory.rb +5 -0
  217. data/lib/spree_core/testing_support/factories/shipping_method_factory.rb +7 -0
  218. data/lib/spree_core/testing_support/factories/state_factory.rb +11 -0
  219. data/lib/spree_core/testing_support/factories/tax_category_factory.rb +8 -0
  220. data/lib/spree_core/testing_support/factories/tax_rate_factory.rb +5 -0
  221. data/lib/spree_core/testing_support/factories/taxon_factory.rb +5 -0
  222. data/lib/spree_core/testing_support/factories/taxonomy_factory.rb +3 -0
  223. data/lib/spree_core/testing_support/factories/tracker_factory.rb +5 -0
  224. data/lib/spree_core/testing_support/factories/user_factory.rb +15 -0
  225. data/lib/spree_core/testing_support/factories/variant_factory.rb +14 -0
  226. data/lib/spree_core/testing_support/factories/zone_factory.rb +18 -0
  227. data/lib/spree_core/theme_support/hook.rb +1 -1
  228. data/lib/spree_core/theme_support/more_patches.rb +20 -20
  229. data/lib/spree_core/version.rb +5 -0
  230. data/lib/tasks/common.rb +30 -0
  231. data/lib/tasks/install.rake +1 -1
  232. data/lib/tasks/rake_util.rb +19 -0
  233. data/lib/tasks/taxon.rake +14 -0
  234. data/public/images/reorder.jpg +0 -0
  235. data/public/javascripts/admin.js +0 -6
  236. data/public/javascripts/admin/unobtrusive_handlers.js +28 -0
  237. data/public/javascripts/checkout.js +3 -3
  238. data/public/stylesheets/admin/admin-forms.css +1 -6
  239. data/public/stylesheets/admin/admin.css +0 -28
  240. data/public/stylesheets/screen.css +0 -280
  241. metadata +81 -43
  242. data/app/controllers/countries_controller.rb +0 -11
  243. data/app/models/spree/alert.rb +0 -13
  244. data/app/models/state_monitor.rb +0 -25
  245. data/app/views/admin/shared/_alert.html.erb +0 -6
  246. data/app/views/countries/index.js.erb +0 -1
  247. data/app/views/shared/_doc_and_xmlns.html.erb +0 -2
  248. data/app/views/users/edit.html.erb +0 -9
  249. data/app/views/users/show.html.erb +0 -46
  250. data/lib/spree_core/validation_group.rb +0 -143
  251. data/public/stylesheets/scaffold.css +0 -54
@@ -1,11 +1,11 @@
1
1
  class Gateway::Eway < Gateway
2
2
  preference :login, :string
3
-
3
+
4
4
  # Note: EWay supports purchase method only (no authorize method).
5
5
  # Ensure Spree::Config[:auto_capture] is set to true
6
-
6
+
7
7
  def provider_class
8
8
  ActiveMerchant::Billing::EwayGateway
9
9
  end
10
-
10
+
11
11
  end
data/app/models/image.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  class Image < Asset
2
2
  validate :no_attachement_errors
3
- has_attached_file :attachment,
4
- :styles => { :mini => '48x48>', :small => '100x100>', :product => '240x240>', :large => '600x600>' },
3
+ has_attached_file :attachment,
4
+ :styles => { :mini => '48x48>', :small => '100x100>', :product => '240x240>', :large => '600x600>' },
5
5
  :default_style => :product,
6
6
  :url => "/assets/products/:id/:style/:basename.:extension",
7
7
  :path => ":rails_root/public/assets/products/:id/:style/:basename.:extension"
@@ -9,8 +9,9 @@ class Image < Asset
9
9
  # save the w,h of the original image (from which others can be calculated)
10
10
  # we need to look at the write-queue for images which have not been saved yet
11
11
  after_post_process :find_dimensions
12
+
12
13
  def find_dimensions
13
- temporary = attachment.queued_for_write[:original]
14
+ temporary = attachment.queued_for_write[:original]
14
15
  filename = temporary.path unless temporary.nil?
15
16
  filename = attachment.path if filename.blank?
16
17
  geometry = Paperclip::Geometry.from_file(filename)
@@ -22,7 +23,7 @@ class Image < Asset
22
23
  def no_attachement_errors
23
24
  unless attachment.errors.empty?
24
25
  # uncomment this to get rid of the less-than-useful interrim messages
25
- # errors.clear
26
+ # errors.clear
26
27
  errors.add :attachment, "Paperclip returned errors for file '#{attachment_file_name}' - check ImageMagick installation or image source file."
27
28
  false
28
29
  end
@@ -3,6 +3,7 @@ class OptionType < ActiveRecord::Base
3
3
  has_many :product_option_types, :dependent => :destroy
4
4
  has_and_belongs_to_many :prototypes
5
5
  validates :name, :presentation, :presence => true
6
+ default_scope :order => "option_types.position"
6
7
 
7
8
  accepts_nested_attributes_for :option_values, :reject_if => lambda { |ov| ov[:name].blank? || ov[:presentation].blank? }, :allow_destroy => true
8
9
  end
data/app/models/order.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  class Order < ActiveRecord::Base
2
2
 
3
- attr_accessible :line_items, :bill_address_attributes, :ship_address_attributes, :payments_attributes, :ship_address, :line_items_attributes,
3
+ attr_accessible :line_items, :bill_address_attributes, :ship_address_attributes, :payments_attributes,
4
+ :ship_address, :line_items_attributes,
4
5
  :shipping_method_id, :email, :use_billing, :special_instructions
5
6
 
6
7
  belongs_to :user
@@ -26,6 +27,7 @@ class Order < ActiveRecord::Base
26
27
  before_create :generate_order_number
27
28
 
28
29
  validates_presence_of :email, :if => :require_email
30
+ validate :has_available_shipment
29
31
 
30
32
  #delegate :ip_address, :to => :checkout
31
33
  def ip_address
@@ -33,8 +35,8 @@ class Order < ActiveRecord::Base
33
35
  end
34
36
 
35
37
  scope :by_number, lambda {|number| where("orders.number = ?", number)}
36
- scope :between, lambda {|*dates| where("orders.created_at between :start and :stop").where(:start, dates.first.to_date).where(:stop, dates.last.to_date)}
37
- scope :by_customer, lambda {|customer| where("uses.email =?", customer).includes(:user)}
38
+ scope :between, lambda {|*dates| where("orders.created_at between ? and ?", dates.first.to_date, dates.last.to_date)}
39
+ scope :by_customer, lambda {|customer| joins(:user).where("users.email =?", customer)}
38
40
  scope :by_state, lambda {|state| where("state = ?", state)}
39
41
  scope :complete, where("orders.completed_at IS NOT NULL")
40
42
  scope :incomplete, where("orders.completed_at IS NULL")
@@ -43,7 +45,7 @@ class Order < ActiveRecord::Base
43
45
 
44
46
  attr_accessor :out_of_stock_items
45
47
 
46
- class_inheritable_accessor :update_hooks
48
+ class_attribute :update_hooks
47
49
  self.update_hooks = Set.new
48
50
 
49
51
  # Use this method in other gems that wish to register their own custom logic that should be called after Order#updat
@@ -75,12 +77,15 @@ class Order < ActiveRecord::Base
75
77
  state_machine :initial => 'cart', :use_transactions => false do
76
78
 
77
79
  event :next do
78
- transition :from => 'cart', :to => 'address'
79
- transition :from => 'address', :to => 'delivery'
80
+ transition :from => 'cart', :to => 'address'
81
+ transition :from => 'address', :to => 'delivery'
80
82
  transition :from => 'delivery', :to => 'payment'
81
- transition :from => 'confirm', :to => 'complete'
83
+ transition :from => 'confirm', :to => 'complete'
84
+
82
85
  # note: some payment methods will not support a confirm step
83
- transition :from => 'payment', :to => 'confirm', :if => Proc.new { Gateway.current and Gateway.current.payment_profiles_supported? }
86
+ transition :from => 'payment', :to => 'confirm',
87
+ :if => Proc.new { Gateway.current && Gateway.current.payment_profiles_supported? }
88
+
84
89
  transition :from => 'payment', :to => 'complete'
85
90
  end
86
91
 
@@ -221,13 +226,15 @@ class Order < ActiveRecord::Base
221
226
  current_item
222
227
  end
223
228
 
229
+ # FIXME refactor this method and implement validation using validates_* utilities
224
230
  def generate_order_number
225
231
  record = true
226
232
  while record
227
233
  random = "R#{Array.new(9){rand(9)}.join}"
228
- record = Order.find(:first, :conditions => ["number = ?", random])
234
+ record = self.class.find(:first, :conditions => ["number = ?", random])
229
235
  end
230
- self.number = random
236
+ self.number = random if self.number.blank?
237
+ self.number
231
238
  end
232
239
 
233
240
  # convenience method since many stores will not allow user to create multiple shipments
@@ -250,12 +257,8 @@ class Order < ActiveRecord::Base
250
257
  # Creates a new tax charge if applicable. Uses the highest possible matching rate and destroys any previous
251
258
  # tax charges if they were created by rates that no longer apply.
252
259
  def create_tax_charge!
253
- return unless rate = TaxRate.match(bill_address) or adjustments.tax.present?
254
- if old_charge = adjustments.tax.first
255
- old_charge.destroy unless old_charge.originator == rate
256
- return
257
- end
258
- rate.create_adjustment(I18n.t(:tax), self, self, true)
260
+ adjustments.tax.each {|e| e.destroy }
261
+ TaxRate.match(ship_address).each {|r| r.create_adjustment(I18n.t(:tax), self, self, true) }
259
262
  end
260
263
 
261
264
  # Creates a new shipment (adjustment is created by shipment model)
@@ -264,7 +267,9 @@ class Order < ActiveRecord::Base
264
267
  if shipment.present?
265
268
  shipment.update_attributes(:shipping_method => shipping_method)
266
269
  else
267
- self.shipments << Shipment.create(:order => self, :shipping_method => shipping_method, :address => self.ship_address)
270
+ self.shipments << Shipment.create(:order => self,
271
+ :shipping_method => shipping_method,
272
+ :address => self.ship_address)
268
273
  end
269
274
 
270
275
  end
@@ -283,8 +288,9 @@ class Order < ActiveRecord::Base
283
288
  end
284
289
 
285
290
  def name
286
- address = bill_address || ship_address
287
- "#{address.firstname} #{address.lastname}" if address
291
+ if (address = bill_address || ship_address)
292
+ "#{address.firstname} #{address.lastname}"
293
+ end
288
294
  end
289
295
 
290
296
  def creditcards
@@ -304,6 +310,13 @@ class Order < ActiveRecord::Base
304
310
  # lock any optional adjustments (coupon promotions, etc.)
305
311
  adjustments.optional.each { |adjustment| adjustment.update_attribute("locked", true) }
306
312
  OrderMailer.confirm_email(self).deliver
313
+
314
+ self.state_events.create({
315
+ :previous_state => "cart",
316
+ :next_state => "complete",
317
+ :name => "order" ,
318
+ :user_id => (User.respond_to?(:current) && User.current.try(:id)) || self.user_id
319
+ })
307
320
  end
308
321
 
309
322
 
@@ -316,12 +329,13 @@ class Order < ActiveRecord::Base
316
329
 
317
330
  def rate_hash
318
331
  @rate_hash ||= available_shipping_methods(:front_end).collect do |ship_method|
332
+ next unless cost = ship_method.calculator.compute(self)
319
333
  { :id => ship_method.id,
320
334
  :shipping_method => ship_method,
321
335
  :name => ship_method.name,
322
- :cost => ship_method.calculator.compute(self)
336
+ :cost => cost
323
337
  }
324
- end.sort_by{|r| r[:cost]}
338
+ end.compact.sort_by{|r| r[:cost]}
325
339
  end
326
340
 
327
341
  def payment
@@ -383,6 +397,16 @@ class Order < ActiveRecord::Base
383
397
  "partial"
384
398
  end
385
399
  self.shipment_state = "backorder" if backordered?
400
+
401
+ if old_shipment_state = self.changed_attributes["shipment_state"]
402
+ self.state_events.create({
403
+ :previous_state => old_shipment_state,
404
+ :next_state => self.shipment_state,
405
+ :name => "shipment" ,
406
+ :user_id => (User.respond_to?(:current) && User.current && User.current.id) || self.user_id
407
+ })
408
+ end
409
+
386
410
  end
387
411
 
388
412
  # Updates the +payment_state+ attribute according to the following logic:
@@ -402,6 +426,15 @@ class Order < ActiveRecord::Base
402
426
  else
403
427
  self.payment_state = "paid"
404
428
  end
429
+
430
+ if old_payment_state = self.changed_attributes["payment_state"]
431
+ self.state_events.create({
432
+ :previous_state => old_payment_state,
433
+ :next_state => self.payment_state,
434
+ :name => "payment" ,
435
+ :user_id => (User.respond_to?(:current) && User.current && User.current.id) || self.user_id
436
+ })
437
+ end
405
438
  end
406
439
 
407
440
  # Updates the following Order total values:
@@ -433,6 +466,12 @@ class Order < ActiveRecord::Base
433
466
  return true unless new_record? or state == 'cart'
434
467
  end
435
468
 
469
+ def has_available_shipment
470
+ return unless :address == state_name.to_sym
471
+ return unless ship_address && ship_address.valid?
472
+ errors.add(:base, :no_shipping_methods_available) if available_shipping_methods.empty?
473
+ end
474
+
436
475
  def after_cancel
437
476
  # TODO: make_shipments_pending
438
477
  # TODO: restock_inventory
@@ -6,26 +6,19 @@ class Payment < ActiveRecord::Base
6
6
  has_many :offsets, :class_name => 'Payment', :foreign_key => 'source_id', :conditions => "source_type = 'Payment' AND amount < 0 AND state = 'completed'"
7
7
  has_many :log_entries, :as => :source
8
8
 
9
- after_save :create_payment_profile, :if => :payment_profiles_supported?
9
+ after_save :create_payment_profile, :if => :profiles_supported?
10
10
 
11
11
  # update the order totals, etc.
12
12
  after_save :update_order
13
13
 
14
- #after_save :check_payments
15
- #after_destroy :check_payments
16
-
17
14
  accepts_nested_attributes_for :source
18
15
 
19
- #validate :amount_is_valid_for_outstanding_balance_or_credit
20
- #validates :payment_method, :presence => true, :if => Proc.new { |payable| payable.is_a? Checkout }
21
-
22
16
  scope :from_creditcard, where(:source_type => 'Creditcard')
23
17
  scope :with_state, lambda {|s| where(:state => s)}
24
18
  scope :completed, with_state('completed')
25
19
  scope :pending, with_state('pending')
26
20
  scope :failed, with_state('failed')
27
21
 
28
-
29
22
  # order state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
30
23
  state_machine :initial => 'checkout' do
31
24
  # With card payments, happens before purchase or authorization happens
@@ -93,26 +86,6 @@ class Payment < ActiveRecord::Base
93
86
 
94
87
  private
95
88
 
96
- # def check_payments
97
- # return unless order and order.complete?
98
- # #sorting by created_at.to_f to ensure millisecond percsision, plus ID - just in case
99
- # events = order.state_events.sort_by { |e| [e.created_at.to_f, e.id] }.reverse
100
- # # TODO: think the below implementation will need replacing
101
- # # if order.returnable_units.nil? && order.return_authorizations.size >0
102
- # # order.return!
103
- # # elsif events.present? and %w(over_paid under_paid).include?(events.first.name)
104
- # # events.each do |event|
105
- # # if %w(shipped paid new).include?(event.previous_state)
106
- # # order.pay!
107
- # # order.update_attribute("state", event.previous_state) if %w(shipped returned).include?(event.previous_state)
108
- # # return
109
- # # end
110
- # # end
111
- # # elsif order.payment_total >= order.total
112
- # # order.pay!
113
- # # end
114
- # end
115
-
116
89
  def amount_is_valid_for_outstanding_balance_or_credit
117
90
  return unless order
118
91
  if amount != order.outstanding_balance
@@ -120,13 +93,13 @@ class Payment < ActiveRecord::Base
120
93
  end
121
94
  end
122
95
 
123
- def payment_profiles_supported?
124
- source && source.respond_to?(:payment_gateway) && source.payment_gateway && source.payment_gateway.payment_profiles_supported?
96
+ def profiles_supported?
97
+ payment_method.respond_to?(:payment_profiles_supported?) && payment_method.payment_profiles_supported?
125
98
  end
126
99
 
127
100
  def create_payment_profile
128
- return unless payment_profiles_supported? and source.number and !source.has_payment_profile?
129
- source.payment_gateway.create_profile(self)
101
+ return unless source.is_a?(Creditcard) && source.number && !source.has_payment_profile?
102
+ payment_method.create_profile(self)
130
103
  rescue ActiveMerchant::ConnectionError => e
131
104
  gateway_error I18n.t(:unable_to_connect_to_gateway)
132
105
  end
@@ -1,7 +1,7 @@
1
1
  # Represents a preferred value for a particular preference on a model.
2
- #
2
+ #
3
3
  # == Targeted preferences
4
- #
4
+ #
5
5
  # In addition to simple named preferences, preferences can also be targeted for
6
6
  # a particular record. For example, a User may have a preferred color for a
7
7
  # particular Car. In this case, the +owner+ is the User, the +preference+ is
@@ -10,10 +10,10 @@
10
10
  class Preference < ActiveRecord::Base
11
11
  belongs_to :owner, :polymorphic => true
12
12
  belongs_to :group, :polymorphic => true
13
-
13
+
14
14
  validates :name, :owner_id, :owner_type, :presence => true
15
15
  validates :group_type, :presence => true, :if => :group_id?
16
-
16
+
17
17
  class << self
18
18
  # Splits the given group into its corresponding id and type
19
19
  def split_group(group = nil)
@@ -25,19 +25,19 @@ class Preference < ActiveRecord::Base
25
25
  return group_id, group_type
26
26
  end
27
27
  end
28
-
28
+
29
29
  # The definition for the attribute
30
30
  def definition
31
31
  owner.preference_definitions[name] unless owner_type.blank?
32
32
  end
33
-
33
+
34
34
  # Typecasts the value depending on the preference definition's declared type
35
35
  def value
36
36
  value = read_attribute(:value)
37
37
  value = definition.type_cast(value) if definition
38
38
  value
39
39
  end
40
-
40
+
41
41
  # Only searches for the group record if the group id is specified
42
42
  def group_with_optional_lookup
43
43
  group_id ? group_without_optional_lookup : group_type
@@ -43,7 +43,8 @@ class Product < ActiveRecord::Base
43
43
  after_save :save_master
44
44
 
45
45
  has_many :variants,
46
- :conditions => ["variants.is_master = ? AND variants.deleted_at IS NULL", false]
46
+ :conditions => ["variants.is_master = ? AND variants.deleted_at IS NULL", false],
47
+ :order => 'variants.position ASC'
47
48
 
48
49
 
49
50
  has_many :variants_including_master,
@@ -51,9 +52,15 @@ class Product < ActiveRecord::Base
51
52
  :conditions => ["variants.deleted_at IS NULL"],
52
53
  :dependent => :destroy
53
54
 
55
+ has_many :variants_with_only_master,
56
+ :class_name => 'Variant',
57
+ :conditions => ["variants.deleted_at IS NULL AND variants.is_master = ?", true],
58
+ :dependent => :destroy
59
+
60
+
54
61
  validates :name, :price, :permalink, :presence => true
55
62
 
56
- accepts_nested_attributes_for :product_properties, :allow_destroy => true, :reject_if => lambda { |pp| pp[:property_name].blank? }
63
+ accepts_nested_attributes_for :product_properties, :allow_destroy => true, :reject_if => lambda { |pp| pp[:property_name].blank? }
57
64
 
58
65
  make_permalink
59
66
 
@@ -64,18 +71,26 @@ class Product < ActiveRecord::Base
64
71
  #RAILS3 TODO - scopes are duplicated here and in scopres/product.rb - can we DRY it up?
65
72
  # default product scope only lists available and non-deleted products
66
73
  scope :not_deleted, where("products.deleted_at is NULL")
74
+
67
75
  scope :available, lambda { |*on| where("products.available_on <= ?", on.first || Time.zone.now ) }
68
- scope :active, not_deleted.available #RAILS 3 TODO - this scope doesn't match the original 2.3.x version, needs attention (but it works)
69
- scope :on_hand, where("products.count_on_hand > 0")
70
76
 
77
+ #RAILS 3 TODO - this scope doesn't match the original 2.3.x version, needs attention (but it works)
78
+ scope :active, lambda{ not_deleted.available }
71
79
 
80
+ scope :on_hand, where("products.count_on_hand > 0")
72
81
 
73
82
  if (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL')
74
- scope :group_by_products_id, { :group => "products." + Product.column_names.join(", products.") } if ActiveRecord::Base.connection.tables.include?("products")
83
+ if ActiveRecord::Base.connection.tables.include?("products")
84
+ scope :group_by_products_id, { :group => "products." + Product.column_names.join(", products.") }
85
+ end
75
86
  else
76
87
  scope :group_by_products_id, { :group => "products.id" }
77
88
  end
89
+ search_methods :group_by_products_id
90
+
91
+ scope :id_equals, lambda { |input_id| where("products.id = ?", input_id) }
78
92
 
93
+ scope :taxons_name_eq, lambda { |name| joins(:taxons).where("taxons.name = ?", name) }
79
94
 
80
95
  # ----------------------------------------------------------------------------------------------------------
81
96
  #
@@ -108,7 +123,7 @@ class Product < ActiveRecord::Base
108
123
  # ----------------------------------------------------------------------------------------------------------
109
124
 
110
125
  def to_param
111
- return permalink unless permalink.blank?
126
+ return permalink if permalink.present?
112
127
  name.to_url
113
128
  end
114
129
 
@@ -148,7 +163,7 @@ class Product < ActiveRecord::Base
148
163
  end
149
164
 
150
165
  def add_properties_and_option_types_from_prototype
151
- if prototype_id and prototype = Prototype.find_by_id(prototype_id)
166
+ if prototype_id && prototype = Prototype.find_by_id(prototype_id)
152
167
  prototype.properties.each do |property|
153
168
  product_properties.create(:property => property)
154
169
  end
@@ -24,10 +24,10 @@
24
24
  # without retriving all records.
25
25
  #
26
26
  # ProductGroup operates on named scopes defined for product in Scopes::Product,
27
- # or generated automatically by Searchlogic
27
+ # or generated automatically by meta_search
28
28
  #
29
29
  class ProductGroup < ActiveRecord::Base
30
- validates :name, :presence => true
30
+ validates :name, :presence => true # TODO ensure that this field is defined as not_null
31
31
  validates_associated :product_scopes
32
32
 
33
33
  before_save :set_permalink
@@ -36,7 +36,7 @@ class ProductGroup < ActiveRecord::Base
36
36
  has_and_belongs_to_many :cached_products, :class_name => "Product"
37
37
  # name
38
38
  has_many :product_scopes
39
- accepts_nested_attributes_for :product_scopes
39
+ accepts_nested_attributes_for :product_scopes
40
40
 
41
41
  # Testing utility: creates new *ProductGroup* from search permalink url.
42
42
  # Follows conventions for accessing PGs from URLs, as decoded in routes
@@ -58,7 +58,7 @@ class ProductGroup < ActiveRecord::Base
58
58
  end
59
59
  taxon = taxons && taxons.split("/").last
60
60
  pg.add_scope("in_taxon", taxon) if taxon
61
-
61
+
62
62
  pg
63
63
  end
64
64
 
@@ -75,7 +75,7 @@ class ProductGroup < ActiveRecord::Base
75
75
  def from_route(attrs)
76
76
  self.order_scope = attrs.pop if attrs.length % 2 == 1
77
77
  attrs.each_slice(2) do |scope|
78
- next unless Product.condition?(scope.first)
78
+ next unless Product.respond_to?(scope.first)
79
79
  add_scope(scope.first, scope.last.split(","))
80
80
  end
81
81
  self
@@ -85,19 +85,15 @@ class ProductGroup < ActiveRecord::Base
85
85
  search_hash.each_pair do |scope_name, scope_attribute|
86
86
  add_scope(scope_name, scope_attribute)
87
87
  end
88
-
88
+
89
89
  self
90
90
  end
91
91
 
92
92
  def add_scope(scope_name, arguments=[])
93
- if scope_name.to_s !~ /eval|send|system|[^a-z0-9_!?]/
94
- self.product_scopes << ProductScope.new({
95
- :name => scope_name.to_s,
96
- :arguments => [*arguments]
97
- })
98
- else
99
- raise ArgumentError.new("'#{scope_name}` can't be used as scope")
100
- end
93
+ self.product_scopes << ProductScope.new({
94
+ :name => scope_name.to_s,
95
+ :arguments => [*arguments]
96
+ })
101
97
  self
102
98
  end
103
99
 
@@ -106,15 +102,14 @@ class ProductGroup < ActiveRecord::Base
106
102
  # from first nested_scope so we have to apply ordering FIRST.
107
103
  # see #2253 on rails LH
108
104
  base_product_scope = scopish
109
- if use_order && !self.order_scope.blank? && Product.condition?(self.order_scope)
105
+ if use_order && !self.order_scope.blank? && Product.respond_to?(self.order_scope.intern)
110
106
  base_product_scope = base_product_scope.send(self.order_scope)
111
107
  end
112
108
 
113
- return self.product_scopes.reject {|s|
114
- s.is_ordering?
115
- }.inject(base_product_scope){|result, scope|
116
- scope.apply_on(result)
117
- }
109
+ return self.product_scopes.reject {|s| s.is_ordering? }.inject(base_product_scope) do |result, scope|
110
+ scope.apply_on(result)
111
+ end
112
+
118
113
  end
119
114
 
120
115
  # returns chain of named scopes generated from order scope and product scopes.
@@ -131,9 +126,9 @@ class ProductGroup < ActiveRecord::Base
131
126
  elsif !use_order
132
127
  cached_group
133
128
  else
134
- product_scopes.select {|s|
129
+ product_scopes.select {|s|
135
130
  s.is_ordering?
136
- }.inject(cached_group) {|res,order|
131
+ }.inject(cached_group) {|res,order|
137
132
  order.apply_on(res)
138
133
  }
139
134
  end
@@ -160,7 +155,7 @@ class ProductGroup < ActiveRecord::Base
160
155
  [ps.name, ps.arguments.join(",")]
161
156
  }.flatten.join('/')
162
157
  result+= self.order_scope if self.order_scope
163
-
158
+
164
159
  result
165
160
  else
166
161
  name.to_url
@@ -170,7 +165,7 @@ class ProductGroup < ActiveRecord::Base
170
165
  def set_permalink
171
166
  self.permalink = self.name.to_url
172
167
  end
173
-
168
+
174
169
  def update_memberships
175
170
  # wipe everything directly to avoid expensive in-rails sorting
176
171
  ActiveRecord::Base.connection.execute "DELETE FROM product_groups_products WHERE product_group_id = #{self.id}"
@@ -188,18 +183,19 @@ class ProductGroup < ActiveRecord::Base
188
183
  def to_s
189
184
  "<ProductGroup" + (id && "[#{id}]").to_s + ":'#{to_url}'>"
190
185
  end
191
-
186
+
192
187
  def order_scope
193
188
  if scope = product_scopes.detect {|s| s.is_ordering?}
194
189
  scope.name
195
190
  end
196
191
  end
192
+
197
193
  def order_scope=(scope_name)
198
194
  if scope = product_scopes.detect {|s| s.is_ordering?}
199
195
  scope.update_attribute(:name, scope_name)
200
196
  else
201
197
  self.product_scopes.build(:name => scope_name, :arguments => [])
202
- end
198
+ end
203
199
  end
204
200
 
205
201
  # Build a new product group with a scope to filter by specified products