spree_core 5.0.4 → 5.1.0.beta
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/assets/images/logo.png +0 -0
- data/app/finders/spree/products/find.rb +28 -1
- data/app/finders/spree/taxons/find.rb +1 -1
- data/app/helpers/spree/integrations_helper.rb +15 -0
- data/app/javascript/spree/core/controllers/disable_submit_button_controller.js +19 -0
- data/app/mailers/spree/invitation_mailer.rb +24 -0
- data/app/models/concerns/spree/integrations_concern.rb +11 -0
- data/app/models/concerns/spree/product_scopes.rb +6 -6
- data/app/models/concerns/spree/translatable_resource.rb +4 -0
- data/app/models/concerns/spree/translatable_resource_scopes.rb +17 -3
- data/app/models/concerns/spree/unique_name.rb +2 -0
- data/app/models/concerns/spree/user_management.rb +33 -0
- data/app/models/concerns/spree/user_methods.rb +19 -0
- data/app/models/concerns/spree/user_roles.rb +43 -17
- data/app/models/spree/ability.rb +8 -6
- data/app/models/spree/asset.rb +1 -6
- data/app/models/spree/base.rb +1 -0
- data/app/models/spree/base_analytics_event_handler.rb +7 -2
- data/app/models/spree/classification.rb +13 -0
- data/app/models/spree/custom_domain.rb +2 -1
- data/app/models/spree/export.rb +1 -1
- data/app/models/spree/integration.rb +63 -0
- data/app/models/spree/invitation.rb +153 -0
- data/app/models/spree/invitations/store.rb +6 -0
- data/app/models/spree/order.rb +16 -5
- data/app/models/spree/order_merger.rb +7 -5
- data/app/models/spree/product_property.rb +1 -1
- data/app/models/spree/property.rb +3 -1
- data/app/models/spree/reports/sales_total.rb +5 -1
- data/app/models/spree/role.rb +16 -0
- data/app/models/spree/role_user.rb +32 -1
- data/app/models/spree/shipment_handler.rb +1 -0
- data/app/models/spree/shipping_method.rb +1 -1
- data/app/models/spree/store.rb +9 -4
- data/app/models/spree/store_credit_category.rb +4 -0
- data/app/models/spree/taxon.rb +4 -3
- data/app/services/spree/country_to_timezone.rb +273 -0
- data/app/services/spree/seeds/admin_user.rb +4 -2
- data/app/services/spree/seeds/all.rb +3 -1
- data/app/services/spree/seeds/digital_delivery.rb +20 -0
- data/app/services/spree/seeds/returns_environment.rb +27 -0
- data/app/services/spree/seeds/tax_categories.rb +12 -0
- data/app/services/spree/stores/settings_defaults_by_country.rb +38 -0
- data/app/services/spree/tags/bulk_add.rb +13 -7
- data/app/views/spree/invitation_mailer/invitation_accepted.html.erb +12 -0
- data/app/views/spree/invitation_mailer/invitation_email.html.erb +21 -0
- data/config/locales/en.yml +43 -9
- data/db/migrate/20250407085228_create_spree_integrations.rb +12 -0
- data/db/migrate/20250410061306_create_spree_invitations.rb +20 -0
- data/db/migrate/20250418174652_add_resource_to_spree_role_users.rb +8 -0
- data/db/migrate/20250508060800_add_selected_locale_to_spree_admin_users.rb +8 -0
- data/db/migrate/20250509143831_add_session_id_to_spree_assets.rb +5 -0
- data/lib/generators/spree/authentication/devise/devise_generator.rb +5 -2
- data/lib/generators/spree/authentication/devise/templates/authentication_helpers.rb.tt +3 -3
- data/lib/generators/spree/install/install_generator.rb +5 -0
- data/lib/spree/core/controller_helpers/auth.rb +15 -14
- data/lib/spree/core/controller_helpers/currency.rb +11 -0
- data/lib/spree/core/controller_helpers/strong_parameters.rb +3 -2
- data/lib/spree/core/engine.rb +13 -2
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +1 -0
- data/lib/spree/permitted_attributes.rb +118 -13
- data/lib/spree/testing_support/factories/integration_factory.rb +7 -0
- data/lib/spree/testing_support/factories/invitation_factory.rb +6 -0
- data/lib/spree/testing_support/factories/promotion_action_factory.rb +4 -0
- data/lib/spree/testing_support/factories/user_factory.rb +14 -1
- data/lib/spree/translation_migrations.rb +27 -15
- data/lib/tasks/core.rake +8 -0
- metadata +41 -4
@@ -0,0 +1,153 @@
|
|
1
|
+
module Spree
|
2
|
+
class Invitation < Spree.base_class
|
3
|
+
has_secure_token
|
4
|
+
acts_as_paranoid
|
5
|
+
|
6
|
+
#
|
7
|
+
# Virtual Attributes
|
8
|
+
#
|
9
|
+
attribute :skip_email, :boolean, default: false
|
10
|
+
|
11
|
+
#
|
12
|
+
# Associations
|
13
|
+
#
|
14
|
+
belongs_to :resource, polymorphic: true # eg. Store, Vendor, Account
|
15
|
+
belongs_to :inviter, polymorphic: true # User or AdminUser
|
16
|
+
belongs_to :invitee, polymorphic: true, optional: true # User or AdminUser
|
17
|
+
belongs_to :role, class_name: 'Spree::Role'
|
18
|
+
has_one :role_user, dependent: :destroy, class_name: 'Spree::RoleUser'
|
19
|
+
|
20
|
+
#
|
21
|
+
# Validations
|
22
|
+
#
|
23
|
+
validates :email, email: true, presence: true
|
24
|
+
validates :token, presence: true, uniqueness: true
|
25
|
+
validates :inviter, :resource, :role, presence: true
|
26
|
+
validate :invitee_is_not_inviter, on: :create
|
27
|
+
validate :invitee_already_exists, on: :create
|
28
|
+
|
29
|
+
#
|
30
|
+
# Scopes
|
31
|
+
#
|
32
|
+
scope :pending, -> { where(status: 'pending') }
|
33
|
+
scope :accepted, -> { where(status: 'accepted') }
|
34
|
+
scope :not_expired, -> { where('expires_at > ?', Time.current) }
|
35
|
+
|
36
|
+
#
|
37
|
+
# State Machine
|
38
|
+
#
|
39
|
+
state_machine initial: :pending, attribute: :status do
|
40
|
+
state :accepted do
|
41
|
+
validate :accept_invitation_within_time_limit
|
42
|
+
validates :invitee, presence: true
|
43
|
+
end
|
44
|
+
|
45
|
+
event :accept do
|
46
|
+
transition pending: :accepted
|
47
|
+
end
|
48
|
+
after_transition to: :accepted, do: :after_accept
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Callbacks
|
53
|
+
#
|
54
|
+
after_initialize :set_defaults, if: :new_record?
|
55
|
+
before_validation :set_invitee_from_email, on: :create
|
56
|
+
after_create :send_invitation_email, unless: :skip_email
|
57
|
+
|
58
|
+
# returns the store for the invitation
|
59
|
+
# if the resource is a store, return the resource
|
60
|
+
# if the resource responds to store, return the store
|
61
|
+
# otherwise, return the current store
|
62
|
+
# @return [Spree::Store]
|
63
|
+
def store
|
64
|
+
if resource.is_a?(Spree::Store)
|
65
|
+
resource
|
66
|
+
elsif resource.respond_to?(:store)
|
67
|
+
resource.store
|
68
|
+
else
|
69
|
+
Spree::Store.current
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# returns true if the invitation has expired
|
74
|
+
# @return [Boolean]
|
75
|
+
def expired?
|
76
|
+
expires_at < Time.current
|
77
|
+
end
|
78
|
+
|
79
|
+
# Resends the invitation email if the invitation is pending and not expired
|
80
|
+
def resend!
|
81
|
+
return if expired? || deleted? || accepted?
|
82
|
+
|
83
|
+
send_invitation_email
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# this method can be extended by developers now
|
89
|
+
def after_accept
|
90
|
+
create_role_user
|
91
|
+
set_accepted_at
|
92
|
+
send_acceptance_notification
|
93
|
+
end
|
94
|
+
|
95
|
+
def send_invitation_email
|
96
|
+
Spree::InvitationMailer.invitation_email(self).deliver_later
|
97
|
+
end
|
98
|
+
|
99
|
+
def send_acceptance_notification
|
100
|
+
Spree::InvitationMailer.invitation_accepted(self).deliver_later
|
101
|
+
end
|
102
|
+
|
103
|
+
def set_defaults
|
104
|
+
self.expires_at ||= 2.weeks.from_now
|
105
|
+
self.resource ||= Spree::Store.current
|
106
|
+
self.role ||= Spree::Role.default_admin_role
|
107
|
+
end
|
108
|
+
|
109
|
+
def invitee_is_not_inviter
|
110
|
+
if invitee == inviter
|
111
|
+
errors.add(:invitee, 'cannot be the same as the inviter')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def invitee_already_exists
|
116
|
+
return if resource.blank?
|
117
|
+
|
118
|
+
exists = if invitee.present?
|
119
|
+
resource.users.include?(invitee)
|
120
|
+
else
|
121
|
+
resource.users.exists?(email: email)
|
122
|
+
end
|
123
|
+
|
124
|
+
if exists
|
125
|
+
errors.add(:email, 'already exists')
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_accepted_at
|
130
|
+
update!(accepted_at: Time.current)
|
131
|
+
end
|
132
|
+
|
133
|
+
def create_role_user
|
134
|
+
return if invitee.blank?
|
135
|
+
|
136
|
+
role_user = resource.add_user(invitee, role)
|
137
|
+
self.role_user = role_user
|
138
|
+
save!
|
139
|
+
end
|
140
|
+
|
141
|
+
def set_invitee_from_email
|
142
|
+
return if invitee.present?
|
143
|
+
|
144
|
+
self.invitee = Spree.admin_user_class.find_by(email: email)
|
145
|
+
end
|
146
|
+
|
147
|
+
def accept_invitation_within_time_limit
|
148
|
+
if Time.current > expires_at
|
149
|
+
errors.add(:base, 'Invitation expired')
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/app/models/spree/order.rb
CHANGED
@@ -91,6 +91,8 @@ module Spree
|
|
91
91
|
acts_as_taggable_on :tags
|
92
92
|
acts_as_taggable_tenant :store_id
|
93
93
|
|
94
|
+
ASSOCIATED_USER_ATTRIBUTES = [:user_id, :email, :created_by_id, :bill_address_id, :ship_address_id]
|
95
|
+
|
94
96
|
belongs_to :user, class_name: "::#{Spree.user_class}", optional: true, autosave: true
|
95
97
|
belongs_to :created_by, class_name: "::#{Spree.admin_user_class}", optional: true
|
96
98
|
belongs_to :approver, class_name: "::#{Spree.admin_user_class}", optional: true
|
@@ -247,6 +249,13 @@ module Spree
|
|
247
249
|
pre_tax_item_amount + shipments.sum(:pre_tax_amount)
|
248
250
|
end
|
249
251
|
|
252
|
+
# Returns the subtotal used for analytics integrations
|
253
|
+
# It's a sum of the item total and the promo total
|
254
|
+
# @return [Float]
|
255
|
+
def analytics_subtotal
|
256
|
+
(item_total + line_items.sum(:promo_total)).to_f
|
257
|
+
end
|
258
|
+
|
250
259
|
def shipping_discount
|
251
260
|
shipment_adjustments.non_tax.eligible.sum(:amount) * - 1
|
252
261
|
end
|
@@ -355,7 +364,7 @@ module Spree
|
|
355
364
|
self.bill_address ||= user.bill_address
|
356
365
|
self.ship_address ||= user.ship_address
|
357
366
|
|
358
|
-
changes = slice(
|
367
|
+
changes = slice(*ASSOCIATED_USER_ATTRIBUTES)
|
359
368
|
|
360
369
|
# immediately persist the changes we just made, but don't use save
|
361
370
|
# since we might have an invalid address associated
|
@@ -364,6 +373,12 @@ module Spree
|
|
364
373
|
end
|
365
374
|
end
|
366
375
|
|
376
|
+
def disassociate_user!
|
377
|
+
nullified_attributes = ASSOCIATED_USER_ATTRIBUTES.index_with(nil)
|
378
|
+
|
379
|
+
update!(nullified_attributes)
|
380
|
+
end
|
381
|
+
|
367
382
|
def quantity_of(variant, options = {})
|
368
383
|
line_item = find_line_item_by_variant(variant, options)
|
369
384
|
line_item ? line_item.quantity : 0
|
@@ -815,10 +830,6 @@ module Spree
|
|
815
830
|
csv_lines
|
816
831
|
end
|
817
832
|
|
818
|
-
def all_line_items
|
819
|
-
line_items
|
820
|
-
end
|
821
|
-
|
822
833
|
private
|
823
834
|
|
824
835
|
def link_by_email
|
@@ -7,7 +7,7 @@ module Spree
|
|
7
7
|
@order = order
|
8
8
|
end
|
9
9
|
|
10
|
-
def merge!(other_order, user = nil)
|
10
|
+
def merge!(other_order, user = nil, discard_merged: true)
|
11
11
|
other_order.line_items.each do |other_order_line_item|
|
12
12
|
next unless other_order_line_item.currency == order.currency
|
13
13
|
|
@@ -16,12 +16,14 @@ module Spree
|
|
16
16
|
end
|
17
17
|
|
18
18
|
set_user(user)
|
19
|
-
clear_addresses(other_order)
|
19
|
+
clear_addresses(other_order) if discard_merged
|
20
20
|
persist_merge
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
if discard_merged
|
23
|
+
# So that the destroy doesn't take out line items which may have been re-assigned
|
24
|
+
other_order.line_items.reload
|
25
|
+
other_order.destroy
|
26
|
+
end
|
25
27
|
end
|
26
28
|
|
27
29
|
# Compare the line item of the other order with mine.
|
@@ -33,7 +33,7 @@ module Spree
|
|
33
33
|
scope :filterable, -> { joins(:property).where(Property.table_name => { filterable: true }) }
|
34
34
|
scope :for_products, ->(products) { joins(:product).merge(products) }
|
35
35
|
scope :sort_by_property_position, -> {
|
36
|
-
joins(:property).order("spree_properties.position ASC")
|
36
|
+
unscope(:order).joins(:property).order("spree_properties.position ASC")
|
37
37
|
}
|
38
38
|
|
39
39
|
self.whitelisted_ransackable_attributes = ['value', 'filter_param']
|
@@ -39,8 +39,10 @@ module Spree
|
|
39
39
|
KIND_OPTIONS = { short_text: 0, long_text: 1, number: 2, rich_text: 3 }.freeze
|
40
40
|
enum :kind, KIND_OPTIONS
|
41
41
|
|
42
|
+
DEPENDENCY_UPDATE_FIELDS = [:presentation, :name, :kind, :filterable, :display_on, :position].freeze
|
43
|
+
|
42
44
|
after_touch :touch_all_products
|
43
|
-
after_update :touch_all_products, if: -> { saved_changes.key?(
|
45
|
+
after_update :touch_all_products, if: -> { DEPENDENCY_UPDATE_FIELDS.any? { |field| saved_changes.key?(field) } }
|
44
46
|
after_save :ensure_product_properties_have_filter_params
|
45
47
|
|
46
48
|
self.whitelisted_ransackable_attributes = ['presentation', 'filterable']
|
@@ -2,12 +2,16 @@ module Spree
|
|
2
2
|
module Reports
|
3
3
|
class SalesTotal < Spree::Report
|
4
4
|
def line_items_scope
|
5
|
-
store.line_items.where(
|
5
|
+
scope = store.line_items.where(
|
6
6
|
order: Spree::Order.complete.where(
|
7
7
|
currency: currency,
|
8
8
|
completed_at: (date_from.to_time.beginning_of_day)..(date_to.to_time.end_of_day)
|
9
9
|
)
|
10
10
|
).includes(:order, variant: :product)
|
11
|
+
|
12
|
+
scope = scope.where(vendor_id: vendor.id) if defined?(vendor) && vendor.present?
|
13
|
+
|
14
|
+
scope
|
11
15
|
end
|
12
16
|
end
|
13
17
|
end
|
data/app/models/spree/role.rb
CHANGED
@@ -4,8 +4,24 @@ module Spree
|
|
4
4
|
|
5
5
|
ADMIN_ROLE = 'admin'
|
6
6
|
|
7
|
+
#
|
8
|
+
# Associations
|
9
|
+
#
|
7
10
|
has_many :role_users, class_name: 'Spree::RoleUser', dependent: :destroy
|
8
11
|
has_many :users, through: :role_users, source: :user, source_type: Spree.user_class.to_s
|
9
12
|
has_many :admin_users, through: :role_users, source: :user, source_type: Spree.admin_user_class.to_s
|
13
|
+
has_many :invitations, class_name: 'Spree::Invitation', dependent: :destroy
|
14
|
+
|
15
|
+
#
|
16
|
+
# Scopes
|
17
|
+
#
|
18
|
+
scope :admin, -> { where(name: ADMIN_ROLE) }
|
19
|
+
|
20
|
+
#
|
21
|
+
# Class Methods
|
22
|
+
#
|
23
|
+
def self.default_admin_role
|
24
|
+
find_or_create_by(name: ADMIN_ROLE)
|
25
|
+
end
|
10
26
|
end
|
11
27
|
end
|
@@ -1,6 +1,37 @@
|
|
1
1
|
module Spree
|
2
2
|
class RoleUser < Spree.base_class
|
3
|
-
|
3
|
+
#
|
4
|
+
# Associations
|
5
|
+
#
|
6
|
+
belongs_to :role, class_name: 'Spree::Role', foreign_key: :role_id
|
4
7
|
belongs_to :user, polymorphic: true
|
8
|
+
belongs_to :resource, polymorphic: true
|
9
|
+
belongs_to :invitation, class_name: 'Spree::Invitation', optional: true
|
10
|
+
|
11
|
+
#
|
12
|
+
# Validations
|
13
|
+
#
|
14
|
+
validates :role, presence: true
|
15
|
+
validates :user, presence: true
|
16
|
+
validates :resource, presence: true
|
17
|
+
validates :role_id, uniqueness: { scope: [:user_id, :resource_id, :user_type, :resource_type] }
|
18
|
+
|
19
|
+
#
|
20
|
+
# Delegations
|
21
|
+
#
|
22
|
+
delegate :name, to: :user
|
23
|
+
|
24
|
+
#
|
25
|
+
# Callbacks
|
26
|
+
#
|
27
|
+
before_validation :set_default_resource
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Set the default resource to the default store if the resource is not set
|
32
|
+
# this will allow a graceful migration from the old roles system to the new one
|
33
|
+
def set_default_resource
|
34
|
+
self.resource ||= Spree::Store.current
|
35
|
+
end
|
5
36
|
end
|
6
37
|
end
|
data/app/models/spree/store.rb
CHANGED
@@ -13,6 +13,7 @@ module Spree
|
|
13
13
|
include Spree::Stores::Socials
|
14
14
|
include Spree::Webhooks::HasWebhooks if defined?(Spree::Webhooks::HasWebhooks)
|
15
15
|
include Spree::Security::Stores if defined?(Spree::Security::Stores)
|
16
|
+
include Spree::UserManagement
|
16
17
|
|
17
18
|
#
|
18
19
|
# Magic methods
|
@@ -100,8 +101,10 @@ module Spree
|
|
100
101
|
has_many :custom_domains, class_name: 'Spree::CustomDomain', dependent: :destroy
|
101
102
|
has_one :default_custom_domain, -> { where(default: true) }, class_name: 'Spree::CustomDomain'
|
102
103
|
|
103
|
-
has_many :posts
|
104
|
-
has_many :post_categories
|
104
|
+
has_many :posts, class_name: 'Spree::Post'
|
105
|
+
has_many :post_categories, class_name: 'Spree::PostCategory'
|
106
|
+
|
107
|
+
has_many :integrations, class_name: 'Spree::Integration'
|
105
108
|
|
106
109
|
#
|
107
110
|
# Page Builder associations
|
@@ -342,7 +345,7 @@ module Spree
|
|
342
345
|
# @return [Spree::StockLocation]
|
343
346
|
def default_stock_location
|
344
347
|
@default_stock_location ||= begin
|
345
|
-
stock_location_scope = Spree::StockLocation.
|
348
|
+
stock_location_scope = Spree::StockLocation.where(default: true)
|
346
349
|
stock_location_scope.first || ActiveRecord::Base.connected_to(role: :writing) do
|
347
350
|
stock_location_scope.create(default: true, name: Spree.t(:default_stock_location_name), country: default_country)
|
348
351
|
end
|
@@ -350,7 +353,9 @@ module Spree
|
|
350
353
|
end
|
351
354
|
|
352
355
|
def admin_users
|
353
|
-
|
356
|
+
Spree::Deprecation.warn('Store#admin_users is deprecated and will be removed in Spree 6.0. Please use Store#users instead.')
|
357
|
+
|
358
|
+
users
|
354
359
|
end
|
355
360
|
|
356
361
|
def favicon
|
data/app/models/spree/taxon.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
# TODO: let friendly id take care of sanitizing the url
|
2
1
|
require 'stringex'
|
3
2
|
|
4
3
|
module Spree
|
@@ -96,9 +95,11 @@ module Spree
|
|
96
95
|
if Spree.use_translations?
|
97
96
|
joins(:taxonomy).
|
98
97
|
join_translation_table(Taxonomy).
|
99
|
-
where(
|
98
|
+
where(
|
99
|
+
Taxonomy.arel_table_alias[:name].lower.matches(taxonomy_name.downcase.strip)
|
100
|
+
)
|
100
101
|
else
|
101
|
-
joins(:taxonomy).where(
|
102
|
+
joins(:taxonomy).where(Spree::Taxonomy.arel_table[:name].lower.matches(taxonomy_name.downcase.strip))
|
102
103
|
end
|
103
104
|
}
|
104
105
|
|