spree_core 2.2.0 → 2.2.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +26 -0
  3. data/app/helpers/spree/taxons_helper.rb +2 -2
  4. data/app/models/concerns/spree/user_api_authentication.rb +13 -0
  5. data/app/models/concerns/spree/user_reporting.rb +27 -0
  6. data/app/models/spree/adjustment.rb +3 -2
  7. data/app/models/spree/app_configuration.rb +1 -0
  8. data/app/models/spree/calculator/default_tax.rb +5 -1
  9. data/app/models/spree/calculator.rb +2 -2
  10. data/app/models/spree/credit_card.rb +11 -5
  11. data/app/models/spree/gateway.rb +25 -0
  12. data/app/models/spree/item_adjustments.rb +2 -0
  13. data/app/models/spree/legacy_user.rb +2 -3
  14. data/app/models/spree/line_item.rb +7 -0
  15. data/app/models/spree/order/checkout.rb +19 -7
  16. data/app/models/spree/order/currency_updater.rb +40 -0
  17. data/app/models/spree/order.rb +19 -3
  18. data/app/models/spree/order_updater.rb +1 -1
  19. data/app/models/spree/payment.rb +5 -2
  20. data/app/models/spree/payment_method.rb +1 -0
  21. data/app/models/spree/product/scopes.rb +13 -17
  22. data/app/models/spree/product.rb +10 -2
  23. data/app/models/spree/promotion/actions/free_shipping.rb +4 -2
  24. data/app/models/spree/promotion/rules/product.rb +0 -9
  25. data/app/models/spree/promotion.rb +1 -1
  26. data/app/models/spree/shipment.rb +19 -14
  27. data/app/models/spree/shipping_method.rb +2 -2
  28. data/app/models/spree/shipping_rate.rb +15 -9
  29. data/app/models/spree/stock/estimator.rb +4 -1
  30. data/app/models/spree/stock_location.rb +1 -0
  31. data/app/models/spree/tax_rate.rb +81 -7
  32. data/config/initializers/user_class_extensions.rb +3 -0
  33. data/config/locales/en.yml +26 -0
  34. data/db/default/spree/countries.rb +2 -1
  35. data/db/migrate/20130213191427_create_default_stock.rb +3 -3
  36. data/db/migrate/20130417120035_update_adjustment_states.rb +2 -2
  37. data/db/migrate/20130417123427_add_shipping_rates_to_shipments.rb +1 -1
  38. data/db/migrate/20130509115210_add_number_to_stock_transfer.rb +1 -1
  39. data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +5 -7
  40. data/db/migrate/20140307235515_add_user_id_to_spree_credit_cards.rb +13 -0
  41. data/lib/generators/spree/dummy/templates/rails/database.yml +6 -6
  42. data/lib/spree/core/controller_helpers/auth.rb +10 -1
  43. data/lib/spree/core/controller_helpers/order.rb +1 -1
  44. data/lib/spree/core/controller_helpers/respond_with.rb +6 -1
  45. data/lib/spree/core/importer/order.rb +168 -0
  46. data/lib/spree/core/importer.rb +8 -0
  47. data/lib/spree/core/product_duplicator.rb +1 -1
  48. data/lib/spree/core/user_address.rb +2 -0
  49. data/lib/spree/core/user_payment_source.rb +20 -0
  50. data/lib/spree/core/version.rb +1 -1
  51. data/lib/spree/core.rb +4 -0
  52. data/lib/spree/migrations.rb +1 -1
  53. data/lib/spree/permitted_attributes.rb +1 -1
  54. data/lib/spree/testing_support/capybara_ext.rb +23 -1
  55. data/lib/spree/testing_support/factories/credit_card_factory.rb +1 -6
  56. data/lib/spree/testing_support/factories/order_factory.rb +8 -8
  57. data/lib/spree/testing_support/factories/shipping_method_factory.rb +3 -1
  58. data/lib/spree/testing_support/factories/user_factory.rb +5 -0
  59. metadata +11 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bec161c547d1c0a301e068937cff9a461ff7a6cc
