solidus_core 2.1.1 → 2.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of solidus_core might be problematic. Click here for more details.

Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +0 -1
  3. data/app/assets/config/solidus_core_manifest.js +1 -0
  4. data/app/assets/javascripts/spree.js.erb +72 -0
  5. data/app/helpers/spree/store_helper.rb +5 -0
  6. data/app/jobs/spree/promotion_code_batch_job.rb +24 -0
  7. data/app/mailers/spree/promotion_code_batch_mailer.rb +13 -0
  8. data/app/models/concerns/spree/calculated_adjustments.rb +1 -1
  9. data/app/models/concerns/spree/ordered_property_value_list.rb +2 -2
  10. data/app/models/concerns/spree/user_address_book.rb +4 -4
  11. data/app/models/concerns/spree/user_methods.rb +7 -0
  12. data/app/models/concerns/spree/user_payment_source.rb +12 -5
  13. data/app/models/spree/address.rb +14 -3
  14. data/app/models/spree/adjustment.rb +13 -1
  15. data/app/models/spree/app_configuration.rb +0 -19
  16. data/app/models/spree/base.rb +2 -0
  17. data/app/models/spree/credit_card.rb +34 -43
  18. data/app/models/spree/gateway/bogus.rb +1 -1
  19. data/app/models/spree/gateway.rb +6 -4
  20. data/app/models/spree/inventory_unit.rb +3 -2
  21. data/app/models/spree/order/checkout.rb +187 -273
  22. data/app/models/spree/order.rb +137 -71
  23. data/app/models/spree/order_contents.rb +1 -1
  24. data/app/models/spree/order_inventory.rb +11 -11
  25. data/app/models/spree/order_promotion.rb +2 -0
  26. data/app/models/spree/order_update_attributes.rb +1 -8
  27. data/app/models/spree/order_updater.rb +67 -63
  28. data/app/models/spree/payment.rb +0 -1
  29. data/app/models/spree/payment_create.rb +27 -7
  30. data/app/models/spree/payment_method/store_credit.rb +3 -3
  31. data/app/models/spree/payment_method.rb +4 -1
  32. data/app/models/spree/payment_source.rb +45 -0
  33. data/app/models/spree/product/scopes.rb +24 -24
  34. data/app/models/spree/product.rb +4 -4
  35. data/app/models/spree/promotion.rb +2 -0
  36. data/app/models/spree/promotion_code/batch_builder.rb +63 -0
  37. data/app/models/spree/promotion_code.rb +1 -0
  38. data/app/models/spree/promotion_code_batch.rb +25 -0
  39. data/app/models/spree/promotion_handler/cart.rb +2 -2
  40. data/app/models/spree/promotion_handler/coupon.rb +1 -2
  41. data/app/models/spree/promotion_handler/free_shipping.rb +32 -21
  42. data/app/models/spree/promotion_handler/page.rb +1 -1
  43. data/app/models/spree/reimbursement.rb +1 -1
  44. data/app/models/spree/return_authorization.rb +0 -28
  45. data/app/models/spree/return_item.rb +1 -1
  46. data/app/models/spree/shipment.rb +4 -4
  47. data/app/models/spree/shipping_method.rb +2 -2
  48. data/app/models/spree/shipping_rate.rb +1 -1
  49. data/app/models/spree/stock/availability_validator.rb +16 -17
  50. data/app/models/spree/stock/coordinator.rb +3 -3
  51. data/app/models/spree/stock/package.rb +1 -1
  52. data/app/models/spree/stock/quantifier.rb +5 -4
  53. data/app/models/spree/stock_location.rb +2 -2
  54. data/app/models/spree/store.rb +2 -2
  55. data/app/models/spree/store_credit.rb +1 -1
  56. data/app/models/spree/tax/tax_helpers.rb +3 -3
  57. data/app/models/spree/tax_rate.rb +7 -1
  58. data/app/models/spree/taxonomy.rb +1 -1
  59. data/app/models/spree/variant/scopes.rb +5 -5
  60. data/app/models/spree/variant/vat_price_generator.rb +8 -5
  61. data/app/models/spree/variant.rb +1 -0
  62. data/app/models/spree/wallet/add_payment_sources_to_wallet.rb +19 -10
  63. data/app/models/spree/wallet/default_payment_builder.rb +6 -6
  64. data/app/models/spree/wallet.rb +71 -0
  65. data/app/models/spree/wallet_payment_source.rb +17 -0
  66. data/app/models/spree/zone.rb +1 -1
  67. data/app/views/spree/carton_mailer/shipped_email.text.erb +1 -1
  68. data/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb +2 -0
  69. data/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb +2 -0
  70. data/app/views/spree/reimbursement_mailer/reimbursement_email.html.erb +0 -7
  71. data/app/views/spree/reimbursement_mailer/reimbursement_email.text.erb +0 -5
  72. data/app/views/spree/shared/_error_messages.html.erb +1 -1
  73. data/app/views/spree/shipment_mailer/shipped_email.html.erb +1 -1
  74. data/config/initializers/assets.rb +1 -1
  75. data/config/initializers/friendly_id.rb +1 -1
  76. data/config/locales/en.yml +50 -12
  77. data/db/default/spree/store_credit.rb +2 -1
  78. data/db/migrate/20130826062534_add_depth_to_spree_taxons.rb +4 -6
  79. data/db/migrate/20160420044191_create_spree_wallet_payment_sources.rb +23 -0
  80. data/db/migrate/20160420181916_migrate_credit_cards_to_wallet_payment_sources.rb +26 -0
  81. data/db/migrate/20161017102621_create_spree_promotion_code_batch.rb +36 -0
  82. data/db/migrate/20161129035810_add_index_to_spree_payments_number.rb +5 -0
  83. data/db/migrate/20170223235001_remove_spree_store_credits_column.rb +5 -0
  84. data/lib/generators/spree/dummy/templates/rails/application.rb +1 -1
  85. data/lib/generators/spree/dummy/templates/rails/test.rb +1 -1
  86. data/lib/generators/spree/install/install_generator.rb +6 -5
  87. data/lib/spree/core/controller_helpers/payment_parameters.rb +54 -0
  88. data/lib/spree/core/engine.rb +6 -9
  89. data/lib/spree/core/version.rb +1 -1
  90. data/lib/spree/core.rb +0 -1
  91. data/lib/spree/money.rb +18 -0
  92. data/lib/spree/permission_sets/default_customer.rb +1 -1
  93. data/lib/spree/permitted_attributes.rb +1 -1
  94. data/lib/spree/testing_support/authorization_helpers.rb +1 -0
  95. data/lib/spree/testing_support/capybara_ext.rb +13 -0
  96. data/lib/spree/testing_support/factories/order_factory.rb +5 -1
  97. data/lib/spree/testing_support/factories/payment_factory.rb +1 -1
  98. data/lib/spree/testing_support/factories/shipment_factory.rb +0 -1
  99. data/solidus_core.gemspec +3 -3
  100. data/spec/jobs/promotion_code_batch_job_spec.rb +65 -0
  101. data/spec/lib/calculated_adjustments_spec.rb +105 -1
  102. data/spec/lib/spree/core/testing_support/factories/order_factory_spec.rb +4 -1
  103. data/spec/lib/spree/core/testing_support/factories/payment_factory_spec.rb +8 -0
  104. data/spec/lib/spree/money_spec.rb +32 -0
  105. data/spec/lib/spree/permission_sets/default_customer_spec.rb +20 -0
  106. data/spec/mailers/promotion_code_batch_mailer_spec.rb +45 -0
  107. data/spec/models/spree/credit_card_spec.rb +86 -86
  108. data/spec/models/spree/gateway_spec.rb +3 -1
  109. data/spec/models/spree/inventory_unit_spec.rb +12 -4
  110. data/spec/models/spree/order/checkout_spec.rb +11 -32
  111. data/spec/models/spree/order/tax_spec.rb +2 -2
  112. data/spec/models/spree/order_contents_spec.rb +24 -1
  113. data/spec/models/spree/order_inventory_spec.rb +130 -83
  114. data/spec/models/spree/order_spec.rb +15 -117
  115. data/spec/models/spree/order_update_attributes_spec.rb +1 -44
  116. data/spec/models/spree/order_updater_spec.rb +10 -13
  117. data/spec/models/spree/payment_create_spec.rb +5 -1
  118. data/spec/models/spree/payment_method_spec.rb +16 -0
  119. data/spec/models/spree/payment_spec.rb +14 -8
  120. data/spec/models/spree/promotion_code/batch_builder_spec.rb +61 -0
  121. data/spec/models/spree/promotion_code_batch_spec.rb +58 -0
  122. data/spec/models/spree/promotion_code_spec.rb +4 -0
  123. data/spec/models/spree/promotion_spec.rb +3 -6
  124. data/spec/models/spree/return_authorization_spec.rb +0 -59
  125. data/spec/models/spree/shipment_spec.rb +4 -4
  126. data/spec/models/spree/stock/availability_validator_spec.rb +64 -9
  127. data/spec/models/spree/tax/item_adjuster_spec.rb +1 -2
  128. data/spec/models/spree/unit_cancel_spec.rb +0 -85
  129. data/spec/models/spree/user_spec.rb +3 -1
  130. data/spec/models/spree/variant/vat_price_generator_spec.rb +8 -2
  131. data/spec/models/spree/variant_spec.rb +16 -4
  132. data/spec/models/spree/wallet_payment_source_spec.rb +46 -0
  133. data/spec/models/spree/wallet_spec.rb +128 -0
  134. data/spec/support/concerns/payment_source.rb +64 -0
  135. metadata +51 -25
  136. data/app/assets/javascripts/spree.js.coffee.erb +0 -64
  137. data/app/models/spree/promotion_builder.rb +0 -55
  138. data/app/models/spree/promotion_code/code_builder.rb +0 -62
  139. data/config/initializers/premailer_assets.rb +0 -1
  140. data/lib/spree/core/unreturned_item_charger.rb +0 -106
  141. data/lib/tasks/exchanges.rake +0 -47
  142. data/spec/lib/spree/core/unreturned_item_charger_spec.rb +0 -126
  143. data/spec/lib/tasks/exchanges_spec.rb +0 -220
  144. data/spec/models/spree/promotion_builder_spec.rb +0 -120
  145. data/spec/models/spree/promotion_code/code_builder_spec.rb +0 -77
