app/services/voucher_presenter.rb
class VoucherPresenter
include ActionView::Helpers
#
# Presentation logic to show ValidVouchers in customer view.
# Vouchers of same type and reserved for same showdate are grouped together.
# For groups of vouchers that are unreserved, we display a dropdown menu so customer can select
# how many of that voucher to confirm a reservation for.
# For groups of vouchers that are reserved, we show the date and, if it's a self-cancelable-by-customer
# voucher, a Cancel button.
#
# The presentation logic takes a whole batch of vouchers and returns an ordered list of
# VoucherPresenter objects.
#
require 'set'
attr_reader :vouchers, :reserved, :group_id, :size, :vouchertype, :name, :showdate, :voucherlist, :has_reserved_seating, :single_production
# Constructor takes a set of vouchers that should be part of a group, and constructs the
# presentation logic for them. It's an error for the provided vouchers not to "belong together"
# (must all have same showdate and vouchertype, OR must all be unreserved and same vouchertype)
class InvalidGroupError < StandardError ; end
def initialize(vouchers,ignore_cutoff=false)
@vouchers = vouchers.sort_by(&:id)
raise InvalidGroupError.new("Vouchers don't belong together") unless vouchers_belong_together
first = @vouchers[0]
@ignore_cutoff = ignore_cutoff
@showdate = first.showdate
@reserved = first.reserved?
@has_reserved_seating = first.reserved? && @showdate.has_reserved_seating?
@group_id = first.id
@size = @vouchers.length
@vouchertype = first.vouchertype
@voucherlist = @vouchers.map { |v| v.id }.join(',')
# group name: if ALL vouchers in group are redeemable for only a single production,
# the production's name is the group name. otherwise, use the vouchertype name (all of
# them are guaranteed to be the same vouchertype anyway).
shows = @vouchertype.valid_vouchers.map(&:show_name).compact.uniq
@single_production = (shows.length == 1) ? shows.first : nil
@name = @vouchertype.name
end
def redeemable_showdates
@redeemable_showdates ||= if @vouchers[0].reservable? then @vouchers[0].redeemable_showdates(@ignore_cutoff) else [] end
end
def menu_label_function(admin_display = false)
if admin_display
single_production ? :date_with_explanation_for_admin : :name_with_explanation_for_admin
else
single_production ? :date_with_explanation : :name_with_explanation
end
end
def cancelable?
vouchers.all?(&:can_be_changed?)
end
def voucher_comments
vouchers.map(&:comments).map(&:to_s).reject(&:blank?).uniq.join('; ')
end
def seats
sd = @vouchers.first.showdate
if sd.stream? then link_to('Access Info', '#', :onclick => %Q{alert('#{escape_javascript sd.access_instructions}')}, :class => 'btn btn-sm btn-outline-primary btn-block')
elsif ! @vouchers.first.reserved? then ''
elsif @vouchers.all? { |v| v.seat.blank? } then 'General Admission'
else Voucher.seats_for(@vouchers)
end
end
# Within a show category, OPEN VOUCHERS are listed last, others are shown by order of showdate
# vouchers for DIFFERENT SHOWS are ordered by opening date of the show
# vouchers NOT VALID FOR any show are ordered by their vouchertype's display_order
def <=>(other)
sd1,vt1 = self.showdate, self.vouchertype
sd2,vt2 = other.showdate, other.vouchertype
if vt1 == vt2
# same vouchertype: order by OPENING DATE of the show for which reserved, or display order
# if not reserved
return (if (sd1 && sd2) then (sd1 <=> sd2) else (vt1.display_order <=> vt2.display_order) end)
end
# else different vouchertypes, so the rules are:
# if same show, order by showdate
if (sd1 && sd2 && (sd1.show == sd2.show))
return sd1 <=> sd2
end
# vouchertypes WITH assigned showdates always go first
shows1,shows2 = vt1.showdates, vt2.showdates # which showdates is vouchertype valid for?
if ! shows1.empty? && ! shows2.empty? then (shows1.min <=> shows2.min)
elsif shows1.empty? && shows2.empty? then (vt1.display_order <=> vt2.display_order) # voucher having show validity goes first
elsif shows1.empty? && ! shows2.empty? then 1 # voucher having show validity goes first
else # !shows1.empty? && shows2.empty?
-1
end
end
def self.groups_from_vouchers(vouchers,ignore_cutoff=false)
# Group the vouchers so that a set of vouchers sharing same vouchertype and showdate stay together
groups = Set.new(vouchers).classify { |v| [v.showdate, v.vouchertype] }
# Create a presenter object for each group
formatted_groups = groups.keys.map { |k| VoucherPresenter.new(groups[k].to_a, ignore_cutoff) }
#
# Ordering rules:
# Subscriber vouchers all reserved for SAME SHOW (ie, same subscriber vouchertype) are grouped.
#
formatted_groups.sort
end
private
def vouchers_belong_together
first = @vouchers.first
if @vouchers.any?(&:reserved?)
@vouchers.all? { |v| v.showdate == first.showdate && v.vouchertype == first.vouchertype }
else
@vouchers.all? { |v| v.vouchertype == first.vouchertype }
end
end
end