'date', 'finalized_at' => 'datetime', 'last_synced_at' => 'datetime', 'cts_data' => 'array', ]; } public function serviceSongs(): HasMany { return $this->hasMany(ServiceSong::class); } public function slides(): HasMany { return $this->hasMany(Slide::class); } public function agendaItems(): HasMany { return $this->hasMany(ServiceAgendaItem::class)->orderBy('sort_order'); } /** * Check finalization prerequisites and return warnings. * * @return array{ready: bool, warnings: string[]} */ public function finalizationStatus(): array { $warnings = []; // Backward compatibility: if no agenda items exist, use old checks if ($this->agendaItems()->count() === 0) { return $this->finalizationStatusLegacy(); } // New agenda-based checks $agendaItems = $this->agendaItems()->with('serviceSong.song')->get(); // Check 1: Unmatched songs (song-type agenda items where song_id IS NULL) $unmatchedSongs = $agendaItems->filter(function (ServiceAgendaItem $item) { if ($item->service_song_id === null) { return false; } $serviceSong = $item->serviceSong; return $serviceSong && $serviceSong->song_id === null; }); foreach ($unmatchedSongs as $item) { $songName = $item->serviceSong?->cts_song_name ?? 'Unbekannter Song'; $warnings[] = "{$songName} wurde noch nicht zugeordnet"; } // Check 2: Matched songs without arrangement $matchedSongsWithoutArrangement = $agendaItems->filter(function (ServiceAgendaItem $item) { if ($item->service_song_id === null) { return false; } $serviceSong = $item->serviceSong; return $serviceSong && $serviceSong->song_id !== null && $serviceSong->song_arrangement_id === null; }); foreach ($matchedSongsWithoutArrangement as $item) { $songName = $item->serviceSong?->cts_song_name ?? 'Unbekannter Song'; $warnings[] = "{$songName} hat kein Arrangement ausgewählt"; } // Check 3: Sermon slides $sermonPatterns = Setting::get('agenda_sermon_matching'); if ($sermonPatterns) { // Settings configured: check if any agenda item flagged as sermon has slides $agendaMatcher = app(AgendaMatcherService::class); $sermonItems = $agendaItems->filter(function (ServiceAgendaItem $item) use ($agendaMatcher, $sermonPatterns) { return $agendaMatcher->matches($item->title, $sermonPatterns); }); $sermonHasSlides = false; foreach ($sermonItems as $item) { if ($item->slides()->whereNull('deleted_at')->exists()) { $sermonHasSlides = true; break; } } if (! $sermonHasSlides) { $warnings[] = 'Keine Predigt-Folien hochgeladen'; } } else { // No settings configured: fall back to checking slides table if (! $this->slides()->where('type', 'sermon')->exists()) { $warnings[] = 'Keine Predigt-Folien hochgeladen'; } } return [ 'ready' => empty($warnings), 'warnings' => $warnings, ]; } /** * Legacy finalization status for services without agenda items. * * @return array{ready: bool, warnings: string[]} */ private function finalizationStatusLegacy(): array { $warnings = []; $totalSongs = $this->serviceSongs()->count(); $mappedSongs = $this->serviceSongs()->whereNotNull('song_id')->count(); $arrangedSongs = $this->serviceSongs()->whereNotNull('song_arrangement_id')->count(); $sermonSlides = $this->slides()->where('type', 'sermon')->count(); if ($totalSongs > 0 && $mappedSongs < $totalSongs) { $warnings[] = "Nur {$mappedSongs} von {$totalSongs} Songs sind zugeordnet."; } if ($totalSongs > 0 && $arrangedSongs < $totalSongs) { $warnings[] = "Nur {$arrangedSongs} von {$totalSongs} Songs haben ein Arrangement."; } if ($sermonSlides === 0) { $warnings[] = 'Es wurden keine Predigtfolien hochgeladen.'; } return [ 'ready' => empty($warnings), 'warnings' => $warnings, ]; } protected function isReadyToFinalize(): Attribute { return Attribute::get(fn () => $this->finalizationStatus()['ready']); } }