@@ -5,7 +5,8 @@ module Spree
5
5
  # @param attributes [Hash,ActionController::Parameters] attributes which are assigned to the new payment
6
6
  # * :payment_method_id Id of payment method used for this payment
7
7
  # * :source_attributes Attributes used to build the source of this payment. Usually a {CreditCard}
8
- # * :existing_card_id (Integer) The id of an existing {CreditCard} object to use
8
+ # * :existing_card_id (Integer) Deprecated: The id of an existing {CreditCard} object to use
9
+ # * :wallet_payment_source_id (Integer): The id of a {WalletPaymentSource} to use
9
10
  # @param request_env [Hash] rack env of user creating the payment
10
11
  # @param payment [Payment] Internal use only. Instead of making a new payment, change the attributes for an existing one.
11
12
  def initialize(order, attributes, payment: nil, request_env: {})
@@ -27,7 +28,13 @@ module Spree
27
28
  @payment.attributes = @attributes
28
29
 
29
30
  if source_attributes[:existing_card_id].present?
31
+ Spree::Deprecation.warn(
32
+ "Passing existing_card_id to PaymentCreate is deprecated. Use wallet_payment_source_id instead.",
33
+ caller,
34
+ )
30
35
  build_existing_card
36
+ elsif source_attributes[:wallet_payment_source_id].present?
37
+ build_from_wallet_payment_source
31
38
  else
