spree_core 2.1.3 → 2.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/admin/images_helper.rb +1 -1
  3. data/app/helpers/spree/base_helper.rb +5 -2
  4. data/app/models/spree/address.rb +9 -1
  5. data/app/models/spree/adjustment.rb +2 -2
  6. data/app/models/spree/calculator/default_tax.rb +5 -1
  7. data/app/models/spree/credit_card.rb +2 -0
  8. data/app/models/spree/gateway.rb +1 -1
  9. data/app/models/spree/inventory_unit.rb +5 -4
  10. data/app/models/spree/legacy_user.rb +2 -11
  11. data/app/models/spree/line_item.rb +2 -3
  12. data/app/models/spree/log_entry.rb +4 -0
  13. data/app/models/spree/option_type.rb +6 -0
  14. data/app/models/spree/option_value.rb +12 -1
  15. data/app/models/spree/order.rb +34 -16
  16. data/app/models/spree/order/checkout.rb +4 -0
  17. data/app/models/spree/order_inventory.rb +1 -1
  18. data/app/models/spree/payment.rb +10 -2
  19. data/app/models/spree/payment/processing.rb +5 -4
  20. data/app/models/spree/payment_method.rb +2 -0
  21. data/app/models/spree/price.rb +5 -0
  22. data/app/models/spree/product.rb +6 -5
  23. data/app/models/spree/product/scopes.rb +12 -6
  24. data/app/models/spree/product_property.rb +1 -1
  25. data/app/models/spree/promotion.rb +1 -8
  26. data/app/models/spree/promotion/rules/user_logged_in.rb +1 -3
  27. data/app/models/spree/property.rb +8 -0
  28. data/app/models/spree/shipment.rb +9 -14
  29. data/app/models/spree/shipping_method.rb +3 -2
  30. data/app/models/spree/shipping_rate.rb +7 -9
  31. data/app/models/spree/stock/estimator.rb +21 -14
  32. data/app/models/spree/stock/package.rb +1 -1
  33. data/app/models/spree/stock/packer.rb +1 -1
  34. data/app/models/spree/stock/quantifier.rb +11 -2
  35. data/app/models/spree/stock_item.rb +2 -2
  36. data/app/models/spree/stock_location.rb +8 -0
  37. data/app/models/spree/stock_movement.rb +3 -1
  38. data/app/models/spree/taxon.rb +2 -2
  39. data/app/models/spree/variant.rb +19 -4
  40. data/app/models/spree/zone.rb +1 -1
  41. data/app/views/spree/shared/_routes.html.erb +1 -1
  42. data/config/locales/en.yml +15 -1
  43. data/db/default/spree/countries.rb +7 -7
  44. data/db/migrate/20130417120034_add_index_to_source_columns_on_adjustments.rb +5 -0
  45. data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +5 -2
  46. data/db/migrate/20131026154747_add_track_inventory_to_variant.rb +5 -0
  47. data/db/migrate/20131120234456_add_updated_at_to_variants.rb +5 -0
  48. data/db/migrate/20131211192741_unique_shipping_method_categories.rb +24 -0
  49. data/db/migrate/20140120160805_add_index_to_variant_id_and_currency_on_prices.rb +5 -0
  50. data/lib/generators/spree/dummy/dummy_generator.rb +14 -3
  51. data/lib/generators/spree/dummy/templates/rails/database.yml +10 -0
  52. data/lib/spree/core.rb +3 -0
  53. data/lib/spree/core/controller_helpers/order.rb +4 -1
  54. data/lib/spree/core/controller_helpers/ssl.rb +5 -7
  55. data/lib/spree/core/controller_helpers/strong_parameters.rb +6 -0
  56. data/lib/spree/core/delegate_belongs_to.rb +16 -10
  57. data/lib/spree/core/engine.rb +11 -2
  58. data/lib/spree/core/mail_method.rb +27 -0
  59. data/lib/spree/core/mail_settings.rb +33 -38
  60. data/lib/spree/core/permalinks.rb +5 -1
  61. data/lib/spree/core/s3_support.rb +1 -1
  62. data/lib/spree/core/user_address.rb +30 -0
  63. data/lib/spree/core/validators/email.rb +9 -3
  64. data/lib/spree/core/version.rb +1 -1
  65. data/lib/spree/i18n.rb +1 -0
  66. data/lib/spree/migrations.rb +55 -0
  67. data/lib/spree/money.rb +171 -1
  68. data/lib/spree/permitted_attributes.rb +6 -6
  69. data/lib/spree/testing_support/capybara_ext.rb +6 -5
  70. data/lib/spree/testing_support/controller_requests.rb +20 -4
  71. data/lib/spree/testing_support/factories/product_factory.rb +4 -0
  72. data/lib/spree/testing_support/factories/variant_factory.rb +15 -0
  73. metadata +158 -164
