spree_core 6.0.0.beta2 → 6.0.0.beta3

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/app/jobs/spree/payments/handle_webhook_job.rb +1 -0
  3. data/app/jobs/spree/sample_data/load_job.rb +7 -2
  4. data/app/jobs/spree/seller_transfers/execute_pending_job.rb +1 -0
  5. data/app/mailers/spree/admin_user_mailer.rb +2 -2
  6. data/app/models/concerns/spree/customer_methods.rb +17 -1
  7. data/app/models/concerns/spree/has_number.rb +3 -2
  8. data/app/models/concerns/spree/prefixed_id.rb +4 -1
  9. data/app/models/concerns/spree/purchase/totals.rb +3 -1
  10. data/app/models/concerns/spree/ransackable_attributes.rb +38 -6
  11. data/app/models/concerns/spree/translatable_resource.rb +6 -7
  12. data/app/models/concerns/spree/webhook_payload_redaction.rb +132 -21
  13. data/app/models/spree/adjusters/promotion.rb +1 -1
  14. data/app/models/spree/authentication/lockout.rb +34 -0
  15. data/app/models/spree/authentication/strategies/email_password_strategy.rb +4 -12
  16. data/app/models/spree/authentication/strategies/oidc_strategy.rb +1 -1
  17. data/app/models/spree/claim.rb +1 -0
  18. data/app/models/spree/commission_line.rb +4 -2
  19. data/app/models/spree/commission_rule.rb +1 -1
  20. data/app/models/spree/coupon_code.rb +14 -2
  21. data/app/models/spree/current.rb +15 -0
  22. data/app/models/spree/exchange.rb +1 -0
  23. data/app/models/spree/export.rb +8 -5
  24. data/app/models/spree/exports/customers.rb +2 -2
  25. data/app/models/spree/fulfillment_group.rb +127 -0
  26. data/app/models/spree/gift_card.rb +9 -0
  27. data/app/models/spree/invitation.rb +18 -5
  28. data/app/models/spree/number_generators/sequential.rb +2 -2
  29. data/app/models/spree/option_value.rb +7 -0
  30. data/app/models/spree/order.rb +35 -12
  31. data/app/models/spree/order_group.rb +78 -6
  32. data/app/models/spree/payment_source.rb +1 -1
  33. data/app/models/spree/payment_split.rb +24 -0
  34. data/app/models/spree/price.rb +74 -0
  35. data/app/models/spree/product.rb +4 -0
  36. data/app/models/spree/product_option_type.rb +11 -0
  37. data/app/models/spree/promotion/actions/create_line_items.rb +92 -30
  38. data/app/models/spree/promotion/rules/category.rb +14 -9
  39. data/app/models/spree/promotion/rules/product.rb +17 -1
  40. data/app/models/spree/promotion.rb +1 -1
  41. data/app/models/spree/promotion_action.rb +12 -1
  42. data/app/models/spree/promotion_handler/coupon.rb +50 -5
  43. data/app/models/spree/promotion_rule.rb +14 -0
  44. data/app/models/spree/return.rb +16 -6
  45. data/app/models/spree/return_line_item.rb +19 -0
  46. data/app/models/spree/search_provider/database.rb +10 -2
  47. data/app/models/spree/seller_payout.rb +3 -1
  48. data/app/models/spree/seller_transfer.rb +3 -1
  49. data/app/models/spree/store_credit.rb +4 -0
  50. data/app/models/spree/translations/batch.rb +4 -2
  51. data/app/models/spree/user_identity.rb +10 -6
  52. data/app/models/spree/variant.rb +8 -3
  53. data/app/models/spree/webhook_delivery.rb +28 -0
  54. data/app/models/spree/webhook_endpoint.rb +14 -0
  55. data/app/presenters/spree/csv/order_line_item_presenter.rb +2 -2
  56. data/app/services/spree/carts/split_by_seller.rb +22 -12
  57. data/app/services/spree/companies/add_member.rb +8 -2
  58. data/app/services/spree/imports/row_processors/price_list_price.rb +6 -0
  59. data/app/services/spree/imports/row_processors/product_translation.rb +1 -1
  60. data/app/services/spree/imports/row_processors/product_variant.rb +17 -11
  61. data/app/services/spree/order_groups/allocate_payments.rb +3 -0
  62. data/app/services/spree/orders/build_fulfillments.rb +6 -0
  63. data/app/services/spree/orders/update_statuses.rb +8 -2
  64. data/app/services/spree/prices/bulk_upsert.rb +186 -47
  65. data/app/services/spree/sample_data/import_builder.rb +1 -1
  66. data/app/services/spree/sample_data/loader.rb +54 -40
  67. data/app/services/spree/seeds/all.rb +1 -26
  68. data/app/services/spree/seeds/allowed_origins.rb +5 -4
  69. data/app/services/spree/seeds/api_keys.rb +10 -9
  70. data/app/services/spree/seeds/channels.rb +9 -8
  71. data/app/services/spree/seeds/commission_rates.rb +21 -20
  72. data/app/services/spree/seeds/customer_groups.rb +5 -4
  73. data/app/services/spree/seeds/digital_delivery.rb +7 -8
  74. data/app/services/spree/seeds/payment_methods.rb +12 -11
  75. data/app/services/spree/seeds/product_types.rb +7 -8
  76. data/app/services/spree/seeds/returns_environment.rb +12 -11
  77. data/app/services/spree/seeds/roles.rb +8 -7
  78. data/app/services/spree/seeds/saved_reports.rb +19 -20
  79. data/app/services/spree/seeds/seller_requirements.rb +5 -4
  80. data/app/services/spree/seeds/store_resources.rb +41 -0
  81. data/app/services/spree/seeds/store_scoped.rb +15 -0
  82. data/app/services/spree/seeds/tax_categories.rb +10 -9
  83. data/app/services/spree/stock_locations/stock_items/create.rb +1 -1
  84. data/app/services/spree/stock_locations/stock_levels/create.rb +19 -3
  85. data/app/subscribers/spree/order_status_subscriber.rb +15 -7
  86. data/app/workflows/spree/carts/add_item.rb +5 -7
  87. data/app/workflows/spree/carts/complete.rb +20 -64
  88. data/app/workflows/spree/carts/recalculate_totals.rb +3 -1
  89. data/app/workflows/spree/claims/create.rb +31 -1
  90. data/app/workflows/spree/claims/resolve.rb +7 -11
  91. data/app/workflows/spree/exchanges/create.rb +11 -0
  92. data/app/workflows/spree/exchanges/fulfill.rb +21 -13
  93. data/app/workflows/spree/orders/complete.rb +99 -2
  94. data/app/workflows/spree/price_lists/update.rb +17 -0
  95. data/app/workflows/spree/refunds/order_payments.rb +55 -1
  96. data/app/workflows/spree/returns/create.rb +9 -15
  97. data/app/workflows/spree/returns/refund.rb +15 -24
  98. data/app/workflows/spree/returns/returnable_quantity.rb +53 -0
  99. data/app/workflows/spree/sellers/invite.rb +1 -1
  100. data/config/locales/en.yml +7 -119
  101. data/db/migrate/20260916000001_add_email_delivery_flags_to_spree_order_groups.rb +14 -0
  102. data/db/migrate/20260922000001_add_refunded_order_to_spree_store_credits.rb +5 -0
  103. data/db/migrate/20260923000001_change_spree_user_identities_tokens_to_text.rb +10 -0
  104. data/db/sample_data/channels.rb +1 -1
  105. data/db/sample_data/collections.rb +1 -1
  106. data/db/sample_data/custom_field_definitions.rb +1 -1
  107. data/db/sample_data/digital_products.rb +1 -1
  108. data/db/sample_data/fulfillment.rb +1 -1
  109. data/db/sample_data/options.rb +4 -2
  110. data/db/sample_data/orders.rb +9 -10
  111. data/db/sample_data/payment_methods.rb +18 -18
  112. data/db/sample_data/product_type_categories.rb +1 -1
  113. data/db/sample_data/product_types.rb +2 -2
  114. data/db/sample_data/promotions.rb +2 -4
  115. data/db/sample_data/wholesale.rb +1 -1
  116. data/lib/generators/spree/api_resource/api_resource_generator.rb +58 -1
  117. data/lib/generators/spree/api_resource/templates/admin_controller.rb.tt +6 -5
  118. data/lib/generators/spree/api_resource/templates/permissions.en.yml.tt +6 -0
  119. data/lib/generators/spree/api_resource/templates/store_controller.rb.tt +2 -2
  120. data/lib/generators/spree/dummy/templates/rails/test.rb +7 -0
  121. data/lib/generators/spree/subscriber/subscriber_generator.rb +3 -3
  122. data/lib/spree/core/engine.rb +8 -8
  123. data/lib/spree/core/version.rb +1 -1
  124. data/lib/spree/iso_data.rb +23 -4
  125. data/lib/spree/store_scope_guard.rb +16 -1
  126. data/lib/spree/testing_support/factories/invitation_factory.rb +1 -0
  127. data/lib/spree/testing_support/factories/order_group_factory.rb +49 -0
  128. data/lib/spree/testing_support/factories/payment_factory.rb +7 -2
  129. data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +34 -0
  130. data/lib/tasks/sample_data.rake +8 -2
  131. data/lib/tasks/upgrade.rake +6 -21
  132. metadata +13 -4
