mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +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.
177 lines
5.7 KiB
Ruby
177 lines
5.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spree/localized_number'
|
|
|
|
# Adjustments represent a change to the +item_total+ of an Order. Each adjustment
|
|
# has an +amount+ that can be either positive or negative.
|
|
#
|
|
# Adjustments can be open/closed/finalized
|
|
#
|
|
# Once an adjustment is finalized, it cannot be changed, but an adjustment can
|
|
# toggle between open/closed as needed
|
|
#
|
|
# Boolean attributes:
|
|
#
|
|
# +mandatory+
|
|
#
|
|
# If this flag is set to true then it means the the charge is required and will not
|
|
# be removed from the order, even if the amount is zero. In other words a record
|
|
# will be created even if the amount is zero. This is useful for representing things
|
|
# such as shipping and tax charges where you may want to make it explicitly clear
|
|
# that no charge was made for such things.
|
|
#
|
|
# +eligible?+
|
|
#
|
|
# This boolean attributes stores whether this adjustment is currently eligible
|
|
# for its order. Only eligible adjustments count towards the order's adjustment
|
|
# total. This allows an adjustment to be preserved if it becomes ineligible so
|
|
# it might be reinstated.
|
|
module Spree
|
|
class Adjustment < ApplicationRecord
|
|
extend Spree::LocalizedNumber
|
|
|
|
self.belongs_to_required_by_default = false
|
|
|
|
# Deletion of metadata is handled in the database.
|
|
# So we don't need the option `dependent: :destroy` as long as
|
|
# AdjustmentMetadata has no destroy logic itself.
|
|
has_one :metadata, class_name: 'AdjustmentMetadata', dependent: nil
|
|
has_many :adjustments, as: :adjustable, dependent: :destroy
|
|
|
|
belongs_to :adjustable, polymorphic: true
|
|
belongs_to :originator, -> { with_deleted }, polymorphic: true
|
|
belongs_to :order, class_name: "Spree::Order"
|
|
belongs_to :tax_category, class_name: 'Spree::TaxCategory'
|
|
|
|
validates :label, presence: true
|
|
validates :amount, numericality: true
|
|
|
|
after_create :update_adjustable_adjustment_total
|
|
|
|
state_machine :state, initial: :open do
|
|
event :close do
|
|
transition from: :open, to: :closed
|
|
end
|
|
|
|
event :open do
|
|
transition from: :closed, to: :open
|
|
end
|
|
|
|
event :finalize do
|
|
transition from: [:open, :closed], to: :finalized
|
|
end
|
|
end
|
|
|
|
scope :tax, -> { where(originator_type: 'Spree::TaxRate') }
|
|
scope :price, -> { where(adjustable_type: 'Spree::LineItem') }
|
|
scope :optional, -> { where(mandatory: false) }
|
|
scope :charge, -> { where('amount >= 0') }
|
|
scope :credit, -> { where('amount < 0') }
|
|
scope :return_authorization, -> { where(originator_type: "Spree::ReturnAuthorization") }
|
|
scope :voucher, -> { where(originator_type: "Voucher") }
|
|
scope :voucher_tax, -> {
|
|
voucher.joins(:metadata).where(metadata: { fee_name: "Tax", fee_type: "Voucher" })
|
|
}
|
|
scope :non_voucher, -> { where.not(originator_type: "Voucher") }
|
|
scope :inclusive, -> { where(included: true) }
|
|
scope :additional, -> { where(included: false) }
|
|
scope :legacy_tax, -> { additional.tax.where(adjustable_type: "Spree::Order") }
|
|
|
|
scope :enterprise_fee, -> { where(originator_type: 'EnterpriseFee') }
|
|
scope :admin, -> { where(originator_type: nil) }
|
|
|
|
scope :payment_fee, -> { where(AdjustmentScopes::PAYMENT_FEE_SCOPE) }
|
|
scope :shipping, -> { where(AdjustmentScopes::SHIPPING_SCOPE) }
|
|
scope :eligible, -> { where(AdjustmentScopes::ELIGIBLE_SCOPE) }
|
|
|
|
localize_number :amount
|
|
|
|
def self.by_originator_and_enterprise_role(originator, enterprise_role)
|
|
joins(:metadata)
|
|
.find_by(
|
|
originator:,
|
|
adjustment_metadata: { enterprise_role: }
|
|
)
|
|
end
|
|
|
|
def self.by_originator_and_not_enterprise_role(originator, enterprise_role)
|
|
joins(:metadata)
|
|
.where(originator:)
|
|
.where.not(
|
|
spree_adjustments: { adjustment_metadata: { enterprise_role: } }
|
|
).first
|
|
end
|
|
|
|
# Update both the eligibility and amount of the adjustment. Adjustments
|
|
# delegate updating of amount to their Originator when present, but only if
|
|
# +locked+ is false. Adjustments that are +locked+ will never change their amount.
|
|
#
|
|
# Adjustments delegate updating of amount to their Originator when present,
|
|
# but only if when they're in "open" state, closed or finalized adjustments
|
|
# are not recalculated.
|
|
#
|
|
# It receives +calculable+ as the updated source here so calculations can be
|
|
# performed on the current values of that source. If we used +source+ it
|
|
# could load the old record from db for the association. e.g. when updating
|
|
# more than on line items at once via accepted_nested_attributes the order
|
|
# object on the association would be in a old state and therefore the
|
|
# adjustment calculations would not performed on proper values
|
|
def update_adjustment!(calculable = nil, force: false)
|
|
return amount if immutable? && !force
|
|
|
|
if calculable.nil? && adjustable.nil?
|
|
delete
|
|
return 0.0
|
|
end
|
|
|
|
if originator.present?
|
|
amount = originator.compute_amount(calculable || adjustable)
|
|
update_columns(
|
|
amount:,
|
|
updated_at: Time.zone.now,
|
|
)
|
|
end
|
|
|
|
amount
|
|
end
|
|
|
|
def currency
|
|
adjustable ? adjustable.currency : CurrentConfig.get(:currency)
|
|
end
|
|
|
|
def display_amount
|
|
Spree::Money.new(amount, currency:)
|
|
end
|
|
|
|
def admin?
|
|
originator_type.nil?
|
|
end
|
|
|
|
def immutable?
|
|
state != "open"
|
|
end
|
|
|
|
def has_tax?
|
|
tax_total.positive?
|
|
end
|
|
|
|
def included_tax_total
|
|
adjustments.tax.inclusive.sum(:amount)
|
|
end
|
|
|
|
def additional_tax_total
|
|
adjustments.tax.additional.sum(:amount)
|
|
end
|
|
|
|
private
|
|
|
|
def tax_total
|
|
adjustments.tax.sum(:amount)
|
|
end
|
|
|
|
def update_adjustable_adjustment_total
|
|
Spree::ItemAdjustments.new(adjustable).update if adjustable
|
|
end
|
|
end
|
|
end
|