spree_core 5.1.0.beta3 → 5.1.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 +4 -4
- data/app/helpers/spree/base_helper.rb +6 -1
- data/app/helpers/spree/images_helper.rb +19 -16
- data/app/helpers/spree/shipment_helper.rb +12 -0
- data/app/jobs/spree/gift_cards/bulk_generate_job.rb +13 -0
- data/app/models/concerns/spree/parameterizable_name.rb +11 -0
- data/app/models/concerns/spree/payment_source_concern.rb +39 -0
- data/app/models/concerns/spree/product_scopes.rb +1 -5
- data/app/models/concerns/spree/stores/socials.rb +6 -2
- data/app/models/concerns/spree/user_methods.rb +2 -1
- data/app/models/spree/ability.rb +2 -0
- data/app/models/spree/address.rb +11 -3
- data/app/models/spree/credit_card.rb +2 -24
- data/app/models/spree/gateway/custom_payment_source_method.rb +33 -0
- data/app/models/spree/gift_card.rb +162 -0
- data/app/models/spree/gift_card_batch.rb +79 -0
- data/app/models/spree/inventory_unit.rb +11 -4
- data/app/models/spree/line_item.rb +13 -2
- data/app/models/spree/option_type.rb +5 -15
- data/app/models/spree/option_value.rb +18 -12
- data/app/models/spree/order/checkout.rb +2 -0
- data/app/models/spree/order/gift_card.rb +51 -0
- data/app/models/spree/order/store_credit.rb +20 -1
- data/app/models/spree/order.rb +51 -6
- data/app/models/spree/order_merger.rb +12 -0
- data/app/models/spree/page_sections/featured_posts.rb +4 -0
- data/app/models/spree/payment.rb +2 -1
- data/app/models/spree/payment_method/store_credit.rb +1 -1
- data/app/models/spree/payment_method.rb +1 -0
- data/app/models/spree/payment_source.rb +21 -0
- data/app/models/spree/post.rb +1 -0
- data/app/models/spree/price.rb +7 -1
- data/app/models/spree/product/slugs.rb +103 -0
- data/app/models/spree/product.rb +7 -81
- data/app/models/spree/product_property.rb +2 -2
- data/app/models/spree/promotion_handler/coupon.rb +39 -0
- data/app/models/spree/property.rb +2 -8
- data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +1 -1
- data/app/models/spree/return_item.rb +4 -0
- data/app/models/spree/shipment.rb +19 -0
- data/app/models/spree/shipping_method.rb +8 -9
- data/app/models/spree/store.rb +2 -0
- data/app/models/spree/store_credit.rb +9 -6
- data/app/models/spree/store_credit_event.rb +8 -4
- data/app/models/spree/taxon.rb +8 -1
- data/app/models/spree/theme.rb +1 -1
- data/app/models/spree/variant.rb +1 -1
- data/app/models/spree/wishlist.rb +7 -0
- data/app/presenters/spree/csv/product_variant_presenter.rb +14 -3
- data/app/services/spree/addresses/phone_validator.rb +20 -0
- data/app/services/spree/cart/destroy.rb +1 -1
- data/app/services/spree/cart/recalculate.rb +1 -0
- data/app/services/spree/gift_cards/apply.rb +66 -0
- data/app/services/spree/gift_cards/redeem.rb +17 -0
- data/app/services/spree/gift_cards/remove.rb +38 -0
- data/app/services/spree/products/prepare_nested_attributes.rb +9 -2
- data/app/views/spree/addresses/_form.html.erb +1 -1
- data/app/views/spree/shared/_payment.html.erb +13 -4
- data/config/locales/en.yml +44 -14
- data/config/routes.rb +1 -0
- data/db/migrate/20250506073057_create_spree_gift_cards_and_spree_gift_card_batches.rb +37 -0
- data/db/migrate/20250530101236_enable_pg_trgm_extension.rb +13 -0
- data/db/migrate/20250605131334_add_missing_fields_to_users.rb +25 -0
- data/lib/generators/spree/install/install_generator.rb +0 -8
- data/lib/spree/core/configuration.rb +4 -0
- data/lib/spree/core/controller_helpers/store.rb +13 -1
- data/lib/spree/core/dependencies.rb +5 -0
- data/lib/spree/core/engine.rb +8 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +2 -1
- data/lib/spree/permitted_attributes.rb +23 -10
- data/lib/spree/testing_support/authorization_helpers.rb +5 -9
- data/lib/spree/testing_support/common_rake.rb +7 -1
- data/lib/spree/testing_support/factories/gift_card_batch_factory.rb +5 -0
- data/lib/spree/testing_support/factories/gift_card_factory.rb +17 -0
- data/lib/spree/testing_support/factories/page_section_factory.rb +2 -1
- data/lib/spree/testing_support/factories/payment_factory.rb +5 -0
- data/lib/spree/testing_support/factories/payment_method_factory.rb +5 -0
- data/lib/spree/testing_support/factories/payment_source_factory.rb +5 -0
- data/lib/spree/testing_support/factories/post_factory.rb +4 -0
- data/lib/spree/testing_support/factories/promotion_rule_factory.rb +4 -0
- data/lib/spree/testing_support/factories/theme_factory.rb +1 -1
- data/lib/spree/testing_support/factories/user_factory.rb +2 -2
- metadata +48 -11
- data/config/initializers/state_machine.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a357b0d9005bb8d7ed5b4b56ebe020d34435ac15a402db9d650880ae01c38127
|
4
|
+
data.tar.gz: 729183094fa42d6d5b00632e5b9d44b61958643aef7b42d1b7931dd98c87b0f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49525a096671b7fce9970204619b1d05d7a421b3d6966b2ca139e492dda1ee2cd3a7339b3ee37ebbb4a31e2e93cdcfc49f8dffd6371ca91f813d9782259bf91f
|
7
|
+
data.tar.gz: faff9b9dd0388d354efcef394b6051951426824ee4d9e05ad5e6c3f952cf3eeba46ba40e631ee306f861529f840efb01e5b138075c188300bded5a88aec65d49
|
@@ -230,7 +230,7 @@ module Spree
|
|
230
230
|
end
|
231
231
|
|
232
232
|
def spree_base_cache_key
|
233
|
-
[
|
233
|
+
@spree_base_cache_key ||= [
|
234
234
|
I18n.locale,
|
235
235
|
(current_currency if defined?(current_currency)),
|
236
236
|
defined?(try_spree_current_user) && try_spree_current_user.present?,
|
@@ -246,6 +246,11 @@ module Spree
|
|
246
246
|
Spree::DatabaseTypeUtilities.maximum_value_for(:integer)
|
247
247
|
end
|
248
248
|
|
249
|
+
def payment_method_icon_tag(payment_method, opts = {})
|
250
|
+
image_tag "payment_icons/#{payment_method}.svg", opts
|
251
|
+
rescue Sprockets::Rails::Helper::AssetNotFound
|
252
|
+
end
|
253
|
+
|
249
254
|
private
|
250
255
|
|
251
256
|
def create_product_image_tag(image, product, options, style)
|
@@ -15,21 +15,23 @@ module Spree
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def spree_image_url(image, options = {})
|
18
|
+
return unless image
|
19
|
+
return unless image.variable?
|
20
|
+
return if image.respond_to?(:attached?) && !image.attached?
|
21
|
+
|
22
|
+
url_helpers = respond_to?(:main_app) ? main_app : Rails.application.routes.url_helpers
|
18
23
|
width = options[:width]
|
19
24
|
height = options[:height]
|
20
25
|
|
21
|
-
width
|
22
|
-
height
|
23
|
-
|
24
|
-
return unless image.attached?
|
25
|
-
return unless image.variable?
|
26
|
+
width *= 2 if width.present?
|
27
|
+
height *= 2 if height.present?
|
26
28
|
|
27
29
|
if width.present? && height.present?
|
28
|
-
|
30
|
+
url_helpers.cdn_image_url(
|
29
31
|
image.variant(spree_image_variant_options(resize_to_fill: [width, height]))
|
30
32
|
)
|
31
33
|
else
|
32
|
-
|
34
|
+
url_helpers.cdn_image_url(
|
33
35
|
image.variant(spree_image_variant_options(resize_to_limit: [width, height]))
|
34
36
|
)
|
35
37
|
end
|
@@ -51,17 +53,18 @@ module Spree
|
|
51
53
|
return unless height
|
52
54
|
return if height.zero?
|
53
55
|
|
54
|
-
w
|
56
|
+
w = width.to_f
|
57
|
+
h = height.to_f
|
55
58
|
|
56
59
|
# Always return width / height, flipping if needed
|
57
|
-
if h > w
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
ratio = if h > w
|
61
|
+
h / w
|
62
|
+
elsif h < w
|
63
|
+
w / h
|
64
|
+
else
|
65
|
+
# h == w, square image
|
66
|
+
1.0
|
67
|
+
end
|
65
68
|
|
66
69
|
ratio.round(3)
|
67
70
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Spree
|
2
|
+
module ShipmentHelper
|
3
|
+
def shipment_tracking_link_to(shipment:, name: nil, html_options: {})
|
4
|
+
tracking_url = shipment.tracking_url.presence
|
5
|
+
return '' unless tracking_url
|
6
|
+
|
7
|
+
display_text = name || shipment.tracking.presence || tracking_url
|
8
|
+
|
9
|
+
link_to display_text, tracking_url, html_options
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -3,12 +3,23 @@ module Spree
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
6
|
+
auto_strip_attributes :name, :presentation
|
7
|
+
|
6
8
|
#
|
7
9
|
# Callbacks
|
8
10
|
#
|
9
11
|
before_validation :set_name_from_presentation, if: -> { name.blank? }
|
10
12
|
before_validation :normalize_name
|
11
13
|
|
14
|
+
#
|
15
|
+
# Scopes
|
16
|
+
#
|
17
|
+
scope :search_by_name, ->(query) do
|
18
|
+
i18n do
|
19
|
+
name.matches("%#{query.downcase}%").or(presentation.matches("%#{query.downcase}%"))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
12
23
|
def set_name_from_presentation
|
13
24
|
self.name = presentation
|
14
25
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Spree
|
2
|
+
module PaymentSourceConcern
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
# Available actions for the payment source.
|
6
|
+
# @return [Array<String>]
|
7
|
+
def actions
|
8
|
+
%w{capture void credit}
|
9
|
+
end
|
10
|
+
|
11
|
+
# Indicates whether its possible to capture the payment
|
12
|
+
# @param payment [Spree::Payment]
|
13
|
+
# @return [Boolean]
|
14
|
+
def can_capture?(payment)
|
15
|
+
payment.pending? || payment.checkout?
|
16
|
+
end
|
17
|
+
|
18
|
+
# Indicates whether its possible to void the payment.
|
19
|
+
# @param payment [Spree::Payment]
|
20
|
+
# @return [Boolean]
|
21
|
+
def can_void?(payment)
|
22
|
+
!payment.failed? && !payment.void?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Indicates whether its possible to credit the payment. Note that most gateways require that the
|
26
|
+
# payment be settled first which generally happens within 12-24 hours of the transaction.
|
27
|
+
# @param payment [Spree::Payment]
|
28
|
+
# @return [Boolean]
|
29
|
+
def can_credit?(payment)
|
30
|
+
payment.completed? && payment.credit_allowed > 0
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns true if the payment source has a payment profile.
|
34
|
+
# @return [Boolean]
|
35
|
+
def has_payment_profile?
|
36
|
+
gateway_customer_profile_id.present? || gateway_payment_profile_id.present?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -306,11 +306,7 @@ module Spree
|
|
306
306
|
if defined?(PgSearch)
|
307
307
|
include PgSearch::Model
|
308
308
|
|
309
|
-
|
310
|
-
pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' }, using: { trigram: { threshold: 0.3, word_similarity: true } }
|
311
|
-
else
|
312
|
-
pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' }, using: { tsearch: { any_word: true, prefix: true } }
|
313
|
-
end
|
309
|
+
pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' }, using: { trigram: { threshold: 0.3, word_similarity: true } }
|
314
310
|
else
|
315
311
|
def self.search_by_name(query)
|
316
312
|
i18n { name.lower.matches("%#{query.downcase}%") }
|
@@ -3,7 +3,7 @@ module Spree
|
|
3
3
|
module Socials
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
-
SUPPORTED_SOCIAL_NETWORKS = %w[instagram facebook twitter pinterest tiktok youtube spotify].freeze
|
6
|
+
SUPPORTED_SOCIAL_NETWORKS = %w[instagram facebook twitter pinterest tiktok youtube spotify discord].freeze
|
7
7
|
|
8
8
|
SOCIAL_NETWORKS_CONFIG = {
|
9
9
|
twitter: {
|
@@ -33,6 +33,10 @@ module Spree
|
|
33
33
|
spotify: {
|
34
34
|
input_placeholder: 'https://open.spotify.com/user/your_handle',
|
35
35
|
profile_link: 'https://open.spotify.com/user/your_handle'
|
36
|
+
},
|
37
|
+
discord: {
|
38
|
+
input_placeholder: 'https://discord.com/invite/your_handle',
|
39
|
+
profile_link: 'https://discord.com/invite/your_handle'
|
36
40
|
}
|
37
41
|
}.freeze
|
38
42
|
|
@@ -61,7 +65,7 @@ module Spree
|
|
61
65
|
end
|
62
66
|
|
63
67
|
def social_links
|
64
|
-
@social_links ||= [instagram_link, facebook_link, twitter_link, pinterest_link, youtube_link, tiktok_link, spotify_link].compact_blank
|
68
|
+
@social_links ||= [instagram_link, facebook_link, twitter_link, pinterest_link, youtube_link, tiktok_link, spotify_link, discord_link].compact_blank
|
65
69
|
end
|
66
70
|
end
|
67
71
|
end
|
@@ -31,6 +31,7 @@ module Spree
|
|
31
31
|
has_many :wishlists, class_name: 'Spree::Wishlist', foreign_key: :user_id, dependent: :destroy
|
32
32
|
has_many :wished_items, through: :wishlists, source: :wished_items
|
33
33
|
has_many :gateway_customers, class_name: 'Spree::GatewayCustomer', foreign_key: :user_id
|
34
|
+
has_many :gift_cards, class_name: 'Spree::GiftCard', foreign_key: :user_id, dependent: :destroy
|
34
35
|
belongs_to :ship_address, class_name: 'Spree::Address', optional: true
|
35
36
|
belongs_to :bill_address, class_name: 'Spree::Address', optional: true
|
36
37
|
|
@@ -103,7 +104,7 @@ module Spree
|
|
103
104
|
def total_available_store_credit(currency = nil, store = nil)
|
104
105
|
store ||= Store.default
|
105
106
|
currency ||= store.default_currency
|
106
|
-
store_credits.for_store(store).where(currency: currency).reload.to_a.sum(&:amount_remaining)
|
107
|
+
store_credits.without_gift_card.for_store(store).where(currency: currency).reload.to_a.sum(&:amount_remaining)
|
107
108
|
end
|
108
109
|
|
109
110
|
# Returns the available store credits for the current store per currency
|
data/app/models/spree/ability.rb
CHANGED
@@ -61,6 +61,8 @@ module Spree
|
|
61
61
|
can :manage, :all
|
62
62
|
cannot :cancel, Spree::Order
|
63
63
|
can :cancel, Spree::Order, &:allow_cancel?
|
64
|
+
cannot :destroy, Spree::Order
|
65
|
+
can :destroy, Spree::Order, &:can_be_deleted?
|
64
66
|
cannot [:edit, :update], Spree::RefundReason, mutable: false
|
65
67
|
cannot [:edit, :update], Spree::ReimbursementType, mutable: false
|
66
68
|
end
|
data/app/models/spree/address.rb
CHANGED
@@ -6,9 +6,6 @@ module Spree
|
|
6
6
|
if defined?(Spree::Webhooks::HasWebhooks)
|
7
7
|
include Spree::Webhooks::HasWebhooks
|
8
8
|
end
|
9
|
-
if defined?(Spree::Security::Addresses)
|
10
|
-
include Spree::Security::Addresses
|
11
|
-
end
|
12
9
|
|
13
10
|
serialize :preferences, type: Hash, coder: YAML, default: {}
|
14
11
|
|
@@ -32,6 +29,10 @@ module Spree
|
|
32
29
|
EXCLUDED_KEYS_FOR_COMPARISON = %w(id updated_at created_at deleted_at label user_id public_metadata private_metadata)
|
33
30
|
FIELDS_TO_NORMALIZE = %w(firstname lastname phone alternative_phone company address1 address2 city zipcode)
|
34
31
|
|
32
|
+
if defined?(Spree::Security::Addresses)
|
33
|
+
include Spree::Security::Addresses
|
34
|
+
end
|
35
|
+
|
35
36
|
scope :not_deleted, -> { where(deleted_at: nil) }
|
36
37
|
|
37
38
|
scope :by_state_name_or_abbr, lambda { |state_name|
|
@@ -64,6 +65,7 @@ module Spree
|
|
64
65
|
end
|
65
66
|
|
66
67
|
validate :state_validate, :postal_code_validate
|
68
|
+
validate :address_validators, on: [:create, :update]
|
67
69
|
|
68
70
|
validates :label, uniqueness: { conditions: -> { where(deleted_at: nil) },
|
69
71
|
scope: :user_id,
|
@@ -71,6 +73,12 @@ module Spree
|
|
71
73
|
allow_blank: true,
|
72
74
|
allow_nil: true }
|
73
75
|
|
76
|
+
def address_validators
|
77
|
+
Rails.application.config.spree.validators.addresses.each do |validator|
|
78
|
+
validates_with validator
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
74
82
|
delegate :name, :iso3, :iso, :iso_name, to: :country, prefix: true
|
75
83
|
delegate :abbr, to: :state, prefix: true, allow_nil: true
|
76
84
|
|
@@ -2,6 +2,8 @@ module Spree
|
|
2
2
|
class CreditCard < Spree.base_class
|
3
3
|
include ActiveMerchant::Billing::CreditCardMethods
|
4
4
|
include Spree::Metadata
|
5
|
+
include Spree::PaymentSourceConcern
|
6
|
+
|
5
7
|
if defined?(Spree::Webhooks::HasWebhooks)
|
6
8
|
include Spree::Webhooks::HasWebhooks
|
7
9
|
end
|
@@ -135,30 +137,6 @@ module Spree
|
|
135
137
|
brand.present? ? brand.upcase : Spree.t(:no_cc_type)
|
136
138
|
end
|
137
139
|
|
138
|
-
def actions
|
139
|
-
%w{capture void credit}
|
140
|
-
end
|
141
|
-
|
142
|
-
# Indicates whether its possible to capture the payment
|
143
|
-
def can_capture?(payment)
|
144
|
-
payment.pending? || payment.checkout?
|
145
|
-
end
|
146
|
-
|
147
|
-
# Indicates whether its possible to void the payment.
|
148
|
-
def can_void?(payment)
|
149
|
-
!payment.failed? && !payment.void?
|
150
|
-
end
|
151
|
-
|
152
|
-
# Indicates whether its possible to credit the payment. Note that most gateways require that the
|
153
|
-
# payment be settled first which generally happens within 12-24 hours of the transaction.
|
154
|
-
def can_credit?(payment)
|
155
|
-
payment.completed? && payment.credit_allowed > 0
|
156
|
-
end
|
157
|
-
|
158
|
-
def has_payment_profile?
|
159
|
-
gateway_customer_profile_id.present? || gateway_payment_profile_id.present?
|
160
|
-
end
|
161
|
-
|
162
140
|
# ActiveMerchant needs first_name/last_name because we pass it a Spree::CreditCard and it calls those methods on it.
|
163
141
|
# Looking at the ActiveMerchant source code we should probably be calling #to_active_merchant before passing
|
164
142
|
# the object to ActiveMerchant but this should do for now.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Spree
|
2
|
+
class Gateway::CustomPaymentSourceMethod < Gateway
|
3
|
+
def provider_class
|
4
|
+
self.class
|
5
|
+
end
|
6
|
+
|
7
|
+
def payment_source_class
|
8
|
+
Spree::PaymentSource
|
9
|
+
end
|
10
|
+
|
11
|
+
def payment_profiles_supported?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def show_in_admin?
|
16
|
+
false
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_profile(payment)
|
20
|
+
return if payment.source.gateway_customer.present?
|
21
|
+
|
22
|
+
user = payment.source.user || payment.order.user
|
23
|
+
return if user.blank?
|
24
|
+
|
25
|
+
find_or_create_customer(user)
|
26
|
+
end
|
27
|
+
|
28
|
+
# simulate a 3rd party payment gateway api to fetch/or create a customer
|
29
|
+
def find_or_create_customer(user)
|
30
|
+
gateway_customers.find_or_create_by!(user: user, profile_id: "CUSTOMER-#{user.id}")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module Spree
|
2
|
+
class GiftCard < Spree.base_class
|
3
|
+
extend DisplayMoney
|
4
|
+
include Spree::SingleStoreResource
|
5
|
+
include Spree::Security::GiftCards if defined?(Spree::Security::GiftCards)
|
6
|
+
|
7
|
+
#
|
8
|
+
# State machine
|
9
|
+
#
|
10
|
+
state_machine :state, initial: :active do
|
11
|
+
event :cancel do
|
12
|
+
transition active: :canceled
|
13
|
+
end
|
14
|
+
|
15
|
+
event :redeem do
|
16
|
+
transition active: :redeemed
|
17
|
+
end
|
18
|
+
after_transition to: :redeemed, do: :after_redeem
|
19
|
+
|
20
|
+
event :partial_redeem do
|
21
|
+
transition active: :partially_redeemed
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Validations
|
27
|
+
#
|
28
|
+
validates :code, presence: true, uniqueness: { scope: :store_id }
|
29
|
+
validates :store, :currency, presence: true
|
30
|
+
validates :amount, presence: true, numericality: { greater_than: 0 }
|
31
|
+
validates :amount_used, :amount_authorized, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
32
|
+
|
33
|
+
#
|
34
|
+
# Associations
|
35
|
+
#
|
36
|
+
belongs_to :store, class_name: 'Spree::Store'
|
37
|
+
belongs_to :user, class_name: Spree.user_class.to_s, optional: true
|
38
|
+
belongs_to :created_by, class_name: Spree.admin_user_class.to_s, optional: true
|
39
|
+
belongs_to :batch, class_name: 'Spree::GiftCardBatch', optional: true, foreign_key: :gift_card_batch_id
|
40
|
+
|
41
|
+
has_many :store_credits, class_name: 'Spree::StoreCredit', as: :originator
|
42
|
+
has_many :orders, inverse_of: :gift_card, class_name: 'Spree::Order'
|
43
|
+
has_many :users, through: :orders, class_name: Spree.user_class.to_s
|
44
|
+
|
45
|
+
#
|
46
|
+
# Scopes
|
47
|
+
#
|
48
|
+
scope :active, -> { where(state: [:active, :partially_redeemed]).where(expires_at: [nil, Date.tomorrow..]) }
|
49
|
+
scope :expired, -> { where(state: :active).where(expires_at: ..Date.current) }
|
50
|
+
scope :redeemed, -> { where(state: [:redeemed]) }
|
51
|
+
scope :partially_redeemed, -> { where(state: [:partially_redeemed]) }
|
52
|
+
|
53
|
+
#
|
54
|
+
# Ransack
|
55
|
+
#
|
56
|
+
self.whitelisted_ransackable_attributes = %w[code user_id]
|
57
|
+
self.whitelisted_ransackable_associations = %w[users orders batch]
|
58
|
+
|
59
|
+
auto_strip_attributes :code
|
60
|
+
|
61
|
+
#
|
62
|
+
# Callbacks
|
63
|
+
#
|
64
|
+
before_validation :generate_code
|
65
|
+
before_validation :normalize_code
|
66
|
+
before_validation :set_currency
|
67
|
+
before_destroy :ensure_can_be_deleted
|
68
|
+
|
69
|
+
#
|
70
|
+
# Money
|
71
|
+
#
|
72
|
+
money_methods :amount, :amount_used, :amount_authorized, :amount_remaining
|
73
|
+
|
74
|
+
# Sets the amount
|
75
|
+
# @param amount [String]
|
76
|
+
def amount=(amount)
|
77
|
+
self[:amount] = Spree::LocalizedNumber.parse(amount)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Calculates the remaining amount
|
81
|
+
# @return [Decimal]
|
82
|
+
def amount_remaining
|
83
|
+
amount - amount_used - amount_authorized
|
84
|
+
end
|
85
|
+
|
86
|
+
delegate :email, to: :user, prefix: true, allow_nil: true
|
87
|
+
|
88
|
+
def self.json_api_columns
|
89
|
+
%w[code amount expires_at]
|
90
|
+
end
|
91
|
+
|
92
|
+
# Checks if the gift card is editable
|
93
|
+
# @return [Boolean]
|
94
|
+
def editable?
|
95
|
+
active?
|
96
|
+
end
|
97
|
+
|
98
|
+
# Checks if the gift card can be deleted
|
99
|
+
# @return [Boolean]
|
100
|
+
def can_be_deleted?
|
101
|
+
!redeemed? && !partially_redeemed?
|
102
|
+
end
|
103
|
+
|
104
|
+
# Displays the code in uppercase, eg. ABC1234
|
105
|
+
# @return [String]
|
106
|
+
def display_code
|
107
|
+
code.upcase
|
108
|
+
end
|
109
|
+
|
110
|
+
# Checks if the gift card is expired
|
111
|
+
# @return [Boolean]
|
112
|
+
def expired?
|
113
|
+
!redeemed? && expires_at.present? && expires_at <= Date.current
|
114
|
+
end
|
115
|
+
|
116
|
+
# Checks if the gift card is active, i.e. not expired and not redeemed
|
117
|
+
# @return [Boolean]
|
118
|
+
def active?
|
119
|
+
super && !expired?
|
120
|
+
end
|
121
|
+
|
122
|
+
# Displays state as expired if the gift card is expired, otherwise displays the state
|
123
|
+
# @return [String]
|
124
|
+
def display_state
|
125
|
+
if expired?
|
126
|
+
:expired
|
127
|
+
else
|
128
|
+
state
|
129
|
+
end.to_s
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def generate_code
|
135
|
+
return if code.present?
|
136
|
+
|
137
|
+
self.code = loop do
|
138
|
+
random_token = SecureRandom.hex(8).downcase
|
139
|
+
break random_token unless self.class.exists?(code: random_token, store_id: store_id)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def normalize_code
|
144
|
+
self.code = code.downcase if code.present?
|
145
|
+
end
|
146
|
+
|
147
|
+
def after_redeem
|
148
|
+
update!(redeemed_at: Time.current)
|
149
|
+
end
|
150
|
+
|
151
|
+
def ensure_can_be_deleted
|
152
|
+
return if can_be_deleted?
|
153
|
+
|
154
|
+
errors.add(:base, :cannot_destroy_used_gift_card)
|
155
|
+
throw(:abort)
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_currency
|
159
|
+
self.currency ||= store&.default_currency
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Spree
|
2
|
+
class GiftCardBatch < Spree.base_class
|
3
|
+
extend DisplayMoney
|
4
|
+
include Spree::SingleStoreResource
|
5
|
+
|
6
|
+
#
|
7
|
+
# Associations
|
8
|
+
#
|
9
|
+
belongs_to :store, class_name: 'Spree::Store'
|
10
|
+
belongs_to :created_by, class_name: Spree.admin_user_class.to_s, optional: true
|
11
|
+
has_many :gift_cards, class_name: 'Spree::GiftCard', inverse_of: :batch, dependent: :destroy
|
12
|
+
|
13
|
+
|
14
|
+
#
|
15
|
+
# Validations
|
16
|
+
#
|
17
|
+
validates :codes_count, :amount, :prefix, presence: true
|
18
|
+
validates :codes_count, numericality: { greater_than: 0, less_than_or_equal_to: Spree::Config[:gift_card_batch_limit].to_i }
|
19
|
+
validates :store, :currency, presence: true
|
20
|
+
validates :amount, numericality: { greater_than: 0 }
|
21
|
+
|
22
|
+
#
|
23
|
+
# Callbacks
|
24
|
+
#
|
25
|
+
before_validation :set_currency
|
26
|
+
after_create :generate_gift_cards
|
27
|
+
|
28
|
+
auto_strip_attributes :prefix
|
29
|
+
|
30
|
+
money_methods :amount
|
31
|
+
|
32
|
+
self.whitelisted_ransackable_attributes = %w[prefix]
|
33
|
+
|
34
|
+
def generate_gift_cards
|
35
|
+
if codes_count < Spree::Config[:gift_card_batch_web_limit].to_i
|
36
|
+
create_gift_cards
|
37
|
+
else
|
38
|
+
Spree::GiftCards::BulkGenerateJob.perform_later(id)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_gift_cards
|
43
|
+
@gift_cards_to_insert = []
|
44
|
+
|
45
|
+
Spree::GiftCard.transaction do
|
46
|
+
(codes_count - gift_cards.count).times do
|
47
|
+
@gift_cards_to_insert << gift_card_hash
|
48
|
+
end
|
49
|
+
Spree::GiftCard.insert_all @gift_cards_to_insert if @gift_cards_to_insert.any?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_code
|
54
|
+
loop do
|
55
|
+
code = "#{prefix.downcase}#{SecureRandom.hex(3).downcase}"
|
56
|
+
break code unless Spree::GiftCard.exists?(code: code) || @gift_cards_to_insert.detect { |gc| gc[:code] == code }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def gift_card_hash
|
63
|
+
{
|
64
|
+
state: :active,
|
65
|
+
gift_card_batch_id: id,
|
66
|
+
amount: amount,
|
67
|
+
currency: currency,
|
68
|
+
code: generate_code,
|
69
|
+
store_id: store_id,
|
70
|
+
created_by_id: created_by_id,
|
71
|
+
expires_at: expires_at
|
72
|
+
}
|
73
|
+
end
|
74
|
+
|
75
|
+
def set_currency
|
76
|
+
self.currency ||= store&.default_currency
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Spree
|
2
2
|
class InventoryUnit < Spree.base_class
|
3
|
+
extend Spree::DisplayMoney
|
3
4
|
if defined?(Spree::Webhooks::HasWebhooks)
|
4
5
|
include Spree::Webhooks::HasWebhooks
|
5
6
|
end
|
@@ -30,6 +31,8 @@ module Spree
|
|
30
31
|
|
31
32
|
validates :quantity, numericality: { greater_than: 0 }
|
32
33
|
|
34
|
+
money_methods :charged_amount
|
35
|
+
|
33
36
|
# state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
|
34
37
|
state_machine initial: :on_hand do
|
35
38
|
event :fill_backorder do
|
@@ -113,6 +116,14 @@ module Spree
|
|
113
116
|
original_return_item_id?
|
114
117
|
end
|
115
118
|
|
119
|
+
def charged_amount
|
120
|
+
percentage_of_line_item * line_item.pre_tax_amount
|
121
|
+
end
|
122
|
+
|
123
|
+
def percentage_of_line_item
|
124
|
+
quantity / BigDecimal(line_item.quantity)
|
125
|
+
end
|
126
|
+
|
116
127
|
private
|
117
128
|
|
118
129
|
def allow_ship?
|
@@ -124,10 +135,6 @@ module Spree
|
|
124
135
|
order.fulfill!
|
125
136
|
end
|
126
137
|
|
127
|
-
def percentage_of_line_item
|
128
|
-
quantity / BigDecimal(line_item.quantity)
|
129
|
-
end
|
130
|
-
|
131
138
|
def current_return_item
|
132
139
|
return_items.not_cancelled.first
|
133
140
|
end
|