2019-09-17 17:39:05 +02:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# This class provides helper methods to deal with coupons
|
2016-11-29 14:57:43 +01:00
|
|
|
class CouponService
|
2016-11-24 17:57:48 +01:00
|
|
|
##
|
|
|
|
# Apply the provided coupon, if active, to the given price. Usability tests will be run depending on the
|
|
|
|
# provided parameters.
|
|
|
|
# If no coupon/coupon code or if the code does not match, return origin price without change
|
|
|
|
#
|
|
|
|
# @param total {Number} invoice total, before any coupon is applied
|
|
|
|
# @param coupon {String|Coupon} Coupon's code OR Coupon object
|
|
|
|
# @param user_id {Number} user's id against the coupon will be tested for usability
|
|
|
|
# @return {Number}
|
|
|
|
##
|
2016-11-29 14:57:43 +01:00
|
|
|
def apply(total, coupon, user_id = nil)
|
2016-11-23 17:17:34 +01:00
|
|
|
price = total
|
|
|
|
|
2019-09-10 11:46:14 +02:00
|
|
|
coupon_object = nil
|
2016-11-24 17:57:48 +01:00
|
|
|
if coupon.instance_of? Coupon
|
2019-09-10 11:46:14 +02:00
|
|
|
coupon_object = coupon
|
2016-11-24 17:57:48 +01:00
|
|
|
elsif coupon.instance_of? String
|
2019-09-10 11:46:14 +02:00
|
|
|
coupon_object = Coupon.find_by(code: coupon)
|
2016-11-24 17:57:48 +01:00
|
|
|
end
|
|
|
|
|
2019-09-10 11:46:14 +02:00
|
|
|
unless coupon_object.nil?
|
|
|
|
if coupon_object.status(user_id, total) == 'active'
|
|
|
|
if coupon_object.type == 'percent_off'
|
|
|
|
price -= price * coupon_object.percent_off / 100.00
|
|
|
|
elsif coupon_object.type == 'amount_off'
|
2016-11-23 17:17:34 +01:00
|
|
|
# do not apply cash coupon unless it has a lower amount that the total price
|
2019-09-10 11:46:14 +02:00
|
|
|
price -= coupon_object.amount_off if coupon_object.amount_off <= price
|
2016-11-23 17:17:34 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
price
|
|
|
|
end
|
2016-11-29 14:57:43 +01:00
|
|
|
|
2020-05-06 12:43:47 +02:00
|
|
|
##
|
|
|
|
# Find the coupon associated with the given code and check it is valid for the given user
|
|
|
|
# @param code {String} the literal code of the coupon
|
|
|
|
# @param user_id {Number} identifier of the user who is applying the coupon
|
|
|
|
# @return {Coupon}
|
|
|
|
##
|
|
|
|
def validate(code, user_id)
|
|
|
|
return nil unless code && user_id
|
|
|
|
|
|
|
|
coupon = Coupon.find_by(code: code)
|
|
|
|
raise InvalidCouponError if coupon.nil? || coupon.status(user_id) != 'active'
|
|
|
|
|
|
|
|
coupon
|
|
|
|
end
|
|
|
|
|
2016-11-29 14:57:43 +01:00
|
|
|
##
|
|
|
|
# Ventilate the discount of the provided coupon over the given amount proportionately to the invoice's total
|
|
|
|
# @param total {Number} total amount of the invoice expressed in monetary units
|
|
|
|
# @param amount {Number} price of the invoice's sub-item expressed in monetary units
|
|
|
|
# @param coupon {Coupon} coupon applied to the invoice, amount_off expressed in centimes if applicable
|
|
|
|
##
|
|
|
|
def ventilate(total, amount, coupon)
|
|
|
|
price = amount
|
2019-08-14 10:54:19 +02:00
|
|
|
if !coupon.nil? && total != 0
|
2016-11-29 14:57:43 +01:00
|
|
|
if coupon.type == 'percent_off'
|
2019-08-14 10:54:19 +02:00
|
|
|
price = amount - (amount * coupon.percent_off / 100.00)
|
2016-11-29 14:57:43 +01:00
|
|
|
elsif coupon.type == 'amount_off'
|
2019-08-14 10:54:19 +02:00
|
|
|
ratio = (coupon.amount_off / 100.00) / total
|
2016-11-29 14:57:43 +01:00
|
|
|
discount = amount * ratio.abs
|
|
|
|
price = amount - discount
|
|
|
|
else
|
|
|
|
raise InvalidCouponError
|
|
|
|
end
|
|
|
|
end
|
|
|
|
price
|
|
|
|
end
|
2019-09-17 17:39:05 +02:00
|
|
|
|
|
|
|
##
|
|
|
|
# Compute the total amount of the given invoice, without the applied coupon
|
|
|
|
# Invoice.total stores the amount payed by the customer, coupon deducted
|
|
|
|
# @param invoice {Invoice} invoice object, its total before discount will be computed
|
|
|
|
##
|
|
|
|
def invoice_total_no_coupon(invoice)
|
|
|
|
total = (invoice.invoice_items.map(&:amount).map(&:to_i).reduce(:+) or 0)
|
|
|
|
total / 100.0
|
|
|
|
end
|
2019-09-10 11:46:14 +02:00
|
|
|
end
|