Create source variants

This commit is contained in:
David Cook
2026-02-09 17:07:26 +11:00
parent 940aa57daf
commit eba2fbcc30
8 changed files with 110 additions and 6 deletions

View File

@@ -107,6 +107,39 @@ module Admin
end
end
# Clone a variant, retaining a link to the "source"
def create_sourced_variant
source_variant = Spree::Variant.find(params[:variant_id])
product_index = params[:product_index]
authorize! :create_sourced_variant, source_variant
status = :ok
begin
variant = source_variant.dup #may need a VariantDuplicator like producs?
variant.price = source_variant.price
variant.save!
variant.on_demand = source_variant.on_demand
variant.on_hand = source_variant.on_hand
variant.save!
#todo: create link to source
flash.now[:success] = t('.success')
variant_index = "-#{variant.id}"
rescue ActiveRecord::RecordInvalid
flash.now[:error] = variant.errors.full_messages.to_sentence
status = :unprocessable_entity
variant_index = "-1" # Create a unique-enough index
end
respond_with do |format|
format.turbo_stream {
locals = { source_variant:, variant:, product_index:, variant_index:,
producer_options:, category_options: categories, tax_category_options: }
render :create_sourced_variant, status:, locals:
}
end
end
def index_url(params)
"/admin/products?#{params.to_query}" # todo: fix routing so this can be automaticly generated
end

View File

@@ -13,7 +13,7 @@
- next if variant.supplier.present? && !allowed_producers.include?(variant.supplier)
= form.fields_for("products][#{product_index}][variants_attributes", variant, index: variant_index) do |variant_form|
%tr.condensed{ id: dom_id(variant), 'data-controller': "variant", 'class': "nested-form-wrapper", 'data-new-record': variant.new_record? ? "true" : false }
= render partial: 'variant_row', locals: { variant:, f: variant_form, category_options:, tax_category_options:, producer_options: }
= render partial: 'variant_row', locals: { variant:, f: variant_form, product_index:, category_options:, tax_category_options:, producer_options: }
= form.fields_for("products][#{product_index}][variants_attributes][NEW_RECORD", prepare_new_variant(product, producer_options)) do |new_variant_form|
%template{ 'data-nested-form-target': "template" }

View File

@@ -1,4 +1,4 @@
-# locals: (variant:, f:, category_options:, tax_category_options:, producer_options:)
-# locals: (variant:, f:, product_index: nil, category_options:, tax_category_options:, producer_options:)
- method_on_demand, method_on_hand = variant.new_record? ? [:on_demand_desired, :on_hand_desired ]: [:on_demand, :on_hand]
%td.col-image
-# empty
@@ -89,7 +89,7 @@
- if variant.persisted?
= link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(variant.product, variant)
/ TODO: only show if have permission. need to load permissions efficiently please. maybe the PErmissions object can cache result for each enterprise. maybe we preload it with the product query.
= link_to t('admin.products_page.actions.create_sourced_variant'), "TODO" # see next commit
= link_to t('admin.products_page.actions.create_sourced_variant'), admin_create_sourced_variant_path(variant_id: variant.id, product_index:), 'data-turbo-method': :post
- if variant.product.variants.size > 1
%a{ "data-controller": "modal-link", "data-action": "click->modal-link#setModalDataSetOnConfirm click->modal-link#open",
"data-modal-link-target-value": "variant-delete-modal", "class": "delete",

View File

@@ -0,0 +1,16 @@
-# locals: (variant:, source_variant:, product_index:, variant_index:, producer_options:, category_options:, tax_category_options:)
-# Pre-render the form, because you can't do it inside turbo stream block
- variant_row = nil
- fields_for("products][#{product_index}][variants_attributes", variant, index: variant_index) do |f|
- variant_row = render(partial: 'variant_row', formats: :html,
locals: { f:,
variant:,
producer_options:,
category_options:,
tax_category_options:})
= turbo_stream.after dom_id(source_variant) do
%tr.condensed{ id: dom_id(variant), 'data-controller': "variant", 'class': "nested-form-wrapper slide-in" }
= variant_row
= turbo_stream.append "flashes" do
= render(partial: 'admin/shared/flashes', locals: { flashes: flash })

View File

@@ -1099,6 +1099,8 @@ en:
clone:
success: Successfully cloned the product
error: Unable to clone the product
create_sourced_variant:
success: "Successfully created sourced variant"
tag_rules:
rules_per_tag:
one: "%{tag} has 1 rule"

View File

@@ -82,6 +82,7 @@ Openfoodnetwork::Application.routes.draw do
delete 'products_v3/:id', to: 'products_v3#destroy', as: 'product_destroy'
delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant'
post 'clone/:id', to: 'products_v3#clone', as: 'clone_product'
post 'products/create_sourced_variant', to: 'products_v3#create_sourced_variant', as: 'create_sourced_variant'
resources :product_preview, only: [:show]
resources :variant_overrides do

View File

@@ -59,4 +59,45 @@ RSpec.describe "Admin::ProductsV3" do
expect(response).to redirect_to('/unauthorized')
end
end
describe "POST /admin/products/create_sourced_variant" do
let(:enterprise) { create(:supplier_enterprise) }
let(:user) { create(:user, enterprises: [enterprise]) }
let(:supplier) { create(:supplier_enterprise) }
let(:variant) { create(:variant, display_name: "Original variant", supplier: supplier) }
before do
sign_in user
end
it "checks for permission" do
params = { variant_id: variant.id, product_index: 1 }
expect {
post(admin_create_sourced_variant_path, as: :turbo_stream, params:)
expect(response).to redirect_to('/unauthorized')
}.not_to change { variant.product.variants.count }
end
context "With create_sourced_variants permissions on supplier" do
let!(:enterprise_relationship) {
create(:enterprise_relationship,
parent: supplier,
child: enterprise,
permissions_list: [:create_sourced_variants])
}
it "creates a clone of the variant, retaining link as source" do
params = { variant_id: variant.id, product_index: 1 }
expect {
post(admin_create_sourced_variant_path, as: :turbo_stream, params:)
expect(response).to have_http_status(:ok)
expect(response.body).to match "Original variant" # cloned variant name
}.to change { variant.product.variants.count }.by(1)
end
end
end
end

View File

@@ -291,19 +291,30 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
end
context "with create_sourced_variants permission for my, and other's variants" do
it "shows an option to create sourced variant" do
it "creates a sourced variant" do
create(:enterprise_relationship, parent: producer, child: producer,
permissions_list: [:create_sourced_variants])
enterprise_relationship.permissions.create! name: :create_sourced_variants
# Check my own variant
within row_containing_name("My box") do
page.find(".vertical-ellipsis-menu").click
expect(page).to have_link "Create sourced variant" # , href: admin_clone_product_path(product_a)
expect(page).to have_link "Create sourced variant"
end
# Create variant sourced from my friend
within row_containing_name("My friends box") do
page.find(".vertical-ellipsis-menu").click
expect(page).to have_link "Create sourced variant" # , href: admin_clone_product_path(product_a)
click_link "Create sourced variant"
end
expect(page).to have_content "Successfully created sourced variant"
within "table.products" do
# There are now two copies
expect(all_input_values).to match /My friends box.*My friends box/
end
end
end