4
- data.tar.gz: 5ea11fca1a903043e7776db55c5092a676a31c40
3
+ metadata.gz: d6877ed94eb40aa392d3e75311ae1c46f07d16c4
4
+ data.tar.gz: 86f962251bf0d966fce2dc5562a4e537b05db77c
5
5
  SHA512:
6
- metadata.gz: 1f618e1c470a7a552a2b6b97d72b2702c52d8beba2298d6ab6400982f024a466bc9b857af1e3b447ca5ab8c079d8d44bac78877516baa1a8962ef7f9eac0b244
7
- data.tar.gz: 3be703d2a9d163edb81d227463859a7cb5a181091539780f5b0eb4173ad2f69cf8ff74e824361d312e6571ffbbedef6c8f12eb06fb6649cd2902771f5f537754
6
+ metadata.gz: b9f99321e59660959d319148d1c7deed17c10caedb97e6d29d95dd15c195b2dac43bcfb35d5117c915bc456a1d8c0fb8f2d319971a7bd681f842f77a91ba25b6
7
+ data.tar.gz: 154ee0e0f7d1bd5fa2ce9bce96008951200122a3c687dfe88b6e75284987bef2c12d464c6bba83a2a97747ccd2e9dae7537b2fddc3a4fb9e9cc924c5c11c8491
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2007-2014, Spree Commerce, Inc. and other contributors
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name Spree nor the names of its contributors may be used to
13
+ endorse or promote products derived from this software without specific
14
+ prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -4,12 +4,12 @@ module Spree
4
4
  # that we can use configurations as well as make it easier for end users to override this determination. One idea is
5
5
  # to show the most popular products for a particular taxon (that is an exercise left to the developer.)
6
6
  def taxon_preview(taxon, max=4)
7
- products = taxon.active_products.limit(max).uniq
7
+ products = taxon.active_products.select("DISTINCT (spree_products.id), spree_products.*, spree_products_taxons.position").limit(max)
8
8
  if (products.size < max)
9
9
  products_arel = Spree::Product.arel_table
10
10
  taxon.descendants.each do |taxon|
11
11
  to_get = max - products.length
12
- products += taxon.active_products.where(products_arel[:id].not_in(products.map(&:id))).limit(to_get).uniq
12
+ products += taxon.active_products.select("DISTINCT (spree_products.id), spree_products.*, spree_products_taxons.position").where(products_arel[:id].not_in(products.map(&:id))).limit(to_get)
13
13
  break if products.size >= max
14
14
  end
15
15
  end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ module UserApiAuthentication
3
+ def generate_spree_api_key!
4
+ self.spree_api_key = SecureRandom.hex(24)
5
+ save!
6
+ end
7
+
8
+ def clear_spree_api_key!
9
+ self.spree_api_key = nil
10
+ save!
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ module UserReporting
3
+ def lifetime_value
4
+ orders.complete.pluck(:total).sum
5
+ end
6
+
7
+ def display_lifetime_value
8
+ Spree::Money.new(lifetime_value)
9
+ end
10
+
11
+ def order_count
12
+ BigDecimal(orders.complete.count)
13
+ end
14
+
15
+ def average_order_value
16
+ if order_count.to_i > 0
17
+ lifetime_value / order_count
18
+ else
19
+ BigDecimal("0.00")
20
+ end
21
+ end
22
+
23
+ def display_average_order_value
24
+ Spree::Money.new(average_order_value)
25
+ end
26
+ end
27
+ end
@@ -34,14 +34,15 @@ module Spree
34
34
  transition from: :open, to: :closed
35
35
  end
36
36
 
37
- event :close do
38
- transition from: :close, to: :open
37
+ event :open do
38
+ transition from: :closed, to: :open
39
39
  end
40
40
  end
41
41
 
42
42
  after_create :update_adjustable_adjustment_total
43
43
 
44
44
  scope :open, -> { where(state: 'open') }
