spree_core 5.1.0.beta4 → 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/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/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_method/store_credit.rb +1 -1
- data/app/models/spree/payment_method.rb +1 -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/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 +2 -2
- data/app/views/spree/addresses/_form.html.erb +1 -1
- data/app/views/spree/shared/_payment.html.erb +4 -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 +7 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +2 -1
- data/lib/spree/permitted_attributes.rb +22 -9
- 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/post_factory.rb +4 -0
- data/lib/spree/testing_support/factories/promotion_rule_factory.rb +4 -0
- data/lib/spree/testing_support/factories/user_factory.rb +2 -2
- metadata +45 -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
|
@@ -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
|
|
@@ -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
|
@@ -48,7 +48,7 @@ module Spree
|
|
48
48
|
|
49
49
|
after_create :update_tax_charge
|
50
50
|
|
51
|
-
delegate :name, :description, :sku, :should_track_inventory?, :product, :options_text, :slug, :product_id, to: :variant
|
51
|
+
delegate :name, :description, :sku, :should_track_inventory?, :product, :options_text, :slug, :product_id, :dimensions_unit, :weight_unit, to: :variant
|
52
52
|
delegate :brand, :category, to: :product
|
53
53
|
delegate :tax_zone, to: :order
|
54
54
|
delegate :digital?, to: :variant
|
@@ -85,7 +85,7 @@ module Spree
|
|
85
85
|
extend DisplayMoney
|
86
86
|
money_methods :amount, :subtotal, :discounted_amount, :final_amount, :total, :price,
|
87
87
|
:adjustment_total, :additional_tax_total, :promo_total, :included_tax_total,
|
88
|
-
:pre_tax_amount, :shipping_cost, :tax_total
|
88
|
+
:pre_tax_amount, :shipping_cost, :tax_total, :compare_at_amount
|
89
89
|
|
90
90
|
alias single_money display_price
|
91
91
|
alias single_display_amount display_price
|
@@ -94,6 +94,10 @@ module Spree
|
|
94
94
|
price * quantity
|
95
95
|
end
|
96
96
|
|
97
|
+
def compare_at_amount
|
98
|
+
(variant.compare_at_amount_in(currency) || 0) * quantity
|
99
|
+
end
|
100
|
+
|
97
101
|
alias subtotal amount
|
98
102
|
|
99
103
|
def taxable_amount
|
@@ -114,6 +118,13 @@ module Spree
|
|
114
118
|
amount + adjustment_total
|
115
119
|
end
|
116
120
|
|
121
|
+
# Returns the weight of the line item
|
122
|
+
#
|
123
|
+
# @return [BigDecimal]
|
124
|
+
def item_weight
|
125
|
+
variant.weight * quantity
|
126
|
+
end
|
127
|
+
|
117
128
|
alias total final_amount
|
118
129
|
alias money display_total
|
119
130
|
|
@@ -10,12 +10,11 @@ module Spree
|
|
10
10
|
include Spree::Webhooks::HasWebhooks
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
translates(*TRANSLATABLE_FIELDS, column_fallback: true)
|
13
|
+
TRANSLATABLE_FIELDS = %i[presentation].freeze
|
14
|
+
translates(*TRANSLATABLE_FIELDS, column_fallback: !Spree.always_use_translations?)
|
15
|
+
|
16
|
+
self::Translation.class_eval do
|
17
|
+
auto_strip_attributes :presentation
|
19
18
|
end
|
20
19
|
|
21
20
|
#
|
@@ -23,7 +22,6 @@ module Spree
|
|
23
22
|
#
|
24
23
|
self.whitelisted_ransackable_scopes = %w[search_by_name]
|
25
24
|
acts_as_list
|
26
|
-
auto_strip_attributes :name, :presentation
|
27
25
|
|
28
26
|
#
|
29
27
|
# Associations
|
@@ -47,14 +45,6 @@ module Spree
|
|
47
45
|
scope :colors, -> { where(name: COLOR_NAMES) }
|
48
46
|
scope :filterable, -> { where(filterable: true) }
|
49
47
|
|
50
|
-
if defined?(PgSearch)
|
51
|
-
# full text search
|
52
|
-
include PgSearch::Model
|
53
|
-
pg_search_scope :search_by_name, against: %i[name presentation]
|
54
|
-
else
|
55
|
-
scope :search_by_name, ->(query) { where('name LIKE ?', "%#{query}%") }
|
56
|
-
end
|
57
|
-
|
58
48
|
#
|
59
49
|
# Attributes
|
60
50
|
#
|
@@ -7,19 +7,17 @@ module Spree
|
|
7
7
|
include Spree::Webhooks::HasWebhooks
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
translates(*TRANSLATABLE_FIELDS, column_fallback: true)
|
10
|
+
TRANSLATABLE_FIELDS = %i[presentation].freeze
|
11
|
+
translates(*TRANSLATABLE_FIELDS, column_fallback: !Spree.always_use_translations?)
|
12
|
+
|
13
|
+
self::Translation.class_eval do
|
14
|
+
auto_strip_attributes :presentation
|
16
15
|
end
|
17
16
|
|
18
17
|
#
|
19
18
|
# Magic methods
|
20
19
|
#
|
21
20
|
acts_as_list scope: :option_type
|
22
|
-
auto_strip_attributes :name, :presentation
|
23
21
|
self.whitelisted_ransackable_attributes = ['presentation']
|
24
22
|
|
25
23
|
#
|
@@ -48,8 +46,8 @@ module Spree
|
|
48
46
|
}
|
49
47
|
|
50
48
|
scope :for_products, lambda { |products|
|
51
|
-
|
52
|
-
|
49
|
+
# we need to use map(&:id) to avoid SQL errors when merging with other scopes
|
50
|
+
joins(:variants).where(Spree::Variant.table_name => { product_id: products.map(&:id) })
|
53
51
|
}
|
54
52
|
|
55
53
|
#
|
@@ -61,15 +59,23 @@ module Spree
|
|
61
59
|
|
62
60
|
delegate :name, :presentation, to: :option_type, prefix: true, allow_nil: true
|
63
61
|
|
62
|
+
# Using map here instead of pluck, as these values are translatable via Mobility gem
|
63
|
+
# @return [Array<Hash>]
|
64
64
|
def self.to_tom_select_json
|
65
|
-
all.
|
65
|
+
all.map do |ov|
|
66
66
|
{
|
67
|
-
id:
|
68
|
-
name: presentation
|
67
|
+
id: ov.name,
|
68
|
+
name: ov.presentation
|
69
69
|
}
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
+
# Returns the presentation with the option type presentation, eg. "Color: Red"
|
74
|
+
# @return [String]
|
75
|
+
def display_presentation
|
76
|
+
@display_presentation ||= "#{option_type.presentation}: #{presentation}"
|
77
|
+
end
|
78
|
+
|
73
79
|
private
|
74
80
|
|
75
81
|
def touch_all_variants
|