@@ -23,7 +23,7 @@ module Spree
23
23
  # We should not define price scopes here, as they require something slightly different
24
24
  next if name.to_s.include?("master_price")
25
25
  parts = name.to_s.match(/(.*)_by_(.*)/)
26
- self.scope(name.to_s, -> { relation.order("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? "ASC" : "DESC"}") })
26
+ self.scope(name.to_s, -> { order("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? "ASC" : "DESC"}") })
27
27
  end
28
28
  end
29
29
 
@@ -207,13 +207,19 @@ module Spree
207
207
  group("spree_products.id").joins(:taxons).where(Taxon.arel_table[:name].eq(name))
208
208
  end
209
209
 
210
- # This method needs to be defined *as a method*, otherwise it will cause the
211
- # problem shown in #1247.
212
- def self.group_by_products_id
210
+ def self.distinct_by_product_ids(sort_order=nil)
213
211
  if (ActiveRecord::Base.connection.adapter_name == 'PostgreSQL')
214
- group(column_names.map { |col_name| "#{table_name}.#{col_name}"})
212
+ sort_column = sort_order.split(" ").first
213
+ # Don't allow sort_column, a variable coming from params,
214
+ # to be anything but a column in the database
215
+ if column_names.include?(sort_column)
216
+ distinct_fields = ["id", sort_column].compact.join(",")
217
+ select("DISTINCT ON(#{distinct_fields}) spree_products.*")
218
+ else
219
+ scoped
220
+ end
215
221
  else
216
- group("#{self.quoted_table_name}.id")
222
+ select("DISTINCT spree_products.*")
217
223
  end
218
224
  end
219
225
 
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  class ProductProperty < ActiveRecord::Base
3
- belongs_to :product, class_name: 'Spree::Product'
3
+ belongs_to :product, touch: true, class_name: 'Spree::Product'
4
4
  belongs_to :property, class_name: 'Spree::Property'
5
5
 
6
6
  validates :property, presence: true
@@ -21,13 +21,6 @@ module Spree
21
21
  validates :path, presence: true, if: lambda{|r| r.event_name == 'spree.content.visited' }
22
22
  validates :usage_limit, numericality: { greater_than: 0, allow_nil: true }
23
23
 
24
- # TODO: This shouldn't be necessary with :autosave option but nested attribute updating of actions is broken without it
25
- after_save :save_rules_and_actions
26
-
27
- def save_rules_and_actions
28
- (rules + actions).each &:save
29
- end
30
-
31
24
  def self.advertised
32
25
  where(advertise: true)
33
26
  end
@@ -91,7 +84,7 @@ module Spree
91
84
  end
92
85
 
93
86
  def credits
94
- Adjustment.promotion.where(originator_id: actions.map(&:id))
87
+ Adjustment.eligible.promotion.where(originator_id: actions.map(&:id))
95
88
  end
96
89
 
97
90
  def credits_count
