mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-03-21 05:09:15 +00:00
This change is introduced in the adjustments updates from Spree 2.2 and used heavily in new scopes and methods (not included here).
172 lines
6.0 KiB
Ruby
172 lines
6.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'spree/localized_number'
|
|
require 'concerns/adjustment_scopes'
|
|
|
|
# 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 < ActiveRecord::Base
|
|
extend Spree::LocalizedNumber
|
|
|
|
# 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'
|
|
|
|
belongs_to :adjustable, polymorphic: true
|
|
belongs_to :source, polymorphic: true
|
|
belongs_to :originator, polymorphic: true
|
|
belongs_to :order, class_name: "Spree::Order"
|
|
|
|
belongs_to :tax_rate, -> { where spree_adjustments: { originator_type: 'Spree::TaxRate' } },
|
|
foreign_key: 'originator_id'
|
|
|
|
validates :label, presence: true
|
|
validates :amount, numericality: true
|
|
|
|
after_save :update_adjustable
|
|
after_destroy :update_adjustable
|
|
|
|
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', adjustable_type: 'Spree::Order') }
|
|
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(source_type: "Spree::ReturnAuthorization") }
|
|
|
|
scope :enterprise_fee, -> { where(originator_type: 'EnterpriseFee') }
|
|
scope :admin, -> { where(source_type: nil, originator_type: nil) }
|
|
scope :included_tax, -> {
|
|
where(originator_type: 'Spree::TaxRate', adjustable_type: 'Spree::LineItem')
|
|
}
|
|
|
|
scope :with_tax, -> { where('spree_adjustments.included_tax <> 0') }
|
|
scope :without_tax, -> { where('spree_adjustments.included_tax = 0') }
|
|
scope :payment_fee, -> { where(AdjustmentScopes::PAYMENT_FEE_SCOPE) }
|
|
scope :shipping, -> { where(AdjustmentScopes::SHIPPING_SCOPE) }
|
|
scope :eligible, -> { where(AdjustmentScopes::ELIGIBLE_SCOPE) }
|
|
|
|
localize_number :amount
|
|
|
|
# Update the boolean _eligible_ attribute which determines which adjustments
|
|
# count towards the order's adjustment_total.
|
|
def set_eligibility
|
|
result = mandatory || amount != 0
|
|
update_column(:eligible, result)
|
|
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!(calculable = nil)
|
|
return if immutable?
|
|
|
|
# Fix for Spree issue #3381
|
|
# If we attempt to call 'source' before the reload, then source is currently
|
|
# the order object. After calling a reload, the source is the Shipment.
|
|
reload
|
|
originator.update_adjustment(self, calculable || source) if originator.present?
|
|
set_eligibility
|
|
end
|
|
|
|
def currency
|
|
adjustable ? adjustable.currency : Spree::Config[:currency]
|
|
end
|
|
|
|
def display_amount
|
|
Spree::Money.new(amount, currency: currency)
|
|
end
|
|
|
|
def immutable?
|
|
state != "open"
|
|
end
|
|
|
|
def set_included_tax!(rate)
|
|
tax = amount - (amount / (1 + rate))
|
|
set_absolute_included_tax! tax
|
|
end
|
|
|
|
def set_absolute_included_tax!(tax)
|
|
# This rubocop issue can now fixed by renaming Adjustment#update! to something else,
|
|
# then AR's update! can be used instead of update_attributes!
|
|
# rubocop:disable Rails/ActiveRecordAliases
|
|
update_attributes! included_tax: tax.round(2)
|
|
# rubocop:enable Rails/ActiveRecordAliases
|
|
end
|
|
|
|
def display_included_tax
|
|
Spree::Money.new(included_tax, currency: currency)
|
|
end
|
|
|
|
def has_tax?
|
|
included_tax.positive?
|
|
end
|
|
|
|
def self.without_callbacks
|
|
skip_callback :save, :after, :update_adjustable
|
|
skip_callback :destroy, :after, :update_adjustable
|
|
|
|
result = yield
|
|
ensure
|
|
set_callback :save, :after, :update_adjustable
|
|
set_callback :destroy, :after, :update_adjustable
|
|
|
|
result
|
|
end
|
|
|
|
private
|
|
|
|
def update_adjustable
|
|
adjustable.update! if adjustable.is_a? Order
|
|
end
|
|
end
|
|
end
|