From 5221825e82602038254cd0f0d865d8218add0993 Mon Sep 17 00:00:00 2001 From: Rahul Date: Thu, 13 Feb 2025 00:50:48 +0530 Subject: [PATCH] Added fields to store for organisation structured data for SEO --- api/lib/spree/api_configuration.rb | 7 +- api/spec/requests/spree/api/stores_spec.rb | 134 +++++++++++++++++- .../spree/admin/stores/_address_form.html.erb | 65 +++++++++ .../views/spree/admin/stores/_form.html.erb | 26 ++++ core/app/models/spree/store.rb | 4 + core/config/locales/en.yml | 13 ++ ...07_add_store_attributes_to_spree_stores.rb | 17 +++ core/lib/spree/permitted_attributes.rb | 6 +- 8 files changed, 262 insertions(+), 10 deletions(-) create mode 100644 backend/app/views/spree/admin/stores/_address_form.html.erb create mode 100644 core/db/migrate/20250202173007_add_store_attributes_to_spree_stores.rb diff --git a/api/lib/spree/api_configuration.rb b/api/lib/spree/api_configuration.rb index 2dce13e62d8..26a7f29797c 100644 --- a/api/lib/spree/api_configuration.rb +++ b/api/lib/spree/api_configuration.rb @@ -125,9 +125,10 @@ def promotion_attributes=(value) deprecate "promotion_attributes=" => promotion_attributes_deprecation_message, deprecator: Spree.deprecator preference :store_attributes, :array, default: [ - :id, :name, :url, :meta_description, :meta_keywords, :seo_title, - :mail_from_address, :default_currency, :code, :default, :available_locales, - :bcc_email + :id, :name, :legal_name, :url, :meta_description, :meta_keywords, :seo_title, + :mail_from_address, :default_currency, :code, :default, + :bcc_email, :contact_phone, :contact_email, :tax_id, :vat_id, :description, + :address1, :address2, :city, :zipcode, :country_id, :state_id, :state_name, :available_locales ] preference :store_credit_history_attributes, :array, default: [ diff --git a/api/spec/requests/spree/api/stores_spec.rb b/api/spec/requests/spree/api/stores_spec.rb index 55b4f102416..8553ddf3d6a 100644 --- a/api/spec/requests/spree/api/stores_spec.rb +++ b/api/spec/requests/spree/api/stores_spec.rb @@ -4,6 +4,11 @@ module Spree::Api describe 'Stores', type: :request do + let(:country) { create :country, states_required: true } + let(:country_without_states) { create :country, states_required: false } + let(:state) { create :state, name: 'maryland', abbr: 'md', country: } + let!(:base_attributes) { Spree::Api::Config.store_attributes } + let!(:store) do create(:store, name: "My Spree Store", url: "spreestore.example.com") end @@ -22,6 +27,58 @@ module Spree::Api default: false) end + describe "store state validation" do + context "when store country has states_required" do + it "is invalid without a state" do + store = Spree::Store.new(name: "Test Store", country: country, state: nil, url: "spreestore.example.com", + mail_from_address: "spreestore@example.com", code: "test-store",) + expect(store).not_to be_valid + expect(store.errors[:state]).to include("can't be blank") + end + + it "is valid with a state" do + store = Spree::Store.new(name: "Test Store", country: country, state: state, url: "spreestore.example.com", + mail_from_address: "spreestore@example.com", code: "test-store",) + expect(store).to be_valid + end + end + + context "when store country has no states" do + it "is valid without a state" do + store = Spree::Store.new(name: "Test Store", country: country_without_states, state: nil, url: "spreestore.example.com", + mail_from_address: "spreestore@example.com", code: "test-store",) + expect(store).to be_valid + end + end + + it "is valid without an address and without country/state" do + expect(store).to be_valid + end + + it "is valid with only correct country and state" do + store = Spree::Store.create!( + name: "Test Store", + url: "spreestore.example.com", + mail_from_address: "spreestore.example.com", + code: "test-store", + address1: "123 Main St", + city: "New York", + zipcode: "10001", + state: state, + country: country, + ) + expect(store).to be_valid + end + end + + describe "#index" do + it "ensures the API store attributes match the expected attributes" do + get spree.api_stores_path + first_store = json_response["stores"].first + expect(first_store.keys).to include(*base_attributes.map(&:to_s)) + end + end + it "can list the available stores" do get spree.api_stores_path expect(json_response["stores"]).to match_array([ @@ -37,7 +94,20 @@ module Spree::Api "default_currency" => nil, "code" => store.code, "default" => true, - "available_locales" => ["en"] + "available_locales" => ["en"], + "legal_name" => nil, + "contact_email" => nil, + "contact_phone" => nil, + "description" => nil, + "tax_id" => nil, + "vat_id" => nil, + "address1" => nil, + "address2" => nil, + "city" => nil, + "zipcode" => nil, + "country_id" => nil, + "state_id" => nil, + "state_name" => nil }, { "id" => non_default_store.id, @@ -51,7 +121,20 @@ module Spree::Api "default_currency" => nil, "code" => non_default_store.code, "default" => false, - "available_locales" => ["en"] + "available_locales" => ["en"], + "legal_name" => nil, + "contact_email" => nil, + "contact_phone" => nil, + "description" => nil, + "tax_id" => nil, + "vat_id" => nil, + "address1" => nil, + "address2" => nil, + "city" => nil, + "zipcode" => nil, + "country_id" => nil, + "state_id" => nil, + "state_name" => nil } ]) end @@ -70,7 +153,20 @@ module Spree::Api "default_currency" => nil, "code" => store.code, "default" => true, - "available_locales" => ["en"] + "available_locales" => ["en"], + "legal_name" => nil, + "contact_email" => nil, + "contact_phone" => nil, + "description" => nil, + "tax_id" => nil, + "vat_id" => nil, + "address1" => nil, + "address2" => nil, + "city" => nil, + "zipcode" => nil, + "country_id" => nil, + "state_id" => nil, + "state_name" => nil ) end @@ -79,7 +175,14 @@ module Spree::Api code: "spree123", name: "Hack0rz", url: "spree123.example.com", - mail_from_address: "me@example.com" + mail_from_address: "me@example.com", + legal_name: 'ABC Corp', + address1: "123 Main St", + city: 'San Francisco', + country_id: country.id, + state_id: state.id, + phone: "123-456-7890", + zipcode: "12345" } post spree.api_stores_path, params: { store: store_hash } expect(response.status).to eq(201) @@ -89,13 +192,34 @@ module Spree::Api store_hash = { url: "spree123.example.com", mail_from_address: "me@example.com", - bcc_email: "bcc@example.net" + bcc_email: "bcc@example.net", + legal_name: 'XYZ Corp', + description: "Leading provider of high-quality tech accessories, offering the latest gadgets, peripherals, and electronics to enhance your digital lifestyle.", + tax_id: "TX-987654321", + vat_id: "VAT-123456789", + address1: "123 Innovation Drive", + address2: "Suite 456", + city: "New York", + country_id: country.id, + state_id: state.id, + contact_phone: "123-456-7888", + zipcode: "10001" } put spree.api_store_path(store), params: { store: store_hash } expect(response.status).to eq(200) expect(store.reload.url).to eql "spree123.example.com" expect(store.reload.mail_from_address).to eql "me@example.com" expect(store.reload.bcc_email).to eql "bcc@example.net" + expect(store.reload.legal_name).to eql "XYZ Corp" + expect(store.reload.tax_id).to eql "TX-987654321" + expect(store.reload.vat_id).to eql "VAT-123456789" + expect(store.reload.address1).to eql "123 Innovation Drive" + expect(store.reload.address2).to eql "Suite 456" + expect(store.reload.city).to eql "New York" + expect(store.reload.country_id).to eql country.id + expect(store.reload.state_id).to eql state.id + expect(store.reload.contact_phone).to eql "123-456-7888" + expect(store.reload.zipcode).to eql "10001" end context "deleting a store" do diff --git a/backend/app/views/spree/admin/stores/_address_form.html.erb b/backend/app/views/spree/admin/stores/_address_form.html.erb new file mode 100644 index 00000000000..0450b311644 --- /dev/null +++ b/backend/app/views/spree/admin/stores/_address_form.html.erb @@ -0,0 +1,65 @@ + +<% s_or_b = type.chars.first %> +
+ +
+
+
"> + <%= f.label :legal_name %> + <%= f.text_field :legal_name, class: 'fullwidth' %> +
+ +
"> + <%= f.label :address1 %> + <%= f.text_field :address1, class: 'fullwidth' %> +
+ +
"> + <%= f.label :address2 %> + <%= f.text_field :address2, class: 'fullwidth' %> +
+ +
"> + <%= f.label :contact_phone %> + <%= f.phone_field :contact_phone, class: 'fullwidth' %> +
+
+ +
+
"> + <%= f.label :city %> + <%= f.text_field :city, class: 'fullwidth' %> +
+ +
"> + <%= f.label :zipcode %> + <%= f.text_field :zipcode, class: 'fullwidth' %> +
+ +
"> + <%= f.label :country_id, Spree::Country.model_name.human %> + + <%= f.collection_select :country_id, available_countries, :id, :name, { include_blank: true }, {class: 'custom-select fullwidth js-country_id'} %> + +
+ +
"> + <%= f.label :state_id, Spree::State.model_name.human %> + + <%= f.hidden_field :state_name, value: nil %> + <% states = f.object.country.try(:states).nil? ? [] : f.object.country.states %> + <%= f.text_field :state_name, + style: "display: #{states.empty? ? 'block' : 'none' };", + disabled: !states.empty?, class: 'fullwidth state_name js-state_name' %> + <%= f.hidden_field :state_id, value: nil %> + <%= f.collection_select :state_id, + states.sort, + :id, :name, + { include_blank: true }, + { class: 'custom-select fullwidth js-state_id', + style: "display: #{states.empty? ? 'none' : 'block' };", + disabled: states.empty? } %> + +
+
+
diff --git a/backend/app/views/spree/admin/stores/_form.html.erb b/backend/app/views/spree/admin/stores/_form.html.erb index 59dc4a4e899..6d6563af063 100644 --- a/backend/app/views/spree/admin/stores/_form.html.erb +++ b/backend/app/views/spree/admin/stores/_form.html.erb @@ -30,7 +30,21 @@ <%= f.text_area :meta_description, class: 'fullwidth' %> <%= f.error_message_on :meta_description %> <% end %> + + <%= f.field_container :tax_id do %> + <%= f.label :tax_id %> + <%= f.text_field :tax_id, class: 'fullwidth' %> + <%= f.error_message_on :tax_id %> + <% end %> + + <%= f.field_container :vat_id do %> + <%= f.label :vat_id %> + <%= f.text_field :vat_id, class: 'fullwidth' %> + <%= f.error_message_on :vat_id %> + <% end %> + +
<%= f.field_container :url do %> <%= f.label :url, class: 'required' %> @@ -81,5 +95,17 @@ { class: 'select2 fullwidth', multiple: true } %> <%= f.error_message_on :default_currency %> <% end %> + + <%= f.field_container :description do %> + <%= f.label :description %> + <%= f.text_area :description, class: 'fullwidth' %> + <%= f.error_message_on :description %> + <% end %> +
+ +
+
+ <%= render partial: 'address_form', locals: { f: f, type: 'store' } %> +
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 e7e1d1910a5..c2b412b9840 100644 --- a/core/config/locales/en.yml +++ b/core/config/locales/en.yml @@ -350,18 +350,31 @@ en: quantity: Quantity variant: Variant spree/store: + address: Address + address1: Street Address + address2: Street Address (cont'd) available_locales: Locales 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 spree/store_credit: amount: Amount amount_authorized: Amount Authorized 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..25ee34f9619 --- /dev/null +++ b/core/db/migrate/20250202173007_add_store_attributes_to_spree_stores.rb @@ -0,0 +1,17 @@ +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, :contact_phone, :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_reference :spree_stores, :country, foreign_key: { to_table: :spree_countries }, index: true + add_reference :spree_stores, :state, foreign_key: { to_table: :spree_states }, index: true + end +end diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb index a09a6eb53e6..d7bc95fc8d3 100644 --- a/core/lib/spree/permitted_attributes.rb +++ b/core/lib/spree/permitted_attributes.rb @@ -110,10 +110,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]