$changes */ public function format(EventDate $eventDate, Collection $changes): ?string { $messages = collect(); if ($eventDate->suspended_at !== null) { $messages->push('Esta fecha fue cancelada.'); } $sourceDates = $this->reschedulesEndingAt($eventDate, $changes) ->pluck('previous_date') ->filter() ->map(fn ($date): string => $date->format('d/m/Y')) ->unique() ->values(); if ($sourceDates->isNotEmpty()) { $verb = $sourceDates->count() === 1 ? 'se reprogramó' : 'se reprogramaron'; $messages->push("{$sourceDates->join(', ', ' y ')} {$verb} para este día."); } return $messages->isEmpty() ? null : $messages->join(' '); } /** * Includes direct and intermediate reschedules that ultimately end at the * displayed event date, while preserving the original change order. * * @param Collection $changes * @return Collection */ private function reschedulesEndingAt(EventDate $eventDate, Collection $changes): Collection { $eventDateId = $eventDate->getKey(); $reschedules = $changes->where('change_type', EventDateChangeType::Rescheduled); if ($eventDateId === null) { return $reschedules->where('destination_event_date_id', null); } $destinationIds = [(int) $eventDateId => true]; do { $foundAncestor = false; foreach ($reschedules as $change) { $destinationId = $change->destination_event_date_id; $sourceId = $change->source_event_date_id; if ($destinationId === null || $sourceId === null) { continue; } if (isset($destinationIds[(int) $destinationId]) && ! isset($destinationIds[(int) $sourceId])) { $destinationIds[(int) $sourceId] = true; $foundAncestor = true; } } } while ($foundAncestor); return $reschedules ->filter(fn (EventDateChange $change): bool => $change->destination_event_date_id !== null && isset($destinationIds[(int) $change->destination_event_date_id])); } }