32
39
  build_source
33
40
  end
@@ -44,27 +51,40 @@ module Spree
44
51
  if source_attributes.present? && payment_method.try(:payment_source_class)
45
52
  payment.source = payment_method.payment_source_class.new(source_attributes)
46
53
  payment.source.payment_method_id = payment_method.id
47
- payment.source.user_id = order.user_id if order
54
+ if order && payment.source.respond_to?(:user=)
55
+ payment.source.user = order.user
56
+ end
48
57
  end
49
58
  end
50
59
 
60
+ def build_from_wallet_payment_source
61
+ wallet_payment_source_id = source_attributes.fetch(:wallet_payment_source_id)
62
+ raise(ActiveRecord::RecordNotFound) if order.user.nil?
63
+ wallet_payment_source = order.user.wallet.find(wallet_payment_source_id)
64
+ raise(ActiveRecord::RecordNotFound) if wallet_payment_source.nil?
65
+ build_from_payment_source(wallet_payment_source.payment_source)
66
+ end
67
+
51
68
  def build_existing_card
52
69
  credit_card = available_cards.find(source_attributes[:existing_card_id])
70
+ build_from_payment_source(credit_card)
71
+ end
53
72
 
73
+ def build_from_payment_source(payment_source)
54
74
  # FIXME: does this work?
55
75
  if source_attributes[:verification_value]
56
- credit_card.verification_value = source_attributes[:verification_value]
76
+ payment_source.verification_value = source_attributes[:verification_value]
57
77
  end
58
78
 
59
- payment.source = credit_card
60
- payment.payment_method_id = credit_card.payment_method_id
79
+ payment.source = payment_source
80
+ payment.payment_method_id = payment_source.payment_method_id
61
81
  end
62
82
 
63
83
  def available_cards
64
84
  if user_id = order.user_id
65
- CreditCard.where(user_id: user_id)
85
+ Spree::CreditCard.where(user_id: user_id)
66
86
  else
67
- CreditCard.none
87
+ Spree::CreditCard.none
68
88
  end
69
89
  end
70
90
  end
@@ -109,7 +109,7 @@ module Spree
109
109
 
110
110
  def handle_action(action, action_name, auth_code)
111
111
  # Find first event with provided auth_code
112
- store_credit = StoreCreditEvent.find_by_authorization_code(auth_code).try(:store_credit)
112
+ store_credit = Spree::StoreCreditEvent.find_by_authorization_code(auth_code).try(:store_credit)
113
113
 
114
114
  if store_credit.nil?
115
115
  ActiveMerchant::Billing::Response.new(false, Spree.t('store_credit.unable_to_find_for_action', auth_code: auth_code, action: action_name), {}, {})
@@ -119,8 +119,8 @@ module Spree
119
119
  end
120
120
 
121
121
  def auth_or_capture_event(auth_code)
122
- capture_event = StoreCreditEvent.find_by(authorization_code: auth_code, action: Spree::StoreCredit::CAPTURE_ACTION)
123
- auth_event = StoreCreditEvent.find_by(authorization_code: auth_code, action: Spree::StoreCredit::AUTHORIZE_ACTION)
122
+ capture_event = Spree::StoreCreditEvent.find_by(authorization_code: auth_code, action: Spree::StoreCredit::CAPTURE_ACTION)
123
+ auth_event = Spree::StoreCreditEvent.find_by(authorization_code: auth_code, action: Spree::StoreCredit::AUTHORIZE_ACTION)
124
124
  capture_event || auth_event
