Files
openfoodnetwork/app/services/order_cycles/form_service.rb

250 lines
8.3 KiB
Ruby

# frozen_string_literal: true
require 'open_food_network/permissions'
require 'open_food_network/order_cycle_form_applicator'
module OrderCycles
class FormService
def initialize(order_cycle, order_cycle_params, user)
@order_cycle = order_cycle
@confirm_datetime_change = order_cycle_params.delete :confirm_datetime_change
@error_class = order_cycle_params.delete :error_class
@order_cycle_params = order_cycle_params
@specified_params = order_cycle_params.keys
@user = user
@permissions = OpenFoodNetwork::Permissions.new(user)
@schedule_ids = order_cycle_params.delete(:schedule_ids)
@selected_distributor_payment_method_ids = order_cycle_params.delete(
:selected_distributor_payment_method_ids
)
@selected_distributor_shipping_method_ids = order_cycle_params.delete(
:selected_distributor_shipping_method_ids
)
end
def save
# Check that order cycle datetime values changed if it has existing orders
verify_datetime_change!
schedule_ids = build_schedule_ids
order_cycle.assign_attributes(order_cycle_params)
return false unless order_cycle.valid?
order_cycle.transaction do
order_cycle.save!
order_cycle.schedule_ids = schedule_ids if parameter_specified?(:schedule_ids)
order_cycle.save!
apply_exchange_changes
if can_update_selected_payment_or_shipping_methods?
attach_selected_distributor_payment_methods
attach_selected_distributor_shipping_methods
end
sync_subscriptions
true
end
rescue ActiveRecord::RecordInvalid => e
add_exception_to_order_cycle_errors(e)
false
end
private
attr_accessor :order_cycle, :order_cycle_params, :user, :permissions
def add_exception_to_order_cycle_errors(exception)
error = exception.message.split(":").last.strip
order_cycle.errors.add(:base, error) if order_cycle.errors.to_a.exclude?(error)
end
def apply_exchange_changes
return if exchanges_unchanged?
OpenFoodNetwork::OrderCycleFormApplicator.new(order_cycle, user).go!
# reload so outgoing exchanges are up-to-date for shipping/payment method validations
order_cycle.reload
end
def attach_selected_distributor_payment_methods
return if @selected_distributor_payment_method_ids.nil?
if distributor_only?
payment_method_ids = order_cycle.selected_distributor_payment_method_ids
payment_method_ids -= user_distributor_payment_method_ids
payment_method_ids += user_only_selected_distributor_payment_method_ids
order_cycle.selected_distributor_payment_method_ids = payment_method_ids
else
order_cycle
.selected_distributor_payment_method_ids = selected_distributor_payment_method_ids
end
order_cycle.save!
end
def attach_selected_distributor_shipping_methods
return if @selected_distributor_shipping_method_ids.nil?
if distributor_only?
# A distributor can only update methods associated with their own
# enterprise, so we load all previously selected methods, and replace
# only the distributor's methods with their selection (not touching other
# distributor's methods).
shipping_method_ids = order_cycle.selected_distributor_shipping_method_ids
shipping_method_ids -= user_distributor_shipping_method_ids
shipping_method_ids += user_only_selected_distributor_shipping_method_ids
order_cycle.selected_distributor_shipping_method_ids = shipping_method_ids
else
order_cycle.selected_distributor_shipping_method_ids =
selected_distributor_shipping_method_ids
end
order_cycle.save!
end
def attachable_distributor_payment_method_ids
@attachable_distributor_payment_method_ids ||=
order_cycle.attachable_distributor_payment_methods.map(&:id)
end
def attachable_distributor_shipping_method_ids
@attachable_distributor_shipping_method_ids ||=
order_cycle.attachable_distributor_shipping_methods.map(&:id)
end
def exchanges_unchanged?
[:incoming_exchanges, :outgoing_exchanges].all? do |direction|
order_cycle_params[direction].nil?
end
end
def selected_distributor_payment_method_ids
@selected_distributor_payment_method_ids = (
attachable_distributor_payment_method_ids &
@selected_distributor_payment_method_ids.compact_blank.map(&:to_i)
)
if attachable_distributor_payment_method_ids.sort ==
@selected_distributor_payment_method_ids.sort
@selected_distributor_payment_method_ids = []
end
@selected_distributor_payment_method_ids
end
def user_only_selected_distributor_payment_method_ids
user_distributor_payment_method_ids.intersection(selected_distributor_payment_method_ids)
end
def selected_distributor_shipping_method_ids
@selected_distributor_shipping_method_ids = (
attachable_distributor_shipping_method_ids &
@selected_distributor_shipping_method_ids.compact_blank.map(&:to_i)
)
if attachable_distributor_shipping_method_ids.sort ==
@selected_distributor_shipping_method_ids.sort
@selected_distributor_shipping_method_ids = []
end
@selected_distributor_shipping_method_ids
end
def user_only_selected_distributor_shipping_method_ids
user_distributor_shipping_method_ids.intersection(selected_distributor_shipping_method_ids)
end
def build_schedule_ids
return unless parameter_specified?(:schedule_ids)
result = existing_schedule_ids
result |= (requested_schedule_ids & permitted_schedule_ids) # Add permitted and requested
# Remove permitted but not requested
result -= ((result & permitted_schedule_ids) - requested_schedule_ids)
result
end
def sync_subscriptions
return unless parameter_specified?(:schedule_ids)
return unless schedule_sync_required?
OrderManagement::Subscriptions::ProxyOrderSyncer.new(subscriptions_to_sync).sync!
end
def schedule_sync_required?
removed_schedule_ids.any? || new_schedule_ids.any?
end
def subscriptions_to_sync
Subscription.where(schedule_id: removed_schedule_ids + new_schedule_ids)
end
def requested_schedule_ids
@schedule_ids.map(&:to_i)
end
def parameter_specified?(key)
@specified_params.map(&:to_s).include?(key.to_s)
end
def permitted_schedule_ids
Schedule.where(id: requested_schedule_ids | existing_schedule_ids)
.merge(permissions.editable_schedules).pluck(:id)
end
def existing_schedule_ids
@existing_schedule_ids ||= order_cycle.persisted? ? order_cycle.schedule_ids : []
end
def removed_schedule_ids
existing_schedule_ids - order_cycle.schedule_ids
end
def new_schedule_ids
@order_cycle.schedule_ids - existing_schedule_ids
end
def can_update_selected_payment_or_shipping_methods?
@user.admin? || coordinator? || distributor?
end
def coordinator?
@user.enterprises.include?(@order_cycle.coordinator)
end
def distributor?
!user_distributors_ids.empty?
end
def distributor_only?
distributor? && !@user.admin? && !coordinator?
end
def user_distributors_ids
@user_distributors_ids ||= @user.enterprises.pluck(:id)
.intersection(@order_cycle.distributors.pluck(:id))
end
def user_distributor_payment_method_ids
@user_distributor_payment_method_ids ||=
DistributorPaymentMethod.where(distributor_id: user_distributors_ids)
.pluck(:id)
end
def user_distributor_shipping_method_ids
@user_distributor_shipping_method_ids ||=
DistributorShippingMethod.where(distributor_id: user_distributors_ids)
.pluck(:id)
end
def verify_datetime_change!
return unless @confirm_datetime_change
return unless @order_cycle.orders.exists?
return if @order_cycle.same_datetime_value(:orders_open_at,
@order_cycle_params[:orders_open_at]) &&
@order_cycle.same_datetime_value(:orders_close_at,
@order_cycle_params[:orders_close_at])
raise @error_class
end
end
end