44
55module SolidusPromotions
66 module Calculators
7+ # A calculator that applies tiered percentage discounts based on the total quantity of eligible items.
8+ #
9+ # This calculator defines discount tiers based on the combined quantity of all eligible line items
10+ # in an order (not their monetary value). Each tier specifies a minimum quantity threshold and the
11+ # corresponding percentage discount to apply. The calculator selects the highest tier that the
12+ # order's eligible item quantity meets or exceeds.
13+ #
14+ # The tier thresholds are evaluated against the total quantity of eligible line items, but the
15+ # percentage discount is applied to each individual item's discountable amount. This makes it
16+ # ideal for "buy more, save more" promotions based on item count rather than order value.
17+ #
18+ # If the total eligible quantity doesn't meet any tier threshold, the base percentage is used.
19+ # The discount is only applied if the currency matches the preferred currency.
20+ #
21+ # @example Use case: Bulk quantity discounts
22+ # # Buy 10+ items get 5% off, 25+ get 10% off, 50+ get 15% off
23+ # calculator = TieredPercentOnEligibleItemQuantity.new(
24+ # preferred_base_percent: 0,
25+ # preferred_tiers: {
26+ # 10 => 5, # 5% off when total eligible quantity >= 10
27+ # 25 => 10, # 10% off when total eligible quantity >= 25
28+ # 50 => 15 # 15% off when total eligible quantity >= 50
29+ # },
30+ # preferred_currency: 'USD'
31+ # )
32+ #
33+ # @example Use case: Multi-item bundle promotions
34+ # # Encourage buying multiple items from a category
35+ # calculator = TieredPercentOnEligibleItemQuantity.new(
36+ # preferred_base_percent: 0,
37+ # preferred_tiers: {
38+ # 3 => 10, # 10% off when buying 3+ eligible items
39+ # 5 => 15, # 15% off when buying 5+ eligible items
40+ # 10 => 20 # 20% off when buying 10+ eligible items
41+ # },
42+ # preferred_currency: 'USD'
43+ # )
744 class TieredPercentOnEligibleItemQuantity < Spree ::Calculator
845 include PromotionCalculator
946
@@ -13,6 +50,42 @@ class TieredPercentOnEligibleItemQuantity < Spree::Calculator
1350
1451 before_validation :transform_preferred_tiers
1552
53+ # Computes the tiered percentage discount for an item based on total eligible item quantity.
54+ #
55+ # Evaluates the total quantity of all eligible line items in the order against all defined
56+ # tiers and selects the highest tier threshold that is met or exceeded. Returns a percentage
57+ # of the item's discountable amount based on the matching tier, or the base percentage if no
58+ # tier threshold is met. Returns 0 if the currency doesn't match.
59+ #
60+ # @param item [Object] The object to calculate the discount for (e.g., LineItem, Shipment, ShippingRate)
61+ #
62+ # @return [BigDecimal] The percentage-based discount amount, rounded to currency precision
63+ #
64+ # @example Computing discount with tier matching
65+ # calculator = TieredPercentOnEligibleItemQuantity.new(
66+ # preferred_base_percent: 0,
67+ # preferred_tiers: { 10 => 10, 25 => 15 }
68+ # )
69+ # # Order has 3 eligible line items with quantities: 5, 6, 4 (total: 15)
70+ # line_item.discountable_amount # => 50.00
71+ # calculator.compute_item(line_item) # => 5.00 (10% of $50, matches quantity tier of 10)
72+ #
73+ # @example Computing discount below all tiers
74+ # calculator = TieredPercentOnEligibleItemQuantity.new(
75+ # preferred_base_percent: 5,
76+ # preferred_tiers: { 10 => 10, 25 => 15 }
77+ # )
78+ # # Order has 2 eligible line items with quantities: 3, 4 (total: 7)
79+ # line_item.discountable_amount # => 30.00
80+ # calculator.compute_item(line_item) # => 1.50 (5% base percent of $30)
81+ #
82+ # @example Computing discount with currency mismatch
83+ # calculator = TieredPercentOnEligibleItemQuantity.new(
84+ # preferred_currency: 'USD',
85+ # preferred_tiers: { 10 => 10 }
86+ # )
87+ # order.currency # => 'EUR'
88+ # calculator.compute_item(line_item) # => 0
1689 def compute_item ( item )
1790 order = item . order
1891
@@ -31,13 +104,21 @@ def compute_item(item)
31104
32105 private
33106
107+ # Transforms preferred_tiers keys to integers and values to BigDecimal.
108+ #
109+ # Converts tier threshold keys (item quantities) to integers and percentage values
110+ # to BigDecimal for consistent calculations.
34111 def transform_preferred_tiers
35112 return unless preferred_tiers . is_a? ( Hash )
36113
37114 preferred_tiers . transform_keys! ( &:to_i )
38115 preferred_tiers . transform_values! { |value | value . to_s . to_d }
39116 end
40117
118+ # Calculates the total quantity of all eligible line items in the order.
119+ #
120+ # @param order [Spree::Order] The order to calculate eligible quantity for
121+ # @return [Integer] The sum of quantities for all applicable line items
41122 def eligible_line_items_quantity_total ( order )
42123 calculable . applicable_line_items ( order ) . sum ( &:quantity )
43124 end
0 commit comments