Files
Christoph Schwörer f045b99a25 Codebasis als Dateien ins Arbeitsrepo statt als Gitlink
QuellCode/CentronERP war nur als Gitlink (Submodul-Referenz auf 79c1142)
getrackt, ohne .gitmodules und ohne erreichbares Remote. Der
Untersuchungsgegenstand der Versuchsreihe war damit nicht reproduzierbar
gesichert: Ein Klon haette ein leeres Verzeichnis erhalten, und die Belege
der 3.287 Anforderungen waeren nicht ueberpruefbar gewesen.

Umstellung:
- Historie nach c:\DEV\CentronERP_git_snapshot_79c1142 ausgelagert
  (vollstaendig lesbar, enthaelt 79c1142 und Vorgaenger 89ccfd6)
- Gitlink aus dem Index entfernt
- Dateiinhalt aufgenommen: 24.557 Dateien, rund 333 MB

Die verschachtelte .gitignore der Codebasis gilt weiter, Build-Artefakte
bleiben ausgeschlossen. Details in Versuche/Versuch_01/_Codebasis-Nachweis.md
2026-08-26 07:43:51 +02:00

430 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Exchange Sync – QS-Protokoll
> **Erstellt:** 2026-03-10
> **Abgeschlossen:** 2026-03-11
> **Status:** ✅ Vollständig implementiert
> **Bearbeiter:** Entwicklung (VÖD)
> **Branch:** `vöd/Exchange_Sync`
---
## Geänderte Dateien
| Datei | Änderungsart |
|-------|-------------|
| `src/backend/Centron.BL/Sales/Calendar/ScheduleBL.cs` | Bugfixes + Refactoring |
| `src/nexus/CentronNexus/ServiceBoard/Scheduler/SchedulerPage.razor` | Frontend-Fix Wochenanzeige |
| `src/centron/Centron.WPF.UI/Modules/MyCentron/CentronInspectors/Inspectors/Schedule/OrphanedSeriesScheduleInspector.cs` | Neu – Inspector für verwaiste Serientermine |
| `src/centron/Centron.WPF.UI/Modules/MyCentron/CentronInspectors/Inspectors/InspectorManager.cs` | Inspector registriert |
| `src/centron/Centron.WPF.UI/Modules/MyCentron/CentronInspectors/Inspectors/Schedule/TimeChangedByGraphInspector.cs` | Namespace-Umbenennung (Time → Schedule) |
| `src/centron/Centron.WPF.UI/Modules/MyCentron/CentronInspectors/Inspectors/Schedule/DuplicateMailEntryIdInspector.cs` | Namespace-Umbenennung (Time → Schedule) |
---
## Übersicht
| # | Ticket | Problem | Schweregrad | Status |
|---|--------|---------|-------------|--------|
| 1 | [164020](#ticket-164020) | CenSU überschreibt fremde Outlook-Termine | 🔴 Kritisch | ✅ Implementiert |
| 2 | [163184](#ticket-163184) | Doppelter Inhalt in Exchange-Termineinladung | 🔴 Kritisch | ✅ Implementiert |
| 3 | [164121](#ticket-164121) | Gelöschte Serientermin-Instanzen bleiben in Nexus | 🟠 Hoch | ✅ Implementiert |
| 4 | [160145](#ticket-160145) | Ganztages-Events um +1 Tag verschoben | 🟠 Hoch | ✅ Implementiert |
| 5 | [158813](#ticket-158813) | Geplante Zeiten mit falschem Datum nach Sync | 🟠 Hoch | ✅ Implementiert |
| 6 | [157036](#ticket-157036) | Fehlerhafte Wochenanzeige in Nexus | 🟡 Mittel | ✅ Implementiert |
| 7 | [162826](#ticket-162826) | Outlook-Erinnerungsflut bei synchronisierten Zeiten | 🟡 Mittel | ✅ Implementiert |
| 8 | [156991](#ticket-156991) | Doppelte Feiertage / falsches Datum in Nexus | 🟡 Mittel | ✅ Implementiert |
| + | [Zusatz](#zusatz-serientermin-kaskade) | Gelöschter Serienmaster löscht Instanzen nicht | 🟠 Hoch | ✅ Implementiert |
| + | [Inspector](#zusatz-inspector) | Manueller Cleanup verwaister Serientermine | — | ✅ Implementiert |
---
## Ticket 164020
**Betreff:** CenSU überschreibt fremde Outlook-Termine mit alten Zeitdaten
### Root Cause
`SyncOldSchedule()` (Nexus → Outlook) hatte keinen Filter auf den Termintyp. Auch Helpdesk-Zeiterfassungs-Schedules (`ObjectType = HelpdeskTimerClass / HelpdeskClass`) wurden über diesen Pfad nach Exchange geschrieben – obwohl diese bereits über `CreateOrUpdateTimeSchedule()` verwaltet werden. Parallel lief `UpdateScheduleByGraph()` (Outlook → Nexus) und schrieb Outlook-Daten in Helpdesk-Schedules zurück (Datum/Uhrzeit), was zu einem Teufelskreis führte.
### Implementierter Fix
**`SyncOldSchedule()` – Helpdesktypen ausschließen:**
```csharp
baseExpression = baseExpression.And(x =>
x.ObjectType != (int)CentronObjectKindNumeric.HelpdeskTimerClass &&
x.ObjectType != (int)CentronObjectKindNumeric.HelpdeskClass);
```
**`UpdateScheduleByGraph()` – Datum-Schutz für Helpdesk-Schedules:**
```csharp
if (!this.IsHelpdeskSchedule(schedule))
{
// Datum nur für reine Outlook-Termine zurückschreiben
schedule.DateStart = this.GetDateTimeFromGraphDateTime(graphEvent.Start, localTimeZone);
schedule.DateEnd = this.GetDateTimeFromGraphDateTime(graphEvent.End, localTimeZone);
}
```
**Neue Hilfsmethode `IsHelpdeskSchedule()`:**
```csharp
private bool IsHelpdeskSchedule(Schedule schedule)
=> schedule.ObjectType == (int)CentronObjectKindNumeric.HelpdeskTimerClass
|| schedule.ObjectType == (int)CentronObjectKindNumeric.HelpdeskClass;
```
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. Helpdeskzeit in Nexus anlegen (z.B. 11.03.2026, 09:00–10:00) | Zeit wird angelegt |
| 2. Webservice-Sync abwarten (60s) | Zeit erscheint als Termin in Outlook mit korrektem Datum |
| 3. Termin in Outlook manuell bearbeiten (Betreff ändern) | — |
| 4. Webservice-Sync abwarten | **Datum der Nexus-Zeit bleibt 11.03.2026, 09:00–10:00** (kein Überschreiben durch Outlook) |
| 5. Techniker legt weiteren eigenen Outlook-Termin an | Exchange-Termin bleibt unverändert – CenSU schreibt nicht in ihn |
---
## Ticket 163184
**Betreff:** Doppelter Inhalt in Exchange-Termineinladung (Body enthält Text 3× und altes Datum)
### Root Cause
In `CreateOrUpdateTimeSchedule()` wurde `schedule.Caption = timer.ExternalNote` gesetzt, bevor `ReplaceVariables(body, schedule)` aufgerufen wurde. Das Body-Template enthält `@@TerminText@@`, welches intern `schedule.Caption` liest. Dadurch wurde `ExternalNote` doppelt in den Body eingebettet: einmal über `@@TerminText@@` und einmal direkt durch das Template `@@WorkOrTime@@`.
### Implementierter Fix
```csharp
// Subject-Auflösung: Caption temporär setzen damit @@TerminText@@ im Subject funktioniert
schedule.Caption = timer.ExternalNote;
schedule.Subject = this.ReplaceVariables(subject, schedule);
// Body-Auflösung: Caption leeren damit @@TerminText@@ im Body leer ist
schedule.Caption = string.Empty;
schedule.Caption = this.ReplaceVariables(body, schedule);
```
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. Helpdeskzeit mit externem Hinweis anlegen (z.B. "Vor-Ort-Einsatz Drucker") | Zeit wird gespeichert |
| 2. Sync abwarten | Outlook-Termin wird erstellt |
| 3. Outlook-Termin öffnen, Body prüfen | Body enthält "Vor-Ort-Einsatz Drucker" **exakt einmal** |
| 4. Nochmals Zeit speichern (Update) | Termin wird aktualisiert, Body enthält Text weiterhin nur einmal |
---
## Ticket 164121
**Betreff:** Gelöschte Serientermin-Instanzen bleiben in Nexus sichtbar
### Root Cause
Microsoft Graph sendet bei gelöschten Serientermin-Instanzen kein `@removed`-Flag, sondern setzt `isCancelled = true`. Der bisherige Code prüfte nur `@removed` → gelöschte Instanzen wurden nie in Nexus als inaktiv markiert.
### Implementierter Fix
**`StoreEvent()` – `isCancelled` gleichwertig zu `@removed` behandeln:**
```csharp
bool isRemoved = graphEvent.AdditionalData.ContainsKey("@removed")
|| graphEvent.IsCancelled is true;
```
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. In Outlook einen Serientermin anlegen (z.B. täglich, 5 Instanzen) | Alle 5 Instanzen erscheinen in Nexus nach Sync |
| 2. Eine einzelne Instanz in Outlook löschen ("Nur diesen Termin") | — |
| 3. Sync abwarten | **Die gelöschte Instanz verschwindet aus Nexus** |
| 4. Verbleibende Instanzen prüfen | Übrige 4 Instanzen bleiben unverändert sichtbar |
---
## Ticket 160145
**Betreff:** Ganztages-Events in Nexus um +1 Tag verschoben
### Root Cause
Microsoft Graph liefert Ganztages-Events mit `Start.TimeZone = "UTC"` und einem exklusiven Enddatum (`End = nächster Tag 00:00 UTC`). Die bisherige UTC→CET-Konvertierung verschob das Datum: `2025-08-16T00:00 UTC` → `2025-08-16T02:00 CEST`. Nexus interpretierte den 16.08 als Enddatum → Termin erschien auf dem falschen Tag.
### Implementierter Fix
**`InsertScheduleByGraph()` und `UpdateScheduleByGraph()` – neue `ParseAllDayEventDates()`-Hilfsmethode:**
```csharp
if (graphEvent.IsAllDay == true)
(schedule.DateStart, schedule.DateEnd) = ParseAllDayEventDates(graphEvent);
```
```csharp
private static (DateTime Start, DateTime End) ParseAllDayEventDates(Event graphEvent)
{
var start = DateTime.Parse(graphEvent.Start.DateTime).Date;
var end = DateTime.Parse(graphEvent.End.DateTime).Date.AddDays(-1); // exklusiv → inklusiv
return (start, end);
}
```
**Zusätzlich: IANA → Windows Timezone-Mapping in `GetDateTimeFromGraphDateTime()`:**
```csharp
// "Europe/Berlin" wird korrekt auf "W. Europe Standard Time" gemappt
if (TimeZoneInfo.TryConvertIanaIdToWindowsId(graphDateTime.TimeZone, out var windowsId))
graphTimeZone = TimeZoneInfo.FindSystemTimeZoneById(windowsId);
```
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. In Outlook einen ganztägigen Termin am **15.08.2026** anlegen | Termin in Outlook korrekt am 15.08 |
| 2. Sync abwarten | **Termin in Nexus erscheint am 15.08.2026** (nicht 16.08) |
| 3. Termin in Outlook auf **10.–12.08.2026** (3 Tage) ändern | — |
| 4. Sync abwarten | Nexus zeigt Termin vom **10.08. bis 12.08.** |
---
## Ticket 158813
**Betreff:** Geplante Zeiten erhalten nach Sync falsches Datum
### Root Cause
`UpdateScheduleByGraph()` überschrieb `DateStart`/`DateEnd` von Helpdesk-Schedules mit den aus Exchange zurückgegeben Werten (Round-Trip-Problem). Zusätzlich: IANA-Zeitzonennamen (`"Europe/Berlin"`) wurden nicht als Windows-Zeitzone erkannt → unnötige Konvertierung, mögliche 1h-Abweichung je Sommerzeit.
### Implementierter Fix
Beide Ursachen behoben:
1. **Helpdesk-Datum-Schutz** (identisch zu Ticket 164020) – `UpdateScheduleByGraph()` überschreibt Datum bei Helpdesk-Schedules nicht mehr.
2. **Timezone-Mapping** (identisch zu Ticket 160145) – IANA → Windows-ID, `HasSameRules()`-Prüfung verhindert unnötige Konvertierungen bei gleicher Zeitzone.
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. In Nexus eine Planung anlegen: **02.03.2026, 08:00–09:00** | Nexus zeigt 02.03.2026 |
| 2. Sync abwarten (Nexus → Outlook) | Outlook zeigt Termin am 02.03.2026 korrekt |
| 3. Erneuten Sync abwarten (Outlook → Nexus, Delta-Rückweg) | **Nexus zeigt weiterhin 02.03.2026** (kein Überschreiben) |
| 4. Prüfen ob Uhrzeit korrekt bleibt | 08:00–09:00 unverändert |
---
## Ticket 157036
**Betreff:** Fehlerhafte Wochenanzeige in Nexus (Arbeitswoche zeigt nur Freitag)
### Root Cause
Der `DxSchedulerWorkWeekView` in `SchedulerPage.razor` hatte kein `WorkDays`-Attribut. Ohne diese Angabe verwendet DevExpress einen internen Default, der in dieser Version nur den letzten Werktag (Freitag) anzeigte.
### Implementierter Fix
**`SchedulerPage.razor` – `WorkDays`-Attribut ergänzt:**
```razor
<DxSchedulerWorkWeekView VisibleTime="VisibleTime"
WorkTime="_workTime"
TimeScale="WeekViewTimeScale"
WorkDays="DayOfWeek.Monday | DayOfWeek.Tuesday | DayOfWeek.Wednesday | DayOfWeek.Thursday | DayOfWeek.Friday">
```
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. Nexus öffnen → Zeitplanung → Ansicht "Arbeitswoche" wählen | **Alle 5 Werktage (Mo–Fr) werden angezeigt** |
| 2. Termin am Mittwoch prüfen | Termin erscheint korrekt auf Mittwoch |
| 3. Ansicht wechseln (Woche, Monat) und zurück zu Arbeitswoche | Arbeitswoche zeigt weiterhin Mo–Fr |
---
## Ticket 162826
**Betreff:** Outlook-Erinnerungsflut bei synchronisierten Helpdeskzeiten
### Root Cause
`UpdateTerminplanungPersonTable()` → `AddScheduleToExchange()` setzte `isReminderOn` nicht explizit auf `false` für Helpdesk-Zeiten. Outlook aktiviert standardmäßig eine 15-Minuten-Erinnerung für alle neuen Termine.
### Implementierter Fix
**`UpdateTerminplanungPersonTable()` – Reminder für Helpdesk-Schedules deaktivieren:**
```csharp
bool? reminderOn = this.IsHelpdeskSchedule(schedule) ? false : (bool?)null;
var result = await this.AddScheduleToExchange(schedule, schedulePerson, isReminderOn: reminderOn);
```
`null` bedeutet: Reminder-Einstellung nicht ändern (Standard-Outlook-Verhalten für normale Termine).
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. Helpdeskzeit speichern → Sync abwarten | Termin in Outlook erscheint |
| 2. Outlook-Termin öffnen → Eigenschaften prüfen | **Feld "Erinnerung" ist deaktiviert** (kein Häkchen) |
| 3. Normalen Nexus-Kalendertermin anlegen → Sync | Erinnerung bleibt auf Standard (15 Min.) |
---
## Ticket 156991
**Betreff:** Doppelte Feiertage / falsches Datum in Nexus
### Root Cause
**A) Datumsversatz:** Identisch zu Ticket 160145 (UTC-Konvertierungsfehler bei `IsAllDay = true`).
**B) Duplikate:** Wenn derselbe Feiertag in mehreren Exchange-Kalendern (z.B. persönlicher Kalender + Firmenkalender) enthalten ist, liefert Graph Delta denselben Feiertag mit unterschiedlichen `MailEntryID`s. Der bisherige Code prüfte nur auf `MailEntryID`-Duplikate → zwei identische Schedules wurden angelegt.
### Implementierter Fix
**A)** UTC-Fix identisch zu Ticket 160145 über `ParseAllDayEventDates()`.
**B) Duplikat-Prüfung in `StoreEvent()` für Ganztages-Events:**
```csharp
if (graphEvent.IsAllDay == true && graphEvent.Subject is not null)
{
var (allDayStart, _) = ParseAllDayEventDates(graphEvent);
var existingAllDay = GetSchedulesByExpression(x =>
x.Subject == graphEvent.Subject &&
x.DateStart == allDayStart &&
x.FullDay == true &&
x.IsActive);
if (existingAllDay.Any(s => GetSchedulePersonsByFilter(
new SchedulePersonsFilter { ScheduleI3D = s.I3D })
.Any(sp => sp.PersonalI3D == employee.I3D)))
return; // Bereits vorhanden → kein Duplikat anlegen
}
```
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. In Outlook einen Feiertag prüfen (z.B. Ostersonntag 05.04.2026) | — |
| 2. Sync abwarten | **Ostersonntag erscheint in Nexus genau einmal am 05.04.2026** |
| 3. Weiteren ganztägigen Termin am selben Tag mit gleichem Betreff anlegen (zweiter Kalender) | — |
| 4. Sync abwarten | **Kein zweiter Eintrag** – Duplikat wird erkannt und übersprungen |
---
## Zusatz: Serientermin-Kaskade
**Problem:** Wenn ein kompletter Serienmaster in Exchange gelöscht wird, wurden die zugehörigen Instanz-Schedules in Nexus nicht mitgelöscht. Graph sendet beim Master-Delete nicht immer einzelne `@removed`-Events für alle Instanzen.
### Implementierter Fix
**`StoreEvent()` – Kaskade bei Master-Delete:**
```csharp
if (isRemoved && graphEvent.SeriesMasterId is null) // SeriesMasterId null = dieser Event IST der Master
await this.DeleteSeriesInstancesByMasterId(graphEvent.Id, employee, cenSU);
```
**Neue Methode `DeleteSeriesInstancesByMasterId()`:**
- Sucht alle `SchedulePerson`-Einträge mit `SeriesMasterId = masterEventId` für den Mitarbeiter
- Lädt nur noch aktive Schedules (`IsActive = true`)
- Setzt diese auf `IsActive = false` via `SaveSchedule()` (korrekte BL-Kette inkl. Exchange-Löschung)
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. In Outlook Serientermin anlegen (z.B. täglich, 7 Instanzen) | Alle 7 Instanzen erscheinen in Nexus |
| 2. Gesamte Serie in Outlook löschen ("Alle Termine der Serie") | — |
| 3. Sync abwarten | **Alle 7 Nexus-Instanzen werden als inaktiv markiert und verschwinden** |
---
## Zusatz: Inspector für verwaiste Serientermine
**Problem:** Historisch bereits entstandene verwaiste Serientermine (vor Einführung des Kaskaden-Fix) müssen manuell bereinigt werden können.
### Implementierter Fix
Neuer Inspector **"Verwaiste Serientermine (Exchange Sync)"** im c-entron WPF-Client unter `MyCentron → c-entron Inspektor`:
- **Check:** Findet alle aktiven Schedules mit `SeriesMasterId`, für die kein aktiver Serienmaster-Schedule mehr in der DB existiert
- **Repair:** Deaktiviert alle gefundenen verwaisten Instanzen per Knopfdruck
- **Kategorie:** Zeiten (alphabetisch sortiert)
### QS-Testfall
| Schritt | Erwartetes Ergebnis |
|---------|-------------------|
| 1. c-entron WPF öffnen → MyCentron → Inspektor | Inspector-Liste erscheint |
| 2. "Verwaiste Serientermine (Exchange Sync)" auswählen und Inspektor ausführen | Check läuft durch |
| 3a. Keine verwaisten Daten vorhanden | Grünes Häkchen: "Es wurden keine verwaisten Serientermine gefunden." |
| 3b. Verwaiste Daten vorhanden | Rotes X mit Anzahl + **Reparieren**-Button |
| 4. Reparieren klicken | Instanzen werden deaktiviert, Check läuft erneut → grünes Häkchen |
---
## Technische Grundlagen
### Sync-Architektur
```
ExchangeSyncService (HostedService, Intervall konfigurierbar, default 60s)
├── ScheduleBL.SyncByGraphV2() ← Outlook → Nexus (Graph API Delta)
└── ScheduleBL.SyncOldSchedule() ← Nexus → Outlook (Schedules ohne MailEntryID)
HelpdeskTimerBL / ScheduleBL
├── CreateOrUpdateTimeSchedule() ← Zeiterfassung → Outlook-Termin
└── UpdateTerminplanungPersonTable() ← Termin gespeichert → Exchange aktualisieren
```
### Schlüssel-Entitäten
| Entity | Beschreibung |
|--------|-------------|
| `Schedule` | Termin in c-entron (DateStart, DateEnd, FullDay, Subject, IsActive, ObjectType) |
| `SchedulePerson` | Verknüpfung Schedule ↔ Mitarbeiter ↔ Exchange (MailEntryID, SeriesMasterId) |
| `CreatedByApp` | Herkunft: `GraphSync = 6`, `HelpdeskTime`, `GraphSyncTimeDelete` |
| `CenSU` | CentronSystemUser – Systemkonto für alle Hintergrundprozesse |
| `IsHelpdeskSchedule()` | Neue Hilfsmethode: prüft ob Schedule an Helpdesk/Zeit gebunden ist |
### Source of Truth
| Schedule-Typ | Datum/Zeit Source of Truth |
|---|---|
| `HelpdeskTimerClass` / `HelpdeskClass` | **Nexus** – Exchange darf nie überschreiben |
| Reine Outlook-Termine (`GraphSync`) | **Exchange/Outlook** |
| Ganztages-Events (`FullDay = true`) | **Datumswert aus Graph, ohne Zeitzonenkonvertierung** |
---
## Bekannte Einschränkungen / Nicht umgesetzt
| Thema | Begründung |
|-------|-----------|
| Kalenderfilter (welche Exchange-Kalender synchronisiert werden) | Konfigurationsthema, kein Code-Fix – muss über Exchange-Einstellungen gesteuert werden |
| Blocker-Termine (`ShowAs = Free`) in Nexus anzeigen | Kein Kundenauftrag für diese Version |
| ApplicationSetting für Erinnerungen (An/Aus im UI) | Für Helpdesk-Zeiten ist `isReminderOn: false` als Standard ausreichend |
---
## Offene Fragen (für Rollout-Planung)
- [x] **Gibt es Kunden mit Exchange On-Premise (kein Graph API)?** → **Ja.** Diese Kunden nutzen den alten EWS-basierten Agent. Für sie greifen die Graph-spezifischen Fixes **nicht** (Delta-Token, `isCancelled`, `ParseAllDayEventDates`, Kaskaden-Delete, Duplikat-Erkennung). Separate Analyse für On-Premise-Kunden erforderlich – ggf. eigener Bugfix-Zyklus.
- [x] **Welche Exchange-Kalender werden synchronisiert – nur Primärkalender?** → **Alle Kalender.** Der Endpunkt `_graphClient.Users[id].CalendarView.Delta` liefert Events aus *allen* Kalendern des Nutzers aggregiert (Primär + abonnierte Kalender wie „Feiertage in Deutschland", Teamkalender etc.). Das erklärt u.a. den Doppel-Feiertage-Bug (Ticket 156991). So gewollt – bleibt so.
- [x] **Sollen Kunden nach dem Update den Inspector manuell ausführen (Kommunikation nötig)?** → **Ja.** In den Release Notes darauf hinweisen, dass Kunden mit Exchange Sync nach dem Update den Inspector „Verwaiste Serientermine (Exchange Sync)" einmalig ausführen sollen, um historisch entstandene verwaiste Serientermine zu bereinigen.
- [x] **Ist der Inspector nur für Admins sichtbar (Rechteprüfung)?** → Zugriff wird über das Modul „c-entron Inspektor" gesteuert – keine separate Rechteprüfung im Inspector selbst erforderlich.
## Geltungsbereich der Fixes
| Fix | Exchange Online (Graph API) | Exchange On-Premise (alter EWS-Agent) |
|-----|-----------------------------|---------------------------------------|
| Ticket 164020 – CenSU überschreibt Helpdesk-Zeiten | ✅ | ⚠️ Teilweise: `SyncOldSchedule()`-Filter greift, EWS-Rückweg unklar |
| Ticket 163184 – Doppelter Body | ✅ | ✅ (`CreateOrUpdateTimeSchedule()` ist gemeinsamer Pfad) |
| Ticket 164121 – Instanz-Delete via `isCancelled` | ✅ | ❌ EWS-Agent nutzt anderen Delete-Mechanismus |
| Ticket 160145 – Ganztages +1 Tag | ✅ | ❌ Graph-spezifisch |
| Ticket 158813 – Falsches Datum nach Sync | ✅ | ⚠️ Helpdesk-Schutz greift, Timezone-Fix ist Graph-spezifisch |
| Ticket 157036 – Wochenanzeige Nexus | ✅ | ✅ (Frontend-Fix, kein Exchange-Bezug) |
| Ticket 162826 – Erinnerungsflut | ✅ | ✅ (`AddScheduleToExchange()` gemeinsamer Pfad) |
| Ticket 156991 – Doppelte Feiertage | ✅ | ❌ Graph Delta-spezifisch |
| Zusatz – Serienkaskade | ✅ | ❌ Graph Delta-spezifisch |
| Inspector – Cleanup verwaiste Serientermine | ✅ | ✅ (DB-Level, unabhängig vom Sync-Pfad) |