feat(sync): sync all CTS agenda items (not just songs)
This commit is contained in:
parent
0b671956d6
commit
88661c6bef
|
|
@ -39,6 +39,7 @@ public function sync(): array
|
|||
'songs_count' => $eventsSummary['songs_count'],
|
||||
'matched_songs_count' => $eventsSummary['matched_songs_count'],
|
||||
'unmatched_songs_count' => $eventsSummary['unmatched_songs_count'],
|
||||
'agenda_items_count' => $eventsSummary['agenda_items_count'],
|
||||
'catalog_songs_count' => $songsCount,
|
||||
];
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ public function sync(): array
|
|||
'songs_count' => 0,
|
||||
'matched_songs_count' => 0,
|
||||
'unmatched_songs_count' => 0,
|
||||
'agenda_items_count' => 0,
|
||||
'catalog_songs_count' => 0,
|
||||
];
|
||||
|
||||
|
|
@ -69,6 +71,7 @@ public function syncEvents(): array
|
|||
'songs_count' => 0,
|
||||
'matched_songs_count' => 0,
|
||||
'unmatched_songs_count' => 0,
|
||||
'agenda_items_count' => 0,
|
||||
];
|
||||
|
||||
foreach ($events as $event) {
|
||||
|
|
@ -87,6 +90,7 @@ public function syncEvents(): array
|
|||
$summary['songs_count'] += $songSummary['songs_count'];
|
||||
$summary['matched_songs_count'] += $songSummary['matched_songs_count'];
|
||||
$summary['unmatched_songs_count'] += $songSummary['unmatched_songs_count'];
|
||||
$summary['agenda_items_count'] += $songSummary['agenda_items_count'] ?? 0;
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('Sync-Fehler für Event', [
|
||||
'event_id' => $event->getId() ?? 'unknown',
|
||||
|
|
@ -321,64 +325,129 @@ private function upsertService(mixed $event, array $serviceRoles): object
|
|||
->firstOrFail();
|
||||
}
|
||||
|
||||
/** @deprecated Use syncServiceAgendaItems() instead. */
|
||||
private function syncServiceAgendaSongs(int $serviceId, int $eventId): array
|
||||
{
|
||||
return $this->syncServiceAgendaItems($serviceId, $eventId);
|
||||
}
|
||||
|
||||
private function syncServiceAgendaItems(int $serviceId, int $eventId): array
|
||||
{
|
||||
$agenda = $this->syncAgenda($eventId);
|
||||
$agendaSongs = method_exists($agenda, 'getSongs') ? $agenda->getSongs() : [];
|
||||
$agendaItems = method_exists($agenda, 'getItems') ? $agenda->getItems() : [];
|
||||
|
||||
$summary = [
|
||||
'songs_count' => 0,
|
||||
'matched_songs_count' => 0,
|
||||
'unmatched_songs_count' => 0,
|
||||
'agenda_items_count' => 0,
|
||||
];
|
||||
|
||||
$songMatchingService = app(SongMatchingService::class);
|
||||
$processedSortOrders = [];
|
||||
|
||||
foreach ($agendaSongs as $index => $song) {
|
||||
$ctsSongId = (string) ($song->getId() ?? '');
|
||||
if ($ctsSongId === '') {
|
||||
foreach ($agendaItems as $index => $item) {
|
||||
if (method_exists($item, 'getIsBeforeEvent') && $item->getIsBeforeEvent()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$ccliId = $this->normalizeCcli($song->getCcli() ?? null);
|
||||
$sortOrder = $index + 1;
|
||||
$processedSortOrders[] = $sortOrder;
|
||||
|
||||
DB::table('service_songs')->updateOrInsert(
|
||||
$responsible = method_exists($item, 'getResponsible') ? $item->getResponsible() : null;
|
||||
|
||||
DB::table('service_agenda_items')->updateOrInsert(
|
||||
[
|
||||
'service_id' => $serviceId,
|
||||
'order' => $index + 1,
|
||||
'sort_order' => $sortOrder,
|
||||
],
|
||||
[
|
||||
'cts_song_name' => (string) ($song->getName() ?? ''),
|
||||
'cts_ccli_id' => $ccliId,
|
||||
'cts_song_id' => $ctsSongId,
|
||||
'cts_agenda_item_id' => method_exists($item, 'getId') ? (string) ($item->getId() ?? '') : null,
|
||||
'position' => (string) (method_exists($item, 'getPosition') ? ($item->getPosition() ?? '') : ''),
|
||||
'title' => (string) (method_exists($item, 'getTitle') ? ($item->getTitle() ?? '') : ''),
|
||||
'type' => (string) (method_exists($item, 'getType') ? ($item->getType() ?? '') : ''),
|
||||
'note' => method_exists($item, 'getNote') ? $item->getNote() : null,
|
||||
'duration' => method_exists($item, 'getDuration') ? $item->getDuration() : null,
|
||||
'start' => method_exists($item, 'getStart') ? $item->getStart() : null,
|
||||
'is_before_event' => false,
|
||||
'responsible' => $responsible !== null ? json_encode($responsible, JSON_THROW_ON_ERROR) : null,
|
||||
'updated_at' => Carbon::now(),
|
||||
'created_at' => Carbon::now(),
|
||||
]
|
||||
);
|
||||
|
||||
$serviceSong = \App\Models\ServiceSong::where('service_id', $serviceId)
|
||||
->where('order', $index + 1)
|
||||
->first();
|
||||
$song = method_exists($item, 'getSong') ? $item->getSong() : null;
|
||||
|
||||
$matched = false;
|
||||
if ($song !== null) {
|
||||
$ctsSongId = (string) ($song->getId() ?? '');
|
||||
if ($ctsSongId !== '') {
|
||||
$ccliId = $this->normalizeCcli($song->getCcli() ?? null);
|
||||
|
||||
if (
|
||||
$serviceSong !== null
|
||||
&& ! $serviceSong->song_id
|
||||
&& ($serviceSong->cts_ccli_id || $serviceSong->cts_song_id)
|
||||
) {
|
||||
$matched = $songMatchingService->autoMatch($serviceSong);
|
||||
} elseif ($serviceSong !== null && $serviceSong->song_id) {
|
||||
$matched = true;
|
||||
DB::table('service_songs')->updateOrInsert(
|
||||
[
|
||||
'service_id' => $serviceId,
|
||||
'order' => $sortOrder,
|
||||
],
|
||||
[
|
||||
'cts_song_name' => (string) ($song->getName() ?? ''),
|
||||
'cts_ccli_id' => $ccliId,
|
||||
'cts_song_id' => $ctsSongId,
|
||||
'updated_at' => Carbon::now(),
|
||||
'created_at' => Carbon::now(),
|
||||
]
|
||||
);
|
||||
|
||||
$serviceSong = \App\Models\ServiceSong::where('service_id', $serviceId)
|
||||
->where('order', $sortOrder)
|
||||
->first();
|
||||
|
||||
if ($serviceSong) {
|
||||
DB::table('service_agenda_items')
|
||||
->where('service_id', $serviceId)
|
||||
->where('sort_order', $sortOrder)
|
||||
->update(['service_song_id' => $serviceSong->id]);
|
||||
}
|
||||
|
||||
$matched = false;
|
||||
|
||||
if (
|
||||
$serviceSong !== null
|
||||
&& ! $serviceSong->song_id
|
||||
&& ($serviceSong->cts_ccli_id || $serviceSong->cts_song_id)
|
||||
) {
|
||||
$matched = $songMatchingService->autoMatch($serviceSong);
|
||||
} elseif ($serviceSong !== null && $serviceSong->song_id) {
|
||||
$matched = true;
|
||||
}
|
||||
|
||||
$summary['songs_count']++;
|
||||
|
||||
if ($matched) {
|
||||
$summary['matched_songs_count']++;
|
||||
} else {
|
||||
$summary['unmatched_songs_count']++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$summary['songs_count']++;
|
||||
$summary['agenda_items_count']++;
|
||||
}
|
||||
|
||||
if ($matched) {
|
||||
$summary['matched_songs_count']++;
|
||||
} else {
|
||||
$summary['unmatched_songs_count']++;
|
||||
}
|
||||
// Clean up orphaned agenda items: first null-ify slide FKs, then delete
|
||||
$orphanedIds = DB::table('service_agenda_items')
|
||||
->where('service_id', $serviceId)
|
||||
->whereNotIn('sort_order', $processedSortOrders)
|
||||
->pluck('id');
|
||||
|
||||
if ($orphanedIds->isNotEmpty()) {
|
||||
DB::table('slides')
|
||||
->whereIn('service_agenda_item_id', $orphanedIds)
|
||||
->update(['service_agenda_item_id' => null]);
|
||||
|
||||
DB::table('service_agenda_items')
|
||||
->where('service_id', $serviceId)
|
||||
->whereNotIn('sort_order', $processedSortOrders)
|
||||
->delete();
|
||||
}
|
||||
|
||||
return $summary;
|
||||
|
|
|
|||
|
|
@ -36,8 +36,9 @@
|
|||
],
|
||||
songFetcher: fn () => [new FakeSong(id: 1, title: 'Way Maker', ccli: '7115744')],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744'),
|
||||
new FakeSong(id: 5002, title: 'Unbekannt', ccli: '9999999'),
|
||||
new FakeAgendaItem(id: 1, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
new FakeAgendaItem(id: 2, position: '2', title: 'Way Maker', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
||||
new FakeAgendaItem(id: 3, position: '3', title: 'Unbekannt', type: 'Song', song: new FakeSong(id: 5002, title: 'Unbekannt', ccli: '9999999')),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [
|
||||
new FakeEventService('Predigt', new FakePerson('Max', 'Mustermann')),
|
||||
|
|
@ -55,12 +56,12 @@
|
|||
expect($service->preacher_name)->toBe('Max Mustermann');
|
||||
expect($service->beamer_tech_name)->toBe('Lisa Technik');
|
||||
|
||||
$matchedSong = DB::table('service_songs')->where('order', 1)->first();
|
||||
$matchedSong = DB::table('service_songs')->where('order', 2)->first();
|
||||
expect($matchedSong)->not->toBeNull();
|
||||
expect((int) $matchedSong->song_id)->toBe($localSongId);
|
||||
expect($matchedSong->matched_at)->not->toBeNull();
|
||||
|
||||
$unmatchedSong = DB::table('service_songs')->where('order', 2)->first();
|
||||
$unmatchedSong = DB::table('service_songs')->where('order', 3)->first();
|
||||
expect($unmatchedSong)->not->toBeNull();
|
||||
expect($unmatchedSong->song_id)->toBeNull();
|
||||
expect($unmatchedSong->cts_ccli_id)->toBe('9999999');
|
||||
|
|
@ -72,6 +73,283 @@
|
|||
expect((int) $syncLog->songs_count)->toBe(2);
|
||||
});
|
||||
|
||||
test('sync speichert alle agenda items (songs und nicht-songs) in service_agenda_items', function () {
|
||||
Carbon::setTestNow('2026-03-01 09:00:00');
|
||||
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 200, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 10, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
new FakeAgendaItem(id: 11, position: '2', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
||||
new FakeAgendaItem(id: 12, position: '3', title: 'Predigt', type: 'Default', note: 'Zum Thema Liebe'),
|
||||
new FakeAgendaItem(id: 13, position: '4', title: 'Abschluss', type: 'Song', song: new FakeSong(id: 5002, title: 'Amazing Grace', ccli: '1234567')),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
$service = DB::table('services')->where('cts_event_id', '200')->first();
|
||||
|
||||
$agendaItems = DB::table('service_agenda_items')
|
||||
->where('service_id', $service->id)
|
||||
->orderBy('sort_order')
|
||||
->get();
|
||||
|
||||
expect($agendaItems)->toHaveCount(4);
|
||||
expect($agendaItems[0]->title)->toBe('Begrüssung');
|
||||
expect($agendaItems[0]->type)->toBe('Default');
|
||||
expect((int) $agendaItems[0]->sort_order)->toBe(1);
|
||||
expect($agendaItems[1]->title)->toBe('Lobpreis');
|
||||
expect($agendaItems[1]->type)->toBe('Song');
|
||||
expect((int) $agendaItems[1]->sort_order)->toBe(2);
|
||||
expect($agendaItems[2]->title)->toBe('Predigt');
|
||||
expect($agendaItems[2]->note)->toBe('Zum Thema Liebe');
|
||||
expect($agendaItems[3]->title)->toBe('Abschluss');
|
||||
});
|
||||
|
||||
test('song items erstellen service_song UND service_agenda_item mit korrekter service_song_id', function () {
|
||||
Carbon::setTestNow('2026-03-01 09:00:00');
|
||||
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 300, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 20, position: '1', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
$service = DB::table('services')->where('cts_event_id', '300')->first();
|
||||
|
||||
$serviceSong = DB::table('service_songs')->where('service_id', $service->id)->first();
|
||||
expect($serviceSong)->not->toBeNull();
|
||||
expect($serviceSong->cts_song_name)->toBe('Way Maker');
|
||||
|
||||
$agendaItem = DB::table('service_agenda_items')->where('service_id', $service->id)->first();
|
||||
expect($agendaItem)->not->toBeNull();
|
||||
expect($agendaItem->title)->toBe('Lobpreis');
|
||||
expect((int) $agendaItem->service_song_id)->toBe((int) $serviceSong->id);
|
||||
});
|
||||
|
||||
test('nicht-song items erstellen keine service_song eintraege', function () {
|
||||
Carbon::setTestNow('2026-03-01 09:00:00');
|
||||
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 400, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 30, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
new FakeAgendaItem(id: 31, position: '2', title: 'Predigt', type: 'Default'),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
$service = DB::table('services')->where('cts_event_id', '400')->first();
|
||||
|
||||
$agendaItems = DB::table('service_agenda_items')->where('service_id', $service->id)->get();
|
||||
expect($agendaItems)->toHaveCount(2);
|
||||
|
||||
$serviceSongs = DB::table('service_songs')->where('service_id', $service->id)->get();
|
||||
expect($serviceSongs)->toHaveCount(0);
|
||||
});
|
||||
|
||||
test('is_before_event items werden uebersprungen', function () {
|
||||
Carbon::setTestNow('2026-03-01 09:00:00');
|
||||
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 500, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 40, position: '0', title: 'Soundcheck', type: 'Default', isBeforeEvent: true),
|
||||
new FakeAgendaItem(id: 41, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
new FakeAgendaItem(id: 42, position: '2', title: 'Probe', type: 'Default', isBeforeEvent: true),
|
||||
new FakeAgendaItem(id: 43, position: '3', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
$service = DB::table('services')->where('cts_event_id', '500')->first();
|
||||
|
||||
$agendaItems = DB::table('service_agenda_items')->where('service_id', $service->id)->get();
|
||||
expect($agendaItems)->toHaveCount(2);
|
||||
|
||||
$titles = $agendaItems->pluck('title')->toArray();
|
||||
expect($titles)->not->toContain('Soundcheck');
|
||||
expect($titles)->not->toContain('Probe');
|
||||
expect($titles)->toContain('Begrüssung');
|
||||
expect($titles)->toContain('Lobpreis');
|
||||
});
|
||||
|
||||
test('verwaiste agenda items werden bei re-sync entfernt', function () {
|
||||
Carbon::setTestNow('2026-03-01 09:00:00');
|
||||
|
||||
// First sync with 3 items
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 600, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 50, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
new FakeAgendaItem(id: 51, position: '2', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
||||
new FakeAgendaItem(id: 52, position: '3', title: 'Predigt', type: 'Default'),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
$service = DB::table('services')->where('cts_event_id', '600')->first();
|
||||
expect(DB::table('service_agenda_items')->where('service_id', $service->id)->count())->toBe(3);
|
||||
|
||||
// Re-sync with only 1 item
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 600, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 50, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
$agendaItems = DB::table('service_agenda_items')->where('service_id', $service->id)->get();
|
||||
expect($agendaItems)->toHaveCount(1);
|
||||
expect($agendaItems[0]->title)->toBe('Begrüssung');
|
||||
});
|
||||
|
||||
test('slides werden erhalten (FK genullt) wenn verwaiste agenda items entfernt werden', function () {
|
||||
Carbon::setTestNow('2026-03-01 09:00:00');
|
||||
|
||||
// First sync with 2 items
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 700, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 60, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
new FakeAgendaItem(id: 61, position: '2', title: 'Predigt', type: 'Default'),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
$service = DB::table('services')->where('cts_event_id', '700')->first();
|
||||
$agendaItem = DB::table('service_agenda_items')
|
||||
->where('service_id', $service->id)
|
||||
->where('sort_order', 2)
|
||||
->first();
|
||||
|
||||
// Create a slide linked to the second agenda item
|
||||
$slideId = DB::table('slides')->insertGetId([
|
||||
'service_id' => $service->id,
|
||||
'service_agenda_item_id' => $agendaItem->id,
|
||||
'type' => 'sermon',
|
||||
'original_filename' => 'predigt.jpg',
|
||||
'stored_filename' => 'predigt_stored.jpg',
|
||||
'thumbnail_filename' => 'predigt_thumb.jpg',
|
||||
'uploaded_at' => now(),
|
||||
'sort_order' => 1,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
// Re-sync with only 1 item (second item removed)
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 700, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 60, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
// Slide still exists but FK is null
|
||||
$slide = DB::table('slides')->where('id', $slideId)->first();
|
||||
expect($slide)->not->toBeNull();
|
||||
expect($slide->service_agenda_item_id)->toBeNull();
|
||||
|
||||
// Orphaned agenda item is gone
|
||||
expect(DB::table('service_agenda_items')->where('service_id', $service->id)->count())->toBe(1);
|
||||
});
|
||||
|
||||
test('sync summary enthaelt agenda_items_count', function () {
|
||||
Carbon::setTestNow('2026-03-01 09:00:00');
|
||||
|
||||
$service = new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 800, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(id: 70, position: '1', title: 'Begrüssung', type: 'Default'),
|
||||
new FakeAgendaItem(id: 71, position: '2', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
);
|
||||
|
||||
$summary = $service->sync();
|
||||
|
||||
expect($summary)->toHaveKey('agenda_items_count');
|
||||
expect($summary['agenda_items_count'])->toBe(2);
|
||||
expect($summary['songs_count'])->toBe(1);
|
||||
expect($summary['services_count'])->toBe(1);
|
||||
});
|
||||
|
||||
test('responsible feld wird als json gespeichert', function () {
|
||||
Carbon::setTestNow('2026-03-01 09:00:00');
|
||||
|
||||
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
||||
eventFetcher: fn () => [
|
||||
new FakeEvent(id: 900, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
||||
],
|
||||
songFetcher: fn () => [],
|
||||
agendaFetcher: fn () => new FakeAgenda([
|
||||
new FakeAgendaItem(
|
||||
id: 80,
|
||||
position: '1',
|
||||
title: 'Begrüssung',
|
||||
type: 'Default',
|
||||
responsible: ['name' => 'Max Mustermann'],
|
||||
),
|
||||
]),
|
||||
eventServiceFetcher: fn (int $eventId) => [],
|
||||
));
|
||||
|
||||
Artisan::call('cts:sync');
|
||||
|
||||
$service = DB::table('services')->where('cts_event_id', '900')->first();
|
||||
$agendaItem = DB::table('service_agenda_items')->where('service_id', $service->id)->first();
|
||||
|
||||
$responsible = json_decode($agendaItem->responsible, true);
|
||||
expect($responsible)->toBe(['name' => 'Max Mustermann']);
|
||||
});
|
||||
|
||||
function ensureSyncTables(): void
|
||||
{
|
||||
if (! Schema::hasTable('services')) {
|
||||
|
|
@ -93,6 +371,7 @@ function ensureSyncTables(): void
|
|||
$table->id();
|
||||
$table->string('title');
|
||||
$table->string('ccli_id')->nullable()->unique();
|
||||
$table->string('cts_song_id')->nullable()->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
|
@ -104,6 +383,7 @@ function ensureSyncTables(): void
|
|||
$table->foreignId('song_id')->nullable()->constrained('songs')->nullOnDelete();
|
||||
$table->string('cts_song_name');
|
||||
$table->string('cts_ccli_id')->nullable();
|
||||
$table->string('cts_song_id')->nullable();
|
||||
$table->unsignedInteger('order')->default(0);
|
||||
$table->timestamp('matched_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
|
@ -111,6 +391,44 @@ function ensureSyncTables(): void
|
|||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('service_agenda_items')) {
|
||||
Schema::create('service_agenda_items', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('service_id')->constrained('services')->cascadeOnDelete();
|
||||
$table->string('cts_agenda_item_id')->nullable()->index();
|
||||
$table->string('position');
|
||||
$table->string('title');
|
||||
$table->string('type');
|
||||
$table->text('note')->nullable();
|
||||
$table->string('duration')->nullable();
|
||||
$table->string('start')->nullable();
|
||||
$table->boolean('is_before_event')->default(false);
|
||||
$table->json('responsible')->nullable();
|
||||
$table->foreignId('service_song_id')->nullable()->constrained('service_songs')->nullOnDelete();
|
||||
$table->unsignedInteger('sort_order');
|
||||
$table->timestamps();
|
||||
$table->unique(['service_id', 'sort_order']);
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('slides')) {
|
||||
Schema::create('slides', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->enum('type', ['information', 'moderation', 'sermon']);
|
||||
$table->foreignId('service_id')->nullable()->constrained()->nullOnDelete();
|
||||
$table->foreignId('service_agenda_item_id')->nullable()->constrained('service_agenda_items')->nullOnDelete();
|
||||
$table->string('original_filename');
|
||||
$table->string('stored_filename');
|
||||
$table->string('thumbnail_filename');
|
||||
$table->date('expire_date')->nullable();
|
||||
$table->string('uploader_name')->nullable();
|
||||
$table->timestamp('uploaded_at');
|
||||
$table->unsignedInteger('sort_order')->default(0);
|
||||
$table->softDeletes();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('cts_sync_log')) {
|
||||
Schema::create('cts_sync_log', function (Blueprint $table) {
|
||||
$table->id();
|
||||
|
|
@ -198,11 +516,85 @@ public function getLastName(): string
|
|||
|
||||
final class FakeAgenda
|
||||
{
|
||||
public function __construct(private readonly array $songs) {}
|
||||
public function __construct(private readonly array $items) {}
|
||||
|
||||
public function getItems(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function getSongs(): array
|
||||
{
|
||||
return $this->songs;
|
||||
return array_values(array_filter(
|
||||
array_map(fn ($item) => $item->getSong(), $this->items),
|
||||
fn ($song) => $song !== null,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
final class FakeAgendaItem
|
||||
{
|
||||
public function __construct(
|
||||
private readonly int $id,
|
||||
private readonly string $position,
|
||||
private readonly string $title,
|
||||
private readonly string $type,
|
||||
private readonly ?FakeSong $song = null,
|
||||
private readonly ?string $note = null,
|
||||
private readonly ?string $duration = null,
|
||||
private readonly ?string $start = null,
|
||||
private readonly bool $isBeforeEvent = false,
|
||||
private readonly ?array $responsible = null,
|
||||
) {}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return (string) $this->id;
|
||||
}
|
||||
|
||||
public function getPosition(): string
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function getSong(): ?FakeSong
|
||||
{
|
||||
return $this->song;
|
||||
}
|
||||
|
||||
public function getNote(): ?string
|
||||
{
|
||||
return $this->note;
|
||||
}
|
||||
|
||||
public function getDuration(): ?string
|
||||
{
|
||||
return $this->duration;
|
||||
}
|
||||
|
||||
public function getStart(): ?string
|
||||
{
|
||||
return $this->start;
|
||||
}
|
||||
|
||||
public function getIsBeforeEvent(): bool
|
||||
{
|
||||
return $this->isBeforeEvent;
|
||||
}
|
||||
|
||||
public function getResponsible(): ?array
|
||||
{
|
||||
return $this->responsible;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue