Refactor order management permissions for producers

Introduces granular permissions control for producers editing orders:

- Adds new :edit_as_producer_only permission for suppliers
- Refactors ability checks to clearly separate producer vs admin/distributor access
- Updates order views to properly restrict actions based on user role
- Prevents admins from being restricted by producer-only edit mode
This commit is contained in:
Ahmed Ejaz
2025-06-15 18:09:02 +05:00
parent 75b2119dd1
commit a37e08c2fd
5 changed files with 84 additions and 20 deletions

View File

@@ -155,8 +155,7 @@ module Spree
end
def filter_by_supplier?(order)
order.distributor&.enable_producers_to_edit_orders &&
spree_current_user.can_manage_line_items_in_orders_only?
can? :edit_as_producer_only, order
end
def display_value_for_producer(order, value)

View File

@@ -20,6 +20,10 @@ module Spree
if user.try(:admin?)
can :manage, :all
# this action was needed for restrictions for distributors and suppliers
# however, admins don't need to be restricted, so, bypassing it for admins
cannot :edit_as_producer_only, Spree::Order
else
can [:index, :read], Country
can :create, Order
@@ -257,8 +261,13 @@ module Spree
end
def add_order_cycle_management_abilities(user)
can [:admin, :index], OrderCycle do |order_cycle|
OrderCycle.visible_by(user).include?(order_cycle) ||
order_cycle.orders.any? { |order| can_edit_as_producer(order, user) }
end
can [
:admin, :index, :read, :edit, :update, :incoming, :outgoing, :checkout_options
:read, :edit, :update, :incoming, :outgoing, :checkout_options
], OrderCycle do |order_cycle|
OrderCycle.visible_by(user).include? order_cycle
end
@@ -274,8 +283,37 @@ module Spree
end
def add_order_management_abilities(user)
can [:index, :create], Spree::Order
can [:read, :update, :fire, :resend, :invoice, :print], Spree::Order do |order|
can [:manage_order_sections], Spree::Order do |order|
user.admin? ||
order.distributor.nil? ||
user.enterprises.include?(order.distributor) ||
order.order_cycle&.coordinated_by?(user)
end
can [:edit_as_producer_only], Spree::Order do |order|
cannot?(:manage_order_sections, order) && can_edit_as_producer(order, user)
end
can [:index], Spree::Order do |order|
user.admin? ||
user.enterprises.any?(&:is_distributor) ||
can_edit_as_producer(order, user)
end
can [:create], Spree::Order
can [:read, :update], Spree::Order do |order|
# We allow editing orders with a nil distributor as this state occurs
# during the order creation process from the admin backend
order.distributor.nil? ||
# Enterprise User can access orders that they are a distributor for
user.enterprises.include?(order.distributor) ||
# Enterprise User can access orders that are placed inside a OC they coordinate
order.order_cycle&.coordinated_by?(user) ||
can_edit_as_producer(order, user)
end
can [:fire, :resend, :invoice, :print], Spree::Order do |order|
# We allow editing orders with a nil distributor as this state occurs
# during the order creation process from the admin backend
order.distributor.nil? ||
@@ -284,22 +322,39 @@ module Spree
# Enterprise User can access orders that are placed inside a OC they coordinate
order.order_cycle&.coordinated_by?(user)
end
can [:admin, :bulk_management, :managed, :distribution], Spree::Order do
can [:admin, :bulk_management], Spree::Order do |order|
user.admin? ||
user.enterprises.any?(&:is_distributor) ||
can_edit_as_producer(order, user)
end
can [:managed, :distribution], Spree::Order do
user.admin? || user.enterprises.any?(&:is_distributor)
end
can [:admin, :index, :create, :show, :poll, :generate], :invoice
can [:admin, :visible], Enterprise
can [:admin, :index, :create, :update, :destroy], :line_item
can [:admin, :index, :create], Spree::LineItem
can [:admin, :index, :create], Spree::LineItem do |item|
user.admin? ||
user.enterprises.any?(&:is_distributor) ||
can_edit_as_producer(item.order, user)
end
can [:destroy, :update], Spree::LineItem do |item|
order = item.order
user.admin? ||
user.enterprises.include?(order.distributor) ||
order.order_cycle&.coordinated_by?(user)
order.order_cycle&.coordinated_by?(user) ||
can_edit_as_producer(order, user)
end
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Shipment do |shipment|
user.admin? ||
user.enterprises.any?(&:is_distributor) ||
can_edit_as_producer(shipment.order, user)
end
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Payment
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Shipment
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::Adjustment
can [:admin, :index, :read, :create, :edit, :update, :fire], Spree::ReturnAuthorization
can [:destroy], Spree::Adjustment do |adjustment|
@@ -350,26 +405,31 @@ module Spree
can [:admin, :edit, :cancel, :resume], ProxyOrder do |proxy_order|
user.enterprises.include?(proxy_order.subscription.shop)
end
can [:visible], Enterprise
end
def can_edit_order(order, user)
def can_edit_as_producer(order, user)
return unless order.distributor&.enable_producers_to_edit_orders
order.variants.any? { |variant| user.enterprises.ids.include?(variant.supplier_id) }
end
def add_manage_line_items_abilities(user)
can [:edit_as_producer_only], Spree::Order do |order|
can_edit_as_producer(order, user)
end
can [:admin, :read, :index, :edit, :update, :bulk_management], Spree::Order do |order|
can_edit_order(order, user)
can_edit_as_producer(order, user)
end
can [:admin, :index, :create, :destroy, :update], Spree::LineItem do |item|
can_edit_order(item.order, user)
can_edit_as_producer(item.order, user)
end
can [:index, :create, :add, :read, :edit, :update], Spree::Shipment do |shipment|
can_edit_order(shipment.order, user)
can_edit_as_producer(shipment.order, user)
end
can [:admin, :index], OrderCycle do |order_cycle|
can_edit_order(order_cycle.order, user)
can_edit_as_producer(order_cycle.order, user)
end
can [:visible], Enterprise
end

View File

@@ -8,7 +8,7 @@
- if @order.shipments.any?
= render :partial => "spree/admin/orders/shipment", :collection => @order.shipments, :locals => { :order => @order }
- if spree_current_user.can_manage_orders?
- if can?(:manage_order_sections, @order)
- if @order.line_items.exists?
= render partial: "spree/admin/orders/note", locals: { order: @order }

View File

@@ -47,10 +47,10 @@
- if local_assigns[:success]
%i.success.icon-ok-sign{"data-controller": "ephemeral"}
= render AdminTooltipComponent.new(text: t('spree.admin.orders.index.edit'), link_text: "", link: edit_admin_order_path(order), link_class: "icon_link with-tip icon-edit no-text")
- if spree_current_user.can_manage_orders? && order.ready_to_ship?
- if can?(:manage_order_sections, order) && order.ready_to_ship?
%form
= render ShipOrderComponent.new(order: order)
= render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-road icon_link with-tip no-text", reflex_data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.ship'), shipment: true}
- if can?(:update, Spree::Payment) && order.payment_required? && order.pending_payments.reject(&:requires_authorization?).any?
- if can?(:manage_order_sections, order) && can?(:update, Spree::Payment) && order.payment_required? && order.pending_payments.reject(&:requires_authorization?).any?
= render partial: 'admin/shared/tooltip_button', locals: {button_class: "icon-capture icon_link no-text", button_reflex: "click->Admin::OrdersReflex#capture", reflex_data_id: order.id.to_s, tooltip_text: t('spree.admin.orders.index.capture')}

View File

@@ -6,7 +6,7 @@
- content_for :page_actions do
- if can?(:fire, @order)
%li= event_links(@order)
- if spree_current_user.can_manage_orders?
- if can?(:manage_order_sections, @order)
= render partial: 'spree/admin/shared/order_links'
- if can?(:admin, Spree::Order)
%li
@@ -14,7 +14,7 @@
= t(:back_to_orders_list)
= render partial: "spree/admin/shared/order_page_title"
- if spree_current_user.can_manage_orders?
- if can?(:manage_order_sections, @order)
= render partial: "spree/admin/shared/order_tabs", locals: { current: 'Order Details' }
%div
@@ -22,7 +22,12 @@
= admin_inject_shops(@shops, module: 'admin.orders')
= admin_inject_order_cycles(@order_cycles)
%div{"ng-controller" => "orderCtrl", "ofn-distributor-id" => @order.distributor_id, "ofn-order-cycle-id" => @order.order_cycle_id}
%div{
"ng-controller" => "orderCtrl",
"ofn-distributor-id" => @order.distributor_id,
"ofn-order-cycle-id" => @order.order_cycle_id,
"ofn-search-variants-as" => (can?(:manage_order_sections, @order) ? 'hub' : 'supplier'),
}
= render :partial => 'add_product' if can?(:update, @order)