@@ -2,11 +2,9 @@ module Spree
2
2
  class Promotion
3
3
  module Rules
4
4
  class UserLoggedIn < PromotionRule
5
-
6
5
  def eligible?(order, options = {})
7
- return order.try(:user).try(:anonymous?) == false
6
+ return order.user.present?
8
7
  end
9
-
10
8
  end
11
9
  end
12
10
  end
@@ -9,11 +9,19 @@ module Spree
9
9
 
10
10
  scope :sorted, -> { order(:name) }
11
11
 
12
+ after_touch :touch_all_products
13
+
12
14
  def self.find_all_by_prototype(prototype)
13
15
  id = prototype
14
16
  id = prototype.id if prototype.class == Prototype
15
17
  joins("LEFT JOIN properties_prototypes ON property_id = #{self.table_name}.id").
16
18
  where(prototype_id: id)
17
19
  end
20
+
21
+ private
22
+
23
+ def touch_all_products
24
+ products.each(&:touch)
25
+ end
18
26
  end
19
27
  end
@@ -2,7 +2,7 @@ require 'ostruct'
2
2
 
3
3
  module Spree
4
4
  class Shipment < ActiveRecord::Base
5
- belongs_to :order, class_name: 'Spree::Order'
5
+ belongs_to :order, class_name: 'Spree::Order', touch: true
6
6
  belongs_to :address, class_name: 'Spree::Address'
7
7
  belongs_to :stock_location, class_name: 'Spree::StockLocation'
8
8
 
@@ -12,7 +12,6 @@ module Spree
12
12
  has_many :inventory_units, dependent: :delete_all
13
13
  has_one :adjustment, as: :source, dependent: :destroy
14
14
 
15
- before_create :generate_shipment_number
16
15
  after_save :ensure_correct_adjustment, :update_order
17
16
 
18
17
  attr_accessor :special_instructions
@@ -20,7 +19,7 @@ module Spree
20
19
  accepts_nested_attributes_for :address
21
20
  accepts_nested_attributes_for :inventory_units
22
21
 
23
- make_permalink field: :number
22
+ make_permalink field: :number, length: 11, prefix: 'H'
24
23
 
25
24
  scope :shipped, -> { with_state('shipped') }
26
25
  scope :ready, -> { with_state('ready') }
@@ -164,8 +163,8 @@ module Spree
164
163
  end
165
164
 
166
165
  def line_items
167
- if order.complete? and Spree::Config[:track_inventory_levels]
168
- order.line_items.select { |li| inventory_units.pluck(:variant_id).include?(li.variant_id) }
166
+ if order.complete? and Spree::Config.track_inventory_levels
167
+ order.line_items.select { |li| !li.should_track_inventory? || inventory_units.pluck(:variant_id).include?(li.variant_id) }
169
168
  else
170
169
  order.line_items
171
170
  end
@@ -238,17 +237,13 @@ module Spree
238
237
  end
239
238
 
240
239
  def manifest_restock(item)
241
- stock_location.restock item.variant, item.quantity, self
242
- end
240
+ if item.states["on_hand"].to_i > 0
241
+ stock_location.restock item.variant, item.states["on_hand"], self
242
+ end
243
243
 
244
- def generate_shipment_number
245
- return number unless number.blank?
246
- record = true
247
- while record
248
- random = "H#{Array.new(11) { rand(9) }.join}"
249
- record = self.class.where(number: random).first
244
+ if item.states["backordered"].to_i > 0
245
+ stock_location.restock_backordered item.variant, item.states["backordered"]
250
246
  end
251
- self.number = random
252
247
  end
253
248
 
254
249
  def description_for_shipping_charge
@@ -8,7 +8,7 @@ module Spree
8
8
  has_many :shipments
9
9
  has_many :shipping_method_categories
10
10
  has_many :shipping_categories, through: :shipping_method_categories
11
- has_many :shipping_rates
11
+ has_many :shipping_rates, inverse_of: :shipping_method
12
12
 
