# frozen_string_literal: true require "active_support/concern" module CalculatedAdjustments extend ActiveSupport::Concern CALCULATORS = %w{ Calculator::DefaultTax Calculator::FlatPercentItemTotal Calculator::FlatPercentPerItem Calculator::FlatRate Calculator::FlexiRate Calculator::None Calculator::PerItem Calculator::PriceSack Calculator::Weight }.freeze included do has_one :calculator, as: :calculable, class_name: "Spree::Calculator", dependent: :destroy accepts_nested_attributes_for :calculator validates :calculator, presence: true end class_methods do def calculators spree_calculators.__send__(model_name_without_spree_namespace) end private def model_name_without_spree_namespace to_s.tableize.gsub('/', '_').sub('spree_', '') end def spree_calculators Rails.application.config.spree.calculators end end def calculator_type calculator.class.to_s if calculator end def calculator_type=(calculator_type) return unless calculator_type return unless CALCULATORS.include?(calculator_type) klass = calculator_type.constantize self.calculator = klass.new if klass && !calculator.is_a?(klass) end # Creates a new adjustment for the target object # (which is any class that has_many :adjustments) and sets amount based on the # calculator as applied to the given calculable (Order, LineItems[], Shipment, etc.) # By default the adjustment will not be considered mandatory def create_adjustment(label, adjustable, mandatory = false, state = "closed", tax_category = nil) amount = compute_amount(adjustable) return if amount.zero? && !mandatory adjustment_attributes = { amount:, originator: self, order: order_object_for(adjustable), label:, mandatory:, state:, tax_category: } if adjustable.respond_to?(:adjustments) adjustable.adjustments.create(adjustment_attributes) else adjustable.create_adjustment(adjustment_attributes) end end # Calculate the amount to be used when creating an adjustment # NOTE: May be overriden by classes where this module is included into. def compute_amount(calculable) calculator.compute(calculable) end def order_object_for(target) # Temporary method for adjustments transition. if target.is_a? Spree::Order target elsif target.respond_to?(:order) target.order end end end