45
+ scope :closed, -> { where(state: 'closed') }
45
46
  scope :tax, -> { where(source_type: 'Spree::TaxRate') }
46
47
  scope :price, -> { where(adjustable_type: 'Spree::LineItem') }
47
48
  scope :shipping, -> { where(adjustable_type: 'Spree::Shipment') }
@@ -58,6 +58,7 @@ module Spree
58
58
  preference :orders_per_page, :integer, default: 15
59
59
  preference :prices_inc_tax, :boolean, default: false
60
60
  preference :products_per_page, :integer, default: 12
61
+ preference :promotions_per_page, :integer, default: 15
61
62
  preference :redirect_https_to_http, :boolean, :default => false
62
63
  preference :require_master_price, :boolean, default: true
63
64
  preference :shipment_inc_vat, :boolean, default: false
@@ -38,7 +38,11 @@ module Spree
38
38
  def compute_shipping_rate(shipping_rate)
39
39
  if rate.included_in_price
40
40
  pre_tax_amount = shipping_rate.cost / (1 + rate.amount)
41
- deduced_total_by_rate(pre_tax_amount, rate)
41
+ if rate.zone == shipping_rate.shipment.order.tax_zone
42
+ deduced_total_by_rate(pre_tax_amount, rate)
43
+ else
44
+ deduced_total_by_rate(pre_tax_amount, rate) * - 1
45
+ end
42
46
  else
43
47
  with_tax_amount = shipping_rate.cost * rate.amount
44
48
  round_to_two_places(with_tax_amount)
@@ -10,9 +10,9 @@ module Spree
10
10
  computable_name = computable.class.name.demodulize.underscore
11
11
  method = "compute_#{computable_name}".to_sym
12
12
  calculator_class = self.class
13
- begin
13
+ if respond_to?(method)
14
14
  self.send(method, computable)
15
- rescue NoMethodError
15
+ else
16
16
  raise NotImplementedError, "Please implement '#{method}(#{computable_name})' in your calculator: #{calculator_class.name}"
17
17
  end
18
18
  end
@@ -1,15 +1,17 @@
1
1
  module Spree
2
2
  class CreditCard < ActiveRecord::Base
3
+ belongs_to :payment_method
3
4
  has_many :payments, as: :source
4
5
 
5
6
  before_save :set_last_digits
6
7
 
7
- attr_accessor :number, :verification_value
8
+ attr_accessor :number, :verification_value, :encrypted_data
9
+
10
+ validates :month, :year, numericality: { only_integer: true }, if: :require_card_numbers?, on: :create
11
+ validates :number, presence: true, if: :require_card_numbers?, on: :create
12
+ validates :name, presence: true, if: :require_card_numbers?, on: :create
13
+ validates :verification_value, presence: true, if: :require_card_numbers?, on: :create
8
14
 
9
- validates :month, :year, numericality: { only_integer: true }, unless: :has_payment_profile?
10
- validates :number, presence: true, unless: :has_payment_profile?, on: :create
11
- validates :name, presence: true
12
- validates :verification_value, presence: true, unless: :has_payment_profile?, on: :create
13
15
  validate :expiry_not_in_the_past
14
16
 
15
17
  scope :with_payment_profile, -> { where('gateway_customer_profile_id IS NOT NULL') }
@@ -118,5 +120,9 @@ module Spree
118
120
  end
119
121
  end
120
122
  end
123
+
124
+ def require_card_numbers?
125
+ !self.encrypted_data.present? && !self.has_payment_profile?
126
+ end
121
127
  end
122
128
  end
@@ -50,5 +50,30 @@ module Spree
50
50
  return false unless source.brand
51
51
  provider_class.supports?(source.brand)
52
52
  end
