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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +6 -1
  3. data/app/helpers/spree/images_helper.rb +19 -16
  4. data/app/helpers/spree/shipment_helper.rb +12 -0
  5. data/app/jobs/spree/gift_cards/bulk_generate_job.rb +13 -0
  6. data/app/models/concerns/spree/parameterizable_name.rb +11 -0
  7. data/app/models/concerns/spree/product_scopes.rb +1 -5
  8. data/app/models/concerns/spree/stores/socials.rb +6 -2
  9. data/app/models/concerns/spree/user_methods.rb +2 -1
  10. data/app/models/spree/ability.rb +2 -0
  11. data/app/models/spree/address.rb +11 -3
  12. data/app/models/spree/gift_card.rb +162 -0
  13. data/app/models/spree/gift_card_batch.rb +79 -0
  14. data/app/models/spree/inventory_unit.rb +11 -4
  15. data/app/models/spree/line_item.rb +13 -2
  16. data/app/models/spree/option_type.rb +5 -15
  17. data/app/models/spree/option_value.rb +18 -12
  18. data/app/models/spree/order/checkout.rb +2 -0
  19. data/app/models/spree/order/gift_card.rb +51 -0
  20. data/app/models/spree/order/store_credit.rb +20 -1
  21. data/app/models/spree/order.rb +51 -6
  22. data/app/models/spree/order_merger.rb +12 -0
  23. data/app/models/spree/page_sections/featured_posts.rb +4 -0
  24. data/app/models/spree/payment_method/store_credit.rb +1 -1
  25. data/app/models/spree/payment_method.rb +1 -0
  26. data/app/models/spree/post.rb +1 -0
  27. data/app/models/spree/price.rb +7 -1
  28. data/app/models/spree/product/slugs.rb +103 -0
  29. data/app/models/spree/product.rb +7 -81
  30. data/app/models/spree/product_property.rb +2 -2
  31. data/app/models/spree/promotion_handler/coupon.rb +39 -0
  32. data/app/models/spree/property.rb +2 -8
  33. data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +1 -1
  34. data/app/models/spree/return_item.rb +4 -0
  35. data/app/models/spree/shipment.rb +19 -0
  36. data/app/models/spree/shipping_method.rb +8 -9
  37. data/app/models/spree/store.rb +2 -0
  38. data/app/models/spree/store_credit.rb +9 -6
  39. data/app/models/spree/store_credit_event.rb +8 -4
  40. data/app/models/spree/taxon.rb +8 -1
  41. data/app/models/spree/theme.rb +1 -1
  42. data/app/models/spree/variant.rb +1 -1
  43. data/app/presenters/spree/csv/product_variant_presenter.rb +14 -3
  44. data/app/services/spree/addresses/phone_validator.rb +20 -0
  45. data/app/services/spree/cart/destroy.rb +1 -1
  46. data/app/services/spree/cart/recalculate.rb +1 -0
  47. data/app/services/spree/gift_cards/apply.rb +66 -0
  48. data/app/services/spree/gift_cards/redeem.rb +17 -0
  49. data/app/services/spree/gift_cards/remove.rb +38 -0
  50. data/app/services/spree/products/prepare_nested_attributes.rb +2 -2
  51. data/app/views/spree/addresses/_form.html.erb +1 -1
  52. data/app/views/spree/shared/_payment.html.erb +4 -4
  53. data/config/locales/en.yml +44 -14
  54. data/config/routes.rb +1 -0
  55. data/db/migrate/20250506073057_create_spree_gift_cards_and_spree_gift_card_batches.rb +37 -0
  56. data/db/migrate/20250530101236_enable_pg_trgm_extension.rb +13 -0
  57. data/db/migrate/20250605131334_add_missing_fields_to_users.rb +25 -0
  58. data/lib/generators/spree/install/install_generator.rb +0 -8
  59. data/lib/spree/core/configuration.rb +4 -0
  60. data/lib/spree/core/controller_helpers/store.rb +13 -1
  61. data/lib/spree/core/dependencies.rb +5 -0
  62. data/lib/spree/core/engine.rb +7 -1
  63. data/lib/spree/core/version.rb +1 -1
  64. data/lib/spree/core.rb +2 -1
  65. data/lib/spree/permitted_attributes.rb +22 -9
  66. data/lib/spree/testing_support/authorization_helpers.rb +5 -9
  67. data/lib/spree/testing_support/common_rake.rb +7 -1
  68. data/lib/spree/testing_support/factories/gift_card_batch_factory.rb +5 -0
  69. data/lib/spree/testing_support/factories/gift_card_factory.rb +17 -0
  70. data/lib/spree/testing_support/factories/page_section_factory.rb +2 -1
  71. data/lib/spree/testing_support/factories/post_factory.rb +4 -0
  72. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +4 -0
  73. data/lib/spree/testing_support/factories/user_factory.rb +2 -2
  74. metadata +45 -11
  75. data/config/initializers/state_machine.rb +0 -37
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 40078ca0fe53f0e6a2af34f66cec79f80823d752aceabac637f5e247b32976a6
4
- data.tar.gz: 5ae9476bc4e0fee22c705e9b1979a4d29d1057066c20d017a8c956392d137712
3
+ metadata.gz: a357b0d9005bb8d7ed5b4b56ebe020d34435ac15a402db9d650880ae01c38127
4
+ data.tar.gz: 729183094fa42d6d5b00632e5b9d44b61958643aef7b42d1b7931dd98c87b0f6
5
5
  SHA512:
6
- metadata.gz: ff673f06e666ffcf7b987182bbc226c6663a645a7fac34d14ae79021f076972f21de05e27639e920885cd2478dc17a2f1f648bcfcbb29987909a71ab71913e6d
7
- data.tar.gz: e52971afaf599280aef97bbad4cc33d81621003770dd43edae06532bb86486584d0bfc758299896dbbb56a1f9fc71c34c68791b47bbe1ce8588ba786ec4a78b5
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 = width * 2 if width.present?
22
- height = height * 2 if height.present?
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
- main_app.cdn_image_url(
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
- main_app.cdn_image_url(
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, h = width.to_f, height.to_f
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
- ratio = h / w
59
- elsif h < w
60
- ratio = w / h
61
- else
62
- # h == w, square image
63
- ratio = 1.0
64
- end
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
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ module GiftCards
3
+ class BulkGenerateJob < ::Spree::BaseJob
4
+ queue_as Spree.queues.gift_cards
5
+
6
+ def perform(id)
7
+ gift_cards_batch = Spree::GiftCardBatch.find(id)
8
+
9
+ gift_cards_batch.create_gift_cards
10
+ end
11
+ end
12
+ end
13
+ 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
- if connected? && connection.extension_enabled?('pg_trgm')
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
@@ -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
@@ -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
- if Spree.always_use_translations?
14
- TRANSLATABLE_FIELDS = %i[name presentation].freeze
15
- translates(*TRANSLATABLE_FIELDS)
16
- else
17
- TRANSLATABLE_FIELDS = %i[presentation].freeze
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
- if Spree.always_use_translations?
11
- TRANSLATABLE_FIELDS = %i[name presentation].freeze
12
- translates(*TRANSLATABLE_FIELDS)
13
- else
14
- TRANSLATABLE_FIELDS = %i[presentation].freeze
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
- joins(:variants).
52
- where(Variant.table_name => { product_id: products.map(&:id) })
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.pluck(:id, :presentation).map do |id, presentation|
65
+ all.map do |ov|
66
66
  {
67
- id: 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