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
@@ -92,6 +92,7 @@ module Spree
|
|
92
92
|
after_transition to: :complete, do: :persist_user_credit_card
|
93
93
|
before_transition to: :payment, do: :set_shipments_cost
|
94
94
|
before_transition to: :payment, do: :create_tax_charge!
|
95
|
+
before_transition to: :payment, do: :recalculate_store_credit_payment
|
95
96
|
end
|
96
97
|
|
97
98
|
before_transition from: :cart, do: :ensure_line_items_present
|
@@ -118,6 +119,7 @@ module Spree
|
|
118
119
|
|
119
120
|
after_transition to: :complete, do: :finalize!
|
120
121
|
after_transition to: :complete, do: :use_all_coupon_codes
|
122
|
+
after_transition to: :complete, do: :redeem_gift_card
|
121
123
|
after_transition to: :resumed, do: :after_resume
|
122
124
|
after_transition to: :canceled, do: :after_cancel
|
123
125
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Spree
|
2
|
+
class Order < Spree.base_class
|
3
|
+
module GiftCard
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
# one GiftCard can be used on many orders, until it runs out
|
8
|
+
belongs_to :gift_card, class_name: 'Spree::GiftCard', optional: true
|
9
|
+
|
10
|
+
money_methods :gift_card_total
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns the total amount of the gift card applied to the order
|
14
|
+
# @return [Decimal]
|
15
|
+
def gift_card_total
|
16
|
+
return 0.to_d unless gift_card.present?
|
17
|
+
|
18
|
+
store_credit_ids = payments.store_credits.valid.pluck(:source_id)
|
19
|
+
store_credits = Spree::StoreCredit.where(id: store_credit_ids, originator: gift_card)
|
20
|
+
|
21
|
+
store_credits.sum(:amount)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Applies a gift card to the order
|
25
|
+
# @param gift_card [Spree::GiftCard] the gift card to apply
|
26
|
+
# @return [Spree::Order] the order with the gift card applied
|
27
|
+
def apply_gift_card(gift_card)
|
28
|
+
Spree::Dependencies.gift_card_apply_service.constantize.call(gift_card: gift_card, order: self)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Removes a gift card from the order
|
32
|
+
# @return [Spree::Order] the order with the gift card removed
|
33
|
+
def remove_gift_card
|
34
|
+
Spree::Dependencies.gift_card_remove_service.constantize.call(order: self)
|
35
|
+
end
|
36
|
+
|
37
|
+
def recalculate_gift_card
|
38
|
+
applied_gift_card = gift_card
|
39
|
+
|
40
|
+
remove_gift_card
|
41
|
+
apply_gift_card(applied_gift_card)
|
42
|
+
end
|
43
|
+
|
44
|
+
def redeem_gift_card
|
45
|
+
return unless gift_card.present?
|
46
|
+
|
47
|
+
Spree::Dependencies.gift_card_redeem_service.constantize.call(gift_card: gift_card)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -14,12 +14,19 @@ module Spree
|
|
14
14
|
end
|
15
15
|
alias covered_by_store_credit covered_by_store_credit?
|
16
16
|
|
17
|
+
# Returns the total amount of store credits available to the user associated with the order.
|
18
|
+
# Returns only store credit for this store and same currency as order
|
19
|
+
#
|
20
|
+
# @return [BigDecimal] The total amount of store credits available to the user associated with the order.
|
17
21
|
def total_available_store_credit
|
18
22
|
return 0.0 unless user
|
19
23
|
|
20
24
|
user.total_available_store_credit(currency, store)
|
21
25
|
end
|
22
26
|
|
27
|
+
# Returns the available store credits for the user associated with the order.
|
28
|
+
#
|
29
|
+
# @return [Array<Spree::StoreCredit>] The available store credits for the user associated with the order.
|
23
30
|
def available_store_credits
|
24
31
|
return Spree::StoreCredit.none if user.nil?
|
25
32
|
|
@@ -27,15 +34,21 @@ module Spree
|
|
27
34
|
end
|
28
35
|
|
29
36
|
def could_use_store_credit?
|
30
|
-
return false if
|
37
|
+
return false if store.payment_methods.store_credit.available.empty?
|
31
38
|
|
32
39
|
total_available_store_credit > 0
|
33
40
|
end
|
34
41
|
|
42
|
+
# Returns the total amount of the order minus the total amount of store credits applied to the order.
|
43
|
+
#
|
44
|
+
# @return [BigDecimal] The total amount of the order minus the total amount of store credits applied to the order.
|
35
45
|
def order_total_after_store_credit
|
36
46
|
total - total_applicable_store_credit
|
37
47
|
end
|
38
48
|
|
49
|
+
# Returns the total amount of the order minus the total amount of store credits applied to the order.
|
50
|
+
#
|
51
|
+
# @return [BigDecimal] The total amount of the order minus the total amount of store credits applied to the order.
|
39
52
|
def total_minus_store_credits
|
40
53
|
total - total_applied_store_credit
|
41
54
|
end
|
@@ -48,10 +61,16 @@ module Spree
|
|
48
61
|
end
|
49
62
|
end
|
50
63
|
|
64
|
+
# Returns the total amount of store credits applied to the order.
|
65
|
+
#
|
66
|
+
# @return [BigDecimal] The total amount of store credits applied to the order.
|
51
67
|
def total_applied_store_credit
|
52
68
|
payments.store_credits.valid.sum(:amount)
|
53
69
|
end
|
54
70
|
|
71
|
+
# Returns true if the order is using store credit.
|
72
|
+
#
|
73
|
+
# @return [Boolean] True if the order is using store credit, false otherwise.
|
55
74
|
def using_store_credit?
|
56
75
|
total_applied_store_credit > 0
|
57
76
|
end
|
data/app/models/spree/order.rb
CHANGED
@@ -4,6 +4,7 @@ require_dependency 'spree/order/digital'
|
|
4
4
|
require_dependency 'spree/order/payments'
|
5
5
|
require_dependency 'spree/order/store_credit'
|
6
6
|
require_dependency 'spree/order/emails'
|
7
|
+
require_dependency 'spree/order/gift_card'
|
7
8
|
|
8
9
|
module Spree
|
9
10
|
class Order < Spree.base_class
|
@@ -11,6 +12,8 @@ module Spree
|
|
11
12
|
SHIPMENT_STATES = %w(backorder canceled partial pending ready shipped)
|
12
13
|
LINE_ITEM_REMOVABLE_STATES = %w(cart address delivery payment confirm resumed)
|
13
14
|
|
15
|
+
extend Spree::DisplayMoney
|
16
|
+
|
14
17
|
include Spree::Order::Checkout
|
15
18
|
include Spree::Order::CurrencyUpdater
|
16
19
|
include Spree::Order::Digital
|
@@ -20,6 +23,7 @@ module Spree
|
|
20
23
|
include Spree::Order::Emails
|
21
24
|
include Spree::Order::Webhooks
|
22
25
|
include Spree::Core::NumberGenerator.new(prefix: 'R')
|
26
|
+
include Spree::Order::GiftCard
|
23
27
|
|
24
28
|
include Spree::NumberIdentifier
|
25
29
|
include Spree::NumberAsParam
|
@@ -38,7 +42,6 @@ module Spree
|
|
38
42
|
|
39
43
|
MEMOIZED_METHODS = %w(tax_zone)
|
40
44
|
|
41
|
-
extend Spree::DisplayMoney
|
42
45
|
money_methods :outstanding_balance, :item_total, :adjustment_total,
|
43
46
|
:included_tax_total, :additional_tax_total, :tax_total,
|
44
47
|
:shipment_total, :promo_total, :total,
|
@@ -313,10 +316,28 @@ module Spree
|
|
313
316
|
shipments.any?(&:backordered?)
|
314
317
|
end
|
315
318
|
|
319
|
+
# Check if the shipping address is a quick checkout address
|
320
|
+
# quick checkout addresses are incomplete as wallet providers like Apple Pay and Google Pay
|
321
|
+
# do not provide all the address fields until the checkout is completed (confirmed) on their side
|
322
|
+
# @return [Boolean]
|
316
323
|
def quick_checkout?
|
317
324
|
shipping_address.present? && shipping_address.quick_checkout?
|
318
325
|
end
|
319
326
|
|
327
|
+
# Check if quick checkout is available for this order
|
328
|
+
# Either fully digital or not digital at all
|
329
|
+
# @return [Boolean]
|
330
|
+
def quick_checkout_available?
|
331
|
+
payment_required? && shipments.count <= 1 && (digital? || !some_digital? || !delivery_required?)
|
332
|
+
end
|
333
|
+
|
334
|
+
# Check if quick checkout requires an address collection
|
335
|
+
# If the order is digital or not delivery required, then we don't need to collect an address
|
336
|
+
# @return [Boolean]
|
337
|
+
def quick_checkout_require_address?
|
338
|
+
!digital? && delivery_required?
|
339
|
+
end
|
340
|
+
|
320
341
|
# Returns the relevant zone (if any) to be used for taxation purposes.
|
321
342
|
# Uses default tax zone unless there is a specific match
|
322
343
|
def tax_zone
|
@@ -631,7 +652,7 @@ module Spree
|
|
631
652
|
#
|
632
653
|
# @return [BigDecimal] the total weight of the inventory units in the order
|
633
654
|
def total_weight
|
634
|
-
@total_weight ||= line_items.joins(:variant).includes(:variant).map
|
655
|
+
@total_weight ||= line_items.joins(:variant).includes(:variant).map(&:item_weight).sum
|
635
656
|
end
|
636
657
|
|
637
658
|
# Returns line items that have no shipping rates
|
@@ -742,6 +763,11 @@ module Spree
|
|
742
763
|
end
|
743
764
|
|
744
765
|
def can_be_destroyed?
|
766
|
+
Spree::Deprecation.warn('Spree::Order#can_be_destroyed? is deprecated and will be removed in the next major version. Use Spree::Order#can_be_deleted? instead.')
|
767
|
+
can_be_deleted?
|
768
|
+
end
|
769
|
+
|
770
|
+
def can_be_deleted?
|
745
771
|
!completed? && payments.completed.empty?
|
746
772
|
end
|
747
773
|
|
@@ -834,6 +860,10 @@ module Spree
|
|
834
860
|
line_items
|
835
861
|
end
|
836
862
|
|
863
|
+
def requires_ship_address?
|
864
|
+
!digital?
|
865
|
+
end
|
866
|
+
|
837
867
|
private
|
838
868
|
|
839
869
|
def link_by_email
|
@@ -870,10 +900,15 @@ module Spree
|
|
870
900
|
|
871
901
|
def after_cancel
|
872
902
|
shipments.each(&:cancel!)
|
873
|
-
payments.completed.each(&:cancel!)
|
874
903
|
|
875
|
-
#
|
876
|
-
|
904
|
+
# payments fully covered by gift card won't be refunded
|
905
|
+
# we want to only void the payment
|
906
|
+
if gift_card.present? && covered_by_store_credit?
|
907
|
+
payments.completed.store_credits.each(&:void!)
|
908
|
+
else
|
909
|
+
payments.completed.each(&:cancel!)
|
910
|
+
payments.store_credits.pending.each(&:void!)
|
911
|
+
end
|
877
912
|
|
878
913
|
send_cancel_email
|
879
914
|
update_with_updater!
|
@@ -895,7 +930,7 @@ module Spree
|
|
895
930
|
end
|
896
931
|
|
897
932
|
def ensure_currency_presence
|
898
|
-
self.currency ||= store
|
933
|
+
self.currency ||= store&.default_currency
|
899
934
|
end
|
900
935
|
|
901
936
|
def collect_payment_methods(store = nil)
|
@@ -911,5 +946,15 @@ module Spree
|
|
911
946
|
def credit_card_nil_payment?(attributes)
|
912
947
|
payments.store_credits.present? && attributes[:amount].to_f.zero?
|
913
948
|
end
|
949
|
+
|
950
|
+
def recalculate_store_credit_payment
|
951
|
+
updater.update_adjustment_total if using_store_credit?
|
952
|
+
|
953
|
+
if gift_card.present?
|
954
|
+
recalculate_gift_card
|
955
|
+
elsif using_store_credit?
|
956
|
+
Spree::Dependencies.checkout_add_store_credit_service.constantize.call(order: self)
|
957
|
+
end
|
958
|
+
end
|
914
959
|
end
|
915
960
|
end
|
@@ -8,6 +8,7 @@ module Spree
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def merge!(other_order, user = nil, discard_merged: true)
|
11
|
+
handle_gift_card(other_order)
|
11
12
|
other_order.line_items.each do |other_order_line_item|
|
12
13
|
next unless other_order_line_item.currency == order.currency
|
13
14
|
|
@@ -26,6 +27,8 @@ module Spree
|
|
26
27
|
end
|
27
28
|
end
|
28
29
|
|
30
|
+
private
|
31
|
+
|
29
32
|
# Compare the line item of the other order with mine.
|
30
33
|
# Make sure you allow any extensions to chime in on whether or
|
31
34
|
# not the extension-specific parts of the line item match
|
@@ -70,5 +73,14 @@ module Spree
|
|
70
73
|
updater.update_item_total
|
71
74
|
updater.persist_totals
|
72
75
|
end
|
76
|
+
|
77
|
+
def handle_gift_card(other_order)
|
78
|
+
return unless other_order.gift_card.present?
|
79
|
+
|
80
|
+
gift_card = other_order.gift_card
|
81
|
+
|
82
|
+
other_order.remove_gift_card
|
83
|
+
order.apply_gift_card(gift_card)
|
84
|
+
end
|
73
85
|
end
|
74
86
|
end
|
@@ -12,6 +12,7 @@ module Spree
|
|
12
12
|
|
13
13
|
scope :active, -> { where(active: true).order(position: :asc) }
|
14
14
|
scope :available, -> { active.where(display_on: [:front_end, :back_end, :both]) }
|
15
|
+
scope :store_credit, -> { where(type: 'Spree::PaymentMethod::StoreCredit') }
|
15
16
|
|
16
17
|
after_initialize :set_name, if: :new_record?
|
17
18
|
|
data/app/models/spree/post.rb
CHANGED
@@ -54,6 +54,7 @@ module Spree
|
|
54
54
|
# Scopes
|
55
55
|
#
|
56
56
|
scope :published, -> { where(published_at: [..Time.current]) }
|
57
|
+
scope :by_newest, -> { order(created_at: :desc) }
|
57
58
|
|
58
59
|
delegate :name, to: :author, prefix: true, allow_nil: true
|
59
60
|
delegate :title, to: :post_category, prefix: true, allow_nil: true
|
data/app/models/spree/price.rb
CHANGED
@@ -36,7 +36,13 @@ module Spree
|
|
36
36
|
scope :with_currency, ->(currency) { where(currency: currency) }
|
37
37
|
scope :non_zero, -> { where.not(amount: [nil, 0]) }
|
38
38
|
scope :discounted, -> { where('compare_at_amount > amount') }
|
39
|
-
scope :for_products, ->(products
|
39
|
+
scope :for_products, ->(products, currency = nil) do
|
40
|
+
currency ||= Spree::Store.default.default_currency
|
41
|
+
|
42
|
+
with_currency(currency).joins(:variant).where(
|
43
|
+
Spree::Variant.table_name => { product_id: products }
|
44
|
+
)
|
45
|
+
end
|
40
46
|
|
41
47
|
extend DisplayMoney
|
42
48
|
money_methods :amount, :price, :compare_at_amount
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Spree
|
2
|
+
class Product < Spree.base_class
|
3
|
+
module Slugs
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
extend FriendlyId
|
8
|
+
include Spree::TranslatableResourceSlug
|
9
|
+
|
10
|
+
translates :slug
|
11
|
+
friendly_id :slug_candidates, use: [:history, :slugged, :scoped, :mobility], scope: spree_base_uniqueness_scope, slug_limit: 255
|
12
|
+
|
13
|
+
Product::Translation.class_eval do
|
14
|
+
before_save :set_slug
|
15
|
+
acts_as_paranoid
|
16
|
+
# deleted translation values also need to be accessible for index views listing deleted resources
|
17
|
+
default_scope { unscope(where: :deleted_at) }
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def set_slug
|
22
|
+
self.slug = generate_slug
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_slug
|
26
|
+
if name.blank? && slug.blank?
|
27
|
+
translated_model.name.to_url
|
28
|
+
elsif slug.blank?
|
29
|
+
name.to_url
|
30
|
+
else
|
31
|
+
slug.to_url
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
before_validation :downcase_slug
|
37
|
+
before_validation :normalize_slug, on: :update
|
38
|
+
after_destroy :punch_slugs
|
39
|
+
after_restore :regenerate_slug
|
40
|
+
|
41
|
+
validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: spree_base_uniqueness_scope }
|
42
|
+
|
43
|
+
def self.slug_available?(slug, id)
|
44
|
+
!where(slug: slug).where.not(id: id).exists?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def ensure_slug_is_unique(candidate_slug)
|
49
|
+
return slug if candidate_slug.blank? || slug.blank?
|
50
|
+
return candidate_slug if self.class.slug_available?(candidate_slug, id)
|
51
|
+
|
52
|
+
normalize_friendly_id([candidate_slug, uuid_for_friendly_id])
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def slug_candidates
|
58
|
+
if defined?(:deleted_at) && deleted_at.present?
|
59
|
+
[
|
60
|
+
['deleted', :name],
|
61
|
+
['deleted', :name, :sku],
|
62
|
+
['deleted', :name, :uuid_for_friendly_id]
|
63
|
+
]
|
64
|
+
else
|
65
|
+
[
|
66
|
+
[:name],
|
67
|
+
[:name, :sku],
|
68
|
+
[:name, :uuid_for_friendly_id]
|
69
|
+
]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def downcase_slug
|
74
|
+
slug&.downcase!
|
75
|
+
end
|
76
|
+
|
77
|
+
def normalize_slug
|
78
|
+
self.slug = normalize_friendly_id(slug)
|
79
|
+
end
|
80
|
+
|
81
|
+
def regenerate_slug
|
82
|
+
self.slug = nil
|
83
|
+
save!
|
84
|
+
end
|
85
|
+
|
86
|
+
def punch_slugs
|
87
|
+
return if new_record? || frozen?
|
88
|
+
|
89
|
+
self.slug = nil
|
90
|
+
|
91
|
+
set_slug
|
92
|
+
update_column(:slug, slug)
|
93
|
+
|
94
|
+
new_slug = ->(rec) { "deleted-#{rec.id}_#{rec.slug}"[..254] }
|
95
|
+
|
96
|
+
translations.with_deleted.each { |rec| rec.update_columns(slug: new_slug.call(rec)) }
|
97
|
+
slugs.with_deleted.each { |rec| rec.update_column(:slug, new_slug.call(rec)) }
|
98
|
+
|
99
|
+
translations.find_by!(locale: I18n.locale).update_column(:slug, slug) if Spree.use_translations?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/app/models/spree/product.rb
CHANGED
@@ -20,14 +20,17 @@
|
|
20
20
|
|
21
21
|
module Spree
|
22
22
|
class Product < Spree.base_class
|
23
|
-
|
23
|
+
acts_as_paranoid
|
24
|
+
acts_as_taggable_on :tags, :labels
|
25
|
+
auto_strip_attributes :name
|
26
|
+
|
24
27
|
include Spree::ProductScopes
|
25
28
|
include Spree::MultiStoreResource
|
26
29
|
include Spree::TranslatableResource
|
27
|
-
include Spree::TranslatableResourceSlug
|
28
30
|
include Spree::MemoizedData
|
29
31
|
include Spree::Metadata
|
30
32
|
include Spree::Product::Webhooks
|
33
|
+
include Spree::Product::Slugs
|
31
34
|
if defined?(Spree::VendorConcern)
|
32
35
|
include Spree::VendorConcern
|
33
36
|
end
|
@@ -50,39 +53,10 @@ module Spree
|
|
50
53
|
if defined?(PgSearch)
|
51
54
|
include PgSearch::Model
|
52
55
|
|
53
|
-
|
54
|
-
pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' }, using: { trigram: { threshold: 0.3, word_similarity: true } }
|
55
|
-
else
|
56
|
-
pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' }, using: { tsearch: { any_word: true, prefix: true } }
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
before_save :set_slug
|
61
|
-
acts_as_paranoid
|
62
|
-
# deleted translation values also need to be accessible for index views listing deleted resources
|
63
|
-
default_scope { unscope(where: :deleted_at) }
|
64
|
-
def set_slug
|
65
|
-
self.slug = generate_slug
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
def generate_slug
|
71
|
-
if name.blank? && slug.blank?
|
72
|
-
translated_model.name.to_url
|
73
|
-
elsif slug.blank?
|
74
|
-
name.to_url
|
75
|
-
else
|
76
|
-
slug.to_url
|
77
|
-
end
|
56
|
+
pg_search_scope :search_by_name, against: { name: 'A', meta_title: 'B' }, using: { trigram: { threshold: 0.3, word_similarity: true } }
|
78
57
|
end
|
79
58
|
end
|
80
59
|
|
81
|
-
friendly_id :slug_candidates, use: [:history, :scoped, :mobility], scope: spree_base_uniqueness_scope
|
82
|
-
acts_as_paranoid
|
83
|
-
auto_strip_attributes :name
|
84
|
-
acts_as_taggable_on :tags, :labels
|
85
|
-
|
86
60
|
# we need to have this callback before any dependent: :destroy associations
|
87
61
|
# https://github.com/rails/rails/issues/3458
|
88
62
|
before_destroy :ensure_not_in_complete_orders
|
@@ -148,17 +122,12 @@ module Spree
|
|
148
122
|
after_initialize :ensure_master
|
149
123
|
after_initialize :assign_default_tax_category
|
150
124
|
|
151
|
-
before_validation :downcase_slug
|
152
|
-
before_validation :normalize_slug, on: :update
|
153
125
|
before_validation :validate_master
|
154
126
|
before_validation :ensure_default_shipping_category
|
155
127
|
|
156
128
|
after_create :add_associations_from_prototype
|
157
129
|
after_create :build_variants_from_option_values_hash, if: :option_values_hash
|
158
130
|
|
159
|
-
after_destroy :punch_slug
|
160
|
-
after_restore :update_slug_history
|
161
|
-
|
162
131
|
after_save :save_master
|
163
132
|
after_save :run_touch_callbacks, if: :anything_changed?
|
164
133
|
after_save :reset_nested_changes
|
@@ -176,7 +145,6 @@ module Spree
|
|
176
145
|
validates :price, if: :requires_price?
|
177
146
|
end
|
178
147
|
|
179
|
-
validates :slug, presence: true, uniqueness: { allow_blank: true, case_sensitive: true, scope: spree_base_uniqueness_scope }
|
180
148
|
validate :discontinue_on_must_be_later_than_make_active_at, if: -> { make_active_at && discontinue_on }
|
181
149
|
|
182
150
|
scope :for_store, ->(store) { joins(:store_products).where(StoreProduct.table_name => { store_id: store.id }) }
|
@@ -231,7 +199,7 @@ module Spree
|
|
231
199
|
having("SUM(#{Spree::StockItem.table_name}.count_on_hand) <= 0")
|
232
200
|
}
|
233
201
|
scope :out_of_stock, lambda {
|
234
|
-
joins(:stock_items).where("#{Spree::
|
202
|
+
joins(:stock_items).where("#{Spree::Variant.table_name}.track_inventory = ? OR #{Spree::StockItem.table_name}.count_on_hand <= ?", false, 0)
|
235
203
|
}
|
236
204
|
|
237
205
|
scope :by_best_selling, lambda { |order_direction = :desc|
|
@@ -515,17 +483,6 @@ module Spree
|
|
515
483
|
where conditions.inject(:or)
|
516
484
|
end
|
517
485
|
|
518
|
-
def self.slug_available?(slug, id)
|
519
|
-
!where(slug: slug).where.not(id: id).exists?
|
520
|
-
end
|
521
|
-
|
522
|
-
def ensure_slug_is_unique(candidate_slug)
|
523
|
-
return slug if candidate_slug.blank? || slug.blank?
|
524
|
-
return candidate_slug if self.class.slug_available?(candidate_slug, id)
|
525
|
-
|
526
|
-
normalize_friendly_id([candidate_slug, uuid_for_friendly_id])
|
527
|
-
end
|
528
|
-
|
529
486
|
# Suitable for displaying only variants that has at least one option value.
|
530
487
|
# There may be scenarios where an option type is removed and along with it
|
531
488
|
# all option values. At that point all variants associated with only those
|
@@ -741,25 +698,6 @@ module Spree
|
|
741
698
|
self.tax_category = Spree::TaxCategory.default if new_record?
|
742
699
|
end
|
743
700
|
|
744
|
-
def normalize_slug
|
745
|
-
self.slug = normalize_friendly_id(slug)
|
746
|
-
end
|
747
|
-
|
748
|
-
def punch_slug
|
749
|
-
# punch slug with date prefix to allow reuse of original
|
750
|
-
return if frozen?
|
751
|
-
|
752
|
-
update_column(:slug, "#{Time.current.to_i}_#{slug}"[0..254])
|
753
|
-
|
754
|
-
translations.with_deleted.each do |t|
|
755
|
-
t.update_column :slug, "#{Time.current.to_i}_#{t.slug}"[0..254]
|
756
|
-
end
|
757
|
-
end
|
758
|
-
|
759
|
-
def update_slug_history
|
760
|
-
save!
|
761
|
-
end
|
762
|
-
|
763
701
|
def anything_changed?
|
764
702
|
saved_changes? || @nested_changes
|
765
703
|
end
|
@@ -815,14 +753,6 @@ module Spree
|
|
815
753
|
end
|
816
754
|
end
|
817
755
|
|
818
|
-
# Try building a slug based on the following fields in increasing order of specificity.
|
819
|
-
def slug_candidates
|
820
|
-
[
|
821
|
-
:name,
|
822
|
-
[:name, :sku]
|
823
|
-
]
|
824
|
-
end
|
825
|
-
|
826
756
|
def run_touch_callbacks
|
827
757
|
run_callbacks(:touch)
|
828
758
|
end
|
@@ -870,10 +800,6 @@ module Spree
|
|
870
800
|
previously_new_record? || tag_list_previously_changed? || available_on_previously_changed?
|
871
801
|
end
|
872
802
|
|
873
|
-
def downcase_slug
|
874
|
-
slug&.downcase!
|
875
|
-
end
|
876
|
-
|
877
803
|
def after_activate
|
878
804
|
# Implement your logic here
|
879
805
|
end
|
@@ -31,9 +31,9 @@ module Spree
|
|
31
31
|
default_scope { order(:position) }
|
32
32
|
|
33
33
|
scope :filterable, -> { joins(:property).where(Property.table_name => { filterable: true }) }
|
34
|
-
scope :for_products, ->(products) {
|
34
|
+
scope :for_products, ->(products) { where(product_id: products) }
|
35
35
|
scope :sort_by_property_position, -> {
|
36
|
-
unscope(:order).joins(:property).order(
|
36
|
+
unscope(:order).joins(:property).order(Spree::Property.table_name => { position: :asc })
|
37
37
|
}
|
38
38
|
|
39
39
|
self.whitelisted_ransackable_attributes = ['value', 'filter_param']
|
@@ -11,6 +11,27 @@ module Spree
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def apply
|
14
|
+
if load_gift_card_code
|
15
|
+
|
16
|
+
if @gift_card.expired?
|
17
|
+
set_error_code :gift_card_expired
|
18
|
+
return self
|
19
|
+
elsif @gift_card.redeemed?
|
20
|
+
set_error_code :gift_card_already_redeemed
|
21
|
+
return self
|
22
|
+
end
|
23
|
+
|
24
|
+
result = order.apply_gift_card(@gift_card)
|
25
|
+
|
26
|
+
if result.success?
|
27
|
+
set_success_code(:gift_card_applied)
|
28
|
+
else
|
29
|
+
set_error_code(result.value, result.error.value || {})
|
30
|
+
end
|
31
|
+
|
32
|
+
return self
|
33
|
+
end
|
34
|
+
|
14
35
|
if order.coupon_code.present?
|
15
36
|
if promotion.present? && promotion.actions.exists?
|
16
37
|
handle_present_promotion
|
@@ -26,6 +47,18 @@ module Spree
|
|
26
47
|
end
|
27
48
|
|
28
49
|
def remove(coupon_code)
|
50
|
+
if order.gift_card
|
51
|
+
result = order.remove_gift_card
|
52
|
+
|
53
|
+
if result.success?
|
54
|
+
set_success_code(:gift_card_removed)
|
55
|
+
else
|
56
|
+
set_error_code(result.value)
|
57
|
+
end
|
58
|
+
|
59
|
+
return self
|
60
|
+
end
|
61
|
+
|
29
62
|
promotion = order.promotions.with_coupon_code(coupon_code)
|
30
63
|
if promotion.present?
|
31
64
|
# Order promotion has to be destroyed before line item removing
|
@@ -179,6 +212,12 @@ module Spree
|
|
179
212
|
def handle_coupon_code(discount, coupon_code)
|
180
213
|
discount.source.promotion.coupon_codes.unused.find_by(code: coupon_code)&.apply_order!(order)
|
181
214
|
end
|
215
|
+
|
216
|
+
def load_gift_card_code
|
217
|
+
return unless order.coupon_code.present?
|
218
|
+
|
219
|
+
@gift_card = order.store.gift_cards.find_by(code: order.coupon_code.downcase)
|
220
|
+
end
|
182
221
|
end
|
183
222
|
end
|
184
223
|
end
|