53
+
54
+ def disable_customer_profile(source)
55
+ if source.is_a? CreditCard
56
+ source.update_column :gateway_customer_profile_id, nil
57
+ else
58
+ raise 'You must implement disable_customer_profile method for this gateway.'
59
+ end
60
+ end
61
+
62
+ def sources_by_order(order)
63
+ source_ids = order.payments.where(source_type: payment_source_class.to_s, payment_method_id: self.id).pluck(:source_id).uniq
64
+ payment_source_class.where(id: source_ids).with_payment_profile
65
+ end
66
+
67
+ def sources_with_profile(order)
68
+ if order.completed?
69
+ sources_by_order order
70
+ else
71
+ if order.user_id
72
+ self.credit_cards.where(user_id: order.user_id).with_payment_profile
73
+ else
74
+ []
75
+ end
76
+ end
77
+ end
53
78
  end
54
79
  end
@@ -7,6 +7,8 @@ module Spree
7
7
 
8
8
  def initialize(item)
9
9
  @item = item
10
+ # Don't attempt to reload the item from the DB if it's not there
11
+ @item.reload if @item.persisted?
10
12
  end
11
13
 
12
14
  def update
@@ -2,6 +2,7 @@
2
2
  module Spree
3
3
  class LegacyUser < ActiveRecord::Base
4
4
  include Core::UserAddress
5
+ include Core::UserPaymentSource
5
6
 
6
7
  self.table_name = 'spree_users'
7
8
 
@@ -9,8 +10,6 @@ module Spree
9
10
 
10
11
  before_destroy :check_completed_orders
11
12
 
12
- class DestroyWithOrdersError < StandardError; end
13
-
14
13
  def has_spree_role?(role)
15
14
  true
16
15
  end
@@ -21,7 +20,7 @@ module Spree
21
20
  private
22
21
 
23
22
  def check_completed_orders
24
- raise DestroyWithOrdersError if orders.complete.present?
23
+ raise Spree::Core::DestroyWithOrdersError if orders.complete.present?
25
24
  end
26
25
  end
27
26
  end
@@ -22,6 +22,7 @@ module Spree
22
22
  validates :price, numericality: true
23
23
  validates_with Stock::AvailabilityValidator
24
24
 
25
+ validate :ensure_proper_currency
25
26
  before_destroy :update_inventory
26
27
 
27
28
  after_save :update_inventory
@@ -118,6 +119,12 @@ module Spree
118
119
  def create_tax_charge
119
120
  Spree::TaxRate.adjust(order, [self])
120
121
  end
122
+
123
+ def ensure_proper_currency
124
+ unless currency == order.currency
125
+ errors.add(:currency, t(:must_match_order_currency))
126
+ end
127
+ end
121
128
  end
122
129
  end
123
130
 
@@ -81,17 +81,17 @@ module Spree
81
81
  before_transition :from => :address, :do => :create_tax_charge!
82
82
  end
83
83
 
84
+ if states[:payment]
85
+ before_transition :to => :payment, :do => :set_shipments_cost
86
+ before_transition :to => :payment, :do => :create_tax_charge!
87
+ end
88
+
84
89
  if states[:delivery]
85
90
  before_transition :to => :delivery, :do => :create_proposed_shipments
86
91
  before_transition :to => :delivery, :do => :ensure_available_shipping_rates
87
92
  before_transition :from => :delivery, :do => :apply_free_shipping_promotions
88
93
  end
89
94
 
90
- if states[:payment]
91
- before_transition :to => :payment, :do => :set_shipments_cost
92
- before_transition :to => :payment, :do => :create_tax_charge!
93
- end
94
-
95
95
  after_transition :to => :complete, :do => :finalize!
96
96
  after_transition :to => :resumed, :do => :after_resume
97
97
  after_transition :to => :canceled, :do => :after_cancel
@@ -211,6 +211,20 @@ module Spree
211
211
  @updating_params = params
212
212
  run_callbacks :updating_from_params do
213
213
  attributes = @updating_params[:order] ? @updating_params[:order].permit(permitted_params) : {}
