module Teams class Entitlements ACTIVE_STREAM_STATUSES = %w[idle connecting live reconnecting paused].freeze def initialize(team) @team = team end def subscription @subscription ||= @team.subscription || ensure_free_subscription! end def plan subscription.plan end def active? subscription.active? end def premium_active? subscription.premium? end def premium_full? subscription.premium_full? end def youtube_enabled? active? && plan.youtube_enabled? end def phone_download_enabled? active? && plan.phone_download_enabled? end def max_staff_transmission plan.max_staff_transmission end def max_staff_regia plan.max_staff_regia end def max_staff plan.max_staff end def staff_count staff_count_for("transmission") + staff_count_for("regia") end def staff_count_for(kind) members_count_for(kind) + pending_invitations_count_for(kind) end def members_count @team.user_teams.where(role: "member").count end def members_count_for(kind) @team.user_teams.where(role: "member", staff_kind: kind).count end def pending_invitations_count @team.team_invitations.pending.count end def pending_invitations_count_for(kind) @team.team_invitations.pending.where(staff_kind: kind).count end def concurrent_streams_limit plan.concurrent_streams_limit end def concurrent_streams_used StreamSession.joins(:match) .where(matches: { team_id: @team.id }) .where(status: ACTIVE_STREAM_STATUSES) .count end def concurrent_streams_limit_reached? limit = concurrent_streams_limit return false if limit.nil? concurrent_streams_used >= limit end def can_stream_on?(platform) return false unless active? platform = platform.to_s return false unless plan.allows_platform?(platform) true end def assert_can_stream_on!(platform) platform = platform.to_s unless active? raise EntitlementError.new( "Abbonamento non attivo per questa squadra", code: "subscription_inactive", billing_url: billing_url ) end if platform == "youtube" && !youtube_enabled? raise EntitlementError.new( "YouTube richiede un piano Premium", code: "premium_required", billing_url: billing_url ) end if platform == "youtube" && premium_full? == false && plan.youtube_mode != "matchlivetv_light" raise EntitlementError.new( "Il canale YouTube della società richiede Premium Full", code: "premium_full_required", billing_url: billing_url ) end return if plan.allows_platform?(platform) raise EntitlementError.new("Piattaforma non consentita", code: "platform_denied", billing_url: billing_url) end def assert_concurrent_stream!(excluding_session: nil) return unless active? used = concurrent_streams_used used -= 1 if excluding_session && ACTIVE_STREAM_STATUSES.include?(excluding_session.status) limit = concurrent_streams_limit return if limit.nil? return if used < limit raise EntitlementError.new( "Limite dirette concorrenti raggiunto (#{limit}). Chiudi una diretta o passa a un piano superiore.", code: "concurrent_limit_reached", billing_url: billing_url ) end def assert_can_invite!(staff_kind: "transmission") return unless active? kind = staff_kind.to_s unless TeamInvitation::STAFF_KINDS.include?(kind) raise EntitlementError.new("Ruolo staff non valido", code: "invalid_staff_kind") end limit = staff_limit_for(kind) return if limit.nil? used = staff_count_for(kind) return if used < limit label = kind == "regia" ? "regia" : "trasmissione" raise EntitlementError.new( "Limite staff #{label} raggiunto (#{limit} all'anno). Passa a un piano superiore.", code: "staff_limit_reached", billing_url: billing_url ) end def staff_limit_for(kind) kind == "regia" ? max_staff_regia : max_staff_transmission end def assert_can_connect_youtube! unless premium_full? raise EntitlementError.new( "Collegamento al canale YouTube della società richiede Premium Full", code: "premium_full_required", billing_url: billing_url ) end assert_can_stream_on!("youtube") end def can_access_recordings? active? && plan.recordings_enabled? end def recording_enabled_for_mediamtx? can_access_recordings? end def recording_retention_days plan.recording_days end def billing_url "#{MatchLiveTv.app_public_url.chomp('/')}/teams/#{@team.id}/billing" end def staff_manage_url "#{MatchLiveTv.app_public_url.chomp('/')}/teams/#{@team.id}/dashboard" end def as_json { plan_slug: plan.slug, plan_name: plan.name, premium_active: premium_active?, premium_full: premium_full?, subscription_status: subscription.status, features: plan.features, billing_url: billing_url, staff_manage_url: staff_manage_url, max_staff: max_staff, max_staff_transmission: max_staff_transmission, max_staff_regia: max_staff_regia, staff_used: staff_count, staff_transmission_used: staff_count_for("transmission"), staff_regia_used: staff_count_for("regia"), concurrent_streams_used: concurrent_streams_used, concurrent_streams_limit: concurrent_streams_limit, recordings_enabled: can_access_recordings?, recording_retention_days: recording_retention_days, phone_download_enabled: phone_download_enabled?, youtube_enabled: youtube_enabled?, youtube_mode: plan.youtube_mode } end private def ensure_free_subscription! Billing::AssignPlan.call(team: @team, plan_slug: "free") @team.reload.subscription end end end