2019-01-16 11:07:09 +01:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# Coupon is a textual code associated with a discount rate or an amount of discount
|
2020-03-25 10:16:47 +01:00
|
|
|
class Coupon < ApplicationRecord
|
2016-08-04 09:45:00 +02:00
|
|
|
has_many :invoices
|
2020-11-12 16:44:55 +01:00
|
|
|
has_many :payment_schedule
|
2022-08-26 20:10:21 +02:00
|
|
|
has_many :orders
|
2016-08-03 17:25:00 +02:00
|
|
|
|
2021-04-30 16:07:19 +02:00
|
|
|
after_create :create_gateway_coupon
|
2021-06-18 17:40:06 +02:00
|
|
|
before_destroy :delete_gateway_coupon
|
2016-08-08 14:42:17 +02:00
|
|
|
|
2016-08-04 18:13:19 +02:00
|
|
|
validates :name, presence: true
|
2016-08-03 17:25:00 +02:00
|
|
|
validates :code, presence: true
|
2019-01-16 11:07:09 +01:00
|
|
|
validates :code, format: { with: /\A[A-Z0-9\-]+\z/, message: 'only caps letters, numbers, and dashes' }
|
2016-08-08 14:42:17 +02:00
|
|
|
validates :code, uniqueness: true
|
2016-08-08 15:43:02 +02:00
|
|
|
validates :validity_per_user, presence: true
|
2018-12-03 15:10:04 +01:00
|
|
|
validates :validity_per_user, inclusion: { in: %w[once forever] }
|
2016-11-23 12:43:42 +01:00
|
|
|
validates_with CouponDiscountValidator
|
2016-12-13 12:01:34 +01:00
|
|
|
validates_with CouponExpirationValidator
|
2016-08-03 17:25:00 +02:00
|
|
|
|
2019-04-08 11:00:00 +02:00
|
|
|
scope :disabled, -> { where(active: false) }
|
2019-12-02 11:57:25 +01:00
|
|
|
scope :expired, -> { where('valid_until IS NOT NULL AND valid_until < ?', DateTime.current) }
|
2019-04-08 11:00:00 +02:00
|
|
|
scope :sold_out, lambda {
|
|
|
|
joins(:invoices).select('coupons.*, COUNT(invoices.id) as invoices_count').group('coupons.id')
|
|
|
|
.where.not(max_usages: nil).having('COUNT(invoices.id) >= coupons.max_usages')
|
|
|
|
}
|
|
|
|
scope :active, lambda {
|
|
|
|
joins('LEFT OUTER JOIN invoices ON invoices.coupon_id = coupons.id')
|
|
|
|
.select('coupons.*, COUNT(invoices.id) as invoices_count')
|
|
|
|
.group('coupons.id')
|
2019-12-02 11:57:25 +01:00
|
|
|
.where('active = true AND (valid_until IS NULL OR valid_until >= ?)', DateTime.current)
|
2019-04-08 11:00:00 +02:00
|
|
|
.having('COUNT(invoices.id) < coupons.max_usages OR coupons.max_usages IS NULL')
|
|
|
|
}
|
|
|
|
|
2016-08-08 12:25:27 +02:00
|
|
|
def safe_destroy
|
2020-12-30 18:39:46 +01:00
|
|
|
if usages.zero?
|
2016-08-08 12:25:27 +02:00
|
|
|
destroy
|
|
|
|
else
|
|
|
|
false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-12-30 18:39:46 +01:00
|
|
|
def usages
|
2021-02-08 15:28:47 +01:00
|
|
|
invoices.count
|
2020-12-30 18:39:46 +01:00
|
|
|
end
|
|
|
|
|
2016-08-10 11:08:01 +02:00
|
|
|
##
|
|
|
|
# Check the status of the current coupon. The coupon:
|
|
|
|
# - may have been disabled by an admin,
|
|
|
|
# - may has expired because the validity date has been reached,
|
|
|
|
# - may have been used the maximum number of times it was allowed
|
|
|
|
# - may have already been used by the provided user, if the coupon is configured to allow only one use per user,
|
2016-11-24 13:58:41 +01:00
|
|
|
# - may exceed the current cart's total amount, if the coupon is configured to discount an amount (and not a percentage)
|
2016-08-10 11:08:01 +02:00
|
|
|
# - may be available for use
|
|
|
|
# @param [user_id] {Number} if provided and if the current coupon's validity_per_user == 'once', check that the coupon
|
|
|
|
# was already used by the provided user
|
2016-11-24 13:58:41 +01:00
|
|
|
# @param [amount] {Number} if provided and if the current coupon's type == 'amont_off' check that the coupon
|
|
|
|
# does not exceed the cart total price
|
2016-08-10 11:08:01 +02:00
|
|
|
# @return {String} status identifier
|
|
|
|
##
|
2016-11-24 13:58:41 +01:00
|
|
|
def status(user_id = nil, amount = nil)
|
2018-12-03 15:10:04 +01:00
|
|
|
if !active?
|
2016-08-08 15:21:33 +02:00
|
|
|
'disabled'
|
2019-12-02 11:57:25 +01:00
|
|
|
elsif !valid_until.nil? && valid_until.at_end_of_day < DateTime.current
|
2016-08-08 15:21:33 +02:00
|
|
|
'expired'
|
2018-12-03 15:10:04 +01:00
|
|
|
elsif !max_usages.nil? && invoices.count >= max_usages
|
2016-08-08 15:21:33 +02:00
|
|
|
'sold_out'
|
2018-12-03 15:10:04 +01:00
|
|
|
elsif !user_id.nil? && validity_per_user == 'once' && users_ids.include?(user_id.to_i)
|
2016-08-10 11:08:01 +02:00
|
|
|
'already_used'
|
2018-12-03 15:10:04 +01:00
|
|
|
elsif !amount.nil? && type == 'amount_off' && amount_off > amount.to_f
|
2016-11-24 13:58:41 +01:00
|
|
|
'amount_exceeded'
|
2016-08-08 15:21:33 +02:00
|
|
|
else
|
|
|
|
'active'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-23 12:43:42 +01:00
|
|
|
def type
|
2019-01-16 11:07:09 +01:00
|
|
|
if amount_off.nil? && !percent_off.nil?
|
2016-11-23 12:43:42 +01:00
|
|
|
'percent_off'
|
2019-01-16 11:07:09 +01:00
|
|
|
elsif percent_off.nil? && !amount_off.nil?
|
2016-11-23 12:43:42 +01:00
|
|
|
'amount_off'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-10 11:08:01 +02:00
|
|
|
def users
|
2022-09-09 16:35:49 +02:00
|
|
|
invoices.map(&:user).uniq(&:id)
|
2016-08-10 11:08:01 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
def users_ids
|
2018-12-03 15:10:04 +01:00
|
|
|
users.map(&:id)
|
2016-08-10 11:08:01 +02:00
|
|
|
end
|
|
|
|
|
2016-08-16 18:12:13 +02:00
|
|
|
def send_to(user_id)
|
|
|
|
NotificationCenter.call type: 'notify_member_about_coupon',
|
|
|
|
receiver: User.find(user_id),
|
|
|
|
attached_object: self
|
|
|
|
end
|
|
|
|
|
2016-12-13 12:01:34 +01:00
|
|
|
private
|
2018-12-03 15:10:04 +01:00
|
|
|
|
2021-04-30 16:07:19 +02:00
|
|
|
def create_gateway_coupon
|
2021-06-03 12:22:37 +02:00
|
|
|
PaymentGatewayService.new.create_coupon(id)
|
2016-08-08 14:42:17 +02:00
|
|
|
end
|
|
|
|
|
2021-04-30 16:07:19 +02:00
|
|
|
def delete_gateway_coupon
|
2021-06-03 12:22:37 +02:00
|
|
|
PaymentGatewayService.new.delete_coupon(id)
|
2016-08-08 14:42:17 +02:00
|
|
|
end
|
2016-08-03 17:25:00 +02:00
|
|
|
end
|