214
+
215
+ # Set existing card after setting permitted parameters because
216
+ # rails would slice parameters containg ruby objects, apparently
217
+ if @updating_params[:existing_card].present?
218
+ credit_card = CreditCard.find(@updating_params[:existing_card])
219
+ if credit_card.user_id != self.user_id || credit_card.user_id.blank?
220
+ raise Core::GatewayError.new Spree.t(:invalid_credit_card)
221
+ end
222
+
223
+ attributes[:payments_attributes].first[:source] = credit_card
224
+ attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
225
+ attributes[:payments_attributes].first.delete :source_attributes
226
+ end
227
+
214
228
  success = self.update_attributes(attributes)
215
229
  end
216
230
  @updating_params = nil
@@ -222,7 +236,6 @@ module Spree
222
236
  # attributes for a single payment and its source, discarding attributes
223
237
  # for payment methods other than the one selected
224
238
  def update_params_payment_source
225
- # respond_to check is necessary due to issue described in #2910
226
239
  if has_checkout_step?("payment") && self.payment?
227
240
  if @updating_params[:payment_source].present?
228
241
  source_params = @updating_params.delete(:payment_source)[@updating_params[:order][:payments_attributes].first[:payment_method_id].underscore]
@@ -237,7 +250,6 @@ module Spree
237
250
  end
238
251
  end
239
252
  end
240
-
241
253
  end
242
254
  end
243
255
  end
@@ -0,0 +1,40 @@
1
+ module Spree
2
+ class Order < ActiveRecord::Base
3
+ module CurrencyUpdater
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+
8
+ def homogenize_line_item_currencies
9
+ update_line_item_currencies!
10
+ update!
11
+ end
12
+
13
+ end
14
+
15
+ # Updates prices of order's line items
16
+ def update_line_item_currencies!
17
+ line_items.where('currency != ?', currency).each do |line_item|
18
+ update_line_item_price!(line_item)
19
+ end
20
+ end
21
+
22
+ # Returns the price object from given item
23
+ def price_from_line_item(line_item)
24
+ line_item.variant.prices.where(currency: currency).first
25
+ end
26
+
27
+ # Updates price from given line item
28
+ def update_line_item_price!(line_item)
29
+ price = price_from_line_item(line_item)
30
+
31
+ if price
32
+ line_item.update_attributes!(currency: price.currency, price: price.amount)
33
+ else
34
+ raise RuntimeError, "no #{currency} price found for #{line_item.product.name} (#{line_item.variant.sku})"
35
+ end
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -4,13 +4,15 @@ require 'spree/order/checkout'
4
4
  module Spree
5
5
  class Order < ActiveRecord::Base
6
6
  include Checkout
7
+ include CurrencyUpdater
7
8
 
8
9
  checkout_flow do
9
10
  go_to_state :address
10
11
  go_to_state :delivery
11
- go_to_state :payment, if: ->(order) {
12
+ go_to_state :payment, if: ->(order) do
13
+ order.set_shipments_cost if order.shipments.any?
12
14
  order.payment_required?
13
- }
15
+ end
14
16
  go_to_state :confirm, if: ->(order) { order.confirmation_required? }
15
17
  go_to_state :complete
16
18
  remove_transition from: :delivery, to: :confirm
@@ -68,6 +70,7 @@ module Spree
68
70
  attr_accessor :use_billing
69
71
 
70
72
  before_create :link_by_email
73
+ before_update :homogenize_line_item_currencies, if: :currency_changed?
71
74
 
72
75
  validates :email, presence: true, if: :require_email
73
76
  validates :email, email: true, if: :require_email, allow_blank: true
@@ -489,7 +492,7 @@ module Spree
489
492
  # to delivery again so that proper updated shipments are created.
490
493
  # e.g. customer goes back from payment step and changes order items
491
494
  def ensure_updated_shipments
492
- if shipments.any?
495
+ if shipments.any? && !self.completed?
493
496
  self.shipments.destroy_all
494
497
  self.update_column(:shipment_total, 0)
495
498
  restart_checkout_flow
@@ -559,6 +562,19 @@ module Spree
559
562
  update_column(:considered_risky, false)
560
563
  end
561
564
 