13
13
  has_and_belongs_to_many :zones, :join_table => 'spree_shipping_methods_zones',
14
14
  :class_name => 'Spree::Zone',
@@ -30,7 +30,8 @@ module Spree
30
30
  end
31
31
 
32
32
  def build_tracking_url(tracking)
33
- tracking_url.gsub(/:tracking/, tracking) unless tracking.blank? || tracking_url.blank?
33
+ return if tracking.blank? || tracking_url.blank?
34
+ tracking_url.gsub(/:tracking/, ERB::Util.url_encode(tracking)) # :url_encode exists in 1.8.7 through 2.1.0
34
35
  end
35
36
 
36
37
  def self.calculators
@@ -1,19 +1,13 @@
1
1
  module Spree
2
2
  class ShippingRate < ActiveRecord::Base
3
3
  belongs_to :shipment, class_name: 'Spree::Shipment'
4
- belongs_to :shipping_method, class_name: 'Spree::ShippingMethod'
4
+ belongs_to :shipping_method, class_name: 'Spree::ShippingMethod', inverse_of: :shipping_rates
5
5
 
6
- scope :frontend,
6
+ scope :with_shipping_method,
7
7
  -> { includes(:shipping_method).
8
- where(ShippingMethod.on_frontend_query).
9
- references(:shipping_method).
10
- order("cost ASC") }
11
- scope :backend,
12
- -> { includes(:shipping_method).
13
- where(ShippingMethod.on_backend_query).
14
8
  references(:shipping_method).
15
9
  order("cost ASC") }
16
-
10
+
17
11
  delegate :order, :currency, to: :shipment
18
12
  delegate :name, to: :shipping_method
19
13
 
@@ -28,5 +22,9 @@ module Spree
28
22
  end
29
23
 
30
24
  alias_method :display_cost, :display_price
25
+
26
+ def shipping_method
27
+ Spree::ShippingMethod.unscoped { super }
28
+ end
31
29
  end
32
30
  end
@@ -9,20 +9,17 @@ module Spree
9
9
  end
10
10
 
11
11
  def shipping_rates(package, frontend_only = true)
12
- shipping_rates = calculate_shipping_rates(package)
13
-
14
- unless shipping_rates.empty?
15
- available_rates = shipping_rates.clone
16
- available_rates.select! { |rate| rate.shipping_method.frontend? } if frontend_only
17
- choose_default_shipping_rate(available_rates)
18
- end
19
-
20
- sort_shipping_rates(shipping_rates)
12
+ rates = calculate_shipping_rates(package)
13
+ rates.select! { |rate| rate.shipping_method.frontend? } if frontend_only
14
+ choose_default_shipping_rate(rates)
15
+ sort_shipping_rates(rates)
21
16
  end
22
17
 
23
18
  private
24
19
  def choose_default_shipping_rate(shipping_rates)
25
- shipping_rates.min_by(&:cost).selected = true
20
+ unless shipping_rates.empty?
21
+ shipping_rates.min_by(&:cost).selected = true
22
+ end
26
23
  end
27
24
 
28
25
  def sort_shipping_rates(shipping_rates)
@@ -39,12 +36,22 @@ module Spree
39
36
  def shipping_methods(package)
40
37
  package.shipping_methods.select do |ship_method|
41
38
  calculator = ship_method.calculator
42
- calculator.available?(package) &&
43
- ship_method.include?(order.ship_address) &&
44
- (calculator.preferences[:currency].nil? ||
45
- calculator.preferences[:currency] == currency)
39
+ begin
40
+ calculator.available?(package) &&
41
+ ship_method.include?(order.ship_address) &&
42
+ (calculator.preferences[:currency].nil? ||
43
+ calculator.preferences[:currency] == currency)
44
+ rescue Exception => exception
45
+ log_calculator_exception(ship_method, exception)
46
+ end
46
47
  end