@@ -4,65 +4,79 @@ module Spree
4
4
  prepend Spree::ServiceModule::Base
5
5
  include Spree::SampleData::Helper
6
6
 
7
- def call
7
+ # Loads the demo data into one store. The sample files and the imports
8
+ # they run read `Spree::Current.store`, so it is set for the whole run
9
+ # and restored afterwards.
10
+ #
11
+ # @param store [Spree::Store, nil] the store to load into; defaults to
12
+ # the default store, resolved after the seeds so a bare install works
13
+ # @return [Spree::ServiceModule::Result]
14
+ def call(store: nil)
8
15
  Spree::Events.disable do
9
16
  without_geocoding do
10
17
  ActiveRecord::Base.no_touching do
11
18
  puts 'Running seeds first...' unless seeds_loaded?
12
19
  ensure_seeds_loaded
13
20
 
14
- puts 'Loading sample configuration data...'
15
- load_configuration_data
21
+ @store = store || Spree::Store.default
22
+ Spree::Current.set(store: @store) { load_sample_data }
23
+ end
24
+ end
25
+ end
26
+ end
16
27
 
17
- puts 'Loading sample channels...'
18
- load_ruby_file('channels')
28
+ private
19
29
 
20
- puts 'Loading sample custom_field definitions...'
21
- load_ruby_file('custom_field_definitions')
30
+ attr_reader :store
22
31
 
