diff --git a/promotions/app/models/solidus_promotions/calculators/tiered_flat_rate.rb b/promotions/app/models/solidus_promotions/calculators/tiered_flat_rate.rb index f73ceee74d..767eb3d78e 100644 --- a/promotions/app/models/solidus_promotions/calculators/tiered_flat_rate.rb +++ b/promotions/app/models/solidus_promotions/calculators/tiered_flat_rate.rb @@ -4,6 +4,37 @@ module SolidusPromotions module Calculators + # A calculator that applies tiered flat-rate discounts based on discountable amount thresholds. + # + # This calculator allows defining multiple discount tiers where each tier specifies a minimum + # discountable amount threshold and the corresponding discount amount to apply. The calculator + # selects the highest tier that the item qualifies for based on its discountable amount. + # + # If the item doesn't meet any tier threshold, the base amount is used. The discount is only + # applied if the currency matches the preferred currency. + # + # @example Use case: Volume-based shipping discounts + # # Free shipping on orders over $100, $5 off on orders over $50 + # calculator = TieredFlatRate.new( + # preferred_base_amount: 0, + # preferred_tiers: { + # 50 => 5, # $5 discount when amount >= $50 + # 100 => 15 # $15 discount when amount >= $100 + # }, + # preferred_currency: 'USD' + # ) + # + # @example Use case: Bulk purchase incentives + # # Tiered discounts for line items based on total line value + # calculator = TieredFlatRate.new( + # preferred_base_amount: 2, + # preferred_tiers: { + # 25 => 5, # $5 off when line total >= $25 + # 50 => 12, # $12 off when line total >= $50 + # 100 => 30 # $30 off when line total >= $100 + # }, + # preferred_currency: 'USD' + # ) class TieredFlatRate < Spree::Calculator include PromotionCalculator @@ -22,6 +53,40 @@ class TieredFlatRate < Spree::Calculator validate :preferred_tiers_content + # Computes the tiered flat-rate discount for an item. + # + # Evaluates the item's discountable amount against all defined tiers and selects + # the highest tier threshold that the item meets or exceeds. Returns the discount + # amount associated with that tier, or the base amount if no tier threshold is met. + # Returns 0 if the currency doesn't match. + # + # @param object [Object] The object to calculate the discount for (e.g., LineItem, Shipment) + # + # @return [BigDecimal] The discount amount from the matching tier, base amount, or 0 + # + # @example Computing discount with tier matching + # calculator = TieredFlatRate.new( + # preferred_base_amount: 2, + # preferred_tiers: { 25 => 5, 50 => 10, 100 => 20 } + # ) + # line_item.discountable_amount # => 75.00 + # calculator.compute_item(line_item) # => 10.00 (matches $50 tier) + # + # @example Computing discount below all tiers + # calculator = TieredFlatRate.new( + # preferred_base_amount: 2, + # preferred_tiers: { 25 => 5, 50 => 10 } + # ) + # line_item.discountable_amount # => 15.00 + # calculator.compute_item(line_item) # => 2.00 (base amount) + # + # @example Computing discount with currency mismatch + # calculator = TieredFlatRate.new( + # preferred_currency: 'USD', + # preferred_tiers: { 50 => 10 } + # ) + # line_item.currency # => 'EUR' + # calculator.compute_item(line_item) # => 0 def compute_item(object) _base, amount = preferred_tiers.sort.reverse.detect do |value, _| object.discountable_amount >= value @@ -38,10 +103,17 @@ def compute_item(object) private + # Converts a value to a BigDecimal. + # + # @param value [String, Numeric] The value to convert + # @return [BigDecimal] The converted decimal value def cast_to_d(value) value.to_s.to_d end + # Validates that preferred_tiers is a hash with positive numeric keys. + # + # Ensures the tiers preference is properly formatted for tier-based calculations. def preferred_tiers_content if preferred_tiers.is_a? Hash unless preferred_tiers.keys.all? { |key| key.is_a?(Numeric) && key > 0 }