solidus_core 2.1.1 → 2.2.0.beta1
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.
- checksums.yaml +4 -4
- data/Rakefile +0 -1
- data/app/assets/config/solidus_core_manifest.js +1 -0
- data/app/assets/javascripts/spree.js.erb +72 -0
- data/app/helpers/spree/store_helper.rb +5 -0
- data/app/jobs/spree/promotion_code_batch_job.rb +24 -0
- data/app/mailers/spree/promotion_code_batch_mailer.rb +13 -0
- data/app/models/concerns/spree/calculated_adjustments.rb +1 -1
- data/app/models/concerns/spree/ordered_property_value_list.rb +2 -2
- data/app/models/concerns/spree/user_address_book.rb +4 -4
- data/app/models/concerns/spree/user_methods.rb +7 -0
- data/app/models/concerns/spree/user_payment_source.rb +12 -5
- data/app/models/spree/address.rb +14 -3
- data/app/models/spree/adjustment.rb +13 -1
- data/app/models/spree/app_configuration.rb +0 -19
- data/app/models/spree/base.rb +2 -0
- data/app/models/spree/credit_card.rb +34 -43
- data/app/models/spree/gateway/bogus.rb +1 -1
- data/app/models/spree/gateway.rb +6 -4
- data/app/models/spree/inventory_unit.rb +3 -2
- data/app/models/spree/order/checkout.rb +187 -273
- data/app/models/spree/order.rb +137 -71
- data/app/models/spree/order_contents.rb +1 -1
- data/app/models/spree/order_inventory.rb +11 -11
- data/app/models/spree/order_promotion.rb +2 -0
- data/app/models/spree/order_update_attributes.rb +1 -8
- data/app/models/spree/order_updater.rb +67 -63
- data/app/models/spree/payment.rb +0 -1
- data/app/models/spree/payment_create.rb +27 -7
- data/app/models/spree/payment_method/store_credit.rb +3 -3
- data/app/models/spree/payment_method.rb +4 -1
- data/app/models/spree/payment_source.rb +45 -0
- data/app/models/spree/product/scopes.rb +24 -24
- data/app/models/spree/product.rb +4 -4
- data/app/models/spree/promotion.rb +2 -0
- data/app/models/spree/promotion_code/batch_builder.rb +63 -0
- data/app/models/spree/promotion_code.rb +1 -0
- data/app/models/spree/promotion_code_batch.rb +25 -0
- data/app/models/spree/promotion_handler/cart.rb +2 -2
- data/app/models/spree/promotion_handler/coupon.rb +1 -2
- data/app/models/spree/promotion_handler/free_shipping.rb +32 -21
- data/app/models/spree/promotion_handler/page.rb +1 -1
- data/app/models/spree/reimbursement.rb +1 -1
- data/app/models/spree/return_authorization.rb +0 -28
- data/app/models/spree/return_item.rb +1 -1
- data/app/models/spree/shipment.rb +4 -4
- data/app/models/spree/shipping_method.rb +2 -2
- data/app/models/spree/shipping_rate.rb +1 -1
- data/app/models/spree/stock/availability_validator.rb +16 -17
- data/app/models/spree/stock/coordinator.rb +3 -3
- data/app/models/spree/stock/package.rb +1 -1
- data/app/models/spree/stock/quantifier.rb +5 -4
- data/app/models/spree/stock_location.rb +2 -2
- data/app/models/spree/store.rb +2 -2
- data/app/models/spree/store_credit.rb +1 -1
- data/app/models/spree/tax/tax_helpers.rb +3 -3
- data/app/models/spree/tax_rate.rb +7 -1
- data/app/models/spree/taxonomy.rb +1 -1
- data/app/models/spree/variant/scopes.rb +5 -5
- data/app/models/spree/variant/vat_price_generator.rb +8 -5
- data/app/models/spree/variant.rb +1 -0
- data/app/models/spree/wallet/add_payment_sources_to_wallet.rb +19 -10
- data/app/models/spree/wallet/default_payment_builder.rb +6 -6
- data/app/models/spree/wallet.rb +71 -0
- data/app/models/spree/wallet_payment_source.rb +17 -0
- data/app/models/spree/zone.rb +1 -1
- data/app/views/spree/carton_mailer/shipped_email.text.erb +1 -1
- data/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb +2 -0
- data/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb +2 -0
- data/app/views/spree/reimbursement_mailer/reimbursement_email.html.erb +0 -7
- data/app/views/spree/reimbursement_mailer/reimbursement_email.text.erb +0 -5
- data/app/views/spree/shared/_error_messages.html.erb +1 -1
- data/app/views/spree/shipment_mailer/shipped_email.html.erb +1 -1
- data/config/initializers/assets.rb +1 -1
- data/config/initializers/friendly_id.rb +1 -1
- data/config/locales/en.yml +50 -12
- data/db/default/spree/store_credit.rb +2 -1
- data/db/migrate/20130826062534_add_depth_to_spree_taxons.rb +4 -6
- data/db/migrate/20160420044191_create_spree_wallet_payment_sources.rb +23 -0
- data/db/migrate/20160420181916_migrate_credit_cards_to_wallet_payment_sources.rb +26 -0
- data/db/migrate/20161017102621_create_spree_promotion_code_batch.rb +36 -0
- data/db/migrate/20161129035810_add_index_to_spree_payments_number.rb +5 -0
- data/db/migrate/20170223235001_remove_spree_store_credits_column.rb +5 -0
- data/lib/generators/spree/dummy/templates/rails/application.rb +1 -1
- data/lib/generators/spree/dummy/templates/rails/test.rb +1 -1
- data/lib/generators/spree/install/install_generator.rb +6 -5
- data/lib/spree/core/controller_helpers/payment_parameters.rb +54 -0
- data/lib/spree/core/engine.rb +6 -9
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +0 -1
- data/lib/spree/money.rb +18 -0
- data/lib/spree/permission_sets/default_customer.rb +1 -1
- data/lib/spree/permitted_attributes.rb +1 -1
- data/lib/spree/testing_support/authorization_helpers.rb +1 -0
- data/lib/spree/testing_support/capybara_ext.rb +13 -0
- data/lib/spree/testing_support/factories/order_factory.rb +5 -1
- data/lib/spree/testing_support/factories/payment_factory.rb +1 -1
- data/lib/spree/testing_support/factories/shipment_factory.rb +0 -1
- data/solidus_core.gemspec +3 -3
- data/spec/jobs/promotion_code_batch_job_spec.rb +65 -0
- data/spec/lib/calculated_adjustments_spec.rb +105 -1
- data/spec/lib/spree/core/testing_support/factories/order_factory_spec.rb +4 -1
- data/spec/lib/spree/core/testing_support/factories/payment_factory_spec.rb +8 -0
- data/spec/lib/spree/money_spec.rb +32 -0
- data/spec/lib/spree/permission_sets/default_customer_spec.rb +20 -0
- data/spec/mailers/promotion_code_batch_mailer_spec.rb +45 -0
- data/spec/models/spree/credit_card_spec.rb +86 -86
- data/spec/models/spree/gateway_spec.rb +3 -1
- data/spec/models/spree/inventory_unit_spec.rb +12 -4
- data/spec/models/spree/order/checkout_spec.rb +11 -32
- data/spec/models/spree/order/tax_spec.rb +2 -2
- data/spec/models/spree/order_contents_spec.rb +24 -1
- data/spec/models/spree/order_inventory_spec.rb +130 -83
- data/spec/models/spree/order_spec.rb +15 -117
- data/spec/models/spree/order_update_attributes_spec.rb +1 -44
- data/spec/models/spree/order_updater_spec.rb +10 -13
- data/spec/models/spree/payment_create_spec.rb +5 -1
- data/spec/models/spree/payment_method_spec.rb +16 -0
- data/spec/models/spree/payment_spec.rb +14 -8
- data/spec/models/spree/promotion_code/batch_builder_spec.rb +61 -0
- data/spec/models/spree/promotion_code_batch_spec.rb +58 -0
- data/spec/models/spree/promotion_code_spec.rb +4 -0
- data/spec/models/spree/promotion_spec.rb +3 -6
- data/spec/models/spree/return_authorization_spec.rb +0 -59
- data/spec/models/spree/shipment_spec.rb +4 -4
- data/spec/models/spree/stock/availability_validator_spec.rb +64 -9
- data/spec/models/spree/tax/item_adjuster_spec.rb +1 -2
- data/spec/models/spree/unit_cancel_spec.rb +0 -85
- data/spec/models/spree/user_spec.rb +3 -1
- data/spec/models/spree/variant/vat_price_generator_spec.rb +8 -2
- data/spec/models/spree/variant_spec.rb +16 -4
- data/spec/models/spree/wallet_payment_source_spec.rb +46 -0
- data/spec/models/spree/wallet_spec.rb +128 -0
- data/spec/support/concerns/payment_source.rb +64 -0
- metadata +51 -25
- data/app/assets/javascripts/spree.js.coffee.erb +0 -64
- data/app/models/spree/promotion_builder.rb +0 -55
- data/app/models/spree/promotion_code/code_builder.rb +0 -62
- data/config/initializers/premailer_assets.rb +0 -1
- data/lib/spree/core/unreturned_item_charger.rb +0 -106
- data/lib/tasks/exchanges.rake +0 -47
- data/spec/lib/spree/core/unreturned_item_charger_spec.rb +0 -126
- data/spec/lib/tasks/exchanges_spec.rb +0 -220
- data/spec/models/spree/promotion_builder_spec.rb +0 -120
- 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.
|
|
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
|
-
|
|
76
|
+
payment_source.verification_value = source_attributes[:verification_value]
|
|
57
77
|
end
|
|
58
78
|
|
|
59
|
-
payment.source =
|
|
60
|
-
payment.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, ->
|
|
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 => { :
|
|
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(:
|
|
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
|
-
# :
|
|
151
|
-
# :
|
|
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
|
data/app/models/spree/product.rb
CHANGED
|
@@ -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,
|
|
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
|
|
@@ -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.
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
28
|
-
|
|
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
|
|
@@ -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, :
|
|
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
|
-
|
|
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
|
|