module Tournaments class ProposeKnockout def self.call(tournament) new(tournament).call end def initialize(tournament) @tournament = tournament end def call Tournaments::Entitlements.new(@tournament.club).assert_writable! first_round = @tournament.rounds.order(:position).first return [] unless first_round pairs = pair_qualified slots = first_round.matches.order(:scheduled_at, :created_at).to_a slots = create_slots!(first_round, [pairs.size, 1].max) if slots.empty? updated = [] slots.each_with_index do |match, idx| home, away = pairs[idx] next if home.blank? && away.blank? match.update!(home_participant: home, away_participant: away) updated << match end updated.concat(seed_later_rounds!(slots)) updated end private def pair_qualified groups = @tournament.groups.order(:position).to_a ranked = groups.map { |group| Tournaments::Standings.call(group).map(&:participant) } return ranked.flatten.each_slice(2).to_a if groups.size < 2 pairs = [] first = ranked[0] || [] second = ranked[1] || [] pairs << [first[0], second[1]] if first[0] || second[1] pairs << [second[0], first[1]] if second[0] || first[1] leftover = (first.drop(2) + second.drop(2) + ranked.drop(2).flatten) pairs.concat(leftover.each_slice(2).to_a) pairs end def seed_later_rounds!(first_round_matches) prev = first_round_matches created = [] @tournament.rounds.order(:position).offset(1).each do |round| needed = [prev.size / 2, 1].max slots = round.matches.order(:scheduled_at, :created_at).to_a last_at = prev.map(&:scheduled_at).compact.max start = last_at&.+(1.hour) slots = create_slots!(round, needed, start: start) if slots.empty? slots.each_with_index do |match, idx| home_src = prev[idx * 2] away_src = prev[idx * 2 + 1] attrs = {} if home_src && match.home_source_match_id.blank? attrs[:home_source_kind] = "winner_match" attrs[:home_source_match_id] = home_src.id end if away_src && match.away_source_match_id.blank? attrs[:away_source_kind] = "winner_match" attrs[:away_source_match_id] = away_src.id end match.update!(attrs) if attrs.any? created << match end prev = slots end created end def create_slots!(round, n, start: nil) count = [n, 1].max day = @tournament.ends_on start ||= Time.zone.local(day.year, day.month, day.day, 18, 0, 0) Array.new(count) do |i| Tournaments::ScheduleMatch.call( tournament: @tournament, attrs: { tournament_round_id: round.id, court: @tournament.court_list.first, scheduled_at: start + i.hours } ) end end end end