23
- puts 'Loading sample options...'
24
- load_ruby_file('options')
32
+ def load_sample_data
33
+ puts 'Loading sample configuration data...'
34
+ load_configuration_data
25
35
 
26
- puts 'Loading sample product types...'
27
- load_ruby_file('product_types')
36
+ puts 'Loading sample channels...'
37
+ load_ruby_file('channels')
28
38
 
29
- puts 'Loading sample products...'
30
- load_products
39
+ puts 'Loading sample custom_field definitions...'
40
+ load_ruby_file('custom_field_definitions')
31
41
 
32
- puts 'Attaching the sample digital product file...'
33
- load_ruby_file('digital_products')
42
+ puts 'Loading sample options...'
43
+ load_ruby_file('options')
34
44
 
35
- puts 'Publishing sample products on the default channel...'
36
- publish_sample_products(Spree::Store.default)
45
+ puts 'Loading sample product types...'
46
+ load_ruby_file('product_types')
37
47
 
38
- puts 'Loading sample product translations...'
39
- load_product_translations
48
+ puts 'Loading sample products...'
49
+ load_products
40
50
 
41
- puts 'Linking product types to categories...'
42
- load_ruby_file('product_type_categories')
51
+ puts 'Attaching the sample digital product file...'
52
+ load_ruby_file('digital_products')
43
53
 
44
- puts 'Loading sample collections...'
45
- load_ruby_file('collections')
54
+ puts 'Publishing sample products on every channel...'
55
+ publish_sample_products(store)
46
56
 
47
- puts 'Loading sample customers...'
48
- load_customers
57
+ puts 'Loading sample product translations...'
58
+ load_product_translations
49
59
 
50
- puts 'Loading wholesale demo data...'
51
- load_ruby_file('wholesale')
60
+ puts 'Linking product types to categories...'
61
+ load_ruby_file('product_type_categories')
52
62
 
53
- puts 'Loading sample orders...'
54
- load_ruby_file('orders')
63
+ puts 'Loading sample collections...'
64
+ load_ruby_file('collections')
55
65
 
56
- puts 'Loading sample posts...'
57
- load_ruby_file('posts')
66
+ puts 'Loading sample customers...'
67
+ load_customers
58
68
 
59
- puts 'Sample data loaded successfully!'
60
- end
61
- end
62
- end
63
- end
69
+ puts 'Loading wholesale demo data...'
70
+ load_ruby_file('wholesale')
64
71
 
