# frozen_string_literal: true class PhantomPlayer < ApplicationRecord belongs_to :crew belongs_to :claimed_by, class_name: 'User', optional: true belongs_to :claimed_from_membership, class_name: 'CrewMembership', optional: true has_many :gw_individual_scores, dependent: :nullify before_validation :set_joined_at, on: :create validates :name, presence: true, length: { maximum: 100 } validates :granblue_id, length: { maximum: 20 }, allow_blank: true validates :granblue_id, uniqueness: { scope: :crew_id }, if: -> { granblue_id.present? } validate :claimed_by_must_be_crew_member, if: :claimed_by_id_changed? validate :claim_confirmed_requires_claimed_by scope :unclaimed, -> { where(claimed_by_id: nil) } scope :claimed, -> { where.not(claimed_by_id: nil) } scope :pending_confirmation, -> { claimed.where(claim_confirmed: false) } scope :active, -> { where(retired: false) } scope :retired, -> { where(retired: true) } scope :not_deleted, -> { where(deleted_at: nil) } scope :deleted, -> { where.not(deleted_at: nil) } # Phantoms who were active during a date range (either still active, or retired after the end date) # Uses joined_at (editable) instead of created_at for historical accuracy scope :active_during, ->(start_date, end_date) { where('retired = false OR retired_at >= ?', start_date) .where('joined_at <= ?', end_date) } # Assign this phantom to a user (officer action) def assign_to(user) raise CrewErrors::MemberNotFoundError unless user.crew == crew self.claimed_by = user self.claim_confirmed = false save! end # Confirm the claim (user action) # After confirmation, soft deletes the phantom since scores have been transferred def confirm_claim!(user) raise CrewErrors::NotClaimedByUserError unless claimed_by == user self.claim_confirmed = true transfer_scores_to_membership! save! soft_delete! end # Soft delete the phantom (keeps record for logging) def soft_delete! update!(deleted_at: Time.current) end # Unassign the phantom (officer action or user rejection) def unassign! self.claimed_by = nil self.claim_confirmed = false save! end # Retire the phantom player (keeps scores) def retire! update!(retired: true, retired_at: Time.current) end private def set_joined_at self.joined_at ||= Time.current end def claimed_by_must_be_crew_member return unless claimed_by.present? return if claimed_by.crew == crew errors.add(:claimed_by, 'must be a member of this crew') end def claim_confirmed_requires_claimed_by return unless claim_confirmed? && claimed_by.blank? errors.add(:claim_confirmed, 'requires a claimed_by user') end def transfer_scores_to_membership! return unless claimed_by.present? membership = claimed_by.active_crew_membership return unless membership # Transfer all phantom scores to the user's membership gw_individual_scores.update_all( crew_membership_id: membership.id, phantom_player_id: nil ) end end