47
48
  end
49
+
50
+ def log_calculator_exception(ship_method, exception)
51
+ Rails.logger.info("Something went wrong calculating rates with the #{ship_method.name} (ID=#{ship_method.id}) shipping method.")
52
+ Rails.logger.info("*" * 50)
53
+ Rails.logger.info(exception.backtrace.join("\n"))
54
+ end
48
55
  end
49
56
  end
50
57
  end
@@ -82,7 +82,7 @@ module Spree
82
82
  end
83
83
 
84
84
  def shipping_methods
85
- shipping_categories.map { |sc| sc.shipping_methods }.flatten.uniq
85
+ shipping_categories.map(&:shipping_methods).reduce(:&).to_a
86
86
  end
87
87
 
88
88
  def inspect
@@ -20,7 +20,7 @@ module Spree
20
20
  def default_package
21
21
  package = Package.new(stock_location, order)
22
22
  order.line_items.each do |line_item|
23
- if Config.track_inventory_levels
23
+ if line_item.should_track_inventory?
24
24
  next unless stock_location.stock_item(line_item.variant)
25
25
 
26
26
  on_hand, backordered = stock_location.fill_status(line_item.variant, line_item.quantity)
@@ -4,12 +4,12 @@ module Spree
4
4
  attr_reader :stock_items
5
5
 
6
6
  def initialize(variant)
7
- @variant = variant
7
+ @variant = resolve_variant_id(variant)
8
8
  @stock_items = Spree::StockItem.joins(:stock_location).where(:variant_id => @variant, Spree::StockLocation.table_name =>{ :active => true})
9
9
  end
10
10
 
11
11
  def total_on_hand
12
- if Spree::Config.track_inventory_levels
12
+ if @variant.should_track_inventory?
13
13
  stock_items.sum(:count_on_hand)
14
14
  else
15
15
  Float::INFINITY
@@ -23,6 +23,15 @@ module Spree
23
23
  def can_supply?(required)
24
24
  total_on_hand >= required || backorderable?
25
25
  end
26
+
27
+ private
28
+
29
+ # return variant when passed either variant object or variant id
30
+ def resolve_variant_id(variant)
31
+ variant = Spree::Variant.find_by_id(variant) unless variant.respond_to?(:should_track_inventory?)
32
+ variant
33
+ end
34
+
26
35
  end
27
36
  end
28
37
  end
@@ -3,13 +3,13 @@ module Spree
3
3
  acts_as_paranoid
4
4
 
5
5
  belongs_to :stock_location, class_name: 'Spree::StockLocation'
6
- belongs_to :variant, class_name: 'Spree::Variant'
6
+ belongs_to :variant, class_name: 'Spree::Variant', touch: true
7
7
  has_many :stock_movements
8
8
 
9
9
  validates_presence_of :stock_location, :variant
10
10
  validates_uniqueness_of :variant_id, scope: [:stock_location_id, :deleted_at]
11
11
 
12
- delegate :weight, to: :variant
12
+ delegate :weight, :should_track_inventory?, to: :variant
13
13
 
14
14
  def backordered_inventory_units
15
15
  Spree::InventoryUnit.backordered_for_stock_item(self)
@@ -44,6 +44,14 @@ module Spree
44
44
  move(variant, quantity, originator)
45
45
  end
46
46
 
47
+ def restock_backordered(variant, quantity, originator = nil)
48
+ item = stock_item_or_create(variant)
49
+ item.update_columns(
50
+ count_on_hand: item.count_on_hand + quantity,
51
+ updated_at: Time.now
52
+ )
53
+ end
54
+
47
55
  def unstock(variant, quantity, originator = nil)
48
56
  move(variant, -quantity, originator)
49
57
  end
@@ -16,10 +16,12 @@ module Spree
16
16
  end
17
17
 
18
18
  private
19
+
19
20
  def update_stock_item_quantity