565
+ # moved from api order_decorator. This is a better place for it.
566
+ def update_line_items(line_item_params)
567
+ return if line_item_params.blank?
568
+ line_item_params.each_value do |attributes|
569
+ if attributes[:id].present?
570
+ self.line_items.find(attributes[:id]).update_attributes!(attributes)
571
+ else
572
+ self.line_items.create!(attributes)
573
+ end
574
+ end
575
+ self.ensure_updated_shipments
576
+ end
577
+
562
578
  private
563
579
 
564
580
  def link_by_email
@@ -53,7 +53,7 @@ module Spree
53
53
  end
54
54
 
55
55
  def update_shipment_total
56
- order.shipment_total = shipments.sum("cost + promo_total")
56
+ order.shipment_total = shipments.sum(:cost)
57
57
  update_order_total
58
58
  end
59
59
 
@@ -104,7 +104,7 @@ module Spree
104
104
  end
105
105
 
106
106
  def credit_allowed
107
- amount - offsets_total
107
+ amount - offsets_total.abs
108
108
  end
109
109
 
110
110
  def can_credit?
@@ -116,6 +116,8 @@ module Spree
116
116
  return if source_attributes.nil?
117
117
  if payment_method and payment_method.payment_source_class
118
118
  self.source = payment_method.payment_source_class.new(source_attributes)
119
+ self.source.payment_method_id = payment_method.id
120
+ self.source.user_id = self.order.user_id if self.order
119
121
  end
120
122
  end
121
123
 
@@ -159,7 +161,8 @@ module Spree
159
161
  end
160
162
 
161
163
  def create_payment_profile
162
- return unless source.is_a?(CreditCard) && source.number && !source.has_payment_profile?
164
+ return unless source.respond_to?(:has_payment_profile?) && !source.has_payment_profile?
165
+
163
166
  payment_method.create_profile(self)
164
167
  rescue ActiveMerchant::ConnectionError => e
165
168
  gateway_error e
@@ -9,6 +9,7 @@ module Spree
9
9
  validates :name, presence: true
10
10
 
11
11
  has_many :payments, class_name: "Spree::Payment"
12
+ has_many :credit_cards, class_name: "Spree::CreditCard"
12
13
 
13
14
  def self.providers
14
15
  Rails.application.config.spree.payment_methods
@@ -27,6 +27,15 @@ module Spree
27
27
  end
28
28
  end
29
29
 
30
+ def self.property_conditions(property)
31
+ properties = Property.table_name
32
+ conditions = case property
33
+ when String then { "#{properties}.name" => property }
34
+ when Property then { "#{properties}.id" => property.id }
35
+ else { "#{properties}.id" => property.to_i }
36
+ end
37
+ end
38
+
30
39
  add_simple_scopes simple_scopes
31
40
 
32
41
  add_search_scope :ascend_by_master_price do
@@ -83,28 +92,15 @@ module Spree
83
92
 
84
93
  # a scope that finds all products having property specified by name, object or id
85
94
  add_search_scope :with_property do |property|
86
- properties = Property.table_name
87
- conditions = case property
88
- when String then { "#{properties}.name" => property }
89
- when Property then { "#{properties}.id" => property.id }
90
- else { "#{properties}.id" => property.to_i }
91
- end
92
-
93
- joins(:properties).where(conditions)
95
+ joins(:properties).where(property_conditions(property))
94
96
  end
95
97
 
96
98
  # a simple test for product with a certain property-value pairing
97
99
  # note that it can test for properties with NULL values, but not for absent values
98
100
  add_search_scope :with_property_value do |property, value|
99
- properties = Spree::Property.table_name
100
- conditions = case property
101
- when String then ["#{properties}.name = ?", property]
102
- when Property then ["#{properties}.id = ?", property.id]
103
- else ["#{properties}.id = ?", property.to_i]
104
- end
105
- conditions = ["#{ProductProperty.table_name}.value = ? AND #{conditions[0]}", value, conditions[1]]
106
-
107
- joins(:properties).where(conditions)
101
+ joins(:properties)
102
+ .where("#{ProductProperty.table_name}.value = ?", value)
103
+ .where(property_conditions(property))
108
104
  end
