spree_core 1.3.0.rc1 → 1.3.0.rc2

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 (54) hide show
  1. data/app/assets/javascripts/admin/admin.js.erb +28 -2
  2. data/app/assets/javascripts/admin/checkouts/edit.js +52 -96
  3. data/app/assets/javascripts/admin/handlebar_extensions.js +9 -0
  4. data/app/assets/javascripts/admin/spree-select2.js.erb +17 -19
  5. data/app/assets/javascripts/admin/spree_core.js +0 -1
  6. data/app/assets/javascripts/admin/states.js +9 -0
  7. data/app/assets/javascripts/admin/taxon_autocomplete.js.erb +1 -1
  8. data/app/assets/javascripts/admin/variant_autocomplete.js.erb +2 -6
  9. data/app/assets/javascripts/store/checkout.js.coffee +4 -4
  10. data/app/assets/stylesheets/store/screen.css.scss +8 -0
  11. data/app/controllers/spree/admin/option_values_controller.rb +11 -0
  12. data/app/controllers/spree/admin/product_properties_controller.rb +12 -0
  13. data/app/controllers/spree/admin/products_controller.rb +9 -0
  14. data/app/helpers/spree/admin/base_helper.rb +2 -8
  15. data/app/models/spree/app_configuration.rb +1 -0
  16. data/app/models/spree/calculator/per_item.rb +3 -1
  17. data/app/models/spree/line_item.rb +2 -2
  18. data/app/models/spree/order/checkout.rb +4 -1
  19. data/app/models/spree/order.rb +5 -1
  20. data/app/models/spree/payment/processing.rb +1 -1
  21. data/app/models/spree/preferences/preferable_class_methods.rb +6 -9
  22. data/app/models/spree/preferences/store.rb +22 -11
  23. data/app/models/spree/product/scopes.rb +5 -1
  24. data/app/models/spree/shipment.rb +7 -4
  25. data/app/models/spree/shipping_method.rb +4 -0
  26. data/app/views/spree/admin/orders/customer_details/_autocomplete.js.erb +19 -0
  27. data/app/views/spree/admin/orders/customer_details/edit.html.erb +4 -3
  28. data/app/views/spree/admin/orders/edit.html.erb +1 -1
  29. data/app/views/spree/admin/products/search.rabl +6 -0
  30. data/app/views/spree/admin/search/users.rabl +23 -25
  31. data/app/views/spree/admin/shared/_head.html.erb +1 -1
  32. data/app/views/spree/admin/shared/_routes.html.erb +3 -2
  33. data/app/views/spree/admin/shared/_translations.html.erb +3 -0
  34. data/app/views/spree/admin/shipping_methods/_form.html.erb +1 -1
  35. data/app/views/spree/admin/variants/_autocomplete.js.erb +2 -2
  36. data/app/views/spree/admin/variants/search.rabl +8 -10
  37. data/app/views/spree/checkout/payment/_gateway.html.erb +2 -2
  38. data/app/views/spree/order_mailer/cancel_email.text.erb +2 -2
  39. data/app/views/spree/order_mailer/confirm_email.text.erb +2 -2
  40. data/app/views/spree/orders/_line_item.html.erb +1 -1
  41. data/app/views/spree/products/_cart_form.html.erb +6 -2
  42. data/app/views/spree/shared/_google_analytics.html.erb +8 -8
  43. data/app/views/spree/shared/_order_details.html.erb +3 -1
  44. data/config/locales/en.yml +9 -7
  45. data/config/routes.rb +9 -0
  46. data/db/migrate/20121126040517_add_last_ip_to_spree_orders.rb +5 -0
  47. data/lib/spree/core/calculated_adjustments.rb +2 -1
  48. data/lib/spree/core/controller_helpers/common.rb +5 -0
  49. data/lib/spree/core/controller_helpers/order.rb +1 -0
  50. data/lib/spree/core/search/base.rb +5 -1
  51. data/lib/spree/core/testing_support/capybara_ext.rb +79 -0
  52. data/lib/spree/core/version.rb +1 -1
  53. metadata +13 -6
  54. data/vendor/assets/javascripts/jquery.tokeninput.js +0 -860
@@ -34,7 +34,9 @@ module Spree
34
34
  # Shipping methods do not have promotions attached, but promotions do
35
35
  # Therefore we must check for promotions
36
36
  if self.calculable.respond_to?(:promotion)
37
- self.calculable.promotion.rules.map(&:products).flatten
37
+ self.calculable.promotion.rules.map do |rule|
38
+ rule.respond_to?(:products) ? rule.products : []
39
+ end.flatten
38
40
  end
39
41
  end