125
125
  end
126
126
  end
@@ -17,7 +17,10 @@ module Spree
17
17
  scope :active, -> { where(active: true) }
18
18
  scope :available_to_users, -> { where(available_to_users: true) }
19
19
  scope :available_to_admin, -> { where(available_to_admin: true) }
20
- scope :available_to_store, -> (store) { (store.present? && store.payment_methods.empty?) ? self : store.payment_methods }
20
+ scope :available_to_store, ->(store) do
21
+ raise ArgumentError, "You must provide a store" if store.nil?
22
+ store.payment_methods.empty? ? all : where(id: store.payment_method_ids)
23
+ end
21
24
 
22
25
  include Spree::Preferences::StaticallyConfigurable
23
26
 
@@ -0,0 +1,45 @@
1
+ module Spree
2
+ class PaymentSource < Spree::Base
3
+ self.abstract_class = true
4
+
5
+ belongs_to :payment_method
6
+
7
+ has_many :payments, as: :source
8
+ has_many :wallet_payment_sources, class_name: 'Spree::WalletPaymentSource', as: :payment_source, inverse_of: :payment_source
9
+
10
+ attr_accessor :imported
11
+
12
+ # @return [Array<String>] the actions available on this payment source
13
+ def actions
14
+ %w(capture void credit)
15
+ end
16
+
17
+ # @param payment [Spree::Payment] the payment we want to know if can be captured
18
+ # @return [Boolean] true when the payment is in the pending or checkout states
19
+ def can_capture?(payment)
20
+ payment.pending? || payment.checkout?
21
+ end
22
+
23
+ # @param payment [Spree::Payment] the payment we want to know if can be voided
24
+ # @return [Boolean] true when the payment is not failed or voided
25
+ def can_void?(payment)
26
+ !payment.failed? && !payment.void?
27
+ end
28
+
29
+ # Indicates whether its possible to credit the payment. Note that most
30
+ # gateways require that the payment be settled first which generally
31
+ # happens within 12-24 hours of the transaction.
32
+ #
33
+ # @param payment [Spree::Payment] the payment we want to know if can be credited
34
+ # @return [Boolean] true when the payment is completed and can be credited
35
+ def can_credit?(payment)
36
+ payment.completed? && payment.credit_allowed > 0
37
+ end
38
+
39
+ # Indicates whether this payment source can be used more than once. E.g. a
40
+ # credit card with a 'payment profile'.
41
+ def reusable?
42
+ false
43
+ end
44
+ end
45
+ end
@@ -46,12 +46,12 @@ module Spree
46
46
  # This scope selects products in taxon AND all its descendants
47
47
  # If you need products only within one taxon use
48
48
  #
49
- # Spree::Product.joins(:taxons).where(Taxon.table_name => { :id => taxon.id })
49
+ # Spree::Product.joins(:taxons).where(Taxon.table_name => { id: taxon.id })
50
50
  #
51
51
  # If you're using count on the result of this scope, you must use the
52
52
  # `:distinct` option as well:
53
53
  #
54
- # Spree::Product.in_taxon(taxon).count(:distinct => true)
54
+ # Spree::Product.in_taxon(taxon).count(distinct: true)
55
55
  #
56
56
  # This is so that the count query is distinct'd:
57
57
  #
@@ -84,12 +84,12 @@ module Spree
84
84
  # note that it can test for properties with NULL values, but not for absent values
85
85
  add_search_scope :with_property_value do |property, value|
86
86
  joins(:properties)
87
- .where("#{ProductProperty.table_name}.value = ?", value)
87
+ .where("#{Spree::ProductProperty.table_name}.value = ?", value)
88
88
  .where(property_conditions(property))
89
89
  end
90
90
 
91
91
  add_search_scope :with_option do |option|
92
- option_types = OptionType.table_name
92
+ option_types = Spree::OptionType.table_name
93
93
  conditions = case option
94
94
  when String then { "#{option_types}.name" => option }
95
95
  when OptionType then { "#{option_types}.id" => option.id }
@@ -100,10 +100,10 @@ module Spree
100
100
  end
101
101
 
102
102
  add_search_scope :with_option_value do |option, value|
103
- option_values = OptionValue.table_name
103
+ option_values = Spree::OptionValue.table_name
104
104
  option_type_id = case option
105
- when String then OptionType.find_by(name: option) || option.to_i
106
- when OptionType then option.id
105
+ when String then Spree::OptionType.find_by(name: option) || option.to_i
106
+ when Spree::OptionType then option.id
107
107
  else option.to_i
108
108
  end
109
109
 
@@ -117,7 +117,7 @@ module Spree
117
117
  add_search_scope :with do |value|
118
118
  includes(variants_including_master: :option_values).
119
119
  includes(:product_properties).
