Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,45 @@

module SolidusPromotions
module Calculators
# A calculator that applies a percentage-based discount.
#
# This calculator computes the discount as a percentage of the item's discountable amount,
# rounded to the appropriate currency precision.
#
# @example
# calculator = Percent.new(preferred_percent: 15)
# # Line item with discountable_amount of $100
# calculator.compute_item(line_item) # => 15.00 (15% of $100)
class Percent < Spree::Calculator
include PromotionCalculator

preference :percent, :decimal, default: 0

def compute(object)
# Computes the percentage-based discount for an item.
#
# Calculates the discount by applying the preferred percentage to the item's
# discountable amount, then rounds the result to the appropriate precision
# for the order's currency.
#
# @param object [Object] The object to calculate the discount for (e.g., LineItem, Shipment, ShippingRate)
#
# @return [BigDecimal] The discount amount, rounded to the order's currency precision
#
# @example Computing a 20% discount on a $50 line item
# calculator = Percent.new(preferred_percent: 20)
# line_item.discountable_amount # => 50.00
# calculator.compute_item(line_item) # => 10.00
#
# @example Computing a 15% discount on a shipment
# calculator = Percent.new(preferred_percent: 15)
# shipment.discountable_amount # => 25.00
# calculator.compute_item(shipment) # => 3.75
def compute_item(object)
round_to_currency(object.discountable_amount * preferred_percent / 100, object.order.currency)
end
alias_method :compute_line_item, :compute_item
alias_method :compute_shipment, :compute_item
alias_method :compute_shipping_rate, :compute_item
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,52 @@

module SolidusPromotions
module Calculators
class PercentWithCap < Percent
# A calculator that applies a percentage-based discount with a maximum cap.
#
# This calculator computes a discount as a percentage of the line item's discountable amount,
# but limits the total discount to a maximum amount distributed across all applicable line items.
# The actual discount applied is the lesser of the percentage discount and the proportional
# share of the maximum cap.
#
# @example
# calculator = PercentWithCap.new(preferred_percent: 20, preferred_max_amount: 50)
# # Line item with $100 discountable amount
# # Percentage would be $20 (20% of $100)
# # But if the max cap distributes only $15 to this item, it gets $15
class PercentWithCap < Spree::Calculator
include PromotionCalculator

preference :percent, :decimal, default: 0
preference :max_amount, :integer, default: 100

def compute(line_item)
percent_discount = super
# Computes the discount for a line item, capped at a maximum amount.
#
# Calculates both a percentage-based discount and a distributed maximum discount,
# then returns whichever is smaller. This ensures the discount never exceeds
# the line item's proportional share of the maximum cap, even if the percentage
# would result in a larger discount.
#
# @param line_item [Spree::LineItem] The line item to calculate the discount for
#
# @return [BigDecimal] The discount amount, limited by both the percentage and the max cap
#
# @example Computing discount when percentage is lower than cap
# calculator = PercentWithCap.new(preferred_percent: 10, preferred_max_amount: 100)
# line_item.discountable_amount # => 50.00
# # Percent discount: $5 (10% of $50)
# # Max distributed: $25 (assuming equal distribution)
# calculator.compute_line_item(line_item) # => 5.00
#
# @example Computing discount when cap is lower than percentage
# calculator = PercentWithCap.new(preferred_percent: 50, preferred_max_amount: 10)
# line_item.discountable_amount # => 100.00
# # Percent discount: $50 (50% of $100)
# # Max distributed: $10 (assuming single line item)
# calculator.compute_line_item(line_item) # => 10.00
#
# @see DistributedAmount
def compute_line_item(line_item)
percent_discount = round_to_currency(line_item.discountable_amount * preferred_percent / 100, line_item.order.currency)
max_discount = DistributedAmount.new(
calculable:,
preferred_amount: preferred_max_amount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,26 @@
RSpec.describe SolidusPromotions::Calculators::Percent, type: :model do
context "compute" do
let(:currency) { "USD" }
let(:order) { double(currency: currency) }
let(:line_item) { double("Spree::LineItem", discountable_amount: 100, order: order) }
let(:order) { Spree::Order.new(currency:) }
let(:item) { Spree::LineItem.new(price: 9.99, quantity: 10, order: order) }
let(:calculator) { described_class.new(preferred_percent: 15) }

before { subject.preferred_percent = 15 }
subject { calculator.compute(item) }

it "computes based on item price and quantity" do
expect(subject.compute(line_item)).to eq 15
expect(subject).to eq 14.99
end

context "with a shipment" do
let(:item) { build(:shipment, cost: 29) }

it { is_expected.to eq(4.35) }
end

context "with a shipping rate" do
let(:item) { build(:shipping_rate, cost: 38) }

it { is_expected.to eq(5.70) }
end
end

Expand Down