diff --git a/backend/spec/features/admin/products/variant_spec.rb b/backend/spec/features/admin/products/variant_spec.rb
index c6f68fcbb46..b3872f557fe 100644
--- a/backend/spec/features/admin/products/variant_spec.rb
+++ b/backend/spec/features/admin/products/variant_spec.rb
@@ -25,6 +25,7 @@
expect(page).to have_field('variant_width', with: "1.00")
expect(page).to have_field('variant_depth', with: "1.50")
expect(page).to have_select('variant[tax_category_id]')
+ expect(page).to have_select('variant[shipping_category_id]')
end
end
diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb
index 0e2f54972c4..cebe46a583b 100644
--- a/core/app/models/spree/variant.rb
+++ b/core/app/models/spree/variant.rb
@@ -30,11 +30,14 @@ class Variant < Spree::Base
belongs_to :product, -> { with_discarded }, touch: true, class_name: 'Spree::Product', inverse_of: :variants_including_master, optional: false
belongs_to :tax_category, class_name: 'Spree::TaxCategory', optional: true
+ belongs_to :shipping_category, class_name: "Spree::ShippingCategory", optional: true
delegate :name, :description, :slug, :available_on, :discontinue_on, :discontinued?,
- :shipping_category_id, :meta_description, :meta_keywords, :shipping_category,
+ :meta_description, :meta_keywords,
to: :product
delegate :tax_category, to: :product, prefix: true
+ delegate :shipping_category, :shipping_category_id,
+ to: :product, prefix: true
delegate :tax_rates, to: :tax_category
has_many :inventory_units, inverse_of: :variant
@@ -142,6 +145,23 @@ def tax_category
super || product_tax_category
end
+ # @return [Spree::ShippingCategory] the variant's shipping category
+ #
+ # This returns the product's shipping category if the shipping category ID on the variant is nil. It looks
+ # like an association, but really is an override.
+ #
+ def shipping_category
+ super || product_shipping_category
+ end
+
+ # @return [Integer] the variant's shipping category id
+ #
+ # This returns the product's shipping category if if the shipping category ID on the variant is nil.
+ #
+ def shipping_category_id
+ super || product_shipping_category_id
+ end
+
# Sets the cost_price for the variant.
#
# @param price [Any] the price to set
diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml
index 042b7354109..ce0212cf0cf 100644
--- a/core/config/locales/en.yml
+++ b/core/config/locales/en.yml
@@ -415,6 +415,7 @@ en:
depth: Depth
height: Height
price: Price
+ shipping_category: Variant Shipping Category
sku: SKU
tax_category: Variant Tax Category
weight: Weight
@@ -993,6 +994,7 @@ en:
pricing: Pricing
pricing_hint: These values are populated from the product details page and can be overridden below
properties: Properties
+ use_product_shipping_category: Use Product Shipping Category
use_product_tax_category: Use Product Tax Category
new:
new_variant: New Variant
@@ -1641,6 +1643,7 @@ en:
deleted: Deleted Variant
deleted_explanation: This variant was deleted on %{date}.
deleted_explanation_with_replacement: This variant was deleted on %{date}. It has since been replaced by another with the same SKU.
+ shipping_category: 'This determines what kind of shipping this variant requires. Default: Use shipping category of the product associated with this variant'
tax_category: 'This determines what kind of taxation is applied to this variant. Default: Use tax category of the product associated with this variant'
home: Home
i18n:
diff --git a/core/db/migrate/20221123152807_add_shipping_category_to_spree_variants.rb b/core/db/migrate/20221123152807_add_shipping_category_to_spree_variants.rb
new file mode 100644
index 00000000000..85c3ed8ed8c
--- /dev/null
+++ b/core/db/migrate/20221123152807_add_shipping_category_to_spree_variants.rb
@@ -0,0 +1,5 @@
+class AddShippingCategoryToSpreeVariants < ActiveRecord::Migration[5.2]
+ def change
+ add_reference :spree_variants, :shipping_category, index: true
+ end
+end
diff --git a/core/lib/spree/permitted_attributes.rb b/core/lib/spree/permitted_attributes.rb
index 5169280508c..7a9d6360f5c 100644
--- a/core/lib/spree/permitted_attributes.rb
+++ b/core/lib/spree/permitted_attributes.rb
@@ -132,7 +132,10 @@ module PermittedAttributes
:name, :presentation, :cost_price, :lock_version,
:position, :track_inventory,
:product_id, :product, :price,
- :weight, :height, :width, :depth, :sku, :cost_currency, :tax_category_id, option_value_ids: [], options: [:name, :value]
+ :weight, :height, :width, :depth, :sku, :cost_currency,
+ :tax_category_id, :shipping_category_id,
+ option_value_ids: [],
+ options: [:name, :value]
]
@@checkout_address_attributes = [
diff --git a/core/spec/models/spree/variant_spec.rb b/core/spec/models/spree/variant_spec.rb
index 05f06cf00d2..cc0fe73c15c 100644
--- a/core/spec/models/spree/variant_spec.rb
+++ b/core/spec/models/spree/variant_spec.rb
@@ -773,6 +773,44 @@
end
end
+ describe '#shipping_category' do
+ context 'when shipping_category is nil' do
+ let(:shipping_category) { build(:shipping_category) }
+ let(:product) { build(:product, shipping_category: shipping_category) }
+ let(:variant) { build(:variant, product: product, shipping_category_id: nil) }
+ it 'returns the parent products shipping_category' do
+ expect(variant.shipping_category).to eq(shipping_category)
+ end
+ end
+
+ context 'when shipping_category is set' do
+ let(:shipping_category) { create(:shipping_category) }
+ let(:variant) { build(:variant, shipping_category: shipping_category) }
+ it 'returns the shipping_category set on itself' do
+ expect(variant.shipping_category).to eq(shipping_category)
+ end
+ end
+ end
+
+ describe '#shipping_category_id' do
+ context 'when shipping_category_id is nil' do
+ let(:shipping_category) { build(:shipping_category) }
+ let(:product) { build(:product, shipping_category: shipping_category) }
+ let(:variant) { build(:variant, product: product, shipping_category_id: nil) }
+ it 'returns the parent products shipping_category_id' do
+ expect(variant.shipping_category_id).to eq(shipping_category.id)
+ end
+ end
+
+ context 'when shipping_category_id is set' do
+ let(:shipping_category) { create(:shipping_category) }
+ let(:variant) { build(:variant, shipping_category_id: shipping_category.id) }
+ it 'returns the shipping_category_id set on itself' do
+ expect(variant.shipping_category_id).to eq(shipping_category.id)
+ end
+ end
+ end
+
describe "touching" do
it "updates a product" do
variant.product.update_column(:updated_at, 1.day.ago)