feat(kalender): Antworten von Graph und Google lesen, Belegung rechnen
Reine Logik ohne fetch: die Antworten beider Anbieter werden mit zod geprüft und in eine gemeinsame Form gebracht. Geprüft wird gegen anonymisierte Beispielantworten in test/fixtures — 61 Tests. Zwei Stellen, an denen bewusst abgebrochen statt geraten wird: - Graph liefert Datum und Zeitzone getrennt, ohne Versatz im Text. Wir fragen mit Prefer: outlook.timezone="UTC" an; kommt etwas anderes zurück, wäre jede Umrechnung geraten und würde Termine um Stunden verschieben. - Ein unbekanntes showAs wird UNBEKANNT und zählt damit als belegt. Lieber eine Stunde zu viel als eine Doppelbuchung. Zur Belegung: Ganztägige Termine zählen nicht — ein Urlaubstag oder Geburtstag im Kalender blockiert sonst den ganzen Tag, obwohl er keine Arbeitszeit kostet. Ebenso wenig zählen ausdrücklich als frei markierte Termine. Überlappende Termine werden zusammengelegt, damit eine Doppelbuchung nicht doppelt zählt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
79e6c0d556
commit
6fda2a579e
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"kind": "calendar#events",
|
||||
"etag": "\"BEISPIEL-LISTE-3\"",
|
||||
"summary": "person@example.invalid",
|
||||
"timeZone": "Europe/Berlin",
|
||||
"nextSyncToken": "BEISPIEL-SYNC-2",
|
||||
"items": []
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"kind": "calendar#events",
|
||||
"etag": "\"BEISPIEL-LISTE-1\"",
|
||||
"summary": "person@example.invalid",
|
||||
"timeZone": "Europe/Berlin",
|
||||
"nextPageToken": "BEISPIEL-SEITE-2",
|
||||
"items": [
|
||||
{
|
||||
"kind": "calendar#event",
|
||||
"etag": "\"BEISPIELETAG10\"",
|
||||
"id": "beispiel-termin-a",
|
||||
"status": "confirmed",
|
||||
"summary": "Zahnarzt",
|
||||
"updated": "2026-03-10T09:00:00.000Z",
|
||||
"start": { "dateTime": "2026-03-17T14:00:00+01:00", "timeZone": "Europe/Berlin" },
|
||||
"end": { "dateTime": "2026-03-17T15:00:00+01:00", "timeZone": "Europe/Berlin" }
|
||||
},
|
||||
{
|
||||
"kind": "calendar#event",
|
||||
"etag": "\"BEISPIELETAG11\"",
|
||||
"id": "beispiel-termin-b",
|
||||
"status": "confirmed",
|
||||
"summary": "Urlaubstag",
|
||||
"updated": "2026-03-11T09:00:00.000Z",
|
||||
"start": { "date": "2026-03-20" },
|
||||
"end": { "date": "2026-03-21" }
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"kind": "calendar#events",
|
||||
"etag": "\"BEISPIEL-LISTE-2\"",
|
||||
"summary": "person@example.invalid",
|
||||
"timeZone": "Europe/Berlin",
|
||||
"nextSyncToken": "BEISPIEL-SYNC-1",
|
||||
"items": [
|
||||
{
|
||||
"kind": "calendar#event",
|
||||
"etag": "\"BEISPIELETAG12\"",
|
||||
"id": "beispiel-termin-c",
|
||||
"status": "confirmed",
|
||||
"summary": "Sport",
|
||||
"transparency": "transparent",
|
||||
"updated": "2026-03-12T09:00:00.000Z",
|
||||
"start": { "dateTime": "2026-03-17T18:00:00+01:00", "timeZone": "Europe/Berlin" },
|
||||
"end": { "dateTime": "2026-03-17T19:30:00+01:00", "timeZone": "Europe/Berlin" }
|
||||
},
|
||||
{
|
||||
"kind": "calendar#event",
|
||||
"etag": "\"BEISPIELETAG13\"",
|
||||
"id": "beispiel-termin-d",
|
||||
"status": "cancelled",
|
||||
"updated": "2026-03-13T09:00:00.000Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(event)",
|
||||
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/calendarView/delta?$deltatoken=BEISPIEL-DELTA-2",
|
||||
"value": [
|
||||
{
|
||||
"@odata.type": "#microsoft.graph.event",
|
||||
"id": "BEISPIEL-TERMIN-4",
|
||||
"subject": "Falsch angefragt",
|
||||
"isAllDay": false,
|
||||
"showAs": "busy",
|
||||
"start": {
|
||||
"dateTime": "2026-03-17T09:00:00.0000000",
|
||||
"timeZone": "W. Europe Standard Time"
|
||||
},
|
||||
"end": {
|
||||
"dateTime": "2026-03-17T10:00:00.0000000",
|
||||
"timeZone": "W. Europe Standard Time"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(event)",
|
||||
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/calendarView/delta?$skiptoken=BEISPIEL-SKIP-1",
|
||||
"value": [
|
||||
{
|
||||
"@odata.type": "#microsoft.graph.event",
|
||||
"@odata.etag": "W/\"BEISPIELETAG1==\"",
|
||||
"id": "BEISPIEL-TERMIN-1",
|
||||
"subject": "Wochenbesprechung",
|
||||
"isAllDay": false,
|
||||
"showAs": "busy",
|
||||
"start": { "dateTime": "2026-03-17T08:00:00.0000000", "timeZone": "UTC" },
|
||||
"end": { "dateTime": "2026-03-17T09:00:00.0000000", "timeZone": "UTC" },
|
||||
"organizer": {
|
||||
"emailAddress": { "name": "Beispiel Person", "address": "person@example.invalid" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"@odata.type": "#microsoft.graph.event",
|
||||
"@odata.etag": "W/\"BEISPIELETAG2==\"",
|
||||
"id": "BEISPIEL-TERMIN-2",
|
||||
"subject": "Betriebsausflug",
|
||||
"isAllDay": true,
|
||||
"showAs": "oof",
|
||||
"start": { "dateTime": "2026-03-18T00:00:00.0000000", "timeZone": "UTC" },
|
||||
"end": { "dateTime": "2026-03-19T00:00:00.0000000", "timeZone": "UTC" }
|
||||
}
|
||||
]
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#Collection(event)",
|
||||
"@odata.deltaLink": "https://graph.microsoft.com/v1.0/me/calendarView/delta?$deltatoken=BEISPIEL-DELTA-1",
|
||||
"value": [
|
||||
{
|
||||
"@odata.type": "#microsoft.graph.event",
|
||||
"@odata.etag": "W/\"BEISPIELETAG3==\"",
|
||||
"id": "BEISPIEL-TERMIN-3",
|
||||
"subject": "Mittagspause",
|
||||
"isAllDay": false,
|
||||
"showAs": "free",
|
||||
"start": { "dateTime": "2026-03-17T11:00:00.0000000", "timeZone": "UTC" },
|
||||
"end": { "dateTime": "2026-03-17T12:00:00.0000000", "timeZone": "UTC" }
|
||||
},
|
||||
{
|
||||
"@odata.type": "#microsoft.graph.event",
|
||||
"id": "BEISPIEL-TERMIN-9",
|
||||
"@removed": { "reason": "deleted" }
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user