Class: Coupon::LineItemDiscountAllocator
- Inherits:
-
Object
- Object
- Coupon::LineItemDiscountAllocator
- Defined in:
- app/services/coupon/line_item_discount_allocator.rb
Instance Attribute Summary collapse
-
#line_item ⇒ Object
readonly
Returns the value of attribute line_item.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#max_discount ⇒ Object
readonly
Returns the value of attribute max_discount.
Instance Method Summary collapse
-
#allocate(discount, allocated_amount, options = {}) ⇒ Object
Allocate a computed discount to a line line_item : instance of LineItem the line item to build discount lines upon discount : A reference to the Discount instance in use allocated_amount : The per-unit discount allocated.
-
#allowable_amount(allocated_amount, discount = nil) ⇒ Object
method to enforce max discount set on catalog item.
-
#initialize(line_item, options = {}) ⇒ LineItemDiscountAllocator
constructor
A new instance of LineItemDiscountAllocator.
-
#prep_line_discount(discount) ⇒ Object
Prepare a new or existing line discount.
Constructor Details
#initialize(line_item, options = {}) ⇒ LineItemDiscountAllocator
Returns a new instance of LineItemDiscountAllocator.
4 5 6 7 |
# File 'app/services/coupon/line_item_discount_allocator.rb', line 4 def initialize(line_item, = {}) @line_item = line_item @logger = [:logger] || Rails.logger end |
Instance Attribute Details
#line_item ⇒ Object (readonly)
Returns the value of attribute line_item.
2 3 4 |
# File 'app/services/coupon/line_item_discount_allocator.rb', line 2 def line_item @line_item end |
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
2 3 4 |
# File 'app/services/coupon/line_item_discount_allocator.rb', line 2 def logger @logger end |
#max_discount ⇒ Object (readonly)
Returns the value of attribute max_discount.
2 3 4 |
# File 'app/services/coupon/line_item_discount_allocator.rb', line 2 def max_discount @max_discount end |
Instance Method Details
#allocate(discount, allocated_amount, options = {}) ⇒ Object
Allocate a computed discount to a line
line_item : instance of LineItem the line item to build discount lines upon
discount : A reference to the Discount instance in use
allocated_amount : The per-unit discount allocated. If its a discount, should be a negative number
preserve_amount : useful for calculation methods which will take multiple
pass at updating the same line discount. Essentially instead of resetting
to zero it will continue adding to it. MsrpAllocator makes uses of it
also this method leaves discount amount untouched
line_total_override : if provided, uses this exact amount for the line_discount instead of calculating
from per-unit. This prevents rounding errors when distributing fixed-amount discounts.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'app/services/coupon/line_item_discount_allocator.rb', line 19 def allocate(discount, allocated_amount, = {}) allocated_amount = allocated_amount.round(2) = { preserve_amount: false, respect_catalog_maximum: true }.merge() line_discount = prep_line_discount(discount) new_allocated_amount = [:respect_catalog_maximum] ? allowable_amount(allocated_amount, discount) : allocated_amount # Handle line_total_override for exact billing amounts if [:line_total_override] line_total_allocated_amount = [:line_total_override].round(2) # Avoid division by zero if quantity is 0 if @line_item.quantity.zero? logger.warn "[LineItemDiscountAllocator] Cannot allocate discount - line_item #{@line_item.id} has zero quantity" return 0 end # Adjust per-unit amount to ensure discounted_price × quantity equals the exact line total # This prevents rounding errors in order total calculations adjusted_per_unit = BigDecimal(line_total_allocated_amount.to_s) / BigDecimal(@line_item.quantity.to_s) rounded_per_unit = adjusted_per_unit.round(2, adjusted_per_unit < 0 ? BigDecimal::ROUND_CEILING : BigDecimal::ROUND_FLOOR) @line_item.discounted_price += rounded_per_unit else # Legacy: use the provided per-unit amount @line_item.discounted_price += new_allocated_amount line_total_allocated_amount = @line_item.quantity.to_i * new_allocated_amount end if new_allocated_amount == 0 discount.line_discounts.delete(line_discount) elsif [:preserve_amount] line_discount.amount ||= 0 line_discount.amount += line_total_allocated_amount else line_discount.amount = line_total_allocated_amount end new_allocated_amount end |
#allowable_amount(allocated_amount, discount = nil) ⇒ Object
method to enforce max discount set on catalog item
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'app/services/coupon/line_item_discount_allocator.rb', line 56 def allowable_amount(allocated_amount, discount = nil) new_discounted_price = line_item.discounted_price + allocated_amount new_allocated_amount = allocated_amount # Perform this check for classic discounts if allocated_amount < 0 && (max_discount = line_item.max_discount) # Annotate discount minimum_discounted_price = (line_item.price * ((100 - max_discount).to_f / 100)).round(2) # Only allow a discount up to the minimum discoutned allowable_discounted_price = [new_discounted_price, minimum_discounted_price].max discount.notes = if discount && allowable_discounted_price != new_discounted_price "Discount capped at #{max_discount}% for item #{line_item.sku}" else nil end # translate this effect back to the allocated amount new_allocated_amount = allowable_discounted_price - line_item.discounted_price end # Prevent over-discount: discounted_price must never go below zero. # Guards against stacked coupons (e.g. a 35% auto-discount + a full-price manual # comp) where the combined discount exceeds 100% of the item price, which would # make delivery.total negative and block shipping (delivery.rb:506). if new_allocated_amount < 0 && (line_item.discounted_price + new_allocated_amount) < 0 new_allocated_amount = -line_item.discounted_price end new_allocated_amount end |
#prep_line_discount(discount) ⇒ Object
Prepare a new or existing line discount
86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'app/services/coupon/line_item_discount_allocator.rb', line 86 def prep_line_discount(discount) # Reuse existing line discounts if available line_discounts = discount.line_discounts.select { |ld| ld.line_item and ld.line_item == @line_item } # line_discounts = discount.line_discounts.joins(:line_item).includes(:line_item).where(LineDiscount[:line_item_id] == @line_item.id).to_a logger.debug " Found #{line_discounts.size} line discounts in discount linked to this line item #{@line_item.id} #{@line_item.item_id}" # In case we have multiple line discounts, only the top one will be the survivor line_discount = line_discounts.pop # Survivor line_discounts.each(&:destroy) # In case of duplicates, destroy the rest # If none present, build one line_discount ||= discount.line_discounts.build(line_item: @line_item, coupon: discount.coupon) line_discount end |