T20: Song DB Page - Songs/Index.vue with search, action buttons, pagination - Upload area for .pro files (calls T23 placeholder) - Song-Datenbank nav link added to AuthenticatedLayout - Tests: 9 new (44 assertions) T21: Song DB Edit Popup - SongEditModal.vue with metadata + ArrangementConfigurator - Auto-save with fetch (500ms debounce for text, immediate on blur) - Tests: 11 new (53 assertions) T22: Song DB Translate Page - Songs/Translate.vue with two-column editor - URL fetch or manual paste, line-count constraints - Group headers with colors, save marks has_translation=true - Tests: 1 new (12 assertions) T23: .pro File Placeholders - ProParserNotImplementedException with HTTP 501 - ProFileController with importPro/downloadPro placeholders - German error messages - Tests: 5 new (7 assertions) T24: Service Finalization + Status - Two-step finalization with warnings (unmatched songs, missing slides) - Download placeholder toast - isReadyToFinalize accessor on Service model - Tests: 11 new (30 assertions) All tests passing: 174/174 (905 assertions) Build: ✓ Vite production build successful German UI: All user-facing text in German with 'Du' form
82 lines
2.1 KiB
PHP
82 lines
2.1 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
class Service extends Model
|
|
{
|
|
use HasFactory;
|
|
|
|
protected $fillable = [
|
|
'cts_event_id',
|
|
'title',
|
|
'date',
|
|
'preacher_name',
|
|
'beamer_tech_name',
|
|
'finalized_at',
|
|
'last_synced_at',
|
|
'cts_data',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'date' => '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);
|
|
}
|
|
|
|
/**
|
|
* Check finalization prerequisites and return warnings.
|
|
*
|
|
* @return array{ready: bool, warnings: string[]}
|
|
*/
|
|
public function finalizationStatus(): 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']);
|
|
}
|
|
}
|