- <%= f.field_container :name do %>
- <%= f.label :name, class: 'required' %>
- <%= f.text_field :name, required: true, class: 'fullwidth' %>
- <%= f.error_message_on :name %>
- <% end %>
+
+
+
<%= t('.store_settings') %>
+
+
+ <%= f.field_container :name do %>
+ <%= f.label :name, class: 'required' %>
+ <%= f.text_field :name, required: true, class: 'fullwidth' %>
+ <%= f.error_message_on :name %>
+ <% end %>
+
- <%= f.field_container :code do %>
- <%= f.label :code, class: 'required' %>
- <%= f.field_hint :code %>
- <%= f.text_field :code, required: true, class: 'fullwidth' %>
- <%= f.error_message_on :code %>
- <% end %>
+
+ <%= f.field_container :url do %>
+ <%= f.label :url, class: 'required' %>
+ <%= f.text_field :url, required: true, class: 'fullwidth' %>
+ <%= f.error_message_on :url %>
+ <% end %>
+
- <%= f.field_container :seo_title do %>
- <%= f.label :seo_title %>
- <%= f.text_field :seo_title, class: 'fullwidth' %>
- <%= f.error_message_on :seo_title %>
- <% end %>
-
- <%= f.field_container :meta_keywords do %>
- <%= f.label :meta_keywords %>
- <%= f.text_field :meta_keywords, class: 'fullwidth' %>
- <%= f.error_message_on :meta_keywords %>
- <% end %>
-
- <%= f.field_container :meta_description do %>
- <%= f.label :meta_description %>
- <%= f.text_area :meta_description, class: 'fullwidth' %>
- <%= f.error_message_on :meta_description %>
- <% end %>
+
+ <%= f.field_container :code do %>
+ <%= f.label :code, class: 'required' %>
+ <%= f.field_hint :code %>
+ <%= f.text_field :code, required: true, class: 'fullwidth' %>
+ <%= f.error_message_on :code %>
+ <% end %>
+
-
- <%= f.field_container :url do %>
- <%= f.label :url, class: 'required' %>
- <%= f.text_field :url, required: true, class: 'fullwidth' %>
- <%= f.error_message_on :url %>
- <% end %>
- <%= f.field_container :mail_from_address do %>
- <%= f.label :mail_from_address, class: 'required' %>
- <%= f.text_field :mail_from_address, required: true, class: 'fullwidth' %>
- <%= f.error_message_on :mail_from_address %>
- <% end %>
+
+
<%= t('.regional_settings') %>
+
+
+ <%= f.field_container :default_currency do %>
+ <%= f.label :default_currency %>
+ <%= f.field_hint :default_currency %>
+ <%= f.select :default_currency,
+ Spree::Config.available_currencies.map(&:iso_code),
+ { include_blank: true },
+ { class: 'custom-select fullwidth' } %>
+ <%= f.error_message_on :default_currency %>
+ <% end %>
+
+
+ <%= f.field_container :cart_tax_country_iso do %>
+ <%= f.label :cart_tax_country_iso %>
+ <%= f.field_hint :cart_tax_country_iso %>
+ <%= f.collection_select :cart_tax_country_iso,
+ available_countries(restrict_to_zone: nil), :iso, :name,
+ { include_blank: t(".no_cart_tax_country") },
+ { class: "custom-select fullwidth" } %>
+ <%= f.error_message_on :cart_tax_country_iso %>
+ <% end %>
+
+
+ <%= f.field_container :available_locales do %>
+ <%= f.label :available_locales %>
+ <%= f.field_hint :available_locales %>
+ <%= f.select :available_locales,
+ Spree.i18n_available_locales.map { |locale|
+ [I18n.t('spree.i18n.this_file_language', locale: locale, default: locale.to_s, fallback: false), locale]
+ }.sort,
+ { },
+ { class: 'select2 fullwidth', multiple: true } %>
+ <%= f.error_message_on :default_currency %>
+ <% end %>
+
+
- <%= f.field_container :bcc_email do %>
- <%= f.label :bcc_email %>
- <%= f.text_field :bcc_email, class: 'fullwidth' %>
- <%= f.error_message_on :bcc_email %>
- <% end %>
+
+
<%= t('.email_settings') %>
+
+
+ <%= f.field_container :mail_from_address do %>
+ <%= f.label :mail_from_address, class: 'required' %>
+ <%= f.text_field :mail_from_address, required: true, class: 'fullwidth' %>
+ <%= f.error_message_on :mail_from_address %>
+ <% end %>
+
+
+ <%= f.field_container :bcc_email do %>
+ <%= f.label :bcc_email %>
+ <%= f.text_field :bcc_email, class: 'fullwidth' %>
+ <%= f.error_message_on :bcc_email %>
+ <% end %>
+
+
- <%= f.field_container :default_currency do %>
- <%= f.label :default_currency %>
- <%= f.field_hint :default_currency %>
- <%= f.select :default_currency,
- Spree::Config.available_currencies.map(&:iso_code),
- { include_blank: true },
- { class: 'custom-select fullwidth' } %>
- <%= f.error_message_on :default_currency %>
- <% end %>
+
+
<%= t('.store_legal_addres') %>
+
+ <%= render partial: 'address_form', locals: { f: f, type: 'store' } %>
+
- <%= f.field_container :cart_tax_country_iso do %>
- <%= f.label :cart_tax_country_iso %>
- <%= f.field_hint :cart_tax_country_iso %>
- <%= f.collection_select :cart_tax_country_iso,
- available_countries(restrict_to_zone: nil), :iso, :name,
- { include_blank: t(".no_cart_tax_country") },
- { class: "custom-select fullwidth" } %>
- <%= f.error_message_on :cart_tax_country_iso %>
- <% end %>
+
+
<%= t('.contact_options') %>
+
+
+ <%= f.field_container :contact_phone do %>
+ <%= f.label :contact_phone %>
+ <%= f.phone_field :contact_phone, class: 'fullwidth' %>
+ <%= f.error_message_on :contact_phone %>
+ <% end %>
+
+
+ <%= f.field_container :contact_email do %>
+ <%= f.label :contact_email %>
+ <%= f.email_field :contact_email, class: 'form-control' %>
+ <%= f.error_message_on :contact_email %>
+ <% end %>
+
+
- <%= f.field_container :available_locales do %>
- <%= f.label :available_locales %>
- <%= f.field_hint :available_locales %>
- <%= f.select :available_locales,
- Spree.i18n_available_locales.map { |locale|
- [I18n.t('spree.i18n.this_file_language', locale: locale, default: locale.to_s, fallback: false), locale]
- }.sort,
- { },
- { class: 'select2 fullwidth', multiple: true } %>
- <%= f.error_message_on :default_currency %>
- <% end %>
+
+
<%= t('.content_on_storefront') %>
+
+
+ <%= f.field_container :seo_title do %>
+ <%= f.label :seo_title %>
+ <%= f.text_field :seo_title, class: 'fullwidth' %>
+ <%= f.error_message_on :seo_title %>
+ <% end %>
+
+
+ <%= f.field_container :meta_keywords do %>
+ <%= f.label :meta_keywords %>
+ <%= f.text_field :meta_keywords, class: 'fullwidth' %>
+ <%= f.error_message_on :meta_keywords %>
+ <% end %>
+
+
+ <%= f.field_container :description do %>
+ <%= f.label :description, class: 'form-label' %>
+ <%= f.text_area :description, class: 'form-control' %>
+ <% end %>
+
+
+ <%= f.field_container :meta_description do %>
+ <%= f.label :meta_description %>
+ <%= f.text_area :meta_description, class: 'fullwidth' %>
+ <%= f.error_message_on :meta_description %>
+ <% end %>
+
diff --git a/core/app/models/spree/order.rb b/core/app/models/spree/order.rb
index 75fbca4d8b2..3de085616a7 100644
--- a/core/app/models/spree/order.rb
+++ b/core/app/models/spree/order.rb
@@ -848,6 +848,8 @@ def after_cancel
send_cancel_email
update_column(:canceled_at, Time.current)
recalculate
+
+ Spree::Bus.publish :order_canceled, order: self
end
def cancel_shipments!
diff --git a/core/app/models/spree/order_cancellations.rb b/core/app/models/spree/order_cancellations.rb
index 9c0a25c7e4d..343e2d2f321 100644
--- a/core/app/models/spree/order_cancellations.rb
+++ b/core/app/models/spree/order_cancellations.rb
@@ -50,6 +50,7 @@ def short_ship(inventory_units, created_by: nil)
end
end
+ Spree::Bus.publish(:order_short_shipped, order: @order, inventory_units:)
unit_cancels
end
diff --git a/core/app/models/spree/order_shipping.rb b/core/app/models/spree/order_shipping.rb
index bfab5ecb910..3b854bc03e4 100644
--- a/core/app/models/spree/order_shipping.rb
+++ b/core/app/models/spree/order_shipping.rb
@@ -72,6 +72,7 @@ def ship(inventory_units:, stock_location:, address:, shipping_method:,
@order.shipments.reload
@order.recalculate
+ Spree::Bus.publish(:carton_shipped, carton:)
carton
end
diff --git a/core/app/models/spree/store.rb b/core/app/models/spree/store.rb
index 9350fb0a2b8..462c55682cd 100644
--- a/core/app/models/spree/store.rb
+++ b/core/app/models/spree/store.rb
@@ -15,12 +15,16 @@ class Store < Spree::Base
has_many :store_shipping_methods, inverse_of: :store
has_many :shipping_methods, through: :store_shipping_methods
+ belongs_to :state, class_name: 'Spree::State', optional: true
+ belongs_to :country, class_name: 'Spree::Country', optional: true
+
has_many :orders, class_name: "Spree::Order"
validates :code, presence: true, uniqueness: { allow_blank: true, case_sensitive: true }
validates :name, presence: true
validates :url, presence: true
validates :mail_from_address, presence: true
+ validates :state, presence: true, if: -> { country&.states_required }
self.allowed_ransackable_attributes = %w[name url code]
diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml
index 0dd3145a750..b66e0c6dc2c 100644
--- a/core/config/locales/en.yml
+++ b/core/config/locales/en.yml
@@ -354,18 +354,32 @@ en:
quantity: Quantity
variant: Variant
spree/store:
- available_locales: Locales Available in the Storefront
+ address: Address
+ address1: Street Address
+ address2: Street Address (cont'd)
+ available_locales: Languages Available in the Storefront
bcc_email: BCC Email
cart_tax_country_iso: Tax Country for Empty Carts
+ city: City
code: Slug
+ contact_email: Contact Email
+ contact_phone: Contact Phone
+ country_id: Country
default: Default
default_currency: Default Currency
+ description: Store Description
+ legal_name: Legal Name
mail_from_address: Mail From Address
meta_description: Meta Description
meta_keywords: Meta Keywords
name: Site Name
+ postal_code: Postal Code
seo_title: Seo Title
+ state_id: State
+ tax_id: Tax-ID
url: Site URL
+ vat_id: VAT-ID
+ zipcode: Zip Code
spree/store_credit:
amount: Amount
amount_authorized: Amount Authorized
@@ -939,7 +953,13 @@ en:
view: View store credit
stores:
form:
+ contact_options: Contact Options
+ content_on_storefront: Content on Storefront
+ email_settings: Email Settings
no_cart_tax_country: No taxes on carts without address
+ regional_settings: Regional Settings
+ store_legal_addres: Store Legal Address
+ store_settings: Store Name & URL / URI Settings
tab:
checkout: Refunds and Returns
configuration: Configuration
@@ -1629,6 +1649,7 @@ en:
cart_tax_country_iso: 'This determines which country is used for taxes on carts (orders which don''t yet have an address).
Default: None.'
code: An identifier for your store. Developers may need this value if you operate multiple storefronts.
default_currency: This determines which currency will be used for the storefront's product prices. Please, be aware that changing this configuration, only products that have prices in the selected currency will be listed on your storefront.
This setting won't change the default currency used when you create a product. For that, only the global `Spree::Config.currency` is taken into account.
+ vat_id: Enter your VAT-ID without the Country Prefix (eg IT for Italy or DE for Germany) but solely the identification string.
spree/tax_category:
is_default: When checked, this tax category will be selected by default when creating new products or variants.
spree/tax_rate:
diff --git a/core/db/migrate/20250202173007_add_store_attributes_to_spree_stores.rb b/core/db/migrate/20250202173007_add_store_attributes_to_spree_stores.rb
new file mode 100644
index 00000000000..f1a854f2b7b
--- /dev/null
+++ b/core/db/migrate/20250202173007_add_store_attributes_to_spree_stores.rb
@@ -0,0 +1,19 @@
+class AddStoreAttributesToSpreeStores < ActiveRecord::Migration[7.2]
+ def change
+ add_column :spree_stores, :legal_name, :string
+ add_column :spree_stores, :contact_email, :string
+ add_column :spree_stores, :description, :text
+ add_column :spree_stores, :vat_id, :string
+ add_column :spree_stores, :tax_id, :string
+ add_column :spree_stores, :address1, :string
+ add_column :spree_stores, :address2, :string
+ add_column :spree_stores, :city, :string
+ add_column :spree_stores, :zipcode, :string
+ add_column :spree_stores, :state_name, :string
+ add_column :spree_stores, :contact_phone, :string
+ add_column :spree_stores, :country_id, :integer
+ add_column :spree_stores, :state_id, :integer
+ add_index :spree_stores, :country_id
+ add_index :spree_stores, :state_id
+ end
+end
diff --git a/core/lib/spree/app_configuration.rb b/core/lib/spree/app_configuration.rb
index c7177454c6a..621aae7aba7 100644
--- a/core/lib/spree/app_configuration.rb
+++ b/core/lib/spree/app_configuration.rb
@@ -222,7 +222,7 @@ class AppConfiguration < Preferences::Configuration
# - The maximum number of keys that can be added to the metadata columns (meta_data_max_keys).
# - The maximum length of each key in the metadata columns (meta_data_max_key_length).
# - The maximum length of each value in the metadata columns (meta_data_max_value_length).
- # (default: +true+)
+ # (default: +false+)
preference :meta_data_validation_enabled, :boolean, default: false
# @!attribute [rw] meta_data_max_keys
diff --git a/core/lib/spree/core/engine.rb b/core/lib/spree/core/engine.rb
index 9f37c1c65d5..cea9f799988 100644
--- a/core/lib/spree/core/engine.rb
+++ b/core/lib/spree/core/engine.rb
@@ -61,9 +61,12 @@ class Engine < ::Rails::Engine
Spree::Bus.clear
%i[
+ carton_shipped
+ order_canceled
order_emptied
order_finalized
order_recalculated
+ order_short_shipped
reimbursement_reimbursed
reimbursement_errored
].each { |event_name| Spree::Bus.register(event_name) }
diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb
index cd59e762e21..eecb914fd57 100644
--- a/core/lib/spree/permitted_attributes.rb
+++ b/core/lib/spree/permitted_attributes.rb
@@ -121,10 +121,12 @@ module PermittedAttributes
:quantity, :stock_item, :stock_item_id, :originator, :action
]
- @@store_attributes = [:name, :url, :seo_title, :meta_keywords,
+ @@store_attributes = [:name, :legal_name, :url, :seo_title, :meta_keywords,
:meta_description, :default_currency,
:mail_from_address, :cart_tax_country_iso,
- :bcc_email]
+ :bcc_email, :contact_email, :contact_phone, :code,
+ :tax_id, :vat_id, :description, :address1, :address2,
+ :city, :zipcode, :country_id, :state_id, :state_name]
@@taxonomy_attributes = [:name]
diff --git a/core/spec/models/spree/order_cancellations_spec.rb b/core/spec/models/spree/order_cancellations_spec.rb
index a120078b98e..32e81dc820b 100644
--- a/core/spec/models/spree/order_cancellations_spec.rb
+++ b/core/spec/models/spree/order_cancellations_spec.rb
@@ -94,6 +94,15 @@
expect { subject }.to change { order.shipment_state }.from('ready').to('shipped')
end
+ it "publishes an 'order_short_shipped' event" do
+ stub_spree_bus
+
+ subject
+
+ expect(:order_short_shipped)
+ .to have_been_published.with(order:, inventory_units: [inventory_unit])
+ end
+
it "adjusts the order" do
expect { subject }.to change { order.reload.total }.by(-10.0)
end
diff --git a/core/spec/models/spree/order_shipping_spec.rb b/core/spec/models/spree/order_shipping_spec.rb
index b8ec4ef4c7b..00cc29c4045 100644
--- a/core/spec/models/spree/order_shipping_spec.rb
+++ b/core/spec/models/spree/order_shipping_spec.rb
@@ -85,6 +85,12 @@ def emails
it "sets the external_number" do
expect(subject.external_number).to eq 'some-external-number'
end
+
+ it "publishes a 'carton_shipped' event" do
+ stub_spree_bus
+
+ expect(:carton_shipped).to have_been_published.with(carton: subject)
+ end
end
context "with a tracking number" do
diff --git a/core/spec/models/spree/order_spec.rb b/core/spec/models/spree/order_spec.rb
index 80acd6658cf..bec34d499db 100644
--- a/core/spec/models/spree/order_spec.rb
+++ b/core/spec/models/spree/order_spec.rb
@@ -82,9 +82,18 @@
end
describe "#cancel!" do
- let!(:order) { create(:completed_order_with_totals) }
subject { order.cancel! }
+ let!(:order) { create(:completed_order_with_totals) }
+
+ it "publishes a 'order_canceled' event" do
+ stub_spree_bus
+
+ subject
+
+ expect(:order_canceled).to have_been_published.with(order:)
+ end
+
it "sends a cancel email" do
perform_enqueued_jobs { subject }
diff --git a/core/spec/rails_helper.rb b/core/spec/rails_helper.rb
index f3b6e161509..fcb0ec82616 100644
--- a/core/spec/rails_helper.rb
+++ b/core/spec/rails_helper.rb
@@ -16,6 +16,7 @@
Dir["./spec/support/**/*.rb"].sort.each { |f| require f }
+require 'spree/testing_support/bus_helpers'
require 'spree/testing_support/factory_bot'
require 'spree/testing_support/preferences'
require 'spree/testing_support/rake'
@@ -45,6 +46,7 @@
Rails.cache.clear
end
+ config.include Spree::TestingSupport::BusHelpers
config.include Spree::TestingSupport::JobHelpers
config.include FactoryBot::Syntax::Methods