Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
8880f83d09 Bump i18n-tasks from 1.0.15 to 1.1.2
Bumps [i18n-tasks](https://github.com/glebm/i18n-tasks) from 1.0.15 to 1.1.2.
- [Release notes](https://github.com/glebm/i18n-tasks/releases)
- [Changelog](https://github.com/glebm/i18n-tasks/blob/main/CHANGES.md)
- [Commits](https://github.com/glebm/i18n-tasks/compare/v1.0.15...v1.1.2)

---
updated-dependencies:
- dependency-name: i18n-tasks
  dependency-version: 1.1.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-20 09:33:07 +00:00
27 changed files with 135 additions and 545 deletions

View File

@@ -407,13 +407,14 @@ GEM
concurrent-ruby (~> 1.0)
i18n-js (3.9.2)
i18n (>= 0.6.6)
i18n-tasks (1.0.15)
i18n-tasks (1.1.2)
activesupport (>= 4.0.2)
ast (>= 2.1.0)
erubi
highline (>= 2.0.0)
highline (>= 3.0.0)
i18n
parser (>= 3.2.2.1)
prism
rails-i18n
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.8, >= 1.8.1)

View File

@@ -6,7 +6,6 @@ angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterpris
'manage_products'
'edit_profile'
'create_variant_overrides'
'create_linked_variants'
]
constructor: ->
@@ -31,4 +30,3 @@ angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterpris
when "manage_products" then t('js.services.manage_products')
when "edit_profile" then t('js.services.edit_profile')
when "create_variant_overrides" then t('js.services.add_products_to_inventory')
when "create_linked_variants" then t('js.services.create_linked_variants')

View File

@@ -107,33 +107,6 @@ module Admin
end
end
# Clone a variant, retaining a link to the "source"
def create_linked_variant
linked_variant = Spree::Variant.find(params[:variant_id])
product_index = params[:product_index]
authorize! :create_linked_variant, linked_variant
status = :ok
begin
variant = linked_variant.create_linked_variant(spree_current_user)
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 = { linked_variant:, variant:, product_index:, variant_index:,
producer_options:, category_options: categories, tax_category_options: }
render :create_linked_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

@@ -47,10 +47,5 @@ module Admin
def variant_tag_enabled?(user)
feature?(:variant_tag, user) || feature?(:variant_tag, *user.enterprises)
end
def allowed_source_producers
@allowed_source_producers ||= OpenFoodNetwork::Permissions.new(spree_current_user)
.enterprises_granting_linked_variants
end
end
end

View File

@@ -202,7 +202,7 @@ module Spree
def add_product_management_abilities(user)
# Enterprise User can only access products that they are a supplier for
can [:create], Spree::Product
# An enterprise user can change a product if they are supplier of at least
# An enterperprise user can change a product if they are supplier of at least
# one of the product's associated variants
can [:admin, :read, :index, :update,
:seo, :group_buy_options,
@@ -214,16 +214,7 @@ module Spree
)
end
# An enterprise user can clone if they have been granted permission to the source variant.
# Technically I'd call this permission clone_linked_variant, but it would be less confusing to
# use the same name as everywhere else.
can [:create_linked_variant], Spree::Variant do |variant|
OpenFoodNetwork::Permissions.new(user).
enterprises_granting_linked_variants.include? variant.supplier
end
can [:admin, :index, :bulk_update, :destroy, :destroy_variant, :clone,
:create_linked_variant], :products_v3
can [:admin, :index, :bulk_update, :destroy, :destroy_variant, :clone], :products_v3
can [:create], Spree::Variant
can [:admin, :index, :read, :edit,

View File

@@ -40,7 +40,6 @@ module Spree
belongs_to :shipping_category, class_name: 'Spree::ShippingCategory', optional: false
belongs_to :primary_taxon, class_name: 'Spree::Taxon', touch: true, optional: false
belongs_to :supplier, class_name: 'Enterprise', optional: false, touch: true
belongs_to :hub, class_name: 'Enterprise', optional: true
delegate :name, :name=, :description, :description=, :meta_keywords, to: :product
@@ -73,15 +72,6 @@ module Spree
has_many :semantic_links, as: :subject, dependent: :delete_all
has_many :supplier_properties, through: :supplier, source: :properties
# Linked variants: I may have one or many sources.
has_many :variant_links_as_target, class_name: 'VariantLink', foreign_key: :target_variant_id,
dependent: :delete_all, inverse_of: :target_variant
has_many :source_variants, through: :variant_links_as_target, source: :source_variant
# I may also have one more many targets.
has_many :variant_links_as_source, class_name: 'VariantLink', foreign_key: :source_variant_id,
dependent: :delete_all, inverse_of: :source_variant
has_many :target_variants, through: :variant_links_as_source, source: :target_variant
localize_number :price, :weight
validates_lengths_from_database
@@ -273,24 +263,6 @@ module Spree
@on_hand_desired = ActiveModel::Type::Integer.new.cast(val)
end
# Clone this variant, retaining a 'source' link to it
def create_linked_variant(user)
# Hub owner is my enterprise which has permission to create variant sourced from that supplier
hub_id = EnterpriseRelationship.permitted_by(supplier).permitting(user.enterprises)
.with_permission(:create_linked_variants)
.pick(:child_id)
dup.tap do |variant|
variant.price = price
variant.source_variants = [self]
variant.stock_items << Spree::StockItem.new(variant:)
variant.hub_id = hub_id
variant.on_demand = on_demand
variant.on_hand = on_hand
variant.save!
end
end
private
def check_currency

View File

@@ -1,6 +0,0 @@
# frozen_string_literal: true
class VariantLink < ApplicationRecord
belongs_to :source_variant, class_name: 'Spree::Variant'
belongs_to :target_variant, class_name: 'Spree::Variant'
end

View File

@@ -11,10 +11,9 @@
-# Filter out variant a user has not permission to update, but keep variant with no supplier
- 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, product_index:, category_options:, tax_category_options:, producer_options: }
= render partial: 'variant_row', locals: { variant:, f: variant_form, 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,10 +1,7 @@
-# haml-lint:disable ViewLength (This file is big, but doesn't make sense to split up at this point)
-# locals: (variant:, f:, product_index: nil, category_options:, tax_category_options:, producer_options:)
-# locals: (variant:, f:, 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
- variant.source_variants.each do |source_variant|
= content_tag(:span, "🔗", title: t('admin.products_page.variant_row.sourced_from', source_name: source_variant.display_name, source_id: source_variant.id, hub_name: variant.hub&.name))
%td.col-name.field.naked_inputs
= f.hidden_field :id
= f.text_field :display_name, 'aria-label': t('admin.products_page.columns.name'), placeholder: variant.product.name
@@ -91,10 +88,6 @@
= render(VerticalEllipsisMenuComponent.new) do
- if variant.persisted?
= link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(variant.product, variant)
- if allowed_source_producers.include?(variant.supplier)
= link_to t('admin.products_page.actions.create_linked_variant'), admin_create_linked_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

@@ -1,16 +0,0 @@
-# locals: (variant:, linked_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(linked_variant) do
%tr.condensed{ id: dom_id(variant), 'data-controller': "variant", 'class': "nested-form-wrapper slide-in",'data-variant-bulk-form-outlet': "#products-form"}
= variant_row
= turbo_stream.append "flashes" do
= render(partial: 'admin/shared/flashes', locals: { flashes: flash })

View File

@@ -6,7 +6,7 @@
%p= t ".description"
%fieldset.no-border-top.no-border-bottom
.row.field
.row
= f.label :email, t(:email)
= f.email_field :email, placeholder: t('.eg_email_address'), data: { controller: "select-user" }, inputmode: "email", autocomplete: "off"
= f.error_message_on :email

View File

@@ -44,17 +44,12 @@ export default class BulkFormController extends Controller {
}
// Register any new elements (may be called by another controller after dynamically adding fields)
// May be called with array of elements to register, otherwise finds all un-registered elements.
registerElements(eventOrElements = null) {
let newElements;
if (Array.isArray(eventOrElements)) {
newElements = eventOrElements;
} else {
const registeredElements = Object.values(this.recordElements).flat();
// Select only elements that haven't been registered yet
newElements = Array.from(this.form.elements).filter((n) => !registeredElements.includes(n));
}
registerElements() {
const registeredElements = Object.values(this.recordElements).flat();
// Select only elements that haven't been registered yet
const newElements = Array.from(this.form.elements).filter(
(n) => !registeredElements.includes(n),
);
this.#registerElements(newElements);
}

View File

@@ -4,8 +4,6 @@ import OptionValueNamer from "js/services/option_value_namer";
// Dynamically update related variant fields
//
export default class VariantController extends Controller {
static outlets = ["bulk-form"];
connect() {
// idea: create a helper that includes a nice getter/setter for Rails model attr values, just pass it the attribute name.
// It could automatically find (and cache a ref to) each dom element and get/set the values.
@@ -42,12 +40,6 @@ export default class VariantController extends Controller {
// on display_as changed; update unit_to_display
// TODO: optimise to avoid unnecessary OptionValueNamer calc
this.displayAs.addEventListener("input", this.#updateUnitDisplay.bind(this), { passive: true });
// Register with bulk products form to listen for changes. Used when dynamically appending variants.
if (this.hasBulkFormOutlet) {
const formElements = this.element.querySelectorAll("input, select, textarea, button");
this.bulkFormOutlet.registerElements(formElements);
}
}
disconnect() {

View File

@@ -54,7 +54,6 @@
.button, button {
@include border-radius(0.5em);
font-family: inherit;
outline: none;
&.x-small {
@@ -66,6 +65,7 @@
}
.button.primary, button.primary {
font-family: $body-font;
background: $orange-450;
color: white;
}

View File

@@ -717,11 +717,8 @@ en:
delete: Delete
remove: Remove
preview: Preview
create_linked_variant: Create linked variant
image:
edit: Edit
variant_row:
sourced_from: "Sourced from: %{source_name} (%{source_id}); Hub: %{hub_name}"
product_preview:
product_preview: Product preview
shop_tab: Shop
@@ -1101,8 +1098,6 @@ en:
clone:
success: Successfully cloned the product
error: Unable to clone the product
create_linked_variant:
success: "Successfully created linked variant"
tag_rules:
rules_per_tag:
one: "%{tag} has 1 rule"
@@ -3906,7 +3901,6 @@ en:
manage_products: "manage products"
edit_profile: "edit profile"
add_products_to_inventory: "add products to inventory"
create_linked_variants: "create linked variants [BETA]"
resources:
could_not_delete_customer: 'Could not delete customer'
product_import:

View File

@@ -82,7 +82,6 @@ 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_linked_variant', to: 'products_v3#create_linked_variant', as: 'create_linked_variant'
resources :product_preview, only: [:show]
resources :variant_overrides do

View File

@@ -1,19 +0,0 @@
# frozen_string_literal: true
class CreateVariantLinks < ActiveRecord::Migration[7.1]
def change
# Create a join table to join two variants. One is the source of the other.
# Primary key index ensures uniqueness and assists querying. target_variant_id is the most
# likely subject and so is first in the index.
# An additional index for source_variant is also included because it may be helpful
# (https://stackoverflow.com/questions/10790518/best-sql-indexes-for-join-table).
create_table :variant_links, primary_key: [:target_variant_id, :source_variant_id] do |t|
t.integer :source_variant_id, null: false, index: true
t.integer :target_variant_id, null: false
t.datetime :created_at, null: false
end
add_foreign_key :variant_links, :spree_variants, column: :source_variant_id
add_foreign_key :variant_links, :spree_variants, column: :target_variant_id
end
end

View File

@@ -1,7 +0,0 @@
# frozen_string_literal: true
class AddHubToSpreeVariants < ActiveRecord::Migration[7.1]
def change
add_reference :spree_variants, :hub, foreign_key: { to_table: :enterprises }
end
end

View File

@@ -1009,8 +1009,6 @@ ActiveRecord::Schema[7.1].define(version: 2026_03_06_015040) do
t.bigint "supplier_id"
t.float "variant_unit_scale"
t.string "variant_unit_name", limit: 255
t.bigint "hub_id"
t.index ["hub_id"], name: "index_spree_variants_on_hub_id"
t.index ["primary_taxon_id"], name: "index_spree_variants_on_primary_taxon_id"
t.index ["product_id"], name: "index_variants_on_product_id"
t.index ["shipping_category_id"], name: "index_spree_variants_on_shipping_category_id"
@@ -1115,13 +1113,6 @@ ActiveRecord::Schema[7.1].define(version: 2026_03_06_015040) do
t.datetime "updated_at", precision: nil, null: false
end
create_table "variant_links", primary_key: ["target_variant_id", "source_variant_id"], force: :cascade do |t|
t.integer "source_variant_id", null: false
t.integer "target_variant_id", null: false
t.datetime "created_at", null: false
t.index ["source_variant_id"], name: "index_variant_links_on_source_variant_id"
end
create_table "variant_overrides", id: :serial, force: :cascade do |t|
t.integer "variant_id", null: false
t.integer "hub_id", null: false
@@ -1271,7 +1262,6 @@ ActiveRecord::Schema[7.1].define(version: 2026_03_06_015040) do
add_foreign_key "spree_tax_rates", "spree_zones", column: "zone_id", name: "spree_tax_rates_zone_id_fk"
add_foreign_key "spree_users", "spree_addresses", column: "bill_address_id", name: "spree_users_bill_address_id_fk"
add_foreign_key "spree_users", "spree_addresses", column: "ship_address_id", name: "spree_users_ship_address_id_fk"
add_foreign_key "spree_variants", "enterprises", column: "hub_id"
add_foreign_key "spree_variants", "enterprises", column: "supplier_id"
add_foreign_key "spree_variants", "spree_products", column: "product_id", name: "spree_variants_product_id_fk"
add_foreign_key "spree_variants", "spree_shipping_categories", column: "shipping_category_id"
@@ -1288,8 +1278,6 @@ ActiveRecord::Schema[7.1].define(version: 2026_03_06_015040) do
add_foreign_key "subscriptions", "spree_payment_methods", column: "payment_method_id", name: "subscriptions_payment_method_id_fk"
add_foreign_key "subscriptions", "spree_shipping_methods", column: "shipping_method_id", name: "subscriptions_shipping_method_id_fk"
add_foreign_key "tag_rules", "enterprises"
add_foreign_key "variant_links", "spree_variants", column: "source_variant_id"
add_foreign_key "variant_links", "spree_variants", column: "target_variant_id"
add_foreign_key "variant_overrides", "enterprises", column: "hub_id", name: "variant_overrides_hub_id_fk"
add_foreign_key "variant_overrides", "spree_variants", column: "variant_id", name: "variant_overrides_variant_id_fk"
add_foreign_key "vouchers", "enterprises"

View File

@@ -86,10 +86,6 @@ module OpenFoodNetwork
managed_and_related_enterprises_granting :manage_products
end
def enterprises_granting_linked_variants
related_enterprises_granting :create_linked_variants
end
def manages_one_enterprise?
@user.enterprises.length == 1
end

View File

@@ -16,4 +16,3 @@ describe "enterprise relationships", ->
expect(EnterpriseRelationships.permission_presentation("manage_products")).toEqual "manage products"
expect(EnterpriseRelationships.permission_presentation("edit_profile")).toEqual "edit profile"
expect(EnterpriseRelationships.permission_presentation("create_variant_overrides")).toEqual "add products to inventory"
expect(EnterpriseRelationships.permission_presentation("create_linked_variants")).toEqual "create linked variants [BETA]"

View File

@@ -364,19 +364,6 @@ RSpec.describe Spree::Ability do
for: p2.variants.first)
end
describe "create_linked_variant" do
it "should not be able to create linked variant without permission" do
is_expected.not_to have_ability([:create_linked_variant], for: p_related.variants.first)
end
it "should be able to create linked variant when granted permission" do
create(:enterprise_relationship, parent: s_related, child: s1,
permissions_list: [:create_linked_variants])
is_expected.to have_ability([:create_linked_variant], for: p_related.variants.first)
end
end
it "should not be able to access admin actions on orders" do
is_expected.not_to have_ability([:admin], for: Spree::Order)
end
@@ -742,19 +729,6 @@ RSpec.describe Spree::Ability do
it "can request permitted enterprise fees for an order cycle" do
is_expected.to have_ability([:for_order_cycle], for: EnterpriseFee)
end
describe "create_linked_variant" do
it "should not be able to create linked variant without permission" do
is_expected.not_to have_ability([:create_linked_variant], for: p_related.variants.first)
end
it "should be able to create linked variant when granted permission" do
create(:enterprise_relationship, parent: s_related, child: d1,
permissions_list: [:create_linked_variants])
is_expected.to have_ability([:create_linked_variant], for: p_related.variants.first)
end
end
end
context 'Order Cycle co-ordinator, distributor enterprise manager' do
@@ -830,19 +804,6 @@ RSpec.describe Spree::Ability do
it "has the ability to manage vouchers" do
is_expected.to have_ability([:admin, :create], for: Voucher)
end
describe "create_linked_variant for own enterprise" do
it "should not be able to create own sourced variant without permission" do
is_expected.not_to have_ability([:create_linked_variant], for: p1.variants.first)
end
it "should be able to create own sourced variant when granted self permission" do
create(:enterprise_relationship, parent: s1, child: s1,
permissions_list: [:create_linked_variants])
is_expected.to have_ability([:create_linked_variant], for: p1.variants.first)
end
end
end
context 'enterprise owner' do

View File

@@ -8,7 +8,6 @@ RSpec.describe Spree::Variant do
it { is_expected.to have_many :semantic_links }
it { is_expected.to belong_to(:product).required }
it { is_expected.to belong_to(:supplier).required }
it { is_expected.to belong_to(:hub).optional }
it { is_expected.to have_many(:inventory_units) }
it { is_expected.to have_many(:line_items) }
it { is_expected.to have_many(:stock_items) }
@@ -21,9 +20,6 @@ RSpec.describe Spree::Variant do
it { is_expected.to have_many(:inventory_items) }
it { is_expected.to have_many(:supplier_properties).through(:supplier) }
it { is_expected.to have_many(:source_variants).through(:variant_links_as_target) }
it { is_expected.to have_many(:target_variants).through(:variant_links_as_source) }
describe "shipping category" do
it "sets a shipping category if none provided" do
variant = build(:variant, shipping_category: nil)
@@ -1005,30 +1001,4 @@ RSpec.describe Spree::Variant do
expect(variant.unit_presentation).to eq "My display"
end
end
describe "#create_linked_variant" do
let(:user) { create(:user, enterprises: [enterprise]) }
let(:supplier) { variant.supplier }
let(:enterprise) { create(:enterprise) }
context "with create_linked_variants permissions on supplier" do
let!(:enterprise_relationship) {
create(:enterprise_relationship,
parent: supplier,
child: enterprise,
permissions_list: [:create_linked_variants])
}
let(:variant) { create(:variant, price: 10.95, on_demand: false, on_hand: 5) }
it "clones the variant, retaining a link to the source" do
linked_variant = variant.create_linked_variant(user)
expect(linked_variant.source_variants).to eq [variant]
expect(linked_variant.hub).to eq enterprise
expect(linked_variant.price).to eq 10.95
expect(linked_variant.on_demand).to eq false
expect(linked_variant.on_hand).to eq 5
end
end
end
end

View File

@@ -59,71 +59,4 @@ RSpec.describe "Admin::ProductsV3" do
expect(response).to redirect_to('/unauthorized')
end
end
describe "POST /admin/products/create_linked_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_linked_variant_path, as: :turbo_stream, params:)
expect(response).to redirect_to('/unauthorized')
}.not_to change { variant.product.variants.count }
end
context "With create_linked_variants permissions on supplier" do
let!(:enterprise_relationship) {
create(:enterprise_relationship,
parent: supplier,
child: enterprise,
permissions_list: [:create_linked_variants])
}
it "clones the variant, retaining link as source" do
params = { variant_id: variant.id, product_index: 1 }
expect {
post(admin_create_linked_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)
new_variant = variant.product.variants.order(:id).last
# The new variant is a target of the original. It is a "sourced" variant.
expect(variant.target_variants.first).to eq new_variant
# The new variant's source is the original
expect(new_variant.source_variants.first).to eq variant
end
context "and I'm also owner of another enterprise" do
let!(:enterprise2) { create(:enterprise) }
let(:user) { create(:user, enterprises: [enterprise, enterprise2]) }
it "clones the variant, owned by my enterprise that has permission" do
enterprise2.owner = user
params = { variant_id: variant.id, product_index: 1 }
expect {
post(admin_create_linked_variant_path, as: :turbo_stream, params:)
expect(response).to have_http_status(:ok)
}.to change { variant.product.variants.count }.by(1)
# The new variant is owned by my enterprise that has permission, not the other one
new_variant = variant.product.variants.order(:id).last
expect(new_variant.hub).to eq enterprise
end
end
end
end
end

View File

@@ -47,23 +47,18 @@ create(:enterprise)
uncheck 'to manage products'
check 'to edit profile'
check 'to add products to inventory'
check 'to create linked variants'
select2_select 'Two', from: 'enterprise_relationship_child_id'
click_button 'Create'
# Wait for row to appear since have_relationship doesn't wait
expect(page).to have_selector 'tr', count: 2
# Permissions appear.. in a different order for some reason.
expect_relationship_with_permissions e1, e2,
['to add to order cycle',
'to create linked variants [BETA]',
'to add products to inventory',
'to edit profile']
'to add products to inventory', 'to edit profile']
er = EnterpriseRelationship.where(parent_id: e1, child_id: e2).first
expect(er).to be_present
expect(er.permissions.map(&:name)).to match_array ['add_to_order_cycle', 'edit_profile',
'create_variant_overrides',
'create_linked_variants']
'create_variant_overrides']
end
it "attempting to create a relationship with invalid data" do

View File

@@ -2,7 +2,7 @@
require "system_helper"
RSpec.describe 'As an enterprise user, I can perform actions on the products screen' do
RSpec.describe 'As an enterprise user, I can manage my products' do
include AdminHelper
include WebHelper
include AuthenticationHelper
@@ -19,6 +19,18 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
let(:categories_search_selector) { 'input[placeholder="Select category"]' }
let(:tax_categories_search_selector) { 'input[placeholder="Search for tax categories"]' }
describe "with no products" do
before { visit admin_products_url }
it "can see the new product page" do
expect(page).to have_content "Bulk Edit Products"
expect(page).to have_text "No products found"
# displays buttons to add products with the correct links
expect(page).to have_link(class: "button", text: "New Product", href: "/admin/products/new")
expect(page).to have_link(class: "button", text: "Import multiple products",
href: admin_product_import_path)
end
end
describe "column selector" do
let!(:product) { create(:simple_product) }
@@ -93,6 +105,8 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
end
end
describe "columns"
describe "Changing producers, category and tax category" do
let!(:variant_a1) {
product_a.variants.first.tap{ |v|
@@ -246,24 +260,24 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
describe "Cloning product" do
it "shows the cloned product on page when clicked on the cloned option" do
# TODO, variant supplier missing, needs to be copied from variant and not product
within "table.products" do
# Gather input values, because page.content doesn't include them.
input_content = page.find_all('input[type=text]').map(&:value).join
# Products does not include the cloned product.
expect(input_content).not_to match /COPY OF Apples/
end
click_product_clone "Apples"
expect(page).to have_content "Successfully cloned the product"
within "table.products" do
# Product list includes the cloned product.
expect(all_input_values).to match /COPY OF Apples/
# Gather input values, because page.content doesn't include them.
input_content = page.find_all('input[type=text]').map(&:value).join
# And I can perform actions on the new product
within row_containing_name "COPY OF Apples" do
page.find(".vertical-ellipsis-menu").click
expect(page).to have_link "Edit"
expect(page).to have_link "Clone"
# expect(page).to have_link "Delete" # it's not a proper link :/
fill_in "Name", with: "My copy of Apples"
end
click_button "Save changes"
# Products include the cloned product.
expect(input_content).to match /COPY OF Apples/
end
end
end
@@ -284,88 +298,6 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
end
end
describe "Create linked variant" do
let!(:variant) {
create(:variant, display_name: "My box", supplier: producer)
}
let!(:other_producer) { create(:supplier_enterprise) }
let!(:other_variant) {
create(:variant, display_name: "My friends box", supplier: other_producer)
}
let!(:enterprise_relationship) {
# Other producer grants me access to manage their variant
create(:enterprise_relationship, parent: other_producer, child: producer,
permissions_list: [:manage_products])
}
context "with create_linked_variants permission for my, and other's variants" do
it "creates a linked variant" do
create(:enterprise_relationship, parent: producer, child: producer,
permissions_list: [:create_linked_variants])
enterprise_relationship.permissions.create! name: :create_linked_variants
visit admin_products_url
# Check my own variant
within row_containing_name("My box") do
page.find(".vertical-ellipsis-menu").click
expect(page).to have_link "Create linked variant"
end
# Create linked variant sourced from my friend
within row_containing_name("My friends box") do
page.find(".vertical-ellipsis-menu").click
click_link "Create linked variant"
end
expect(page).to have_content "Successfully created linked variant"
within "table.products" do
# There are now two copies
expect(all_input_values).to match /My friends box.*My friends box/
# One of them is designated as a linked variant
expect(page).to have_content "🔗"
last_box = page.all(row_containing_name("My friends box")).last
# Close action menu (shouldn't need this, it should close itself)
last_box.click
# And I can perform actions on the new product
within last_box do
page.find(".vertical-ellipsis-menu").click
expect(page).to have_link "Edit"
# expect(page).to have_link "Clone" # tofix: menu is partially obscured
# expect(page).to have_link "Delete" # it's not a proper link
fill_in "Name", with: "My copy of Apples"
end
click_button "Save changes"
# initially obscured by the previous message, then disappears before capybara sees it.
# expect(page).to have_content "Changes saved"
end
end
end
context "without create_linked_variants permission" do
it "does not show the option in the menu" do
visit admin_products_url
within row_containing_name("My box") do
page.find(".vertical-ellipsis-menu").click
expect(page).not_to have_link "Create linked variant"
end
within row_containing_name("My friends box") do
page.find(".vertical-ellipsis-menu").click
expect(page).not_to have_link "Create linked variant"
end
end
end
end
describe "delete" do
let!(:product_a) { create(:simple_product, name: "Apples", sku: "APL-00") }
let(:delete_option_selector) { "a[data-controller='modal-link'].delete" }
@@ -595,6 +527,90 @@ RSpec.describe 'As an enterprise user, I can perform actions on the products scr
end
end
context "as an enterprise manager" do
let(:supplier_managed1) { create(:supplier_enterprise, name: 'Supplier Managed 1') }
let(:supplier_managed2) { create(:supplier_enterprise, name: 'Supplier Managed 2') }
let(:supplier_unmanaged) { create(:supplier_enterprise, name: 'Supplier Unmanaged') }
let(:supplier_permitted) { create(:supplier_enterprise, name: 'Supplier Permitted') }
let(:distributor_managed) { create(:distributor_enterprise, name: 'Distributor Managed') }
let(:distributor_unmanaged) { create(:distributor_enterprise, name: 'Distributor Unmanaged') }
let!(:product_supplied) { create(:product, supplier_id: supplier_managed1.id, price: 10.0) }
let!(:product_not_supplied) { create(:product, supplier_id: supplier_unmanaged.id) }
let!(:product_supplied_permitted) {
create(:product, name: 'Product Permitted', supplier_id: supplier_permitted.id, price: 10.0)
}
let(:product_supplied_inactive) {
create(:product, supplier_id: supplier_managed1.id, price: 10.0)
}
let!(:supplier_permitted_relationship) do
create(:enterprise_relationship, parent: supplier_permitted, child: supplier_managed1,
permissions_list: [:manage_products])
end
before do
enterprise_user = create(:user)
enterprise_user.enterprise_roles.build(enterprise: supplier_managed1).save
enterprise_user.enterprise_roles.build(enterprise: supplier_managed2).save
enterprise_user.enterprise_roles.build(enterprise: distributor_managed).save
login_as enterprise_user
end
it "shows only products that I supply" do
visit spree.admin_products_path
# displays permitted product list only
expect(page).to have_selector row_containing_name(product_supplied.name)
expect(page).to have_selector row_containing_name(product_supplied_permitted.name)
expect(page).not_to have_selector row_containing_name(product_not_supplied.name)
end
it "shows only suppliers that I manage or have permission to" do
visit spree.admin_products_path
within row_containing_placeholder(product_supplied.name) do
expect(page).to have_select(
'_products_0_variants_attributes_0_supplier_id',
options: [
'Select producer',
supplier_managed1.name, supplier_managed2.name, supplier_permitted.name
], selected: supplier_managed1.name
)
end
within row_containing_placeholder(product_supplied_permitted.name) do
expect(page).to have_select(
'_products_1_variants_attributes_0_supplier_id',
options: [
'Select producer',
supplier_managed1.name, supplier_managed2.name, supplier_permitted.name
], selected: supplier_permitted.name
)
end
end
it "shows inactive products that I supply" do
product_supplied_inactive
visit spree.admin_products_path
expect(page).to have_selector row_containing_name(product_supplied_inactive.name)
end
it "allows me to update a product" do
visit spree.admin_products_path
within row_containing_name(product_supplied.name) do
fill_in "Name", with: "Pommes"
end
click_button "Save changes"
expect(page).to have_content "Changes saved"
expect(page).to have_selector row_containing_name("Pommes")
end
end
def open_action_menu
page.find(".vertical-ellipsis-menu").click
end

View File

@@ -2,13 +2,13 @@
require "system_helper"
RSpec.describe 'As an enterprise user, I can browse my products' do
RSpec.describe 'As an enterprise user, I can manage my products' do
include AdminHelper
include WebHelper
include AuthenticationHelper
include FileHelper
let(:producer) { create(:supplier_enterprise, name: "My Enterprise") }
let(:producer) { create(:supplier_enterprise) }
let(:user) { create(:user, enterprises: [producer]) }
before do
@@ -19,25 +19,15 @@ RSpec.describe 'As an enterprise user, I can browse my products' do
let(:categories_search_selector) { 'input[placeholder="Search for categories"]' }
let(:tax_categories_search_selector) { 'input[placeholder="Search for tax categories"]' }
describe "with no products" do
before { visit admin_products_url }
it "can see the new product page" do
expect(page).to have_content "Bulk Edit Products"
expect(page).to have_text "No products found"
# displays buttons to add products with the correct links
expect(page).to have_link(class: "button", text: "New Product", href: "/admin/products/new")
expect(page).to have_link(class: "button", text: "Import multiple products",
href: admin_product_import_path)
end
end
describe "listing" do
let!(:p1) { create(:product, name: "Product1") }
let!(:p2) { create(:product, name: "Product2") }
it "displays a list of products" do
visit admin_products_path
before do
visit admin_products_url
end
it "displays a list of products" do
within ".products" do
# displays table header
expect(page).to have_selector "th", text: "Name"
@@ -139,34 +129,6 @@ RSpec.describe 'As an enterprise user, I can browse my products' do
expect(page).to have_select "variant_unit_with_scale", selected: "Items"
expect(page).to have_field "variant_unit_name", with: "packet"
end
context "with sourced variant" do
let(:source_producer) { create(:supplier_enterprise) }
let(:p3) { create(:product, name: "Product3", supplier_id: source_producer.id) }
let!(:v3_source) { p3.variants.first }
let!(:v3_sourced) {
create(:variant, display_name: "Variant3-sourced", product: p3, supplier: source_producer,
hub: producer)
}
let!(:enterprise_relationship) {
# Other producer grants me access to manage their variant
create(:enterprise_relationship, parent: source_producer, child: producer,
permissions_list: [:manage_products])
}
before do
v3_sourced.source_variants << v3_source
visit admin_products_url
end
it "shows sourced variant with indicator" do
within row_containing_name("Variant3-sourced") do
expect(page).to have_selector 'span[title*="Sourced from: "]'
expect(page).to have_selector 'span[title*="Hub: My Enterprise"]'
end
end
end
end
describe "sorting" do
@@ -501,88 +463,4 @@ RSpec.describe 'As an enterprise user, I can browse my products' do
end
end
end
context "as an enterprise manager" do
let(:supplier_managed1) { create(:supplier_enterprise, name: 'Supplier Managed 1') }
let(:supplier_managed2) { create(:supplier_enterprise, name: 'Supplier Managed 2') }
let(:supplier_unmanaged) { create(:supplier_enterprise, name: 'Supplier Unmanaged') }
let(:supplier_permitted) { create(:supplier_enterprise, name: 'Supplier Permitted') }
let(:distributor_managed) { create(:distributor_enterprise, name: 'Distributor Managed') }
let(:distributor_unmanaged) { create(:distributor_enterprise, name: 'Distributor Unmanaged') }
let!(:product_supplied) { create(:product, supplier_id: supplier_managed1.id, price: 10.0) }
let!(:product_not_supplied) { create(:product, supplier_id: supplier_unmanaged.id) }
let!(:product_supplied_permitted) {
create(:product, name: 'Product Permitted', supplier_id: supplier_permitted.id, price: 10.0)
}
let(:product_supplied_inactive) {
create(:product, supplier_id: supplier_managed1.id, price: 10.0)
}
let!(:supplier_permitted_relationship) do
create(:enterprise_relationship, parent: supplier_permitted, child: supplier_managed1,
permissions_list: [:manage_products])
end
before do
enterprise_user = create(:user)
enterprise_user.enterprise_roles.build(enterprise: supplier_managed1).save
enterprise_user.enterprise_roles.build(enterprise: supplier_managed2).save
enterprise_user.enterprise_roles.build(enterprise: distributor_managed).save
login_as enterprise_user
end
it "shows only products that I supply" do
visit spree.admin_products_path
# displays permitted product list only
expect(page).to have_selector row_containing_name(product_supplied.name)
expect(page).to have_selector row_containing_name(product_supplied_permitted.name)
expect(page).not_to have_selector row_containing_name(product_not_supplied.name)
end
it "shows only suppliers that I manage or have permission to" do
visit spree.admin_products_path
within row_containing_placeholder(product_supplied.name) do
expect(page).to have_select(
'_products_0_variants_attributes_0_supplier_id',
options: [
'Select producer',
supplier_managed1.name, supplier_managed2.name, supplier_permitted.name
], selected: supplier_managed1.name
)
end
within row_containing_placeholder(product_supplied_permitted.name) do
expect(page).to have_select(
'_products_1_variants_attributes_0_supplier_id',
options: [
'Select producer',
supplier_managed1.name, supplier_managed2.name, supplier_permitted.name
], selected: supplier_permitted.name
)
end
end
it "shows inactive products that I supply" do
product_supplied_inactive
visit spree.admin_products_path
expect(page).to have_selector row_containing_name(product_supplied_inactive.name)
end
it "allows me to update a product" do
visit spree.admin_products_path
within row_containing_name(product_supplied.name) do
fill_in "Name", with: "Pommes"
end
click_button "Save changes"
expect(page).to have_content "Changes saved"
expect(page).to have_selector row_containing_name("Pommes")
end
end
end