65
- private
72
+ puts 'Loading sample orders...'
73
+ load_ruby_file('orders')
74
+
75
+ puts 'Loading sample posts...'
76
+ load_ruby_file('posts')
77
+
78
+ puts 'Sample data loaded successfully!'
79
+ end
66
80
 
67
81
  def load_configuration_data
68
82
  load_ruby_file('fulfillment')
@@ -72,19 +86,19 @@ module Spree
72
86
 
73
87
  def load_products
74
88
  csv_path = sample_data_path.join('products.csv')
75
- Spree::SampleData::ImportRunner.call(csv_path: csv_path, import_class: Spree::Imports::Products, inline: true)
89
+ Spree::SampleData::ImportRunner.call(csv_path: csv_path, import_class: Spree::Imports::Products, store: store, inline: true)
76
90
  end
77
91
 
78
92
  def load_product_translations
79
93
  csv_path = sample_data_path.join('product_translations.csv')
80
94
  return unless csv_path.exist?
81
95
 
82
- Spree::SampleData::ImportRunner.call(csv_path: csv_path, import_class: Spree::Imports::ProductTranslations, inline: true)
96
+ Spree::SampleData::ImportRunner.call(csv_path: csv_path, import_class: Spree::Imports::ProductTranslations, store: store, inline: true)
83
97
  end
84
98
 
85
99
  def load_customers
86
100
  csv_path = sample_data_path.join('customers.csv')
87
- Spree::SampleData::ImportRunner.call(csv_path: csv_path, import_class: Spree::Imports::Customers, inline: true)
101
+ Spree::SampleData::ImportRunner.call(csv_path: csv_path, import_class: Spree::Imports::Customers, store: store, inline: true)
88
102
  end
89
103
  end
90
104
  end
@@ -10,39 +10,14 @@ module Spree
10
10
  # countries gem, and zones are migration-only, so none of them
11
11
  # are seeded.
12
12
 
13
- # additional data
14
- TaxCategories.call
15
-
16
- # store & channels
17
13
  Stores.call
18
- Channels.call
19
- # Roles are store-owned, so they can only be seeded once a store
20
- # exists — every store gets its own immutable admin role.
21
- Roles.call
22
- # Store-scoped, so it can only run once a store exists.
23
- DigitalDelivery.call
14
+ Spree::Store.find_each { |store| StoreResources.call(store: store) }
24
15
  # The warehouse, delivery zones and pickup are not seeded: their
25
16
  # shape depends on which country the shop sells from, and nobody
26
17
  # has answered that yet. Spree::Stores::ProvisionDefaults builds
27
18
  # them from the merchant's answer — at first-run setup, or here
28
19
  # when ADMIN_EMAIL/ADMIN_PASSWORD name an install that skips it.
29
20
  AdminUser.call
30
-
31
- # add store resources
32
- PaymentMethods.call
33
- TaxCategories.call
34
- ProductTypes.call
35
- CustomerGroups.call
36
- ReturnsEnvironment.call
37
- # The marketplace's catch-all rate, at the bottom of the list so
38
- # narrower rates added later resolve ahead of it.
39
- CommissionRates.call
40
- # What a new seller has to do before this marketplace will let
41
- # them trade. Runs after stores for the same reason roles do.
42
- SellerRequirements.call
43
- ApiKeys.call
44
- SavedReports.call
45
- AllowedOrigins.call
46
21
  end
47
22
  end
48
23
  end
@@ -2,11 +2,12 @@ module Spree
2
2
  module Seeds
3
3
  class AllowedOrigins
4
4
  prepend Spree::ServiceModule::Base
5
+ include StoreScoped
5
6
 
6
- def call
7
- Spree::Store.all.each do |store|
8
- store.allowed_origins.find_or_create_by!(origin: 'http://localhost')
9
- end
7
+ private
8
+
9
+ def seed(store)
10
+ store.allowed_origins.find_or_create_by!(origin: 'http://localhost')
10
11
  end
11
12
  end
12
13
  end
@@ -2,17 +2,18 @@ module Spree
2
2
  module Seeds
3
3
  class ApiKeys
4
4
  prepend Spree::ServiceModule::Base
5
+ include StoreScoped
5
6
 
6
- def call
7
- Spree::Store.find_each do |store|
8
- unless store.api_keys.active.publishable.where(channel_id: nil).exists?
9
- store.api_keys.create!(name: 'Default', key_type: 'publishable')
10
- end
7
+ private
11
8
 
12
- wholesale = store.channels.find_by(code: Channels::WHOLESALE_CODE)
13
- if wholesale && !store.api_keys.active.publishable.where(channel: wholesale).exists?
14
- store.api_keys.create!(name: 'Storefront (Wholesale)', key_type: 'publishable', channel: wholesale)
15
- end
9
+ def seed(store)
10
+ unless store.api_keys.active.publishable.where(channel_id: nil).exists?
11
+ store.api_keys.create!(name: 'Default', key_type: 'publishable')
12
+ end
13
+
14
+ wholesale = store.channels.find_by(code: Channels::WHOLESALE_CODE)
15
+ if wholesale && !store.api_keys.active.publishable.where(channel: wholesale).exists?
16
+ store.api_keys.create!(name: 'Storefront (Wholesale)', key_type: 'publishable', channel: wholesale)
16
17
  end
17
18
  end
18
19
  end
@@ -6,18 +6,19 @@ module Spree
6
6
  # See docs/plans/5.6-store-channel-context-and-key-binding.md.
7
7
  class Channels
8
8
  prepend Spree::ServiceModule::Base
9
+ include StoreScoped
9
10
 
10
11
  WHOLESALE_CODE = 'wholesale'.freeze
11
12
 
12
- def call
13
- Spree::Store.find_each do |store|
14
- store.ensure_default_channel
13
+ private
15
14
 
16
- store.channels.find_or_create_by!(code: WHOLESALE_CODE) do |channel|
17
- channel.name = 'Wholesale'
18
- channel.preferred_storefront_access = 'login_required'
19
- channel.preferred_guest_checkout = false
20
- end
15
+ def seed(store)
16
+ store.ensure_default_channel
17
+
18
+ store.channels.find_or_create_by!(code: WHOLESALE_CODE) do |channel|
19
+ channel.name = 'Wholesale'
20
+ channel.preferred_storefront_access = 'login_required'
21
+ channel.preferred_guest_checkout = false
21
22
  end
22
23
  end
23
24
  end
@@ -22,31 +22,32 @@ module Spree
22
22
  # setting the value is the operator's first commission decision.
23
23
  class CommissionRates
24
24
  prepend Spree::ServiceModule::Base
25
+ include StoreScoped
25
26
 
26
27
  DEFAULT_CODE = 'marketplace-default'.freeze
27
28
 
28
- def call
29
- Spree::Store.find_each do |store|
30
- # Live rows only, matching the partial unique index: an operator who
31
- # deleted the seeded fallback gets it back on the next run rather
32
- # than being left with no bottom-of-list catch-all forever.
33
- next if store.commission_rates.exists?(code: DEFAULT_CODE)
29
+ private
34
30
 
35
- rate = store.commission_rates.create!(
36
- name: Spree.t('seed.commission_rates.marketplace_default'),
37
- code: DEFAULT_CODE,
38
- kind: 'percentage',
39
- value: 0,
40
- enabled: false
41
- )
31
+ def seed(store)
32
+ # Live rows only, matching the partial unique index: an operator who
33
+ # deleted the seeded fallback gets it back on the next run rather
34
+ # than being left with no bottom-of-list catch-all forever.
35
+ return if store.commission_rates.exists?(code: DEFAULT_CODE)
42
36
 
43
- # New rates are born at the top, which is right for one an operator
44
- # adds and wrong for this one: it matches every sale, so anywhere but
45
- # the bottom leaves the rates below it unreachable. A store seeded
46
- # after it already has rates would otherwise have them all shadowed
47
- # the moment this was enabled.
48
- rate.move_to_bottom
49
- end
37
+ rate = store.commission_rates.create!(
38
+ name: Spree.t('seed.commission_rates.marketplace_default'),
39
+ code: DEFAULT_CODE,
40
+ kind: 'percentage',
41
+ value: 0,
42
+ enabled: false
43
+ )
44
+
45
+ # New rates are born at the top, which is right for one an operator
46
+ # adds and wrong for this one: it matches every sale, so anywhere but
47
+ # the bottom leaves the rates below it unreachable. A store seeded
48
+ # after it already has rates would otherwise have them all shadowed
49
+ # the moment this was enabled.
50
+ rate.move_to_bottom
50
51
  end
51
52
  end
52
53
  end
@@ -5,13 +5,14 @@ module Spree
5
5
  # wholesale price lists key off.
6
6
  class CustomerGroups
7
7
  prepend Spree::ServiceModule::Base
8
+ include StoreScoped
8
9
 
9
10
  WHOLESALE_NAME = 'Wholesale'.freeze
10
11
 