120
- where("#{OptionValue.table_name}.name = ? OR #{ProductProperty.table_name}.value = ?", value, value)
120
+ where("#{Spree::OptionValue.table_name}.name = ? OR #{Spree::ProductProperty.table_name}.value = ?", value, value)
121
121
  end
122
122
 
123
123
  # Finds all products that have a name containing the given words.
@@ -147,38 +147,38 @@ module Spree
147
147
  # there is alternative faster and more elegant solution, it has small drawback though,
148
148
  # it doesn stack with other scopes :/
149
149
  #
150
- # :joins => "LEFT OUTER JOIN (SELECT line_items.variant_id as vid, COUNT(*) as cnt FROM line_items GROUP BY line_items.variant_id) AS popularity_count ON variants.id = vid",
151
- # :order => 'COALESCE(cnt, 0) DESC'
150
+ # joins: "LEFT OUTER JOIN (SELECT line_items.variant_id as vid, COUNT(*) as cnt FROM line_items GROUP BY line_items.variant_id) AS popularity_count ON variants.id = vid",
151
+ # order: 'COALESCE(cnt, 0) DESC'
152
152
  add_search_scope :descend_by_popularity do
153
153
  joins(:master).
154
154
  order(%{
155
155
  COALESCE((
156
156
  SELECT
157
- COUNT(#{LineItem.quoted_table_name}.id)
157
+ COUNT(#{Spree::LineItem.quoted_table_name}.id)
158
158
  FROM
159
- #{LineItem.quoted_table_name}
159
+ #{Spree::LineItem.quoted_table_name}
160
160
  JOIN
161
- #{Variant.quoted_table_name} AS popular_variants
161
+ #{Spree::Variant.quoted_table_name} AS popular_variants
162
162
  ON
163
- popular_variants.id = #{LineItem.quoted_table_name}.variant_id
163
+ popular_variants.id = #{Spree::LineItem.quoted_table_name}.variant_id
164
164
  WHERE
165
- popular_variants.product_id = #{Product.quoted_table_name}.id
165
+ popular_variants.product_id = #{Spree::Product.quoted_table_name}.id
166
166
  ), 0) DESC
167
167
  })
168
168
  end
169
169
 
170
170
  add_search_scope :not_deleted do
171
- where("#{Product.quoted_table_name}.deleted_at IS NULL or #{Product.quoted_table_name}.deleted_at >= ?", Time.current)
171
+ where("#{Spree::Product.quoted_table_name}.deleted_at IS NULL or #{Spree::Product.quoted_table_name}.deleted_at >= ?", Time.current)
172
172
  end
173
173
 
174
174
  # Can't use add_search_scope for this as it needs a default argument
175
175
  def self.available(available_on = nil)
176
- joins(master: :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.current)
176
+ joins(master: :prices).where("#{Spree::Product.quoted_table_name}.available_on <= ?", available_on || Time.current)
177
177
  end
178
178
  search_scopes << :available
179
179
 
180
180
  add_search_scope :taxons_name_eq do |name|
181
- group("spree_products.id").joins(:taxons).where(Taxon.arel_table[:name].eq(name))
181
+ group("spree_products.id").joins(:taxons).where(Spree::Taxon.arel_table[:name].eq(name))
182
182
  end
183
183
 
184
184
  def self.distinct_by_product_ids(sort_order = nil)
@@ -210,13 +210,13 @@ module Spree
210
210
  private
211
211
 
212
212
  def price_table_name
213
- Price.quoted_table_name
213
+ Spree::Price.quoted_table_name
214
214
  end
215
215
 
216
216
  # specifically avoid having an order for taxon search (conflicts with main order)
217
217
  def prepare_taxon_conditions(taxons)
218
218
  ids = taxons.map { |taxon| taxon.self_and_descendants.pluck(:id) }.flatten.uniq
219
- joins(:taxons).where("#{Taxon.table_name}.id" => ids)
219
+ joins(:taxons).where("#{Spree::Taxon.table_name}.id" => ids)
220
220
  end
221
221
 
222
222
  # Produce an array of keywords for use in scopes.
@@ -228,14 +228,14 @@ module Spree
228
228
  end
229
229
 
230
230
  def get_taxons(*ids_or_records_or_names)
231
- taxons = Taxon.table_name
231
+ taxons = Spree::Taxon.table_name
232
232
  ids_or_records_or_names.flatten.map { |t|
233
233
  case t
234
- when Integer then Taxon.find_by(id: t)
234
+ when Integer then Spree::Taxon.find_by(id: t)
235
235
  when ActiveRecord::Base then t
236
236
  when String
237
- Taxon.find_by(name: t) ||
238
- Taxon.where("#{taxons}.permalink LIKE ? OR #{taxons}.permalink = ?", "%/#{t}/", "#{t}/").first
237
+ Spree::Taxon.find_by(name: t) ||
238
+ Spree::Taxon.where("#{taxons}.permalink LIKE ? OR #{taxons}.permalink = ?", "%/#{t}/", "#{t}/").first
239
239
  end
240
240
  }.compact.flatten.uniq
241
241
  end
@@ -91,7 +91,7 @@ module Spree
91
91
  validates :name, presence: true
92
92
  validates :price, presence: true, if: proc { Spree::Config[:require_master_price] }
93
93
  validates :shipping_category_id, presence: true
94
- validates :slug, length: { minimum: 3 }, uniqueness: { allow_blank: true }
94
+ validates :slug, presence: true, uniqueness: { allow_blank: true }
95
95
 
96
96
  attr_accessor :option_values_hash
97
97
 
@@ -110,7 +110,7 @@ module Spree
110
110
 
111
111
  # @return [Spree::TaxCategory] tax category for this product, or the default tax category
112
112
  def tax_category
113
- super || TaxCategory.find_by(is_default: true)
113
+ super || Spree::TaxCategory.find_by(is_default: true)
114
114
  end
115
115
 
116
116
  # Ensures option_types and product_option_types exist for keys in
@@ -232,8 +232,8 @@ module Spree
232
232
  def set_property(property_name, property_value)
233
233
  ActiveRecord::Base.transaction do
234
234
  # Works around spree_i18n https://github.com/spree/spree/issues/301
235
- property = Property.create_with(presentation: property_name).find_or_create_by(name: property_name)
236
- product_property = ProductProperty.where(product: self, property: property).first_or_initialize
235
+ property = Spree::Property.create_with(presentation: property_name).find_or_create_by(name: property_name)
236
+ product_property = Spree::ProductProperty.where(product: self, property: property).first_or_initialize
237
237
  product_property.value = property_value
238
238
  product_property.save!
239
239
  end
@@ -19,6 +19,8 @@ module Spree
19
19
  has_many :codes, class_name: "Spree::PromotionCode", inverse_of: :promotion, dependent: :destroy
20
20
  alias_method :promotion_codes, :codes
21
21
 
22
+ has_many :promotion_code_batches, class_name: "Spree::PromotionCodeBatch", dependent: :destroy
23
+
22
24
  accepts_nested_attributes_for :promotion_actions, :promotion_rules
23
25
 
24
26
  validates_associated :rules
@@ -0,0 +1,63 @@
1
+ class ::Spree::PromotionCode::BatchBuilder
2
+ attr_reader :promotion_code_batch
3
+ delegate :promotion, :number_of_codes, :base_code, to: :promotion_code_batch
4
+
5
+ class_attribute :random_code_length, :batch_size, :sample_characters
6
+ self.random_code_length = 6
7
+ self.batch_size = 1_000
8
+ self.sample_characters = ('a'..'z').to_a + (2..9).to_a.map(&:to_s)
9
+
10
+ def initialize(promotion_code_batch)
11
+ @promotion_code_batch = promotion_code_batch
12
+ end
13
+
14
+ def build_promotion_codes
15
+ generate_random_codes
16
+ promotion_code_batch.update!(state: "completed")
17
+ rescue => e
18
+ promotion_code_batch.update!(
19
+ error: e.inspect,
20
+ state: "failed"
21
+ )
22
+ raise e
23
+ end
24
+
25
+ private
26
+
27
+ def generate_random_codes
28
+ total_batches = (number_of_codes.to_f / self.class.batch_size).ceil
29
+
30
+ total_batches.times do |i|
31
+ codes_for_current_batch = Set.new
32
+ codes_to_generate = [self.class.batch_size, number_of_codes - i * batch_size].min
33
+
34
+ while codes_for_current_batch.size < codes_to_generate
35
+ new_codes = Array.new(codes_to_generate) { generate_random_code }.to_set
36
+ codes_for_current_batch += get_unique_codes(new_codes)
37
+ end
38
+
39
+ codes_for_current_batch.map do |value|
40
+ promotion.codes.build(value: value, promotion_code_batch: promotion_code_batch)
41
+ end
42
+
43
+ promotion.save!
44
+
45
+ # We have to reload the promotion because otherwise all promotion codes
46
+ # we are creating will remain in memory. Doing a reload will remove all
47
+ # codes from memory.
48
+ promotion.reload
49
+ end
50
+ end
51
+
52
+ def generate_random_code
53
+ suffix = Array.new(self.class.random_code_length) do
54
+ sample_characters.sample
55
+ end.join
56
+
57
+ "#{base_code}_#{suffix}"
58
+ end
59
+
60
+ def get_unique_codes(code_set)
61
+ code_set - Spree::PromotionCode.where(value: code_set.to_a).pluck(:value)
62
+ end
63
+ end
@@ -1,5 +1,6 @@
1
1
  class Spree::PromotionCode < Spree::Base
2
2
  belongs_to :promotion, inverse_of: :codes
3
+ belongs_to :promotion_code_batch, class_name: "Spree::PromotionCodeBatch"
3
4
  has_many :adjustments
4
5
 
5
6
  validates :value, presence: true, uniqueness: { allow_blank: true }
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ class PromotionCodeBatch < ActiveRecord::Base
3
+ class CantProcessStartedBatch < StandardError
4
+ end
5
+
6
+ belongs_to :promotion, class_name: "Spree::Promotion"
7
+ has_many :promotion_codes, class_name: "Spree::PromotionCode", dependent: :destroy
8
+
9
+ validates :number_of_codes, numericality: { greater_than: 0 }
10
+ validates_presence_of :base_code, :number_of_codes
11
+
12
+ def finished?
13
+ state == "completed"
14
+ end
15
+
16
+ def process
17
+ if state == "pending"
18
+ update!(state: "processing")
19
+ PromotionCodeBatchJob.perform_later(self)
20
+ else
21
+ raise CantProcessStartedBatch.new("Batch #{id} already started")
22
+ end
23
+ end
24
+ end
25
+ end
@@ -35,13 +35,13 @@ module Spree
35
35
  end
36
36
 
37
37
  def connected_order_promotions
38
- Promotion.active.includes(:promotion_rules).
38
+ Spree::Promotion.active.includes(:promotion_rules).
39
39
  joins(:order_promotions).
40
40
  where(spree_orders_promotions: { order_id: order.id }).readonly(false).to_a
41
41
  end
42
42
 
43
43
  def sale_promotions
44
- Promotion.where(apply_automatically: true).active.includes(:promotion_rules)
44
+ Spree::Promotion.where(apply_automatically: true).active.includes(:promotion_rules)
45
45
  end
46
46
 
47
47
  def promotion_code(promotion)
@@ -95,8 +95,7 @@ module Spree
95
95
  discount ||= order.adjustments.promotion.detect(&detector)
96
96
 
97
97
  if discount && discount.eligible
98
- order.update_totals
99
- order.persist_totals
98
+ order.update!
100
99
  set_success_code :coupon_code_applied
101
100
  elsif order.promotions.with_coupon_code(order.coupon_code)
102
101
  # if the promotion exists on an order, but wasn't found above,
@@ -10,38 +10,49 @@ module Spree
10
10
  end
11
11
 
12
12
  def activate
13
- promotions.each do |promotion|
14
- if order_promotion = existing_order_promotion(promotion)
15
- promotion.activate(
16
- order: order,
17
- promotion_code: order_promotion.promotion_code,
18
- )
19
- elsif promotion.apply_automatically?
20
- promotion.activate(order: order)
21
- end
13
+ connected_promotions.each do |order_promotion|
14
+ order_promotion.promotion.activate(
15
+ order: order,
16
+ promotion_code: order_promotion.promotion_code,
17
+ )
18
+ end
19
+
20
+ not_connected_automatic_promotions.each do |promotion|
21
+ promotion.activate(order: order)
22
22
  end
23
23
  end
24
24
 
25
25
  private
26
26
 
27
- def promotions
28
- Spree::Promotion.
27
+ def not_connected_automatic_promotions
28
+ automatic_promotions - connected_promotions.map(&:promotion)
29
+ end
30
+
31
+ def automatic_promotions
32
+ @automatic_promotions ||= active_free_shipping_promotions.
33
+ where(apply_automatically: true).
34
+ to_a.
35
+ uniq
36
+ end
37
+
38
+ def connected_promotions
39
+ @connected_promotions ||= order.order_promotions.
40
+ joins(:promotion).
41
+ includes(:promotion).
42
+ merge(active_free_shipping_promotions).
43
+ to_a.
44
+ uniq
45
+ end
46
+
47
+ def active_free_shipping_promotions
48
+ Spree::Promotion.all.
29
49
  active.
30
50
  joins(:promotion_actions).
31
51
  merge(
32
52
  Spree::PromotionAction.of_type(
33
53
  Spree::Promotion::Actions::FreeShipping
34
54
  )
35
- ).
36
- distinct
37
- end
38
-
39
- def existing_order_promotion(promotion)
40
- @lookup ||= order.order_promotions.map do |order_promotion|
41
- [order_promotion.promotion_id, order_promotion]
42
- end.to_h
43
-
44
- @lookup[promotion.id]
55
+ )
45
56
  end
46
57
  end
47
58
  end
@@ -17,7 +17,7 @@ module Spree
17
17
  private
18
18
 
19
19
  def promotion
20
- @promotion ||= Promotion.active.find_by(path: path)
20
+ @promotion ||= Spree::Promotion.active.find_by(path: path)
21
21
  end
22
22
  end
23
23
  end
@@ -38,7 +38,7 @@ module Spree
38
38
  # Refund.total_amount_reimbursed_for(reimbursement)
39
39
  # See the `reimbursement_generator` property regarding the generation of custom reimbursements.
40
40
  class_attribute :reimbursement_models
41
- self.reimbursement_models = [Refund, Reimbursement::Credit]
41
+ self.reimbursement_models = [Spree::Refund, Spree::Reimbursement::Credit]
42
42
 
43
43
  # The reimbursement_performer property should be set to an object that responds to the following methods:
44
44
  # - #perform
@@ -13,8 +13,6 @@ module Spree
13
13
 
14
14
  before_create :generate_number
15
15
 
16
- after_save :generate_expedited_exchange_reimbursements
17
-
18
16
  accepts_nested_attributes_for :return_items, allow_destroy: true
19
17
 
20
18
  validates :order, presence: true
@@ -22,11 +20,6 @@ module Spree
22
20
  validate :must_have_shipped_units, on: :create
23
21
  validate :no_previously_exchanged_inventory_units, on: :create
24
22
 
25
- # These are called prior to generating expedited exchanges shipments.
26
- # Should respond to a "call" method that takes the list of return items
27
- class_attribute :pre_expedited_exchange_hooks
28
- self.pre_expedited_exchange_hooks = []
29
-
30
23
  state_machine initial: :authorized do
31
24
  before_transition to: :canceled, do: :cancel_return_items
32
25
 
@@ -88,26 +81,5 @@ module Spree
88
81
  def cancel_return_items
89
82
  return_items.each { |item| item.cancel! if item.can_cancel? }
90
83
  end
91
-
92
- def generate_expedited_exchange_reimbursements
93
- return unless Spree::Config[:expedited_exchanges]
94
-
95
- items_to_exchange = return_items.select(&:exchange_required?)
96
- items_to_exchange.each(&:attempt_accept)
97
- items_to_exchange.select!(&:accepted?)
98
-
99
- return if items_to_exchange.blank?
100
-
101
- pre_expedited_exchange_hooks.each { |h| h.call items_to_exchange }
102
-
103
- reimbursement = Reimbursement.new(return_items: items_to_exchange, order: order)
104
-
105
- if reimbursement.save
106
- reimbursement.perform!
107
- else
108
- errors.add(:base, reimbursement.errors.full_messages)
109
- raise ActiveRecord::RecordInvalid.new(self)
110
- end
111
- end
112
84
  end
113
85
  end
@@ -124,7 +124,7 @@ module Spree
124
124
  transition to: :manual_intervention_required, from: [:accepted, :pending, :manual_intervention_required]
125
125
  end
126
126
 
127
- after_transition any => any, :do => :persist_acceptance_status_errors
127
+ after_transition any => any, do: :persist_acceptance_status_errors
128
128
  end
129
129
 
130
130
  # @param inventory_unit [Spree::InventoryUnit] the inventory for which we
@@ -72,7 +72,7 @@ module Spree
72
72
  self.whitelisted_ransackable_associations = ['order']
73
73
  self.whitelisted_ransackable_attributes = ['number']
74
74
 
75
- delegate :tax_category, to: :selected_shipping_rate, allow_nil: true
75
+ delegate :tax_category, :tax_category_id, to: :selected_shipping_rate, allow_nil: true
76
76
 
77
77
  def can_transition_from_pending_to_shipped?
78
78
  !requires_shipment?
@@ -136,9 +136,9 @@ module Spree
136
136
  def finalize!
137
137
  transaction do
138
138
  pending_units = inventory_units.select(&:pending?)
139
- pending_manifest = ShippingManifest.new(inventory_units: pending_units)
139
+ pending_manifest = Spree::ShippingManifest.new(inventory_units: pending_units)
140
140
  pending_manifest.items.each { |item| manifest_unstock(item) }
141
- InventoryUnit.finalize_units!(pending_units)
141
+ Spree::InventoryUnit.finalize_units!(pending_units)
142
142
  end
143
143
  end
144
144
 
@@ -391,7 +391,7 @@ module Spree
391
391
  end
392
392
 
393
393
  def ensure_can_destroy
394
- unless pending?
394
+ if shipped? || canceled?
395
395
  errors.add(:state, :cannot_destroy, state: state)
396
396
  throw :abort
397
397
  end
@@ -34,7 +34,7 @@ module Spree
34
34
  # Some extra care is needed with the having clause to ensure we are
35
35
  # counting distinct records of the join table. Otherwise a join could
36
36
  # cause this to return incorrect results.
37
- join_table = ShippingMethodCategory.arel_table
37
+ join_table = Spree::ShippingMethodCategory.arel_table
38
38
  having = join_table[:id].count(true).eq(shipping_category_ids.count)
39
39
  joins(:shipping_method_categories).
40
40
  where(spree_shipping_method_categories: { shipping_category_id: shipping_category_ids }).
@@ -46,7 +46,7 @@ module Spree
46
46
  # @return [ActiveRecord::Relation] shipping methods which are available
47
47
  # with the stock location or are marked available_to_all
48
48
  def self.available_in_stock_location(stock_location)
49
- smsl_table = ShippingMethodStockLocation.arel_table
49
+ smsl_table = Spree::ShippingMethodStockLocation.arel_table
50
50
 
51
51
  # We are searching for either a matching entry in the stock location join
52
52
  # table or available_to_all being true.
@@ -12,7 +12,7 @@ module Spree
12
12
  dependent: :destroy
13
13
 
14
14
  delegate :order, :currency, to: :shipment
15
- delegate :name, :tax_category, to: :shipping_method
15
+ delegate :name, :tax_category, :tax_category_id, to: :shipping_method
16
16
  delegate :code, to: :shipping_method, prefix: true
17
17
  alias_attribute :amount, :cost
18
18