mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-25 20:46:48 +00:00
A fee can be associated to both the incoming and outgoing exchange, the previous logic did not account for that, resulting in the fee not being correctly removed. Now the delete logic also check for the metadata enterprise role to see if any additional fee need to be removed.
172 lines
5.6 KiB
Ruby
172 lines
5.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Orders
|
|
class HandleFeesService # rubocop:disable Metrics/ClassLength
|
|
attr_reader :order
|
|
|
|
delegate :distributor, :order_cycle, to: :order
|
|
|
|
FeeValue = Struct.new(:fee, :role, keyword_init: true)
|
|
|
|
def initialize(order)
|
|
@order = order
|
|
end
|
|
|
|
def recreate_all_fees!
|
|
# `with_lock` acquires an exclusive row lock on order so no other
|
|
# requests can update it until the transaction is commited.
|
|
# See https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/locking/pessimistic.rb#L69
|
|
# and https://www.postgresql.org/docs/current/static/sql-select.html#SQL-FOR-UPDATE-SHARE
|
|
order.with_lock do
|
|
EnterpriseFee.clear_order_adjustments order
|
|
|
|
# To prevent issue with fee being removed when a product is not linked to the order cycle
|
|
# anymore, we now create or update line item fees.
|
|
# Previously fees were deleted and recreated, like we still do for order fees.
|
|
create_or_update_line_item_fees!
|
|
create_order_fees!
|
|
end
|
|
|
|
tax_enterprise_fees! unless order.before_payment_state?
|
|
order.update_order!
|
|
end
|
|
|
|
def create_or_update_line_item_fees!
|
|
order.line_items.includes(:variant).each do |line_item|
|
|
# No fee associated with the line item so we just create them
|
|
if line_item.enterprise_fee_adjustments.blank?
|
|
create_line_item_fees!(line_item)
|
|
next
|
|
end
|
|
create_or_update_line_item_fee!(line_item)
|
|
|
|
# delete any fees removed from the Order Cycle
|
|
delete_removed_fees!(line_item)
|
|
end
|
|
end
|
|
|
|
def create_order_fees!
|
|
return unless order_cycle
|
|
|
|
calculator.create_order_adjustments_for order
|
|
end
|
|
|
|
def tax_enterprise_fees!
|
|
Spree::TaxRate.adjust(order, order.all_adjustments.enterprise_fee)
|
|
end
|
|
|
|
def update_line_item_fees!(line_item)
|
|
line_item.adjustments.enterprise_fee.each do |fee|
|
|
fee.update_adjustment!(line_item, force: true)
|
|
end
|
|
end
|
|
|
|
def update_order_fees!
|
|
order.adjustments.enterprise_fee.where(adjustable_type: 'Spree::Order').each do |fee|
|
|
fee.update_adjustment!(order, force: true)
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def create_line_item_fees!(line_item)
|
|
return unless provided_by_order_cycle? line_item
|
|
|
|
calculator.create_line_item_adjustments_for(line_item)
|
|
end
|
|
|
|
def create_or_update_line_item_fee!(line_item)
|
|
applicators = calculator.per_item_enterprise_fee_applicators_for(line_item.variant)
|
|
|
|
applicators.each do |fee_applicator|
|
|
fee_adjustment = line_item.adjustments.by_originator_and_enterprise_role(
|
|
fee_applicator.enterprise_fee, fee_applicator.role
|
|
)
|
|
|
|
if fee_adjustment
|
|
fee_adjustment.update_adjustment!(line_item, force: true)
|
|
elsif provided_by_order_cycle? line_item
|
|
fee_applicator.create_line_item_adjustment(line_item)
|
|
end
|
|
end
|
|
|
|
# Update any fees not already processed
|
|
fees_to_update = order_cycle_fees.map(&:fee) - applicators.map(&:enterprise_fee)
|
|
update_fee_adjustments!(line_item, fees_to_update)
|
|
end
|
|
|
|
def update_fee_adjustments!(line_item, fees_to_update)
|
|
fees_to_update.each do |fee|
|
|
fee_adjustment = line_item.adjustments.find_by(originator: fee)
|
|
|
|
fee_adjustment&.update_adjustment!(line_item, force: true)
|
|
end
|
|
end
|
|
|
|
def delete_removed_fees!(line_item)
|
|
removed_fees = line_item.enterprise_fee_adjustments.where.not(
|
|
originator: order_cycle_fees.map(&:fee)
|
|
)
|
|
|
|
# The same fee can be used in the incoming and outgoing exchange, (supplier and distributor
|
|
# fees), so we need an extra check to see if a fee linked to both exchanges has been removed
|
|
order_cycle_fees.each do |order_cycle_fee|
|
|
# Check if there is any fee adjustment with a role other than the one in the order cycle fee
|
|
fee = line_item.enterprise_fee_adjustments.by_originator_and_not_enterprise_role(
|
|
order_cycle_fee.fee, order_cycle_fee.role
|
|
)
|
|
|
|
# Check if the fee matches a fee linked to the order cycle
|
|
if fee.nil? || order_cycle_fees_include_fee?(fee)
|
|
next
|
|
end
|
|
|
|
# If not linked to the order cycle we add it to the list of fee to be removed
|
|
removed_fees = removed_fees.to_a.push(fee)
|
|
end
|
|
|
|
removed_fees.each(&:destroy)
|
|
end
|
|
|
|
def order_cycle_fees
|
|
return @order_cycle_fees if defined? @order_cycle_fees
|
|
|
|
@order_cycle_fees = begin
|
|
fees = []
|
|
|
|
return fees unless order_cycle && distributor
|
|
|
|
order_cycle.exchanges.supplying_to(distributor).each do |exchange|
|
|
exchange.enterprise_fees.per_item.each do |enterprise_fee|
|
|
fee_value = FeeValue.new(fee: enterprise_fee, role: exchange.role)
|
|
fees << fee_value
|
|
end
|
|
end
|
|
|
|
order_cycle.coordinator_fees.per_item.each do |enterprise_fee|
|
|
fees << FeeValue.new(fee: enterprise_fee, role: "coordinator")
|
|
end
|
|
|
|
fees
|
|
end
|
|
end
|
|
|
|
def order_cycle_fees_include_fee?(fee)
|
|
matching = order_cycle_fees.select do |order_cycle_fee|
|
|
order_cycle_fee.fee == fee.originator &&
|
|
order_cycle_fee.role == fee.metadata.enterprise_role
|
|
end
|
|
matching.present?
|
|
end
|
|
|
|
def calculator
|
|
@calculator ||= OpenFoodNetwork::EnterpriseFeeCalculator.new(distributor, order_cycle)
|
|
end
|
|
|
|
def provided_by_order_cycle?(line_item)
|
|
@order_cycle_variant_ids ||= order_cycle&.variants&.map(&:id) || []
|
|
@order_cycle_variant_ids.include? line_item.variant_id
|
|
end
|
|
end
|
|
end
|