40
42
  end
@@ -100,7 +100,7 @@ module Spree
100
100
 
101
101
  def ensure_not_shipped
102
102
  if order.try(:inventory_units).to_a.any?{ |unit| unit.variant_id == variant_id && unit.shipped? }
103
- errors.add :base, I18n.t('validation.cannot_destory_line_item_as_inventory_units_have_shipped')
103
+ errors.add :base, I18n.t('validation.cannot_destroy_line_item_as_inventory_units_have_shipped')
104
104
  return false
105
105
  end
106
106
  end
@@ -108,7 +108,7 @@ module Spree
108
108
  # Validation
109
109
  def stock_availability
110
110
  return if sufficient_stock?
111
- errors.add(:quantity, I18n.t('validation.cannot_be_greater_than_available_stock'))
111
+ errors.add(:quantity, I18n.t('validation.exceeds_available_stock'))
112
112
  end
113
113
 
114
114
  def quantity_no_less_than_shipped
@@ -126,7 +126,10 @@ module Spree
126
126
  end
127
127
  checkout_steps << step
128
128
  end
129
- checkout_steps.map(&:to_s)
129
+ steps = checkout_steps.map(&:to_s)
130
+ # Ensure there is always a complete step
131
+ steps << "complete" unless steps.include?("complete")
132
+ steps
130
133
  end
131
134
  end
132
135
  end
@@ -24,7 +24,7 @@ module Spree
24
24
  order.payment_required?
25
25
  }
26
26
  go_to_state :confirm, :if => lambda { |order| order.confirmation_required? }
27
- go_to_state :complete, :if => lambda { |order| (order.payment_required? && order.paid?) || !order.payment_required? }
27
+ go_to_state :complete, :if => lambda { |order| (order.payment_required? && order.payments.exists?) || !order.payment_required? }
28
28
  remove_transition :from => :delivery, :to => :confirm
29
29
  end
30
30
 
@@ -355,6 +355,10 @@ module Spree
355
355
  end
356
356
  end
357
357
 
358
+ def can_ship?
359
+ self.complete? || self.resumed?
360
+ end
361
+
358
362
  def credit_cards
359
363
  credit_card_ids = payments.from_credit_card.map(&:source_id).uniq
360
364
  CreditCard.scoped(:conditions => { :id => credit_card_ids })
@@ -110,7 +110,7 @@ module Spree
110
110
  def gateway_options
111
111
  options = { :email => order.email,
112
112
  :customer => order.email,
113
- :ip => '192.168.1.100', # TODO: Use an actual IP
113
+ :ip => order.last_ip_address,
114
114
  :order_id => order.number }
115
115
 