20
- return unless Spree::Config[:track_inventory_levels]
21
+ return unless self.stock_item.should_track_inventory?
21
22
  stock_item.adjust_count_on_hand quantity
22
23
  end
24
+
23
25
  end
24
26
  end
25
27
 
@@ -16,7 +16,7 @@ module Spree
16
16
  url: '/spree/taxons/:id/:style/:basename.:extension',
17
17
  path: ':rails_root/public/spree/taxons/:id/:style/:basename.:extension',
18
18
  default_url: '/assets/default_taxon.png'
19
-
19
+
20
20
  include Spree::Core::S3Support
21
21
  supports_s3 :icon
22
22
 
@@ -76,7 +76,7 @@ module Spree
76
76
  #
77
77
  # See #3390 for background.
78
78
  def child_index=(idx)
79
- move_to_child_with_index(parent, idx.to_i)
79
+ move_to_child_with_index(parent, idx.to_i) unless self.new_record?
80
80
  end
81
81
  end
82
82
  end
@@ -23,7 +23,7 @@ module Spree
23
23
  class_name: 'Spree::Price',
24
24
  dependent: :destroy
25
25
 
26
- delegate_belongs_to :default_price, :display_price, :display_amount, :price, :price=, :currency
26
+ delegate_belongs_to :default_price, :display_price, :display_amount, :price, :price=, :currency
27
27
 
28
28
  has_many :prices,
29
29
  class_name: 'Spree::Price',
@@ -40,7 +40,7 @@ module Spree
40
40
  after_create :set_position
41
41
 
42
42
  # default variant scope only lists non-deleted variants
43
- scope :deleted, lambda { where('deleted_at IS NOT NULL') }
43
+ scope :deleted, lambda { where.not(deleted_at: nil) }
44
44
 
45
45
  def self.active(currency = nil)
46
46
  joins(:prices).where(deleted_at: nil).where('spree_prices.currency' => currency || Spree::Config[:currency]).where('spree_prices.amount IS NOT NULL')
@@ -76,6 +76,12 @@ module Spree
76
76
  deleted_at
77
77
  end
78
78
 
79
+ def options=(options = {})
80
+ options.each do |option|
81
+ set_option_value(option[:name], option[:value])
82
+ end
83
+ end
84
+
79
85
  def set_option_value(opt_name, opt_value)
80
86
  # no option values on master
81
87
  return if self.is_master
@@ -94,7 +100,6 @@ module Spree
94
100
  # then we have to check to make sure that the product has the option type
95
101
  unless self.product.option_types.include? option_type
96
102
  self.product.option_types << option_type
97
- self.product.save
98
103
  end
99
104
  end
100
105
 
@@ -127,13 +132,17 @@ module Spree
127
132
  "#{name} - #{sku}"
128
133
  end
129
134
 
135
+ def sku_and_options_text
136
+ "#{sku} #{options_text}".strip
137
+ end
138
+
130
139
  # Product may be created with deleted_at already set,
131
140
  # which would make AR's default finder return nil.
132
141
  # This is a stopgap for that little problem.
133
142
  def product
134
143
  Spree::Product.unscoped { super }
135
144
  end
136
-
145
+
137
146
  def in_stock?(quantity=1)
138
147
  Spree::Stock::Quantifier.new(self).can_supply?(quantity)
139
148
  end
@@ -142,6 +151,12 @@ module Spree
142
151
  Spree::Stock::Quantifier.new(self).total_on_hand
143
152
  end
144
153
 
154
+ # Shortcut method to determine if inventory tracking is enabled for this variant
155
+ # This considers both variant tracking flag and site-wide inventory tracking settings
156
+ def should_track_inventory?
157
+ self.track_inventory? && Spree::Config.track_inventory_levels
158
+ end
159
+
145
160
  private
146
161
  # strips all non-price-like characters from the price, taking into account locale settings
147
162
  def parse_price(price)