11
- def call
12
- Spree::Store.find_each do |store|
13
- store.customer_groups.find_or_create_by!(name: WHOLESALE_NAME)
14
- end
12
+ private
13
+
14
+ def seed(store)
15
+ store.customer_groups.find_or_create_by!(name: WHOLESALE_NAME)
15
16
  end
16
17
  end
17
18
  end
@@ -2,16 +2,11 @@ module Spree
2
2
  module Seeds
3
3
  class DigitalDelivery
4
4
  prepend Spree::ServiceModule::Base
5
-
6
- def call
7
- Spree::Store.all.find_each do |store|
8
- create_for(store)
9
- end
10
- end
5
+ include StoreScoped
11
6
 
12
7
  private
13
8
 
14
- def create_for(store)
9
+ def seed(store)
15
10
  # Digital goods are a distinct product set, so they get their own
16
11
  # profile; assigning a product to it is what makes it digital.
17
12
  profile = Spree::DeliveryProfiles::Digital.find_by(store: store) ||
@@ -22,7 +17,11 @@ module Spree
22
17
  digital_delivery_method.delivery_profile = profile
23
18
  digital_delivery_method.storefront_visible = true
24
19
  digital_delivery_method.fulfillment_provider = 'Spree::FulfillmentProvider::Digital'
25
- digital_delivery_method.calculator ||= Spree::Calculator::Shipping::DigitalDelivery.create!
20
+ # Explicit, because the calculator's own default is the default
21
+ # store's currency rather than this store's.
22
+ digital_delivery_method.calculator ||= Spree::Calculator::Shipping::DigitalDelivery.create!(
23
+ preferred_currency: store.default_currency
24
+ )
26
25
  digital_delivery_method.save!
27
26
  end
28
27
  end
@@ -2,19 +2,20 @@ module Spree
2
2
  module Seeds
3
3
  class PaymentMethods
4
4
  prepend Spree::ServiceModule::Base
5
+ include StoreScoped
5
6
 
6
- def call
7
- Spree::Store.all.find_each do |store|
8
- payment_method = store.payment_methods.find_or_initialize_by(
9
- type: 'Spree::PaymentMethod::StoreCredit'
10
- )
11
- next if payment_method.persisted?
7
+ private
12
8
 
13
- payment_method.name = Spree.t(:store_credit_name)
14
- payment_method.description = Spree.t(:store_credit_name)
15
- payment_method.active = true
16
- payment_method.save!
17
- end
9
+ def seed(store)
10
+ payment_method = store.payment_methods.find_or_initialize_by(
11
+ type: 'Spree::PaymentMethod::StoreCredit'
12
+ )
13
+ return if payment_method.persisted?
14
+
15
+ payment_method.name = Spree.t(:store_credit_name)
16
+ payment_method.description = Spree.t(:store_credit_name)
17
+ payment_method.active = true
18
+ payment_method.save!
18
19
  end
19
20
  end
20
21
  end
@@ -2,18 +2,17 @@ module Spree
2
2
  module Seeds
3
3
  class ProductTypes
4
4
  prepend Spree::ServiceModule::Base
5
+ include StoreScoped
5
6
 
6
- def call
7
- Spree::Store.all.find_each do |store|
8
- Spree::ProductType.where(store: store).find_or_create_by!(name: I18n.t('spree.seed.product_types.default'))
9
- Spree::ProductType.where(store: store).find_or_create_by!(name: I18n.t('spree.seed.product_types.digital')) do |product_type|
10
- product_type.delivery_profile = digital_profile_for(store)
11
- end
7
+ private
8
+
9
+ def seed(store)
10
+ Spree::ProductType.where(store: store).find_or_create_by!(name: I18n.t('spree.seed.product_types.default'))
11
+ Spree::ProductType.where(store: store).find_or_create_by!(name: I18n.t('spree.seed.product_types.digital')) do |product_type|
12
+ product_type.delivery_profile = digital_profile_for(store)
12
13
  end
13
14
  end
14
15
 
15
- private
16
-
17
16
  def digital_profile_for(store)
18
17
  Spree::DeliveryProfiles::Digital.find_by(store: store) ||
19
18
  Spree::DeliveryProfiles::Digital.create!(store: store, name: I18n.t('spree.seed.delivery_profiles.digital'))
@@ -2,6 +2,7 @@ module Spree
2
2
  module Seeds
3
3
  class ReturnsEnvironment
4
4
  prepend Spree::ServiceModule::Base
5
+ include StoreScoped
5
6
 