116
116
  options.merge!({ :shipping => order.ship_total * 100,
@@ -10,16 +10,13 @@ module Spree::Preferences
10
10
  # cache_key will be nil for new objects, then if we check if there
11
11
  # is a pending preference before going to default
12
12
  define_method preference_getter_method(name) do
13
- if preference_cache_key(name) && preference_store.exist?(preference_cache_key(name))
14
- preference_store.get preference_cache_key(name)
13
+
14
+ # perference_cache_key will only be nil/false for new records
15
+ #
16
+ if preference_cache_key(name)
17
+ preference_store.get(preference_cache_key(name), default)
15
18
  else
16
- if get_pending_preference(name)
17
- get_pending_preference(name)
18
- elsif Spree::Preference.table_exists? && preference = Spree::Preference.find_by_key(name.to_s)
19
- preference.value
20
- else
21
- send self.class.preference_default_getter_method(name)
22
- end
19
+ get_pending_preference(name) || default
23
20
  end
24
21
  end
25
22
  alias_method prefers_getter_method(name), preference_getter_method(name)
@@ -27,25 +27,36 @@ module Spree::Preferences
27
27
  should_persist? && Spree::Preference.where(:key => key).exists?
28
28
  end
29
29
 
30
- def get(key)
30
+ def get(key,fallback=nil)
31
31
  # return the retrieved value, if it's in the cache
32
- if (val = @cache.read(key)).present?
32
+ # use unless nil? incase the value is actually boolean false
33
+ #
34
+ unless (val = @cache.read(key)).nil?
33
35
  return val
34
36
  end
35
37
 
36
- return nil unless should_persist?
38
+ if should_persist?
39
+ # If it's not in the cache, maybe it's in the database, but
40
+ # has been cleared from the cache
37
41
 
38
- # If it's not in the cache, maybe it's in the database, but
39
- # has been cleared from the cache
42
+ # does it exist in the database?
43
+ if Spree::Preference.table_exists? && preference = Spree::Preference.find_by_key(key)
44
+ # it does exist, so let's put it back into the cache
45
+ @cache.write(preference.key, preference.value)
40
46
 
41
- # does it exist in the database?
42
- if preference = Spree::Preference.find_by_key(key)
43
- # it does exist, so let's put it back into the cache
44
- @cache.write(preference.key, preference.value)
47
+ # and return the value
48
+ return preference.value
49
+ end
50
+ end
45
51
 
46
- # and return the value
47
- preference.value
52
+ unless fallback.nil?
53
+ # cache fallback so we won't hit the db above on
54
+ # subsequent queries for the same key
55
+ #
56
+ @cache.write(key, fallback)
48
57
  end
58
+
59
+ return fallback
49
60
  end
50
61
 
51
62
  def delete(key)
@@ -189,7 +189,11 @@ module Spree
189
189
 
190
190
  # Can't use add_search_scope for this as it needs a default argument
191
191
  def self.available(available_on = nil, currency = nil)
192
- joins(:master => :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.now).where('spree_prices.currency' => currency || Spree::Config[:currency]).where('spree_prices.amount IS NOT NULL')
192
+ scope = joins(:master => :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.now)
193
+ unless Spree::Config.show_products_without_price
194
+ scope = scope.where('spree_prices.currency' => currency || Spree::Config[:currency]).where('spree_prices.amount IS NOT NULL')
195
+ end
196
+ scope
193
197
  end
194
198
  search_scopes << :available
195
199
 
@@ -28,7 +28,7 @@ module Spree
28
28
 
29
29
  scope :with_state, lambda { |s| where(:state => s) }
30
30
  scope :shipped, with_state('shipped')
31
- scope :ready, with_state('ready')
31
+ scope :ready, with_state('ready')
32
32
  scope :pending, with_state('pending')
33
33
 
34
34
  def to_param
@@ -51,11 +51,13 @@ module Spree
51
51
  def cost
52
52
  adjustment ? adjustment.amount : 0
53
53
  end
54
+
54
55
  alias_method :amount, :cost
55
56
 
56
57
  def display_cost
57
58
  Spree::Money.new(cost, { :currency => currency })
58
59
  end
60
+
59
61
  alias_method :display_amount, :display_cost
60
62
 
61
63
  # shipment state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
@@ -110,7 +112,7 @@ module Spree
110
112
  # shipped if already shipped (ie. does not change the state)
111
113
  # ready all other cases
112
114
  def determine_state(order)
113
- return 'pending' unless order.complete?
115
+ return 'pending' unless order.can_ship?
114
116
  return 'pending' if inventory_units.any? &:backordered?
115
117
  return 'shipped' if state == 'shipped'
116
118
  order.paid? ? 'ready' : 'pending'
@@ -121,7 +123,7 @@ module Spree
121
123
  return number unless number.blank?
122
124
  record = true
123
125
  while record
124
- random = "H#{Array.new(11){rand(9)}.join}"
126
+ random = "H#{Array.new(11) { rand(9) }.join}"
125
127
  record = self.class.where(:number => random).first
126
128
  end
127
129
  self.number = random
@@ -158,9 +160,10 @@ module Spree
158
160
  def ensure_correct_adjustment
159
161
  if adjustment
160
162
  adjustment.originator = shipping_method
163
+ adjustment.label = shipping_method.adjustment_label
161
164
  adjustment.save
162
165
  else
163
- shipping_method.create_adjustment(I18n.t(:shipping), order, self, true)
166
+ shipping_method.create_adjustment(shipping_method.adjustment_label, order, self, true)
164
167
  reload #ensure adjustment is present on later saves
165
168
  end
166
169
  end
@@ -15,6 +15,10 @@ module Spree
15
15
 
16
16
  calculated_adjustments
17
17
 
18
+ def adjustment_label
19
+ I18n.t(:shipping)
20
+ end
21
+
18
22
  def available?(order, display_on = nil)
19
23
  displayable?(display_on) && calculator.available?(order)
20
24
  end
@@ -0,0 +1,19 @@
1
+ <script type='text/template' id='customer_autocomplete_template'>
2
+ <div class='customer-autocomplete-item'>
3
+ <div class='customer-details'>
4
+ <h5>{{customer.email}}</h5>
5
+ {{#if bill_address.firstname }}
6
+ <strong>{{t 'bill_address' }}</strong>
7
+ {{bill_address.firstname}} {{bill_address.lastname}}<br>
8
+ {{bill_address.address1}}, {{bill_address.address2}}<br>
9
+ {{bill_address.city}}<br>
10
+ {{#if bill_address.state_id }}
11
+ {{bill_address.state.name}}
12
+ {{else}}
13
+ {{bill_address.state_name}}
14
+ {{/if}}
15
+ {{bill_address.country.name}}
16
+ {{/if}}
17
+ </div>
18
+ </div>
19
+ </script>
@@ -11,15 +11,16 @@
11
11
  <% end %>
12
12
 
13
13
  <% if @order.cart? %>
14
- <div id="add-line-item" data-hook>
14
+ <div id="select-customer" data-hook>
15
15
  <fieldset class="no-border-bottom">
16
16
  <legend align="center"><%= t(:customer_search) %></legend>
17
- <%= label_tag :customer_search, t(:enter_at_least_five_letters) %>
18
- <%= text_field_tag :customer_search, nil, :class => 'fullwidth title' %>
17
+ <%= hidden_field_tag :customer_search, nil, :class => 'fullwidth title' %>
18
+ <%= render :partial => "spree/admin/orders/customer_details/autocomplete", :formats => :js %>
19
19
  </fieldset>
20
20
  </div>
21
21
  <% end %>
22
22
 
23
+
23
24
  <%= render :partial => 'spree/shared/error_messages', :locals => { :target => @order } %>
24
25
 
25
26
  <%= form_for @order, :url => admin_order_customer_url(@order) do |f| %>
@@ -1,7 +1,7 @@
1
1
  <%= csrf_meta_tags %>
2
2
  <% content_for :page_actions do %>
3
3
  <li><%= event_links %></li>
4
- <li><%= button_link_to t(:resend), resend_admin_order_url(@order), :method => :post, :icon => 'icon-email' if @order.user %></li>
4
+ <li><%= button_link_to t(:resend), resend_admin_order_url(@order), :method => :post, :icon => 'icon-email' %></li>
5
5
  <li><%= button_link_to t(:back_to_orders_list), admin_orders_path, :icon => 'icon-arrow-left' %></li>
6
6
  <% end %>
7
7
 
@@ -0,0 +1,6 @@
1
+ collection @products
2
+ attributes :sku, :name, :id
3
+
4
+ child(:variant_images => :images) do
5
+ attributes :mini_url
6
+ end
@@ -1,32 +1,30 @@
1
- object false
2
- child @users => :users do
3
- attributes :email, :id
4
- address_fields = [:firstname, :lastname,
5
- :address1, :address2,
6
- :city, :zipcode,
7
- :phone, :state_name,
8
- :state_id, :country_id,
9
- :company]
1
+ collection(@users)
2
+ attributes :email, :id
3
+ address_fields = [:firstname, :lastname,
4
+ :address1, :address2,
5
+ :city, :zipcode,
6
+ :phone, :state_name,
7
+ :state_id, :country_id,
8
+ :company]
10
9
 
11
- child :ship_address => :ship_address do
12
- attributes *address_fields
13
- child :state do
14
- attributes :name
15
- end
10
+ child :ship_address => :ship_address do
11
+ attributes *address_fields
12
+ child :state do
13
+ attributes :name
14
+ end
16
15
 
17
- child :country do
18
- attributes :name
19
- end
16
+ child :country do
17
+ attributes :name
20
18
  end
19
+ end
21
20
 
22
- child :bill_address => :bill_address do
23
- attributes *address_fields
24
- child :state do
25
- attributes :name
26
- end
21
+ child :bill_address => :bill_address do
22
+ attributes *address_fields
23
+ child :state do
24
+ attributes :name
25
+ end
27
26
 
28
- child :country do
29
- attributes :name
30
- end
27
+ child :country do
28
+ attributes :name
31
29
  end
32
30
  end
@@ -8,7 +8,7 @@
8
8
  </title>
9
9
 
10
10
  <!-- Get "Open Sans" font from Google -->
11
- <link href='http://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600&subset=latin,cyrillic,greek,vietnamese' rel='stylesheet' type='text/css'>
11
+ <link href='//fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600&subset=latin,cyrillic,greek,vietnamese' rel='stylesheet' type='text/css'>
12
12
 
13
13
  <%= stylesheet_link_tag 'admin/all' %>
14
14
 
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  Spree.routes = <%== {
3
3
  :variants_search => spree.admin_search_variants_path(:format => 'json'),
4
- :taxon_search => spree.search_admin_taxons_path(:format => 'json'),
5
- :user_search => spree.admin_search_users_path(:format => 'json')
4
+ :taxon_search => spree.search_admin_taxons_path(:format => 'json'),
5
+ :user_search => spree.admin_search_users_path(:format => 'json'),
6
+ :product_search => spree.search_admin_products_path(:format => 'json')
6
7
  }.to_json %>;
7
8
  </script>
@@ -6,6 +6,8 @@
6
6
  :abbr_day_names => I18n.t(:abbr_day_names, :scope => :date),
7
7
  :add => I18n.t(:add),
8
8
  :are_you_sure_delete => I18n.t(:are_you_sure_delete),
9
+ :bill_address => I18n.t(:bill_address),
10
+ :choose_a_customer => I18n.t(:choose_a_customer),
9
11
  :confirm_delete => I18n.t(:confirm_delete),
10
12
  :cut => I18n.t(:cut),
11
13
  :destroy => I18n.t(:destroy),
@@ -24,6 +26,7 @@
24
26
  :searching => I18n.t(:searching),
25
27
  :sku => I18n.t(:sku),
26
28
  :type_to_search => I18n.t(:type_to_search),
29
+ :taxon_placeholder => I18n.t(:taxon_placeholder),
27
30
  :value => I18n.t(:value)
28
31
  }.to_json
29
32
  %>
@@ -24,7 +24,7 @@
24
24
  </div>
25
25
  </div>
26
26
 
27
- <div data-hook"admin_shipping_method_form_availability_fields" class="alpha six columns">
27
+ <div data-hook="admin_shipping_method_form_availability_fields" class="alpha six columns">
28
28
  <fieldset class="categories no-border-bottom">
29
29
  <legend align="center"><%= t(:availability) %></legend>
30
30
 
@@ -13,8 +13,8 @@
13
13
  <h6 class="variant-name">{{variant.name}}</h6>
14
14
 
15
15
  <ul class='variant-data'>
16
- <li class='variant-sku'><strong>{{translations.sku}}:</strong> {{variant.sku}}</li>
17
- <li class='variant-on_hand'><strong>{{translations.on_hand}}:</strong> {{variant.count_on_hand}}</li>
16
+ <li class='variant-sku'><strong>{{t 'sku'}}:</strong> {{variant.sku}}</li>
17
+ <li class='variant-on_hand'><strong>{{t 'on_hand' }}:</strong> {{variant.count_on_hand}}</li>
18
18
  </ul>
19
19
 
20
20
  {{#if variant.option_values}}
@@ -1,15 +1,13 @@
1
- object false
2
- child(@variants => :variants) do
3
- attributes :sku, :options_text, :count_on_hand, :id, :name
1
+ collection @variants
2
+ attributes :sku, :options_text, :count_on_hand, :id, :name
4
3
 
5
- child(:images => :images) do
6
- attributes :mini_url
7
- end
4
+ child(:images => :images) do
5
+ attributes :mini_url
6
+ end
8
7
 
9
- child(:option_values => :option_values) do
10
- child(:option_type => :option_type) do
11
- attributes :name, :presentation
12
- end
8
+ child(:option_values => :option_values) do
9
+ child(:option_type => :option_type) do
13
10
  attributes :name, :presentation
14
11
  end
12
+ attributes :name, :presentation
15
13
  end
@@ -15,8 +15,8 @@
15
15
  </p>
16
16
  <p class="field" data-hook="card_expiration">
17
17
  <%= label_tag nil, t(:expiration) %><br />
18
- <%= select_month(Date.today, :prefix => param_prefix, :field_name => 'month', :use_month_numbers => true, :class => 'required') %>
19
- <%= select_year(Date.today, :prefix => param_prefix, :field_name => 'year', :start_year => Date.today.year, :end_year => Date.today.year + 15, :class => 'required') %>
18
+ <%= select_month(Date.today, { :prefix => param_prefix, :field_name => 'month', :use_month_numbers => true }, :class => 'required') %>
19
+ <%= select_year(Date.today, { :prefix => param_prefix, :field_name => 'year', :start_year => Date.today.year, :end_year => Date.today.year + 15 }, :class => 'required') %>
20
20
  <span class="required">*</span>
21
21
  </p>
22
22
  <p class="field" data-hook="card_code">
@@ -9,8 +9,8 @@
9
9
  <%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.variant.display_amount %> = <%= item.display_amount %>
10
10
  <% end %>
11
11
  ============================================================
12
- <%= t('order_mailer.cancel_email.subtotal') %> <%= @order.display_item_total %>
12
+ <%= t('order_mailer.cancel_email.subtotal', :subtotal => @order.display_item_total) %>
13
13
  <% @order.adjustments.eligible.each do |adjustment| %>
14
14
  <%= raw(adjustment.label) %> <%= adjustment.display_amount %>
15
15
  <% end %>
16
- <%= t('order_mailer.cancel_email.total') %> <%= @order.display_total %>
16
+ <%= t('order_mailer.cancel_email.total', :total => @order.display_total) %>