spree_core 0.40.4 → 0.50.0

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