spree_core 3.4.6 → 3.5.0.rc1
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 +5 -5
- data/app/assets/javascripts/spree.js.coffee +1 -1
- data/app/helpers/spree/base_helper.rb +4 -0
- data/app/models/concerns/spree/named_type.rb +1 -1
- data/app/models/concerns/spree/user_methods.rb +21 -4
- data/app/models/concerns/spree/user_reporting.rb +2 -2
- data/app/models/spree/address.rb +6 -12
- data/app/models/spree/adjustable/adjustments_updater.rb +2 -1
- data/app/models/spree/country.rb +2 -1
- data/app/models/spree/line_item.rb +8 -2
- data/app/models/spree/log_entry.rb +1 -1
- data/app/models/spree/order.rb +8 -6
- data/app/models/spree/order/checkout.rb +1 -0
- data/app/models/spree/order_contents.rb +20 -12
- data/app/models/spree/order_inventory.rb +24 -12
- data/app/models/spree/payment/processing.rb +2 -2
- data/app/models/spree/preferences/preferable.rb +1 -1
- data/app/models/spree/product/scopes.rb +1 -1
- data/app/models/spree/promotion.rb +15 -1
- data/app/models/spree/promotion/rules/option_value.rb +13 -5
- data/app/models/spree/promotion/rules/product.rb +2 -1
- data/app/models/spree/promotion/rules/taxon.rb +3 -1
- data/app/models/spree/promotion_action_line_item.rb +3 -0
- data/app/models/spree/promotion_handler/promotion_duplicator.rb +52 -0
- data/app/models/spree/refund.rb +1 -1
- data/app/models/spree/reimbursement.rb +1 -1
- data/app/models/spree/reimbursement/reimbursement_type_engine.rb +7 -18
- data/app/models/spree/reimbursement_performer.rb +3 -7
- data/app/models/spree/reimbursement_type/original_payment.rb +2 -2
- data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +3 -7
- data/app/models/spree/reimbursement_type/store_credit.rb +2 -10
- data/app/models/spree/shipment.rb +10 -4
- data/app/models/spree/stock/availability_validator.rb +1 -1
- data/app/models/spree/stock/packer.rb +1 -1
- data/app/models/spree/stock/splitter/backordered.rb +5 -7
- data/app/models/spree/stock/splitter/base.rb +1 -0
- data/app/models/spree/stock/splitter/shipping_category.rb +9 -16
- data/app/models/spree/stock/splitter/weight.rb +18 -20
- data/app/models/spree/stock_transfer.rb +2 -1
- data/app/models/spree/store_credit_category.rb +13 -0
- data/app/models/spree/taxon.rb +7 -0
- data/app/models/spree/variant.rb +1 -1
- data/app/validators/email_validator.rb +7 -0
- data/config/locales/en.yml +18 -27
- data/db/default/spree/states.rb +9 -27
- data/db/migrate/20150128032538_remove_environment_from_tracker.rb +2 -0
- data/db/migrate/20171004223836_remove_icon_from_taxons.rb +8 -0
- data/db/migrate/20180222133746_add_unique_index_on_spree_promotions_code.rb +6 -0
- data/lib/generators/spree/dummy_model/dummy_model_generator.rb +23 -0
- data/lib/generators/spree/dummy_model/templates/migration.rb.tt +10 -0
- data/lib/generators/spree/dummy_model/templates/model.rb.tt +6 -0
- data/lib/spree/core/controller_helpers/auth.rb +1 -1
- data/lib/spree/core/controller_helpers/common.rb +4 -0
- data/lib/spree/core/controller_helpers/order.rb +6 -5
- data/lib/spree/core/engine.rb +10 -10
- data/lib/spree/core/environment_extension.rb +3 -0
- data/lib/spree/core/importer/order.rb +1 -1
- data/lib/spree/core/validators/email.rb +1 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/money.rb +1 -5
- data/lib/spree/permitted_attributes.rb +1 -1
- data/lib/spree/testing_support/capybara_ext.rb +16 -13
- data/lib/spree/testing_support/common_rake.rb +4 -1
- data/lib/spree/testing_support/factories/inventory_unit_factory.rb +7 -0
- data/lib/spree/testing_support/factories/taxon_factory.rb +1 -1
- data/spree_core.gemspec +1 -1
- data/vendor/assets/javascripts/jsuri.js +458 -2
- metadata +13 -7
- data/app/models/spree/tracker.rb +0 -25
- data/lib/spree/testing_support/factories/tracker_factory.rb +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2114cd601e774d50896839767298308294adc60e
|
4
|
+
data.tar.gz: be32dd8365af3332edbf8b878493bb6787b66f8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e95baff0a25bbff9bd76cb6952303234735a063f32f6c1df521fe75f7eb8cea28b5fff67bc92da889051b70530798051afaa08c4bfdbea025fa659b2024d0149
|
7
|
+
data.tar.gz: 44cdda9fbf073099a0b588170ccaa2f49e35065d58c582826657d74d4e1b4b65a7fc3df4b2d6e650da5669bc9790e57b836766b91e73ee3b5f3c10daf6b5d1db
|
@@ -4,7 +4,7 @@ module Spree
|
|
4
4
|
|
5
5
|
included do
|
6
6
|
scope :active, -> { where(active: true) }
|
7
|
-
default_scope { order("LOWER(#{table_name}.name)") }
|
7
|
+
default_scope { order(Arel.sql("LOWER(#{table_name}.name)")) }
|
8
8
|
|
9
9
|
validates :name, presence: true, uniqueness: { case_sensitive: false }
|
10
10
|
end
|
@@ -9,7 +9,11 @@ module Spree
|
|
9
9
|
included do
|
10
10
|
# we need to have this callback before any dependent: :destroy associations
|
11
11
|
# https://github.com/rails/rails/issues/3458
|
12
|
+
before_validation :clone_billing_address, if: :use_billing?
|
12
13
|
before_destroy :check_completed_orders
|
14
|
+
after_destroy :nullify_approver_id_in_approved_orders
|
15
|
+
|
16
|
+
attr_accessor :use_billing
|
13
17
|
|
14
18
|
has_many :role_users, class_name: 'Spree::RoleUser', foreign_key: :user_id, dependent: :destroy
|
15
19
|
has_many :spree_roles, through: :role_users, class_name: 'Spree::Role', source: :role
|
@@ -39,10 +43,6 @@ module Spree
|
|
39
43
|
first
|
40
44
|
end
|
41
45
|
|
42
|
-
def analytics_id
|
43
|
-
id
|
44
|
-
end
|
45
|
-
|
46
46
|
def total_available_store_credit
|
47
47
|
store_credits.reload.to_a.sum(&:amount_remaining)
|
48
48
|
end
|
@@ -52,5 +52,22 @@ module Spree
|
|
52
52
|
def check_completed_orders
|
53
53
|
raise Spree::Core::DestroyWithOrdersError if orders.complete.present?
|
54
54
|
end
|
55
|
+
|
56
|
+
def nullify_approver_id_in_approved_orders
|
57
|
+
Spree::Order.where(approver_id: id).update_all(approver_id: nil)
|
58
|
+
end
|
59
|
+
|
60
|
+
def clone_billing_address
|
61
|
+
if bill_address && ship_address.nil?
|
62
|
+
self.ship_address = bill_address.clone
|
63
|
+
else
|
64
|
+
ship_address.attributes = bill_address.attributes.except('id', 'updated_at', 'created_at')
|
65
|
+
end
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
def use_billing?
|
70
|
+
use_billing.in?([true, 'true', '1'])
|
71
|
+
end
|
55
72
|
end
|
56
73
|
end
|
data/app/models/spree/address.rb
CHANGED
@@ -10,6 +10,10 @@ module Spree
|
|
10
10
|
'TO', 'TV', 'UG', 'AE', 'VU', 'YE', 'ZW'
|
11
11
|
].freeze
|
12
12
|
|
13
|
+
# we're not freezing this on purpose so developers can extend and manage
|
14
|
+
# those attributes depending of the logic of their applications
|
15
|
+
EXCLUDED_KEYS_FOR_COMPARISION = %w(id updated_at created_at)
|
16
|
+
|
13
17
|
belongs_to :country, class_name: 'Spree::Country'
|
14
18
|
belongs_to :state, class_name: 'Spree::State', optional: true
|
15
19
|
|
@@ -31,12 +35,7 @@ module Spree
|
|
31
35
|
self.whitelisted_ransackable_attributes = %w[firstname lastname company]
|
32
36
|
|
33
37
|
def self.build_default
|
34
|
-
country
|
35
|
-
Spree::Country.find(Spree::Config[:default_country_id])
|
36
|
-
rescue
|
37
|
-
Spree::Country.first
|
38
|
-
end
|
39
|
-
new(country: country)
|
38
|
+
new(country: Spree::Country.default)
|
40
39
|
end
|
41
40
|
|
42
41
|
def self.default(user = nil, kind = 'bill')
|
@@ -47,11 +46,6 @@ module Spree
|
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
50
|
-
# Can modify an address if it's not been used in an order (but checkouts controller has finer control)
|
51
|
-
# def editable?
|
52
|
-
# new_record? || (shipments.empty? && checkouts.empty?)
|
53
|
-
# end
|
54
|
-
|
55
49
|
def full_name
|
56
50
|
"#{firstname} #{lastname}".strip
|
57
51
|
end
|
@@ -62,7 +56,7 @@ module Spree
|
|
62
56
|
|
63
57
|
def same_as?(other)
|
64
58
|
return false if other.nil?
|
65
|
-
attributes.except(
|
59
|
+
attributes.except(*EXCLUDED_KEYS_FOR_COMPARISION) == other.attributes.except(*EXCLUDED_KEYS_FOR_COMPARISION)
|
66
60
|
end
|
67
61
|
|
68
62
|
alias same_as same_as?
|
@@ -7,10 +7,11 @@ module Spree
|
|
7
7
|
|
8
8
|
def initialize(adjustable)
|
9
9
|
@adjustable = adjustable
|
10
|
-
adjustable.reload if shipment? && adjustable.persisted?
|
10
|
+
adjustable.reload if shipment? && adjustable && adjustable.persisted?
|
11
11
|
end
|
12
12
|
|
13
13
|
def update
|
14
|
+
return unless @adjustable
|
14
15
|
return unless @adjustable.persisted?
|
15
16
|
|
16
17
|
totals = {
|
data/app/models/spree/country.rb
CHANGED
@@ -18,7 +18,8 @@ module Spree
|
|
18
18
|
|
19
19
|
def self.default
|
20
20
|
country_id = Spree::Config[:default_country_id]
|
21
|
-
|
21
|
+
default = find_by(id: country_id) if country_id.present?
|
22
|
+
default || find_by(iso: 'US') || first
|
22
23
|
end
|
23
24
|
|
24
25
|
def <=>(other)
|
@@ -23,7 +23,7 @@ module Spree
|
|
23
23
|
validates_with Stock::AvailabilityValidator
|
24
24
|
validate :ensure_proper_currency, if: -> { order.present? }
|
25
25
|
|
26
|
-
before_destroy :
|
26
|
+
before_destroy :verify_order_inventory_before_destroy, if: -> { order.has_checkout_step?('delivery') }
|
27
27
|
|
28
28
|
before_destroy :destroy_inventory_units
|
29
29
|
|
@@ -116,7 +116,9 @@ module Spree
|
|
116
116
|
def update_price_from_modifier(currency, opts)
|
117
117
|
if currency
|
118
118
|
self.currency = currency
|
119
|
-
|
119
|
+
# variant.price_in(currency).amount can be nil if
|
120
|
+
# there's no price for this currency
|
121
|
+
self.price = (variant.price_in(currency).amount || 0) +
|
120
122
|
variant.price_modifier_amount_in(currency, opts)
|
121
123
|
else
|
122
124
|
self.price = variant.price +
|
@@ -131,6 +133,10 @@ module Spree
|
|
131
133
|
end
|
132
134
|
|
133
135
|
def verify_order_inventory
|
136
|
+
Spree::OrderInventory.new(order, self).verify(target_shipment, is_updated: true)
|
137
|
+
end
|
138
|
+
|
139
|
+
def verify_order_inventory_before_destroy
|
134
140
|
Spree::OrderInventory.new(order, self).verify(target_shipment)
|
135
141
|
end
|
136
142
|
|
@@ -4,7 +4,7 @@ module Spree
|
|
4
4
|
|
5
5
|
# Fix for #1767
|
6
6
|
# If a payment fails, we want to make sure we keep the record of it failing
|
7
|
-
after_rollback :save_anyway
|
7
|
+
after_rollback :save_anyway, if: proc { !Rails.env.test? }
|
8
8
|
|
9
9
|
def save_anyway
|
10
10
|
Spree::LogEntry.create!(source: source, details: details)
|
data/app/models/spree/order.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'spree/core/validators/email'
|
2
1
|
require 'spree/order/checkout'
|
3
2
|
|
4
3
|
module Spree
|
@@ -51,7 +50,7 @@ module Spree
|
|
51
50
|
remove_transition from: :delivery, to: :confirm
|
52
51
|
end
|
53
52
|
|
54
|
-
self.whitelisted_ransackable_associations = %w[shipments user promotions bill_address ship_address line_items]
|
53
|
+
self.whitelisted_ransackable_associations = %w[shipments user promotions bill_address ship_address line_items store]
|
55
54
|
self.whitelisted_ransackable_attributes = %w[completed_at email number state payment_state shipment_state total considered_risky]
|
56
55
|
|
57
56
|
attr_reader :coupon_code
|
@@ -155,7 +154,7 @@ module Spree
|
|
155
154
|
scope :incomplete, -> { where(completed_at: nil) }
|
156
155
|
|
157
156
|
# shows completed orders first, by their completed_at date, then uncompleted orders by their created_at
|
158
|
-
scope :reverse_chronological, -> { order('spree_orders.completed_at IS NULL', completed_at: :desc, created_at: :desc) }
|
157
|
+
scope :reverse_chronological, -> { order(Arel.sql('spree_orders.completed_at IS NULL'), completed_at: :desc, created_at: :desc) }
|
159
158
|
|
160
159
|
# Use this method in other gems that wish to register their own custom logic
|
161
160
|
# that should be called after Order#update
|
@@ -314,6 +313,10 @@ module Spree
|
|
314
313
|
Spree::TaxRate.adjust(self, shipments) if shipments.any?
|
315
314
|
end
|
316
315
|
|
316
|
+
def create_shipment_tax_charge!
|
317
|
+
Spree::TaxRate.adjust(self, shipments) if shipments.any?
|
318
|
+
end
|
319
|
+
|
317
320
|
def update_line_item_prices!
|
318
321
|
transaction do
|
319
322
|
line_items.each(&:update_price)
|
@@ -500,9 +503,8 @@ module Spree
|
|
500
503
|
|
501
504
|
def apply_free_shipping_promotions
|
502
505
|
Spree::PromotionHandler::FreeShipping.new(self).activate
|
503
|
-
shipments.each { |shipment| Adjustable::AdjustmentsUpdater.update(shipment) }
|
504
|
-
|
505
|
-
persist_totals
|
506
|
+
shipments.each { |shipment| Spree::Adjustable::AdjustmentsUpdater.update(shipment) }
|
507
|
+
update_with_updater!
|
506
508
|
end
|
507
509
|
|
508
510
|
# Clean shipments and make order back to address state
|
@@ -100,6 +100,7 @@ module Spree
|
|
100
100
|
before_transition to: :delivery, do: :create_proposed_shipments
|
101
101
|
before_transition to: :delivery, do: :ensure_available_shipping_rates
|
102
102
|
before_transition to: :delivery, do: :set_shipments_cost
|
103
|
+
before_transition to: :delivery, do: :create_shipment_tax_charge!
|
103
104
|
before_transition from: :delivery, do: :apply_free_shipping_promotions
|
104
105
|
end
|
105
106
|
|
@@ -8,19 +8,25 @@ module Spree
|
|
8
8
|
|
9
9
|
def add(variant, quantity = 1, options = {})
|
10
10
|
timestamp = Time.current
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
ActiveRecord::Base.transaction do
|
12
|
+
line_item = add_to_line_item(variant, quantity, options)
|
13
|
+
options[:line_item_created] = true if timestamp <= line_item.created_at
|
14
|
+
after_add_or_remove(line_item, options)
|
15
|
+
end
|
14
16
|
end
|
15
17
|
|
16
18
|
def remove(variant, quantity = 1, options = {})
|
17
|
-
|
18
|
-
|
19
|
+
ActiveRecord::Base.transaction do
|
20
|
+
line_item = remove_from_line_item(variant, quantity, options)
|
21
|
+
after_add_or_remove(line_item, options)
|
22
|
+
end
|
19
23
|
end
|
20
24
|
|
21
25
|
def remove_line_item(line_item, options = {})
|
22
|
-
|
23
|
-
|
26
|
+
ActiveRecord::Base.transaction do
|
27
|
+
line_item.destroy!
|
28
|
+
after_add_or_remove(line_item, options)
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
def update_cart(params)
|
@@ -29,11 +35,13 @@ module Spree
|
|
29
35
|
# Update totals, then check if the order is eligible for any cart promotions.
|
30
36
|
# If we do not update first, then the item total will be wrong and ItemTotal
|
31
37
|
# promotion rules would not be triggered.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
38
|
+
ActiveRecord::Base.transaction do
|
39
|
+
persist_totals
|
40
|
+
PromotionHandler::Cart.new(order).activate
|
41
|
+
order.ensure_updated_shipments
|
42
|
+
order.payments.store_credits.checkout.destroy_all
|
43
|
+
persist_totals
|
44
|
+
end
|
37
45
|
true
|
38
46
|
else
|
39
47
|
false
|
@@ -17,17 +17,19 @@ module Spree
|
|
17
17
|
# In case shipment is passed the stock location should only unstock or
|
18
18
|
# restock items if the order is completed. That is so because stock items
|
19
19
|
# are always unstocked when the order is completed through +shipment.finalize+
|
20
|
-
def verify(shipment = nil)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
20
|
+
def verify(shipment = nil, is_updated: false)
|
21
|
+
return unless order.completed? || shipment.present?
|
22
|
+
|
23
|
+
units_count = inventory_units.reload.sum(&:quantity)
|
24
|
+
line_item_changed = is_updated ? !line_item.saved_changes? : !line_item.changed?
|
25
|
+
|
26
|
+
if units_count < line_item.quantity
|
27
|
+
quantity = line_item.quantity - units_count
|
28
|
+
|
29
|
+
shipment = determine_target_shipment unless shipment
|
30
|
+
add_to_shipment(shipment, quantity)
|
31
|
+
elsif (units_count > line_item.quantity) || (units_count == line_item.quantity && line_item_changed)
|
32
|
+
remove(units_count, shipment)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -92,6 +94,7 @@ module Spree
|
|
92
94
|
shipment_units = shipment.inventory_units_for_item(line_item, variant).reject(&:shipped?).sort_by(&:state)
|
93
95
|
|
94
96
|
removed_quantity = 0
|
97
|
+
removed_backordered = 0
|
95
98
|
|
96
99
|
shipment_units.each do |inventory_unit|
|
97
100
|
inventory_unit.quantity.times do
|
@@ -101,6 +104,7 @@ module Spree
|
|
101
104
|
else
|
102
105
|
inventory_unit.destroy
|
103
106
|
end
|
107
|
+
removed_backordered += 1 if inventory_unit.backordered?
|
104
108
|
removed_quantity += 1
|
105
109
|
end
|
106
110
|
inventory_unit.save! if inventory_unit.persisted?
|
@@ -110,7 +114,15 @@ module Spree
|
|
110
114
|
|
111
115
|
# removing this from shipment, and adding to stock_location
|
112
116
|
if order.completed?
|
113
|
-
shipment.stock_location.
|
117
|
+
current_on_hand = shipment.stock_location.count_on_hand(variant)
|
118
|
+
|
119
|
+
if current_on_hand.negative? && current_on_hand.abs < removed_backordered
|
120
|
+
shipment.stock_location.restock_backordered variant, current_on_hand.abs, shipment
|
121
|
+
else
|
122
|
+
shipment.stock_location.restock_backordered variant, removed_backordered, shipment
|
123
|
+
end
|
124
|
+
|
125
|
+
shipment.stock_location.restock variant, removed_quantity - removed_backordered, shipment
|
114
126
|
end
|
115
127
|
|
116
128
|
removed_quantity
|
@@ -29,7 +29,7 @@ module Spree
|
|
29
29
|
# a new pending payment record for the remaining amount to capture later.
|
30
30
|
def capture!(amount = nil)
|
31
31
|
return true if completed?
|
32
|
-
amount ||= money.
|
32
|
+
amount ||= money.money.cents
|
33
33
|
started_processing!
|
34
34
|
protect_from_connection_error do
|
35
35
|
# Standard ActiveMerchant capture usage
|
@@ -114,7 +114,7 @@ module Spree
|
|
114
114
|
|
115
115
|
def gateway_action(source, action, success_state)
|
116
116
|
protect_from_connection_error do
|
117
|
-
response = payment_method.send(action, money.
|
117
|
+
response = payment_method.send(action, money.money.cents,
|
118
118
|
source,
|
119
119
|
gateway_options)
|
120
120
|
handle_response(response, success_state, :failure)
|
@@ -24,7 +24,7 @@
|
|
24
24
|
#
|
25
25
|
# # Typecasting is performed on assignment
|
26
26
|
# s.preferred_temperature = '24'
|
27
|
-
# s.
|
27
|
+
# s.preferred_temperature # => 24
|
28
28
|
#
|
29
29
|
# # Modifications have been made to the .preferences hash
|
30
30
|
# s.preferences #=> {color: 'blue', temperature: 24}
|
@@ -23,7 +23,7 @@ module Spree
|
|
23
23
|
# We should not define price scopes here, as they require something slightly different
|
24
24
|
next if name.to_s.include?('master_price')
|
25
25
|
parts = name.to_s.match(/(.*)_by_(.*)/)
|
26
|
-
scope(name.to_s, -> { order("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? 'ASC' : 'DESC'}") })
|
26
|
+
scope(name.to_s, -> { order(Arel.sql("#{Product.quoted_table_name}.#{parts[2]} #{parts[1] == 'ascend' ? 'ASC' : 'DESC'}")) })
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -3,7 +3,7 @@ module Spree
|
|
3
3
|
MATCH_POLICIES = %w(all any)
|
4
4
|
UNACTIVATABLE_ORDER_STATES = ['complete', 'awaiting_return', 'returned']
|
5
5
|
|
6
|
-
attr_reader :eligibility_errors
|
6
|
+
attr_reader :eligibility_errors, :generate_code
|
7
7
|
|
8
8
|
belongs_to :promotion_category, optional: true
|
9
9
|
|
@@ -54,6 +54,12 @@ module Spree
|
|
54
54
|
order && !UNACTIVATABLE_ORDER_STATES.include?(order.state)
|
55
55
|
end
|
56
56
|
|
57
|
+
def generate_code=(generating_code)
|
58
|
+
if ActiveModel::Type::Boolean.new.cast(generating_code)
|
59
|
+
self.code = random_code
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
57
63
|
def expired?
|
58
64
|
!!(starts_at && Time.current < starts_at || expires_at && Time.current > expires_at)
|
59
65
|
end
|
@@ -227,5 +233,13 @@ module Spree
|
|
227
233
|
def expires_at_must_be_later_than_starts_at
|
228
234
|
errors.add(:expires_at, :invalid_date_range) if expires_at < starts_at
|
229
235
|
end
|
236
|
+
|
237
|
+
def random_code
|
238
|
+
coupon_code = loop do
|
239
|
+
random_token = SecureRandom.hex(4)
|
240
|
+
break random_token unless self.class.exists?(code: random_token)
|
241
|
+
end
|
242
|
+
coupon_code
|
243
|
+
end
|
230
244
|
end
|
231
245
|
end
|