109
105
 
110
106
  add_search_scope :with_option do |option|
@@ -78,6 +78,8 @@ module Spree
78
78
  validates :shipping_category_id, presence: true
79
79
  validates :slug, length: { minimum: 3 }
80
80
 
81
+ before_validation :normalize_slug, on: :update
82
+
81
83
  attr_accessor :option_values_hash
82
84
 
83
85
  accepts_nested_attributes_for :product_properties, allow_destroy: true, reject_if: lambda { |pp| pp[:property_name].blank? }
@@ -132,8 +134,11 @@ module Spree
132
134
  !!deleted_at
133
135
  end
134
136
 
137
+ # determine if product is available.
138
+ # deleted products and products with nil or future available_on date
139
+ # are not available
135
140
  def available?
136
- !(available_on.nil? || available_on.future?)
141
+ !(available_on.nil? || available_on.future?) && !deleted?
137
142
  end
138
143
 
139
144
  # split variants list into hash which shows mapping of opt value onto matching variants
@@ -188,7 +193,7 @@ module Spree
188
193
  end
189
194
 
190
195
  def possible_promotions
191
- promotion_ids = promotion_rules.map(&:activator_id).uniq
196
+ promotion_ids = promotion_rules.map(&:promotion_id).uniq
192
197
  Spree::Promotion.advertised.where(id: promotion_ids).reject(&:expired?)
193
198
  end
194
199
 
@@ -208,6 +213,9 @@ module Spree
208
213
  end
209
214
 
210
215
  private
216
+ def normalize_slug
217
+ self.slug = normalize_friendly_id(slug)
218
+ end
211
219
 
212
220
  # Builds variants from a hash of option types & values
213
221
  def build_variants_from_option_values_hash
@@ -4,8 +4,6 @@ module Spree
4
4
  class FreeShipping < Spree::PromotionAction
5
5
  def perform(payload={})
6
6
  order = payload[:order]
7
-
8
- label = "#{Spree.t(:promotion)} (#{promotion.name})"
9
7
  results = order.shipments.map do |shipment|
10
8
  return false if promotion_credit_exists?(shipment)
11
9
  shipment.adjustments.create!(
@@ -21,6 +19,10 @@ module Spree
21
19
  results.any? { |r| r == true }
22
20
  end
23
21
 
22
+ def label
23
+ "#{Spree.t(:promotion)} (#{promotion.name})"
24
+ end
25
+
24
26
  def compute_amount(shipment)
25
27
  shipment.cost * -1
26
28
  end
@@ -6,7 +6,6 @@ module Spree
6
6
  module Rules
7
7
  class Product < PromotionRule
8
8
  has_and_belongs_to_many :products, class_name: '::Spree::Product', join_table: 'spree_products_promotion_rules', foreign_key: 'promotion_rule_id'
9
- validate :only_one_promotion_per_product
10
9
 
11
10
  MATCH_POLICIES = %w(any all)
12
11
  preference :match_policy, :string, default: MATCH_POLICIES.first
@@ -36,14 +35,6 @@ module Spree
36
35
  def product_ids_string=(s)
37
36
  self.product_ids = s.to_s.split(',').map(&:strip)
38
37
  end
39
-
40
- private
41
-
42
- def only_one_promotion_per_product
43
- if Spree::Promotion::Rules::Product.all.map(&:products).flatten.uniq!
44
- errors[:base] << "You can't create two promotions for the same product"
45
- end
46
- end
47
38
  end
48
39
  end
49
40
  end
@@ -40,7 +40,7 @@ module Spree
40
40
  end
41
41
 
42
42
  def expired?
43
- starts_at && Time.now < starts_at || expires_at && Time.now > expires_at
43
+ !!(starts_at && Time.now < starts_at || expires_at && Time.now > expires_at)
44
44
  end
45
45
 
46
46
  def activate(payload)