6
7
  RETURN_REASONS = [
7
8
  'Better price available',
@@ -37,18 +38,18 @@ module Spree
37
38
  'Item not as described'
38
39
  ].freeze
39
40
 
40
- def call
41
- # Every vocabulary is store-owned, so every store gets its own.
42
- Spree::Store.all.find_each do |store|
43
- # Through the model helper, not a literal — the workflows look this
44
- # row up by the same constant, so a hard-coded string here would
45
- # silently create a second, unfindable reason if the name ever moves.
46
- Spree::RefundReason.return_processing_reason(store)
41
+ private
47
42
 
48
- RETURN_REASONS.each { |name| Spree::ReturnReason.where(store: store).find_or_create_by!(name: name) }
49
- CLAIM_REASONS.each { |name| Spree::ClaimReason.where(store: store).find_or_create_by!(name: name) }
50
- ORDER_CANCELLATION_REASONS.each { |name| Spree::OrderCancellationReason.where(store: store).find_or_create_by!(name: name) }
51
- end
43
+ # Every vocabulary is store-owned, so every store gets its own.
44
+ def seed(store)
45
+ # Through the model helper, not a literal — the workflows look this
46
+ # row up by the same constant, so a hard-coded string here would
47
+ # silently create a second, unfindable reason if the name ever moves.
48
+ Spree::RefundReason.return_processing_reason(store)
49
+
50
+ RETURN_REASONS.each { |name| Spree::ReturnReason.where(store: store).find_or_create_by!(name: name) }
51
+ CLAIM_REASONS.each { |name| Spree::ClaimReason.where(store: store).find_or_create_by!(name: name) }
52
+ ORDER_CANCELLATION_REASONS.each { |name| Spree::OrderCancellationReason.where(store: store).find_or_create_by!(name: name) }
52
53
  end
53
54
  end
54
55
  end
@@ -1,15 +1,16 @@
1
1
  module Spree
2
2
  module Seeds
3
+ # A role belongs to what it governs, so every store gets its own
4
+ # immutable admin.
3
5
  class Roles
4
6
  prepend Spree::ServiceModule::Base
7
+ include StoreScoped
5
8
 
6
- # A role belongs to what it governs, so every store gets its own
7
- # immutable admin.
8
- def call
9
- Spree::Store.find_each do |store|
10
- admin = Spree::Role.default_admin_role(store)
11
- admin.update_column(:mutable, false) if admin.read_attribute(:mutable)
12
- end
9
+ private
10
+
11
+ def seed(store)
12
+ admin = Spree::Role.default_admin_role(store)
13
+ admin.update_column(:mutable, false) if admin.read_attribute(:mutable)
13
14
  end
14
15
  end
15
16
  end
@@ -6,6 +6,7 @@ module Spree
6
6
  # only never-created ones are added.
7
7
  class SavedReports
8
8
  prepend Spree::ServiceModule::Base
9
+ include StoreScoped
9
10
 
10
11
  REPORTS = [
11
12
  { key: 'sales_over_time',
@@ -70,31 +71,29 @@ module Spree
70
71
  'time_range' => { 'preset' => 'last_4_weeks' } } }
71
72
  ].freeze
72
73
 
73
- def call
74
- Spree::Store.find_each do |store|
75
- # Compared case-insensitively like the model's uniqueness rule, so a
76
- # merchant's own "top products" never makes a re-seed raise.
77
- existing = store.saved_reports.pluck(:name).map(&:downcase).to_set
74
+ private
75
+
76
+ def seed(store)
77
+ # Compared case-insensitively like the model's uniqueness rule, so a
78
+ # merchant's own "top products" never makes a re-seed raise.
79
+ existing = store.saved_reports.pluck(:name).map(&:downcase).to_set
78
80
 
79
- REPORTS.each do |report|
80
- # Every locale's name for this report, not just the current one:
81
- # the name is translated, so a store seeded in one language and
82
- # re-seeded in another would otherwise recognise none of its own
83
- # built-ins and create a second full set.
84
- next if known_names(report[:key]).intersect?(existing)
81
+ REPORTS.each do |report|
82
+ # Every locale's name for this report, not just the current one:
83
+ # the name is translated, so a store seeded in one language and
84
+ # re-seeded in another would otherwise recognise none of its own
85
+ # built-ins and create a second full set.
86
+ next if known_names(report[:key]).intersect?(existing)
85
87
 
86
- store.saved_reports.create!(
87
- name: Spree.t("reporting.seeds.#{report[:key]}.name"),
88
- description: Spree.t("reporting.seeds.#{report[:key]}.description"),
89
- query: report[:query],
90
- seeded: true
91
- )
92
- end
88
+ store.saved_reports.create!(
89
+ name: Spree.t("reporting.seeds.#{report[:key]}.name"),
90
+ description: Spree.t("reporting.seeds.#{report[:key]}.description"),
91
+ query: report[:query],
92
+ seeded: true
93
+ )
93
94
  end
94
95
  end
95
96
 
96
- private
97
-
98
97
  # The downcased name this report carries in every locale core ships.
99
98
  def known_names(key)
100
99
  @known_names ||= {}
@@ -8,11 +8,12 @@ module Spree
8
8
  # a licence) is theirs to add, and there is no sensible default for it.
9
9
  class SellerRequirements
10
10
  prepend Spree::ServiceModule::Base
11
+ include StoreScoped
11
12
 
12
- def call
13
- Spree::Store.find_each do |store|
14
- Spree::SellerRequirement.provision_defaults(store)
15
- end
13
+ private
14
+
15
+ def seed(store)
16
+ Spree::SellerRequirement.provision_defaults(store)
16
17
  end
17
18
  end
18
19
  end
@@ -0,0 +1,41 @@
1
+ module Spree
2
+ module Seeds
3
+ # Seeds what a single store needs to trade, without touching any other
4
+ # store.
5
+ #
6
+ # Country-shaped defaults (market, warehouse, delivery zones, pickup) are
7
+ # not part of it: the caller runs Spree::Stores::ProvisionDefaults
8
+ # afterwards with the merchant's answers.
9
+ class StoreResources
10
+ prepend Spree::ServiceModule::Base
11
+
12
+ # @param store [Spree::Store] the store to seed, already persisted
13
+ # @return [Spree::ServiceModule::Result]
14
+ def call(store:)
15
+ # A nil store would fall through to every seed's all-stores mode.
16
+ raise ArgumentError, 'store is required' if store.nil?
17
+
18
+ Spree::Events.disable do
19
+ ActiveRecord::Base.no_touching do
20
+ TaxCategories.call(store: store)
21
+ Channels.call(store: store)
22
+ Roles.call(store: store)
23
+ DigitalDelivery.call(store: store)
24
+ PaymentMethods.call(store: store)
25
+ ProductTypes.call(store: store)
26
+ CustomerGroups.call(store: store)
27
+ ReturnsEnvironment.call(store: store)
28
+ # The marketplace's catch-all rate, at the bottom of the list so
29
+ # narrower rates added later resolve ahead of it.
30
+ CommissionRates.call(store: store)
31
+ SellerRequirements.call(store: store)
32
+ # Binds to the wholesale channel that Channels creates above.
33
+ ApiKeys.call(store: store)
34
+ SavedReports.call(store: store)
35
+ AllowedOrigins.call(store: store)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ module Seeds
3
+ # Shared entry point for seeds whose rows belong to a store. Including
4
+ # classes implement a private `seed(store)` that seeds exactly one store
5
+ # and is safe to re-run.
6
+ module StoreScoped
7
+ # @param store [Spree::Store, nil] the store to seed; every store when nil
8
+ def call(store: nil)
9
+ return seed(store) if store
10
+
11
+ Spree::Store.find_each { |each_store| seed(each_store) }
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,17 +2,18 @@ module Spree
2
2
  module Seeds
3
3
  class TaxCategories
4
4
  prepend Spree::ServiceModule::Base
5
+ include StoreScoped
5
6
 
6
- def call
7
- Spree::Store.find_each do |store|
8
- # The block form only runs on create, so a store that already had a
9
- # "Default" category with the flag unset kept no default at all — and
10
- # the default is what taxes an item carrying no category of its own.
11
- default = store.tax_categories.find_or_create_by!(name: 'Default')
12
- default.update!(is_default: true) unless default.is_default?
7
+ private
13
8
 
14
- store.tax_categories.find_or_create_by!(name: 'Non-taxable')
15
- end
9
+ def seed(store)
10
+ # The block form only runs on create, so a store that already had a
11
+ # "Default" category with the flag unset kept no default at all — and
12
+ # the default is what taxes an item carrying no category of its own.
13
+ default = store.tax_categories.find_or_create_by!(name: 'Default')
14
+ default.update!(is_default: true) unless default.is_default?
15
+
16
+ store.tax_categories.find_or_create_by!(name: 'Non-taxable')
16
17
  end
17
18
  end
18
19
  end