## 3.3 Ticket schließen / Close Ticket

**Pfad**: `src/CentronNexus/ServiceBoard/CloseTicket/CloseTicketPage.razor`
**Komponente**: `CloseTicket.razor`
**Kategorie**: Ticket Management (Workflow - Final State)
**Status**: ✅ Vollständig implementiert
**Komplexität**: 🟡 Mittel-Hoch (Multi-Step Workflow mit E-Mail & PDF-Generierung)
**Lizenz**: `LicenseGuids.Centron` OR `LicenseGuids.ServiceBoard`

### Beschreibung

**Ticket-Abschluss** ist der finale Workflow-Schritt im Ticket-Lifecycle. Diese Komponente ermöglicht:
- **Multi-Step Workflow** mit Validierung an jedem Schritt
- **7 verschiedene Schließungsgründe** (Closing Reason Types)
- **E-Mail-Benachrichtigungen** mit anpassbaren Vorlagen (Kunde + Team)
- **Service-Report PDF-Generierung** mit 7 Abschnitten
- **Automatische Historie-Einträge** und Account-Activities
- **SignalR Real-Time Updates** (Ticket-Status-Änderung live)
- **ToDo-Bereinigung** (Automatisches Löschen offener ToDos)
- **SLA-Tracking** (ClosedDate wird gesetzt für Reporting)

---

## WORKFLOW STATE MACHINE

### Ticket-Close Workflow (5-Step Process)

```
┌─────────────────────────────────────────────────────────────┐
│                    TICKET CLOSE WORKFLOW                    │
└─────────────────────────────────────────────────────────────┘

STEP 1: Ticket-Auswahl & Validierung
   ↓
   ├─ Prüfung: Ticket Status ist "Gelöst" oder "In Arbeit"?
   ├─ Prüfung: Benutzer hat Rechte zum Schließen? (UserRight: CLOSE_TICKET)
   ├─ Prüfung: RMA abgeschlossen? (falls IsRMA = true)
   └─ → [Validierung erfolgreich] → Weiter zu Step 2

STEP 2: Schließungs-Grund wählen (Closing Reason)
   ↓
   ├─ 1. Gelöst (Solved) ✅
   ├─ 2. Nicht reproduzierbar (Cannot Reproduce) 🔍
   ├─ 3. Nicht relevant (Not Relevant) ❌
   ├─ 4. Duplikat (Duplicate) 📋
   ├─ 5. Kunde reagiert nicht (Customer No Response) 📧
   ├─ 6. Workaround bereitgestellt (Workaround Provided) 🔧
   └─ 7. Extern gelöst (Solved Externally) 🌐
   ↓
STEP 3: Notizen & Dokumentation eingeben
   ↓
   ├─ Solution Summary (Kunde sichtbar) - WYSIWYG Editor
   │  └─ Merge Tags unterstützt: {TicketNumber}, {CustomerName}, {TotalHours}
   ├─ Internal Notes (Nur Team) - Optional
   ├─ Service-Report anhängen? (Checkbox) → PDF generieren
   └─ → Weiter zu Step 4

STEP 4: Benachrichtigungen konfigurieren
   ↓
   ├─ EXTERNE BENACHRICHTIGUNG (Kunde)
   │  ├─ Kunde benachrichtigen? (Toggle)
   │  ├─ E-Mail Vorlage wählen (Dropdown: 5+ Vorlagen)
   │  ├─ Empfänger: Customer.Email (auto) + Zusätzliche CC/BCC
   │  ├─ Service-Report als Anhang? (Checkbox)
   │  └─ Time Records anhängen? (Checkbox - für MSP-Kunden)
   │
   ├─ INTERNE BENACHRICHTIGUNG (Team)
   │  ├─ Team benachrichtigen? (Toggle)
   │  ├─ E-Mail Vorlage wählen (Dropdown: 3+ Vorlagen)
   │  ├─ Empfänger: Responsible + Editors + Optional (Advisers 1-4)
   │  └─ Zusätzliche Empfänger (Multi-Email Input)
   │
   └─ → Weiter zu Step 5

STEP 5: Bestätigung & Speichern
   ↓
   ├─ Zusammenfassung anzeigen:
   │  ├─ Ticket #12345 wird geschlossen
   │  ├─ Grund: "Gelöst"
   │  ├─ Solution Summary: "Netzwerkfehler behoben..."
   │  ├─ Benachrichtigungen: 1 Kunde, 3 Team-Mitglieder
   │  └─ Anhänge: Service-Report.pdf (245 KB)
   │
   ├─ [Button: "Ticket schließen und senden"]
   │
   └─ BACKEND PROCESSING:
      ├─ 1. Transaction Start
      ├─ 2. Helpdesk.HelpdeskState → "Closed" (HelpdeskStateI3D)
      ├─ 3. Helpdesk.ClosedAt → DateTime.Now
      ├─ 4. Helpdesk.ClosedBy → CurrentUser
      ├─ 5. Delete all open ToDos (HelpdeskToDo.HelpdeskI3D)
      ├─ 6. Create History Entry (HelpdeskHistory: "Ticket abgeschlossen")
      ├─ 7. Create Account Activity (AccountActivity: "Ticket closed")
      ├─ 8. SignalR Notification → All Editors (Real-Time Update)
      ├─ 9. Transaction Commit
      ├─ 10. Generate Service Report PDF (async)
      ├─ 11. Send External Email (with attachments)
      ├─ 12. Send Internal Email (with attachments)
      └─ → SUCCESS → Navigate to Ticket Details
```

---

## CLOSING REASON TYPES (7 Typen)

### 1. Gelöst (Solved) ✅
**Code**: `ClosingReasonKind.Solved`
**Use Case**: Standard-Abschluss für erfolgreich gelöste Tickets
**Workflow**:
```
1. Techniker hat Problem gelöst
2. Kunde bestätigt Lösung (optional)
3. Solution Summary dokumentieren (für Wissensdatenbank)
4. Service-Report wird generiert mit vollständiger Time Tracking
5. Kunde erhält E-Mail: "Ihr Ticket wurde erfolgreich gelöst"
6. SLA-Status: ✅ Compliant (falls innerhalb SLA)
```
**Automatische Aktionen**:
- HelpdeskState → "Closed"
- ClosedAt → DateTime.Now
- SLA Metrics aktualisieren (Resolution Time)
- Knowledge Base Article vorschlagen (falls aktiviert)

---

### 2. Nicht reproduzierbar (Cannot Reproduce) 🔍
**Code**: `ClosingReasonKind.CannotReproduce`
**Use Case**: Problem konnte nicht nachgestellt werden
**Workflow**:
```
1. Techniker konnte Fehler nicht reproduzieren
2. Mehrere Versuche dokumentiert
3. Solution Summary: "Versuche zur Reproduktion fehlgeschlagen..."
4. Kunde wird benachrichtigt: "Bitte melden Sie sich wenn Problem erneut auftritt"
5. Ticket wird als "Cannot Reproduce" markiert
```
**Automatische Aktionen**:
- Tag "cannot-reproduce" wird hinzugefügt
- Internal Note: "Re-open wenn Kunde neue Informationen liefert"
- Niedrigere Priorität bei Re-open (Mittel → Niedrig)

---

### 3. Nicht relevant (Not Relevant) ❌
**Code**: `ClosingReasonKind.NotRelevant`
**Use Case**: Ticket war kein Support-Fall oder außerhalb des Scopes
**Workflow**:
```
1. Techniker identifiziert: Keine Support-Anfrage (z.B. Verkaufsanfrage)
2. Solution Summary: "Ticket wurde an Vertrieb weitergeleitet"
3. Optional: Create ToDo für anderen Abteilung
4. Keine Abrechnung (IsCalculable = false)
5. Keine SLA-Berechnung
```
**Automatische Aktionen**:
- IsCalculable → false (keine Abrechnung)
- SLA Metrics werden nicht gewertet
- Tag "not-relevant" wird hinzugefügt

---

### 4. Duplikat (Duplicate) 📋
**Code**: `ClosingReasonKind.Duplicate`
**Use Case**: Ticket ist Duplikat eines anderen Tickets
**Workflow**:
```
1. Techniker identifiziert Duplikat
2. Parent-Ticket auswählen (Ticket-Suche Dialog)
3. Solution Summary: "Duplikat von Ticket #12340"
4. ParentHelpdeskI3D → OriginalTicketI3D
5. Automatische Verlinkung in Historie
6. Kunde erhält E-Mail: "Ihr Ticket wird unter #12340 bearbeitet"
```
**Automatische Aktionen**:
- ParentHelpdeskI3D → Original Ticket
- History Entry: "Merged with Ticket #12340"
- Time Records werden zu Parent-Ticket kopiert (optional)
- Anhänge werden zu Parent-Ticket kopiert (optional)

---

### 5. Kunde reagiert nicht (Customer No Response) 📧
**Code**: `ClosingReasonKind.CustomerNoResponse`
**Use Case**: Kunde antwortet nicht nach mehreren Versuchen
**Workflow**:
```
1. Techniker hat mehrmals nachgefragt (History zeigt E-Mails)
2. 7+ Tage keine Antwort vom Kunden
3. Solution Summary: "Geschlossen wegen fehlender Rückmeldung"
4. Kunde erhält E-Mail: "Ticket wird geschlossen. Bitte melden Sie sich bei Bedarf"
5. Auto-Reopen möglich wenn Kunde antwortet (innerhalb 30 Tage)
```
**Automatische Aktionen**:
- Tag "no-response" wird hinzugefügt
- Auto-Reopen Email Template wird aktiviert
- Re-open ist möglich (innerhalb 30 Tage ohne neue Ticket-Nummer)

---

### 6. Workaround bereitgestellt (Workaround Provided) 🔧
**Code**: `ClosingReasonKind.WorkaroundProvided`
**Use Case**: Temporäre Lösung bereitgestellt, Root Cause nicht behoben
**Workflow**:
```
1. Techniker hat Workaround implementiert
2. Solution Summary: "Workaround: Verwenden Sie alternative Methode X"
3. Internal Note: "Root Cause: Bug in Software Y - Vendor wurde benachrichtigt"
4. Optional: Create Follow-Up Ticket für permanente Lösung
5. Kunde erhält E-Mail mit Workaround-Anleitung
```
**Automatische Aktionen**:
- Tag "workaround" wird hinzugefügt
- Optional: ToDo erstellen "Permanent Solution implementieren"
- Knowledge Base Article vorschlagen

---

### 7. Extern gelöst (Solved Externally) 🌐
**Code**: `ClosingReasonKind.SolvedExternally`
**Use Case**: Problem wurde von Drittanbieter / Vendor gelöst
**Workflow**:
```
1. Ticket wurde an Hersteller eskaliert
2. Hersteller hat Problem gelöst
3. Solution Summary: "Gelöst durch Vendor XYZ (Ticket #VENDOR-12345)"
4. ConnectionNumber → "VENDOR-12345" (externe Ticket-Nummer)
5. Service-Report enthält Vendor-Informationen
```
**Automatische Aktionen**:
- ConnectionNumber → Vendor Ticket Number
- Tag "vendor-solved" wird hinzugefügt
- Externe Kosten können erfasst werden (für Abrechnung)

---

## TICKETCLOSE ENTITY - VOLLSTÄNDIGE DOKUMENTATION

### 📌 TicketCloseData (Request Entity - 35+ Properties)

Diese Entität wird vom Frontend an das Backend gesendet, um Ticket-Abschluss zu initiieren.

#### **Ticket-Identifikation**
| Feld | Datentyp | Erforderlich | Beschreibung |
|------|----------|--------------|-------------|
| `HelpdeskI3D` | int | ✅ Ja | Eindeutige Ticket-ID die geschlossen wird |
| `ClosingReasonKind` | enum | ✅ Ja | Schließungs-Grund (1-7) |
| `ClosingReasonText` | nvarchar(200) | ❌ Nein | Optional: Freitext-Erklärung |

---

#### **Dokumentation & Notizen**
| Feld | Datentyp | Erforderlich | Beschreibung |
|------|----------|--------------|-------------|
| `SolutionSummary` | nvarchar(max) | ✅ Ja | Lösungs-Zusammenfassung (Kunde sichtbar) - HTML/RTF |
| `InternalNotes` | nvarchar(max) | ❌ Nein | Interne Notizen (nur Team sichtbar) |
| `InternalNotePlainText` | nvarchar(max) | ❌ Nein | Plaintext Version der internen Notizen |

**REST-API Endpoint**:
```csharp
POST /Helpdesk/CloseTicketWithNotification
   → Request: CloseTicketWithNotificationRequest
   → Response: Result<CloseTicketInformation>
```

---

#### **Service-Report Generierung**
| Feld | Datentyp | Erforderlich | Beschreibung |
|------|----------|--------------|-------------|
| `IncludeServiceReport` | bool | ❌ Nein | Service-Report PDF generieren? (Default: false) |
| `ServiceReportPdf` | byte[] | ❌ Nein | Generiertes PDF (wird vom Backend gefüllt) |
| `ServiceReportFilename` | nvarchar(255) | ❌ Nein | Dateiname: "ServiceReport_Ticket{Number}_{Date}.pdf" |
| `HelpdeskTimerI3Ds` | List&lt;int&gt; | ❌ Nein | Welche Time Records im Report? (leer = alle) |

**Service-Report Abschnitte** (7 Sections):
```
1. TICKET OVERVIEW
   ├─ Ticket Number & Title
   ├─ Customer Information (Name, Kontakt)
   ├─ Status, Priority, Typ
   ├─ Created Date → Closed Date (Timeline)
   └─ Responsible Person & Editors

2. PROBLEM DESCRIPTION
   ├─ Original Description (vom Kunden)
   ├─ Category & Subcategories
   └─ Tags & Labels

3. SOLUTION SUMMARY
   ├─ Solution Summary (formatted)
   ├─ Closing Reason (deutsch/englisch)
   └─ Workaround Notizen (falls applicable)

4. WORK PERFORMED (Durchgeführte Arbeiten)
   ├─ History Timeline (alle Aktionen)
   ├─ Comments (öffentliche + interne)
   └─ Status Changes (Offen → In Arbeit → Gelöst → Geschlossen)

5. TIME TRACKING
   ├─ Total Working Time (Summe aller Time Records)
   ├─ Breakdown by Technician:
   │  ├─ Klaus: 2h 30min
   │  ├─ Maria: 1h 15min
   │  └─ Total: 3h 45min
   ├─ Time Record Details (Datum, Techniker, Dauer, Beschreibung)
   └─ Stopwatch Times (falls verwendet)

6. KOSTEN & ABRECHNUNG (falls IsCalculable = true)
   ├─ Vertragsdetails (ContractName, Kontingent)
   ├─ Kosten berechnet:
   │  ├─ 3.75h × €120/h = €450
   │  ├─ Kontingent verwendet: 3.75h von 60h (6.25%)
   │  └─ Verbleibend: 56.25h
   └─ Abrechnung Status (Berechnet, Noch nicht abgerechnet)

7. ANHÄNGE & REFERENZEN
   ├─ Liste aller Anhänge (Dateiname, Größe, Upload-Datum)
   ├─ Externe Referenzen (ConnectionNumber)
   └─ Related Tickets (Parent, Children, Duplicates)
```

---

#### **E-Mail Benachrichtigungen - EXTERNE (Kunde)**
| Feld | Datentyp | Erforderlich | Beschreibung |
|------|----------|--------------|-------------|
| `SendExternalMail` | bool | ❌ Nein | Kunde benachrichtigen? (Default: true) |
| `ExternalEmailTemplateI3D` | int | ❌ Nein | Welche Vorlage verwenden? (Default Template wenn null) |
| `ExternalSubject` | nvarchar(500) | ✅ Ja (wenn SendExternalMail) | E-Mail Betreff |
| `ExternalBody` | nvarchar(max) | ✅ Ja (wenn SendExternalMail) | E-Mail Body (HTML/RTF) |
| `ExternalToAddresses` | nvarchar(1000) | ✅ Ja | Empfänger (Customer.Email + Optional) |
| `ExternalCcAddresses` | nvarchar(1000) | ❌ Nein | CC Empfänger |
| `ExternalBccAddresses` | nvarchar(1000) | ❌ Nein | BCC Empfänger |
| `ExternalSender` | MailRecipientDTO | ❌ Nein | Absender (Default: System-Einstellung) |
| `ExternalAttachments` | List&lt;MailAttachmentDTO&gt; | ❌ Nein | Zusätzliche Anhänge (neben Service-Report) |

**REST-API Validation**:
```csharp
Validation Rules:
- ExternalToAddresses MUST be valid email (Regex: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$)
- ExternalSubject MUST NOT be empty
- ExternalBody MUST contain valid HTML (not plaintext)
- If AttachServiceReport=true, ServiceReportPdf MUST be generated
- Max Attachments: 10 files, Max Size per file: 25 MB
```

---

#### **E-Mail Benachrichtigungen - INTERNE (Team)**
| Feld | Datentyp | Erforderlich | Beschreibung |
|------|----------|--------------|-------------|
| `SendInternalMail` | bool | ❌ Nein | Team benachrichtigen? (Default: true) |
| `InternalEmailTemplateI3D` | int | ❌ Nein | Welche Vorlage verwenden? |
| `InternalSubject` | nvarchar(500) | ✅ Ja (wenn SendInternalMail) | E-Mail Betreff |
| `InternalBody` | nvarchar(max) | ✅ Ja (wenn SendInternalMail) | E-Mail Body (HTML/RTF) |
| `InternalToAddresses` | nvarchar(1000) | ✅ Ja | Empfänger (Responsible + Editors) |
| `InternalCcAddresses` | nvarchar(1000) | ❌ Nein | CC Empfänger |
| `InternalBccAddresses` | nvarchar(1000) | ❌ Nein | BCC Empfänger |
| `InternalSender` | MailRecipientDTO | ❌ Nein | Absender (Default: System-Einstellung) |
| `InternalAttachments` | List&lt;MailAttachmentDTO&gt; | ❌ Nein | Zusätzliche Anhänge |

**Auto-Population Logic**:
```csharp
// Automatische Empfänger-Berechnung
InternalToAddresses = BuildRecipientList()
   ├─ Responsible Person Email (falls ResponsiblePersonI3D != null)
   ├─ All Editors Emails (Helpdesk.Editors[].Email)
   ├─ Adviser1 Email (falls SendMailToAdviser1 = true)
   ├─ Adviser2 Email (falls SendMailToAdviser2 = true)
   ├─ Adviser3 Email (falls SendMailToAdviser3 = true)
   ├─ Adviser4 Email (falls SendMailToAdviser4 = true)
   └─ NotificationMail (aus ApplicationSettings - global)
```

---

#### **Berater-Benachrichtigungen (Optional)**
| Feld | Datentyp | Beschreibung |
|------|----------|-------------|
| `SendMailToAdviser1` | bool | Berater 1 des Kunden benachrichtigen? |
| `SendMailToAdviser2` | bool | Berater 2 des Kunden benachrichtigen? |
| `SendMailToAdviser3` | bool | Berater 3 des Kunden benachrichtigen? |
| `SendMailToAdviser4` | bool | Berater 4 des Kunden benachrichtigen? |

**Use Case**: "Vertriebsmitarbeiter benachrichtigen bei Ticket-Abschluss"
```
Workflow:
1. Kunde ACME GmbH hat 4 Berater:
   - Adviser1: Klaus (Vertrieb)
   - Adviser2: Maria (Technischer Vertrieb)
   - Adviser3: Peter (Account Manager)
   - Adviser4: null
2. Techniker schließt Ticket und wählt: SendMailToAdviser1=true, SendMailToAdviser2=true
3. Klaus und Maria erhalten E-Mail: "Ticket #12345 für Ihren Kunden ACME wurde geschlossen"
4. E-Mail enthält: Solution Summary, Time Tracking, Kosten (falls berechnet)
```

---

#### **System-Felder (Response)**
| Feld | Datentyp | Beschreibung |
|------|----------|-------------|
| `NewStatusI3D` | int | Neue Status-ID (immer = Closed) |
| `NewStatusCaption` | nvarchar(100) | Neue Status-Name ("Geschlossen") |
| `ClosedDate` | datetime2(2) | Wann wurde Ticket geschlossen? |
| `InternalNotificationSuccessful` | bool | Interne E-Mail erfolgreich gesendet? |
| `InternalNotificationMessage` | nvarchar(max) | Fehlermeldung (falls nicht erfolgreich) |
| `ExternalNotificationSuccessful` | bool | Externe E-Mail erfolgreich gesendet? |
| `ExternalNotificationMessage` | nvarchar(max) | Fehlermeldung (falls nicht erfolgreich) |

---

## E-MAIL TEMPLATES - VOLLSTÄNDIGE DOKUMENTATION

### 📧 5 Standard-Vorlagen (Templates)

#### **Template 1: "Ticket abgeschlossen - Standard" (Kunde - Deutsch)**
```html
Template ID: 1
Name: "Ticket abgeschlossen - Standard (DE)"
Language: Deutsch
Type: External (Customer)
File: EmailTemplates/CloseTicket_Standard_DE.html

Subject: "Ticket #{TicketNumber} abgeschlossen - {ShortDescription}"

Body:
Sehr geehrte/r {CustomerContactName},

wir freuen uns, Ihnen mitteilen zu können, dass Ihr Support-Ticket erfolgreich
abgeschlossen wurde.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TICKET-DETAILS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Ticket-Nummer:    #{TicketNumber}
Titel:            {ShortDescription}
Erstellt am:      {CreatedDate:dd.MM.yyyy HH:mm}
Geschlossen am:   {ClosedDate:dd.MM.yyyy HH:mm}
Bearbeitungszeit: {TotalHours}h

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
LÖSUNGSZUSAMMENFASSUNG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{SolutionSummary}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
BEARBEITUNG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Zuständiger Techniker: {ResponsiblePersonName}
Aufgewendete Zeit:     {TotalWorkingTimeFormatted}
Status:                Geschlossen

Im Anhang finden Sie einen detaillierten Service-Report mit allen durchgeführten
Arbeiten und Zeitaufzeichnungen.

Bei weiteren Fragen oder Problemen stehen wir Ihnen gerne zur Verfügung.

Mit freundlichen Grüßen,
{CompanyName} Support Team

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{CompanyName}
{SupportEmail} | {SupportPhone}
{CompanyWebsite}
```

**Merge Tags Supported** (25+ Tags):
```
{TicketNumber}              → Ticket-Nummer (z.B. 12345)
{ShortDescription}          → Ticket-Titel
{Description}               → Vollständige Beschreibung
{CustomerName}              → Kundenname
{CustomerContactName}       → Kontaktperson Name
{CreatedDate}               → Erstellungs-Datum (formatierbar)
{ClosedDate}                → Schließungs-Datum (formatierbar)
{TotalHours}                → Gesamtzeit in Stunden (decimal)
{TotalWorkingTimeFormatted} → Gesamtzeit formatiert (z.B. "3h 45min")
{ResponsiblePersonName}     → Zuständiger Techniker
{Priority}                  → Priorität (Niedrig, Mittel, Hoch, Kritisch)
{Status}                    → Status (Geschlossen)
{SolutionSummary}           → Lösungs-Zusammenfassung (HTML)
{ClosingReason}             → Schließungs-Grund (deutsch)
{CompanyName}               → Firmenname (aus Settings)
{SupportEmail}              → Support E-Mail
{SupportPhone}              → Support Telefon
{CompanyWebsite}            → Firmen-Website
{ContractName}              → Vertragsname (falls vorhanden)
{ContingentHoursUsed}       → Kontingent verwendet (MSP)
{ContingentHoursRemaining}  → Kontingent verbleibend (MSP)
{TotalCost}                 → Gesamtkosten (falls berechnet)
{Category}                  → Hauptkategorie
{SubCategory1}              → Unterkategorie 1
{SubCategory2}              → Unterkategorie 2
```

---

#### **Template 2: "Ticket abgeschlossen - Standard" (Kunde - Englisch)**
```html
Template ID: 2
Name: "Ticket Closed - Standard (EN)"
Language: English
Type: External (Customer)
File: EmailTemplates/CloseTicket_Standard_EN.html

Subject: "Ticket #{TicketNumber} Closed - {ShortDescription}"

Body:
Dear {CustomerContactName},

We are pleased to inform you that your support ticket has been successfully closed.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TICKET DETAILS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Ticket Number:    #{TicketNumber}
Title:            {ShortDescription}
Created:          {CreatedDate:MM/dd/yyyy hh:mm tt}
Closed:           {ClosedDate:MM/dd/yyyy hh:mm tt}
Processing Time:  {TotalHours}h

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
SOLUTION SUMMARY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{SolutionSummary}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PROCESSING
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Responsible Technician: {ResponsiblePersonName}
Time Spent:             {TotalWorkingTimeFormatted}
Status:                 Closed

Please find attached a detailed service report with all work performed and time records.

If you have any further questions or issues, please do not hesitate to contact us.

Best regards,
{CompanyName} Support Team

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{CompanyName}
{SupportEmail} | {SupportPhone}
{CompanyWebsite}
```

---

#### **Template 3: "Ticket abgeschlossen - Mit Umfrage" (Kunde - Deutsch)**
```html
Template ID: 3
Name: "Ticket abgeschlossen - Mit Kundenzufriedenheits-Umfrage (DE)"
Language: Deutsch
Type: External (Customer)
File: EmailTemplates/CloseTicket_WithSurvey_DE.html

Subject: "Ticket #{TicketNumber} abgeschlossen - Ihre Meinung ist uns wichtig!"

Body:
Sehr geehrte/r {CustomerContactName},

Ihr Support-Ticket #{TicketNumber} wurde erfolgreich abgeschlossen.

[... Standard Content wie Template 1 ...]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
WIE WAR UNSERE UNTERSTÜTZUNG?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Ihre Meinung ist uns wichtig! Bitte nehmen Sie sich 2 Minuten Zeit und
bewerten Sie unseren Service:

👉 [Umfrage starten] → {SurveyLink}

Ihre Bewertung hilft uns, unseren Service kontinuierlich zu verbessern.

Mit freundlichen Grüßen,
{CompanyName} Support Team
```

**Survey Integration**:
```csharp
// Umfrage-Link wird automatisch generiert wenn:
ApplicationSettings.SurveyEnabled = true
ApplicationSettings.HelpdeskSurveyTemplateI3D = {SurveyTemplateI3D}

// Survey-Link Format:
{SurveyLink} = https://nexus.company.com/survey/{SurveyGuid}?ticket={TicketNumber}

// Survey fragt ab:
1. Wie zufrieden waren Sie mit der Bearbeitung? (1-5 Sterne)
2. Wurde Ihr Problem gelöst? (Ja/Nein)
3. Wie bewerten Sie die Kommunikation? (1-5 Sterne)
4. Wie bewerten Sie die Reaktionszeit? (1-5 Sterne)
5. Würden Sie uns weiterempfehlen? (NPS 0-10)
6. Zusätzliche Kommentare (Freitext)
```

---

#### **Template 4: "Ticket abgeschlossen - Intern" (Team - Deutsch)**
```html
Template ID: 4
Name: "Ticket abgeschlossen - Team-Benachrichtigung (DE)"
Language: Deutsch
Type: Internal (Team)
File: EmailTemplates/CloseTicket_Internal_DE.html

Subject: "✅ Ticket #{TicketNumber} abgeschlossen von {ClosedByName}"

Body:
Hallo Team,

Ticket #{TicketNumber} wurde soeben abgeschlossen.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
TICKET-INFORMATIONEN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Ticket-Nummer:      #{TicketNumber}
Titel:              {ShortDescription}
Kunde:              {CustomerName}
Geschlossen von:    {ClosedByName}
Geschlossen am:     {ClosedDate:dd.MM.yyyy HH:mm}
Grund:              {ClosingReason}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ZEITAUFWAND
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Gesamt:             {TotalWorkingTimeFormatted}
Breakdown:
{TimeRecordBreakdown}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
INTERNE NOTIZEN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{InternalNotes}

Ticket anzeigen: {TicketUrl}

Beste Grüße,
c-entron.NET System
```

**TimeRecordBreakdown Format**:
```
- Klaus Meyer:     2h 30min (Details: "Netzwerk-Analyse, Fehlersuche")
- Maria Schmidt:   1h 15min (Details: "Konfiguration, Tests")
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Gesamt:            3h 45min
```

---

#### **Template 5: "Ticket abgeschlossen - MSP Abrechnung" (Kunde)**
```html
Template ID: 5
Name: "Ticket abgeschlossen - Mit MSP-Abrechnungsdetails (DE)"
Language: Deutsch
Type: External (Customer)
File: EmailTemplates/CloseTicket_MSP_DE.html

Subject: "Ticket #{TicketNumber} abgeschlossen - Kontingent-Übersicht"

Body:
Sehr geehrte/r {CustomerContactName},

Ihr Support-Ticket #{TicketNumber} wurde erfolgreich abgeschlossen.

[... Standard Content ...]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MSP-KONTINGENT & ABRECHNUNG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Vertrag:                {ContractName}
Verwendete Zeit:        {TotalHours}h
Kontingent vorher:      {ContingentHoursBefore}h
Kontingent jetzt:       {ContingentHoursRemaining}h
Verbleibend:            {ContingentPercentageRemaining}%

Stundensatz:            {HourlyRate}
Kosten dieses Tickets:  {TotalCost}

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ZEITÜBERSICHT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{TimeRecordTableForCustomer}

Im Anhang finden Sie einen detaillierten Service-Report und die Zeitaufstellung.

Mit freundlichen Grüßen,
{CompanyName} Support Team
```

**TimeRecordTableForCustomer Format**:
```html
<table border="1" cellpadding="5">
  <thead>
    <tr>
      <th>Datum</th>
      <th>Techniker</th>
      <th>Dauer</th>
      <th>Beschreibung</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>20.01.2025 14:30</td>
      <td>Klaus Meyer</td>
      <td>2h 30min</td>
      <td>Netzwerk-Analyse und Fehlersuche</td>
    </tr>
    <tr>
      <td>21.01.2025 09:00</td>
      <td>Maria Schmidt</td>
      <td>1h 15min</td>
      <td>Konfiguration und Tests</td>
    </tr>
  </tbody>
</table>
```

---

## USE CASES - DETAILLIERTE SZENARIEN

### UC 11.7.1: Standard-Ticket-Abschluss mit E-Mail

**Szenario**: Techniker Klaus hat Ticket #12345 "Netzwerkfehler bei ACME GmbH" erfolgreich gelöst.

**Ablauf**:
```
1. Klaus öffnet Ticket #12345 Details
2. Status ist "Gelöst" (wurde bereits gesetzt)
3. Klaus klickt auf Button: "Ticket schließen"
4. Close-Ticket-Dialog öffnet sich (Multi-Step Wizard)

STEP 1: Schließungs-Grund wählen
   - Klaus wählt: "Gelöst" ✅
   - Weiter →

STEP 2: Notizen & Dokumentation
   - Solution Summary eingeben:
     "Netzwerkfehler wurde behoben. Problem war defektes Switch-Kabel in Raum 3.12.
      Kabel wurde ausgetauscht. Netzwerk läuft jetzt stabil."
   - Internal Notes (optional):
     "Empfehlung: Netzwerk-Infrastruktur-Check im nächsten Quartal"
   - Service-Report anhängen? ☑️ Ja
   - Weiter →

STEP 3: Benachrichtigungen konfigurieren
   EXTERNE BENACHRICHTIGUNG (Kunde):
   - ☑️ Kunde benachrichtigen
   - Vorlage: "Ticket abgeschlossen - Standard (DE)"
   - Empfänger: support@acme-gmbh.de (auto from Customer.Email)
   - CC: it-leiter@acme-gmbh.de (manuell hinzugefügt)
   - ☑️ Service-Report anhängen

   INTERNE BENACHRICHTIGUNG (Team):
   - ☑️ Team benachrichtigen
   - Vorlage: "Ticket abgeschlossen - Team-Benachrichtigung (DE)"
   - Empfänger: klaus.meyer@company.com, maria.schmidt@company.com (auto)
   - Weiter →

STEP 4: Bestätigung
   - Zusammenfassung anzeigen:
     ✅ Ticket #12345 wird geschlossen
     ✅ Grund: Gelöst
     ✅ 2 E-Mails werden versendet (1 Kunde, 1 Team)
     ✅ Service-Report wird angehängt (2 Empfänger)
   - [Button: "Ticket schließen und senden"]

5. Klaus klickt "Ticket schließen und senden"

BACKEND PROCESSING:
   1. Validation: Ticket kann geschlossen werden? ✅ Ja
   2. Transaction Start
   3. Update Helpdesk:
      - HelpdeskState → "Closed" (I3D = 5)
      - ClosedAt → 2025-01-21 15:30:00
      - ClosedByI3D → Klaus.EmployeeI3D
   4. Delete open ToDos (2 ToDos gelöscht)
   5. Create History: "Ticket abgeschlossen von Klaus Meyer (Grund: Gelöst)"
   6. Create AccountActivity: "Ticket closed"
   7. SignalR Notification → Maria (Editor) sieht Real-Time Update
   8. Transaction Commit ✅
   9. Generate Service Report PDF (async - 2 Sekunden)
      - PDF Größe: 245 KB
      - Filename: "ServiceReport_Ticket12345_20250121.pdf"
   10. Send External Email:
       - To: support@acme-gmbh.de
       - CC: it-leiter@acme-gmbh.de
       - Subject: "Ticket #12345 abgeschlossen - Netzwerkfehler bei ACME GmbH"
       - Attachment: ServiceReport_Ticket12345_20250121.pdf
       - Status: ✅ Sent
   11. Send Internal Email:
       - To: klaus.meyer@company.com, maria.schmidt@company.com
       - Subject: "✅ Ticket #12345 abgeschlossen von Klaus Meyer"
       - Attachment: ServiceReport_Ticket12345_20250121.pdf
       - Status: ✅ Sent

6. Success-Meldung: "Ticket #12345 wurde erfolgreich geschlossen. 2 E-Mails versendet."
7. Navigation: → Ticket Details (Status jetzt "Geschlossen")
```

**Betroffene Entitäten**:
- `Helpdesk` (HelpdeskStateI3D, ClosedAt, ClosedByI3D)
- `HelpdeskHistory` (neuer Eintrag: "Ticket abgeschlossen")
- `AccountActivity` (Activity: "Ticket closed")
- `HelpdeskToDo` (alle gelöscht)
- `MailQueue` (2 E-Mails hinzugefügt)
- `HelpdeskAttachment` (Service-Report PDF gespeichert)

---

### UC 11.7.2: Ticket abschließen mit Duplikat-Verlinkung

**Szenario**: Techniker Maria findet heraus, dass Ticket #12350 ein Duplikat von #12345 ist.

**Ablauf**:
```
1. Maria öffnet Ticket #12350 "Netzwerkfehler" (ACME GmbH)
2. Maria prüft History und sieht: Identisches Problem wie #12345
3. Maria klickt: "Ticket schließen"

STEP 1: Schließungs-Grund
   - Maria wählt: "Duplikat" 📋

STEP 2: Parent-Ticket auswählen (neuer Dialog)
   - Ticket-Suche öffnet sich
   - Maria gibt ein: "#12345" oder "Netzwerkfehler ACME"
   - System zeigt: Ticket #12345 "Netzwerkfehler bei ACME GmbH" (Status: Geschlossen)
   - Maria wählt: Ticket #12345 als Parent
   - Bestätigung: "Ticket #12350 wird als Duplikat von #12345 markiert"

STEP 3: Notizen & Dokumentation
   - Solution Summary (auto-generated):
     "Dieses Ticket ist ein Duplikat von Ticket #12345.
      Das Problem wurde bereits gelöst. Siehe Ticket #12345 für Details."
   - Internal Notes:
     "Kunde hat zweite Anfrage gestellt weil erste E-Mail nicht gesehen."
   - Service-Report? ☐ Nein (nicht notwendig für Duplikate)

STEP 4: Benachrichtigungen
   - ☑️ Kunde benachrichtigen
   - Vorlage: "Ticket abgeschlossen - Duplikat (DE)"
   - Empfänger: support@acme-gmbh.de
   - E-Mail Text (auto):
     "Ihr Ticket #12350 wurde als Duplikat von Ticket #12345 identifiziert.
      Das Problem wurde bereits unter Ticket #12345 gelöst."

5. Maria klickt: "Ticket schließen"

BACKEND PROCESSING:
   1. Update Helpdesk #12350:
      - HelpdeskState → "Closed"
      - ParentHelpdeskI3D → 12345
      - ClosingReasonKind → Duplicate
   2. Update Helpdesk #12345:
      - Add to History: "Duplikat-Ticket #12350 wurde geschlossen"
   3. Optional: Time Records von #12350 → #12345 kopieren (wenn setting enabled)
   4. Optional: Anhänge von #12350 → #12345 kopieren (wenn setting enabled)
   5. Create cross-reference in History (beide Tickets)
   6. Send Email to Customer

6. Success: "Ticket #12350 wurde als Duplikat von #12345 geschlossen"
7. Navigation: → Ticket #12345 Details (Parent-Ticket)
```

**Daten-Verlinkung**:
```csharp
Helpdesk #12350 (Duplicate):
├─ ParentHelpdeskI3D = 12345
├─ ClosingReasonKind = Duplicate
└─ ClosedAt = 2025-01-21 16:00

Helpdesk #12345 (Parent):
├─ History: "Duplikat-Ticket #12350 wurde zugeordnet"
└─ (optional) Time Records += #12350 Time Records
```

---

### UC 11.7.3: Ticket abschließen mit "Kunde reagiert nicht"

**Szenario**: Techniker Klaus wartet seit 10 Tagen auf Rückmeldung vom Kunden zu Ticket #12360.

**Ablauf**:
```
1. Klaus öffnet Ticket #12360 "Software-Installation fehlgeschlagen" (Beta Corp)
2. History zeigt:
   - 2025-01-10: E-Mail an Kunde: "Bitte geben Sie Admin-Zugang für Installation"
   - 2025-01-13: E-Mail Reminder: "Warten auf Antwort"
   - 2025-01-16: E-Mail Reminder: "Letzter Reminder vor Schließung"
   - Heute: 2025-01-21 (11 Tage keine Antwort)

3. Klaus klickt: "Ticket schließen"

STEP 1: Schließungs-Grund
   - Klaus wählt: "Kunde reagiert nicht" 📧

STEP 2: Notizen & Dokumentation
   - Solution Summary (Template):
     "Dieses Ticket wird geschlossen, da wir trotz mehrerer Anfragen keine
      Rückmeldung vom Kunden erhalten haben. Das Problem bleibt ungelöst.

      Anfragen am: 2025-01-10, 2025-01-13, 2025-01-16

      Falls Sie weitere Unterstützung benötigen, melden Sie sich bitte.
      Wir können dieses Ticket jederzeit wieder öffnen."

   - Internal Notes:
     "Kunde hat vermutlich keine Zeit. Software-Installation noch nicht durchgeführt.
      Wenn Kunde sich meldet: Termin für Remote-Session vereinbaren."

   - Service-Report? ☐ Nein

STEP 3: Benachrichtigungen
   - ☑️ Kunde benachrichtigen
   - Vorlage: "Ticket abgeschlossen - Keine Rückmeldung (DE)"
   - E-Mail Text:
     "Sehr geehrter Kunde,

      Ihr Ticket #12360 wird geschlossen, da wir trotz mehrfacher Nachfrage
      keine Rückmeldung erhalten haben.

      Das Ticket kann jederzeit wieder geöffnet werden. Antworten Sie einfach
      auf diese E-Mail oder erstellen Sie ein neues Ticket.

      Ihr Problem bleibt weiterhin in unserem System gespeichert.

      Mit freundlichen Grüßen,
      Support Team"

4. Klaus klickt: "Ticket schließen"

BACKEND PROCESSING:
   1. Update Helpdesk:
      - HelpdeskState → "Closed"
      - ClosingReasonKind → CustomerNoResponse
      - Tag hinzufügen: "no-response"
   2. Create History: "Ticket geschlossen: Kunde reagiert nicht"
   3. Enable Auto-Reopen (innerhalb 30 Tage):
      - Wenn Kunde antwortet (E-Mail): Ticket wird automatisch wieder geöffnet
      - Status → "Offen" (nicht "Geschlossen")
      - History: "Ticket automatisch wieder geöffnet (Kunde-Antwort)"
   4. Send Email to Customer

5. Success: "Ticket #12360 wurde geschlossen (Grund: Kunde reagiert nicht)"

AUTO-REOPEN LOGIC (wenn Kunde antwortet):
   - E-Mail kommt rein von beta@betacorp.com (Customer Email)
   - System prüft: Ticket #12360 ist geschlossen seit 5 Tagen
   - System prüft: ClosingReasonKind = CustomerNoResponse
   - System prüft: Innerhalb 30 Tage? → ✅ Ja (5 Tage)
   - Action: Ticket wieder öffnen
     - HelpdeskState → "Offen"
     - History: "Ticket automatisch wieder geöffnet (Kunde-Antwort nach 5 Tagen)"
     - Benachrichtigung an Klaus: "Ticket #12360 wurde automatisch wieder geöffnet"
```

---

### UC 11.7.4: MSP-Ticket abschließen mit Kontingent-Tracking

**Szenario**: Techniker Maria schließt Ticket #12370 für MSP-Kunde "Premium Services GmbH" mit Kontingent-Vertrag.

**Ablauf**:
```
1. Maria öffnet Ticket #12370 "Server-Migration" (Premium Services GmbH)
2. Ticket Details zeigen:
   - Vertrag: "Premium Support 60h/Monat"
   - Kontingent vorher: 60h
   - Verwendete Zeit (dieses Ticket): 8h 30min
   - Time Records:
     - 2025-01-18: Maria: 4h (Server-Vorbereitung)
     - 2025-01-19: Klaus: 3h (Migration)
     - 2025-01-20: Maria: 1h 30min (Tests & Validation)

3. Maria klickt: "Ticket schließen"

STEP 1: Schließungs-Grund
   - Maria wählt: "Gelöst" ✅

STEP 2: Notizen & Dokumentation
   - Solution Summary:
     "Server-Migration erfolgreich abgeschlossen.
      - Alter Server: Windows Server 2016
      - Neuer Server: Windows Server 2022
      - Datenbank migriert: 50 GB
      - Services getestet: Alle laufen stabil
      - Downtime: 2h (geplantes Wartungsfenster)"

   - Service-Report? ☑️ Ja
   - ☑️ Time Records im Report detailliert auflisten

STEP 3: Benachrichtigungen
   - ☑️ Kunde benachrichtigen
   - Vorlage: "Ticket abgeschlossen - MSP mit Abrechnung (DE)"
   - E-Mail enthält:
     - Solution Summary
     - Kontingent-Übersicht:
       * Verwendete Zeit: 8.5h
       * Kontingent vorher: 60h
       * Kontingent jetzt: 51.5h (85.8%)
       * Verbleibend: 51.5h
     - Kosten: 8.5h × €120/h = €1,020 (aus Kontingent)
     - Detaillierte Zeitaufstellung (Tabelle)

4. Maria klickt: "Ticket schließen"

BACKEND PROCESSING:
   1. Update Helpdesk:
      - HelpdeskState → "Closed"
      - IsCalculable → true (abrechenbar)
   2. Update Contract Contingent:
      - Contract.UsedHours += 8.5h
      - Contract.RemainingHours = 51.5h
   3. Calculate Cost:
      - TotalCost = 8.5h × Contract.HourlyRate (€120)
      - TotalCost = €1,020
   4. Generate Service Report (with detailed time tracking)
   5. Send Email with MSP details

5. Success: "Ticket #12370 geschlossen. Kontingent aktualisiert: 51.5h verbleibend"

KUNDE ERHÄLT E-MAIL:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MSP-KONTINGENT & ABRECHNUNG
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Vertrag:                Premium Support 60h/Monat
Verwendete Zeit:        8.5h
Kontingent vorher:      60.0h
Kontingent jetzt:       51.5h
Verbleibend:            85.8%

Stundensatz:            €120/h
Kosten dieses Tickets:  €1,020 (aus Kontingent)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ZEITÜBERSICHT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
18.01.2025 | Maria Schmidt | 4h 00min | Server-Vorbereitung
19.01.2025 | Klaus Meyer   | 3h 00min | Migration
20.01.2025 | Maria Schmidt | 1h 30min | Tests & Validation
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Gesamt:                     8h 30min
```

---

### UC 11.7.5: Bulk-Close (Mehrere Tickets gleichzeitig schließen)

**Szenario**: Techniker Klaus möchte 5 "Spam"-Tickets gleichzeitig schließen.

**Ablauf**:
```
1. Klaus navigiert zu Ticket-Liste
2. Filter setzen: Status = "Offen", Tag = "spam" → 5 Tickets gefunden:
   #12380, #12381, #12382, #12383, #12384

3. Klaus selektiert alle 5 Tickets (Multi-Select Checkbox)
4. Klaus klickt: "Bulk-Aktionen" → "Tickets schließen"

BULK-CLOSE DIALOG:
   STEP 1: Schließungs-Grund (für alle)
   - Klaus wählt: "Nicht relevant" ❌

   STEP 2: Notizen (für alle)
   - Solution Summary (gleich für alle):
     "Spam-Ticket. Keine Support-Anfrage. Automatisch geschlossen."
   - Internal Notes: "Spam-Filter sollte verbessert werden"

   STEP 3: Benachrichtigungen
   - ☐ Kunde benachrichtigen (bei Spam keine E-Mail)
   - ☑️ Team benachrichtigen (zusammengefasste E-Mail)

5. Klaus klickt: "5 Tickets schließen"

BACKEND PROCESSING (Loop über alle 5 Tickets):
   FOR EACH TicketI3D IN [12380, 12381, 12382, 12383, 12384]:
      1. Update Helpdesk:
         - HelpdeskState → "Closed"
         - ClosingReasonKind → NotRelevant
         - IsCalculable → false (keine Abrechnung)
      2. Create History: "Ticket geschlossen (Bulk-Aktion)"
      3. Tag hinzufügen: "bulk-closed"

   Batch Email an Team:
   Subject: "5 Tickets wurden geschlossen (Bulk-Aktion von Klaus)"
   Body:
   "Folgende Tickets wurden als 'Nicht relevant' geschlossen:
    - Ticket #12380
    - Ticket #12381
    - Ticket #12382
    - Ticket #12383
    - Ticket #12384

    Grund: Spam-Ticket. Keine Support-Anfrage."

6. Success: "5 Tickets erfolgreich geschlossen"
7. Ticket-Liste wird aktualisiert (Real-Time via SignalR)
```

---

## HIDDEN FEATURES & ADVANCED FUNCTIONALITY

### 🔍 8 Versteckte Features

#### 1. **Auto-Reopen bei Kunden-Antwort**
```
Feature: Ticket wird automatisch wieder geöffnet wenn Kunde antwortet

Bedingung:
- ClosingReasonKind = CustomerNoResponse
- E-Mail kommt vom Kunden (Customer.Email matched)
- Ticket wurde vor weniger als 30 Tagen geschlossen

Action:
- HelpdeskState → "Offen"
- History: "Ticket automatisch wieder geöffnet (Kunde-Antwort)"
- Benachrichtigung an Responsible Person
- Original Thread bleibt erhalten (E-Mail Threading)
```

---

#### 2. **Service-Report Caching**
```
Feature: Generierte Service-Reports werden gecacht (24h)

Vorteil:
- Wenn mehrere Empfänger, PDF wird nur 1x generiert
- Bei Re-Send wird gecachtes PDF verwendet (innerhalb 24h)
- Performance: PDF-Generierung dauert 2-5 Sekunden

Cache-Key: "ServiceReport_{TicketI3D}_{Date}"
Cache-Expiration: 24 Stunden
Storage: FileSystem (Temp-Ordner) OR Azure Blob Storage
```

---

#### 3. **Closing Reason Statistics Dashboard**
```
Feature: Dashboard zeigt Statistiken über Schließungsgründe

Metriken:
- Gelöst: 82% (1,240 Tickets)
- Nicht reproduzierbar: 8% (120 Tickets)
- Duplikat: 5% (75 Tickets)
- Kunde reagiert nicht: 3% (45 Tickets)
- Nicht relevant: 2% (30 Tickets)

Charts:
- Pie Chart: Verteilung der Schließungsgründe
- Line Chart: Trend über Zeit (letzten 6 Monate)
- Table: Top 10 Techniker nach Closing Rate
```

---

#### 4. **Email Template Versioning**
```
Feature: E-Mail-Vorlagen haben Versionierung

Struktur:
EmailTemplate
├─ I3D: 1
├─ Name: "Ticket abgeschlossen - Standard (DE)"
├─ Version: 3 (aktuelle Version)
├─ LastModified: 2025-01-15
├─ ModifiedBy: Admin
└─ History:
   ├─ Version 1 (2024-01-01): Initial Template
   ├─ Version 2 (2024-06-15): Merge Tags hinzugefügt
   └─ Version 3 (2025-01-15): Layout verbessert

Vorteil:
- Änderungen werden nachverfolgt
- Rollback möglich
- A/B Testing möglich (Template Version 2 vs. Version 3)
```

---

#### 5. **Smart Duplicate Detection**
```
Feature: System schlägt automatisch Parent-Ticket vor bei Duplikat-Closing

Algorithmus:
1. Wenn ClosingReasonKind = Duplicate gewählt
2. System analysiert:
   - Gleicher Kunde? (CustomerI3D)
   - Ähnliche ShortDescription? (Levenshtein Distance < 20%)
   - Gleiche Category? (MainCategoryI3D)
   - Zeitlicher Zusammenhang? (CreatedDate < 7 Tage)
3. System zeigt Top 5 Vorschläge:
   "Diese Tickets könnten Parent sein:"
   - Ticket #12345 (Match: 95%) ⭐⭐⭐⭐⭐
   - Ticket #12340 (Match: 82%) ⭐⭐⭐⭐
   - Ticket #12330 (Match: 67%) ⭐⭐⭐

4. Techniker wählt Parent oder sucht manuell
```

---

#### 6. **Conditional Email Sending (Rules-Engine)**
```
Feature: E-Mails werden nur unter bestimmten Bedingungen gesendet

Regeln (konfigurierbar):
1. KEINE E-Mail wenn:
   - ClosingReasonKind = NotRelevant
   - Customer.EmailOptOut = true
   - Priority = Low AND TotalHours < 0.5h

2. IMMER E-Mail wenn:
   - Priority = Critical OR High
   - IsCalculable = true (MSP-Kunden)
   - Customer.NotificationSettings.AlwaysNotifyOnClose = true

3. VERZÖGERTE E-Mail wenn:
   - After-Hours Close (außerhalb Geschäftszeiten)
   - E-Mail wird am nächsten Werktag 08:00 Uhr versendet
```

---

#### 7. **Knowledge Base Article Suggestion**
```
Feature: System schlägt vor, Lösung als Knowledge Base Article zu speichern

Trigger:
- ClosingReasonKind = Solved
- TotalHours > 2h (komplexes Problem)
- SolutionSummary.Length > 200 characters (detaillierte Lösung)
- Tag "wiederkehrend" vorhanden

Action:
- Dialog: "Diese Lösung als Knowledge Base Article speichern?"
- [Button: "Ja, erstellen"] → Öffnet KB-Editor mit pre-filled Content:
  * Titel: ShortDescription
  * Content: SolutionSummary
  * Category: Helpdesk.MainCategory
  * Tags: Helpdesk.Tags
  * Related Tickets: [Ticket #12345]
```

---

#### 8. **Multi-Language Email Auto-Detection**
```
Feature: System erkennt Kunden-Sprache und wählt Template automatisch

Algorithmus:
1. Prüfe Customer.LanguageCode (falls gesetzt) → "DE" oder "EN"
2. Falls null: Analysiere letzte E-Mail vom Kunden:
   - Enthält deutsche Keywords? (z.B. "Vielen Dank", "Mit freundlichen Grüßen")
   - Enthält englische Keywords? (z.B. "Thank you", "Best regards")
3. Falls null: Prüfe Customer.Country (Deutschland → DE, USA/UK → EN)
4. Default: DE (Deutsch)

Resultat:
- Deutsche Kunden: "Ticket abgeschlossen - Standard (DE)"
- English Customers: "Ticket Closed - Standard (EN)"
- Automatisch ohne manuelle Auswahl
```

---

## REST-API ENDPOINTS - VOLLSTÄNDIGE DOKUMENTATION

### 📡 15+ Close-Ticket Related Endpoints

#### **1. Close Single Ticket with Notification**
```csharp
POST /Helpdesk/CloseTicketWithNotification

Request:
{
  "HelpdeskI3D": 12345,
  "ClosingReasonKind": "Solved", // enum: Solved, CannotReproduce, NotRelevant, Duplicate, CustomerNoResponse, WorkaroundProvided, SolvedExternally
  "SolutionSummary": "<p>Netzwerkfehler wurde behoben...</p>",
  "InternalNotes": "Empfehlung: Netzwerk-Check nächstes Quartal",

  // External Email (Customer)
  "SendExternalMail": true,
  "ExternalEmailTemplateI3D": 1,
  "ExternalSubject": "Ticket #12345 abgeschlossen - Netzwerkfehler",
  "ExternalBody": "<html>...</html>",
  "ExternalToAddresses": "support@acme.com;it-leiter@acme.com",
  "ExternalCcAddresses": "",
  "ExternalBccAddresses": "",
  "ExternalSender": {
    "RecipientAddress": "support@company.com",
    "DisplayName": "Support Team"
  },
  "ExternalAttachments": [
    {
      "Filename": "Additional_Doc.pdf",
      "Content": "base64-encoded-pdf-content",
      "ContentType": "application/pdf"
    }
  ],

  // Internal Email (Team)
  "SendInternalMail": true,
  "InternalSubject": "Ticket #12345 abgeschlossen",
  "InternalBody": "<html>...</html>",
  "InternalToAddresses": "klaus@company.com;maria@company.com",

  // Service Report
  "AttachServiceReport": true,
  "HelpdeskTimerI3Ds": [101, 102, 103], // Optional: Welche Time Records im Report?

  // Adviser Notifications
  "SendMailToAdviser1": true,
  "SendMailToAdviser2": false,
  "SendMailToAdviser3": false,
  "SendMailToAdviser4": false
}

Response (Success):
{
  "Status": "Success",
  "Data": {
    "NewStatusI3D": 5,
    "NewStatusCaption": "Geschlossen",
    "ClosedDate": "2025-01-21T15:30:00",
    "InternalNotificationSuccessful": true,
    "InternalNotificationMessage": null,
    "ExternalNotificationSuccessful": true,
    "ExternalNotificationMessage": null
  },
  "Message": "Ticket #12345 erfolgreich geschlossen. 2 E-Mails versendet."
}

Response (Partial Error - E-Mail failed):
{
  "Status": "Success",
  "Data": {
    "NewStatusI3D": 5,
    "NewStatusCaption": "Geschlossen",
    "ClosedDate": "2025-01-21T15:30:00",
    "InternalNotificationSuccessful": true,
    "InternalNotificationMessage": null,
    "ExternalNotificationSuccessful": false,
    "ExternalNotificationMessage": "SMTP Error: Connection timeout"
  },
  "Message": "Ticket geschlossen, aber externe E-Mail konnte nicht versendet werden."
}

Response (Error):
{
  "Status": "Error",
  "Message": "Ticket #12345 kann nicht geschlossen werden: Offene RMA vorhanden."
}
```

---

#### **2. Get Last Closed Tickets**
```csharp
GET /Helpdesk/GetLastClosedTickets?employeeI3D={id}&count={count}

Request:
- employeeI3D (optional): Filter by responsible person (0 = all)
- count (optional): Max tickets to return (default: 50)

Response:
{
  "Status": "Success",
  "Data": [
    {
      "I3D": 12345,
      "Number": 12345,
      "ShortDescription": "Netzwerkfehler bei ACME",
      "ClosedAt": "2025-01-21T15:30:00",
      "ClosedByName": "Klaus Meyer",
      "ClosingReasonKind": "Solved",
      "CustomerName": "ACME GmbH",
      "TotalHours": 3.75
    },
    // ... more tickets
  ]
}
```

---

#### **3. Validate Can Close Ticket**
```csharp
GET /Helpdesk/CanCloseTicket/{ticketI3D}

Response (Can Close):
{
  "Status": "Success",
  "Data": true,
  "Message": "Ticket kann geschlossen werden."
}

Response (Cannot Close):
{
  "Status": "Error",
  "Data": false,
  "Message": "Ticket kann nicht geschlossen werden: Offene RMA (RMA #RMA-1234) muss zuerst abgeschlossen werden."
}

Validation Rules:
1. Ticket exists?
2. User has UserRight: CLOSE_TICKET?
3. RMA finished? (if IsRMA = true)
4. Open ToDos? (warning, but allowed)
5. Time Records without description? (warning, but allowed)
```

---

#### **4. Generate Service Report (Without Closing)**
```csharp
POST /Helpdesk/GenerateServiceReport

Request:
{
  "HelpdeskI3D": 12345,
  "HelpdeskTimerI3Ds": [101, 102], // Optional: Specific time records
  "IncludeInternalNotes": false,   // Internal notes in report?
  "Language": "DE"                  // DE or EN
}

Response:
{
  "Status": "Success",
  "Data": {
    "Filename": "ServiceReport_Ticket12345_20250121.pdf",
    "Content": "base64-encoded-pdf-content",
    "SizeInBytes": 250880,
    "GeneratedAt": "2025-01-21T15:30:00"
  }
}
```

---

#### **5. Get Available Email Templates**
```csharp
GET /Helpdesk/GetCloseTicketEmailTemplates?language={lang}&type={type}

Request:
- language: "DE" or "EN"
- type: "External" or "Internal"

Response:
{
  "Status": "Success",
  "Data": [
    {
      "I3D": 1,
      "Name": "Ticket abgeschlossen - Standard (DE)",
      "Subject": "Ticket #{TicketNumber} abgeschlossen",
      "Language": "DE",
      "Type": "External",
      "Version": 3,
      "LastModified": "2025-01-15T10:00:00"
    },
    {
      "I3D": 2,
      "Name": "Ticket Closed - Standard (EN)",
      "Subject": "Ticket #{TicketNumber} Closed",
      "Language": "EN",
      "Type": "External",
      "Version": 2,
      "LastModified": "2025-01-10T14:00:00"
    }
    // ... more templates
  ]
}
```

---

#### **6. Preview Email (Before Sending)**
```csharp
POST /Helpdesk/PreviewCloseTicketEmail

Request:
{
  "HelpdeskI3D": 12345,
  "EmailTemplateI3D": 1,
  "CustomMergeTags": {
    "SolutionSummary": "<p>Custom solution...</p>"
  }
}

Response:
{
  "Status": "Success",
  "Data": {
    "Subject": "Ticket #12345 abgeschlossen - Netzwerkfehler bei ACME",
    "Body": "<html><body>Sehr geehrter Kunde, ...</body></html>",
    "To": ["support@acme.com"],
    "Cc": ["it-leiter@acme.com"],
    "Bcc": [],
    "Attachments": [
      {
        "Filename": "ServiceReport_Ticket12345.pdf",
        "SizeInBytes": 250880
      }
    ]
  }
}
```

---

#### **7. Bulk Close Tickets**
```csharp
POST /Helpdesk/BulkCloseTickets

Request:
{
  "HelpdeskI3Ds": [12380, 12381, 12382, 12383, 12384],
  "ClosingReasonKind": "NotRelevant",
  "SolutionSummary": "Spam-Ticket. Keine Support-Anfrage.",
  "SendCustomerNotification": false,
  "SendTeamNotification": true
}

Response:
{
  "Status": "Success",
  "Data": {
    "SuccessfullyClosedCount": 5,
    "FailedCount": 0,
    "FailedTickets": [],
    "TotalProcessingTime": "00:00:02.345"
  },
  "Message": "5 Tickets erfolgreich geschlossen."
}

Response (Partial Success):
{
  "Status": "PartialSuccess",
  "Data": {
    "SuccessfullyClosedCount": 4,
    "FailedCount": 1,
    "FailedTickets": [
      {
        "HelpdeskI3D": 12382,
        "Error": "Ticket #12382 kann nicht geschlossen werden: Offene RMA."
      }
    ],
    "TotalProcessingTime": "00:00:02.500"
  },
  "Message": "4 von 5 Tickets geschlossen. 1 Fehler."
}
```

---

#### **8. Reopen Closed Ticket**
```csharp
POST /Helpdesk/ReopenTicket

Request:
{
  "HelpdeskI3D": 12345,
  "Reason": "Kunde meldet Problem besteht weiterhin",
  "NewPriority": "High", // Optional: Increase priority on reopen
  "NotifyTeam": true
}

Response:
{
  "Status": "Success",
  "Data": {
    "NewStatusI3D": 1,
    "NewStatusCaption": "Offen",
    "ReopenedAt": "2025-01-22T10:00:00"
  },
  "Message": "Ticket #12345 wurde wieder geöffnet."
}
```

---

#### **9. Get Closing Reason Statistics**
```csharp
GET /Helpdesk/GetClosingReasonStatistics?startDate={start}&endDate={end}&employeeI3D={id}

Request:
- startDate: Filter start (optional)
- endDate: Filter end (optional)
- employeeI3D: Filter by technician (optional, 0 = all)

Response:
{
  "Status": "Success",
  "Data": {
    "TotalClosedTickets": 1510,
    "ReasonBreakdown": [
      {
        "ReasonKind": "Solved",
        "Count": 1240,
        "Percentage": 82.1
      },
      {
        "ReasonKind": "CannotReproduce",
        "Count": 120,
        "Percentage": 7.9
      },
      {
        "ReasonKind": "Duplicate",
        "Count": 75,
        "Percentage": 5.0
      },
      {
        "ReasonKind": "CustomerNoResponse",
        "Count": 45,
        "Percentage": 3.0
      },
      {
        "ReasonKind": "NotRelevant",
        "Count": 30,
        "Percentage": 2.0
      }
    ],
    "AverageClosingTime": "2.5", // days
    "Top5TechniciansByClosingRate": [
      {
        "EmployeeName": "Klaus Meyer",
        "ClosedCount": 245,
        "ClosingRate": 95.5 // % of assigned tickets closed
      },
      // ... more
    ]
  }
}
```

---

## VALIDATION RULES & BUSINESS LOGIC

### ✅ Pre-Close Validations

```csharp
Validation Rule 1: User Rights
├─ Check: User has UserRight "CLOSE_TICKET"?
├─ Error: "Sie haben keine Berechtigung, Tickets zu schließen."
└─ Severity: CRITICAL (blocking)

Validation Rule 2: Ticket Status
├─ Check: Current Status is "Gelöst" OR "In Arbeit" OR "Wartet"?
├─ Error: "Ticket mit Status '{CurrentStatus}' kann nicht direkt geschlossen werden."
├─ Suggestion: "Bitte setzen Sie Status zuerst auf 'Gelöst'."
└─ Severity: WARNING (can override)

Validation Rule 3: RMA Check
├─ Check: If IsRMA = true, RMA is finished?
├─ Error: "Offene RMA (RMA #{RMANumber}) muss zuerst abgeschlossen werden."
└─ Severity: CRITICAL (blocking)

Validation Rule 4: Required Fields
├─ Check: SolutionSummary is not empty?
├─ Error: "Lösungs-Zusammenfassung ist erforderlich."
└─ Severity: CRITICAL (blocking)

Validation Rule 5: Email Validation
├─ Check: ExternalToAddresses contains valid emails?
├─ Error: "Ungültige E-Mail-Adresse: '{InvalidEmail}'"
└─ Severity: CRITICAL (blocking)

Validation Rule 6: Service Report Attachment Size
├─ Check: If AttachServiceReport=true, PDF size < 25 MB?
├─ Error: "Service-Report zu groß (28 MB). Maximum: 25 MB."
└─ Severity: WARNING (can send without attachment)

Validation Rule 7: Open ToDos
├─ Check: Are there open ToDos for this ticket?
├─ Warning: "3 offene ToDos werden automatisch gelöscht."
└─ Severity: WARNING (proceed with confirmation)

Validation Rule 8: Time Records Without Description
├─ Check: All Time Records have description?
├─ Warning: "2 Time Records haben keine Beschreibung. Service-Report könnte unvollständig sein."
└─ Severity: WARNING (proceed with confirmation)
```

---

### 🔄 Post-Close Actions (Automated)

```csharp
Action 1: Update Helpdesk Entity
├─ HelpdeskState → "Closed" (I3D = 5)
├─ ClosedAt → DateTime.Now
├─ ClosedByI3D → CurrentUser.EmployeeI3D
└─ ParentHelpdeskI3D → (if Duplicate)

Action 2: Delete Open ToDos
├─ Query: SELECT * FROM HelpdeskToDo WHERE HelpdeskI3D = {id} AND IsCompleted = 0
├─ Action: DELETE all found ToDos
└─ History: "3 offene ToDos wurden automatisch gelöscht"

Action 3: Create History Entry
├─ Action: "Beendet"
├─ Name: CurrentUser.Fullname
├─ Description: "Ticket abgeschlossen (Grund: {ClosingReason})"
├─ CreatedDate: DateTime.Now
└─ IsVisibleToCustomer: false

Action 4: Create Account Activity
├─ Type: "TicketClosed"
├─ AccountI3D: Helpdesk.Customer.AccountI3D
├─ Description: "Ticket #{Number} abgeschlossen"
└─ CreatedBy: CurrentUser

Action 5: SignalR Real-Time Notification
├─ Recipients: All Editors + Responsible Person
├─ Message: "Ticket #{Number} wurde geschlossen von {User}"
├─ Action: Update UI (Ticket Status Badge)
└─ Sound: Play "ticket-closed.mp3" (optional)

Action 6: Update SLA Metrics
├─ ResolutionTime: ClosedAt - CreatedAt
├─ SLAStatus: Calculate (OnTrack / Breached)
├─ FirstResponseTime: (already set when first comment)
└─ Update Dashboard Metrics

Action 7: Update Contract Contingent (if MSP)
├─ Contract.UsedHours += Helpdesk.TotalHours
├─ Contract.RemainingHours -= Helpdesk.TotalHours
└─ Check: ContingentAlert if RemainingHours < 10% (trigger warning email)

Action 8: Tag Auto-Add
├─ If ClosingReason = Duplicate → Add Tag "duplicate"
├─ If ClosingReason = CustomerNoResponse → Add Tag "no-response"
├─ If ClosingReason = WorkaroundProvided → Add Tag "workaround"
└─ If TotalHours > 10h → Add Tag "complex"
```

---

## BUSINESS LOGIC - SPECIAL CASES

### 📋 Special Case 1: Duplicate Ticket Handling

```csharp
IF ClosingReasonKind = Duplicate THEN:

   1. Validate Parent Ticket:
      - ParentHelpdeskI3D must be set
      - Parent Ticket must exist
      - Parent Ticket cannot be Duplicate itself (no chaining)

   2. Update Current Ticket (Duplicate):
      - ParentHelpdeskI3D → SelectedParentI3D
      - HelpdeskState → "Closed"
      - Tag: "duplicate"

   3. Update Parent Ticket:
      - History: "Duplikat-Ticket #{DuplicateNumber} wurde zugeordnet"
      - Optional: Copy Time Records from Duplicate to Parent
      - Optional: Copy Attachments from Duplicate to Parent

   4. Cross-Reference:
      - HelpdeskRelation.HelpdeskI3D = ParentI3D
      - HelpdeskRelation.RelatedHelpdeskI3D = DuplicateI3D
      - HelpdeskRelation.RelationType = "Duplicate"

   5. Customer Notification:
      - E-Mail: "Ihr Ticket #{DuplicateNumber} wurde als Duplikat von #{ParentNumber} identifiziert."
      - Link to Parent Ticket (if Customer Portal enabled)
```

---

### 📧 Special Case 2: Customer No Response - Auto-Reopen

```csharp
IF ClosingReasonKind = CustomerNoResponse THEN:

   1. Set Auto-Reopen Window:
      - AutoReopenExpiration = ClosedAt + 30 days
      - Tag: "no-response"

   2. Email Monitoring (Background Job):
      - Listen for incoming emails from Customer.Email
      - Check: Is there a closed ticket with "no-response" tag?
      - Check: Is AutoReopenExpiration > DateTime.Now?

   3. IF Email Received AND Within Window:
      - Action: Reopen Ticket automatically
      - HelpdeskState → "Offen" (not "Geschlossen")
      - History: "Ticket automatisch wieder geöffnet (Kunde-Antwort nach {Days} Tagen)"
      - Benachrichtigung an Responsible Person:
         "Ticket #{Number} wurde automatisch wieder geöffnet. Kunde hat geantwortet."
      - E-Mail wird als neuer Comment hinzugefügt

   4. IF Auto-Reopen Window Expired (30+ days):
      - Action: Normal new ticket creation
      - Link to old ticket via ConnectionNumber
```

---

### 🔧 Special Case 3: Workaround Provided - Follow-Up

```csharp
IF ClosingReasonKind = WorkaroundProvided THEN:

   1. Optional: Create Follow-Up ToDo:
      - Dialog: "Follow-Up ToDo erstellen für permanente Lösung?"
      - ToDo.Title: "Permanente Lösung für Ticket #{Number} implementieren"
      - ToDo.DueDate: +14 days
      - ToDo.AssignedTo: Responsible Person OR Team Lead

   2. Knowledge Base Suggestion:
      - Dialog: "Workaround als Knowledge Base Article speichern?"
      - Pre-fill KB Article:
         * Title: "Workaround: {ShortDescription}"
         * Content: SolutionSummary
         * Category: "Workarounds"
         * Tags: ["workaround", "temporary", {OriginalTags}]

   3. Tag Auto-Add:
      - Tag: "workaround"
      - Tag: "follow-up-required"
```

---

## CODE EXAMPLES

### Example 1: Close Ticket with Notification (C# Backend)

```csharp
// HelpdeskCloseBL.cs
public Result<CloseTicketInformation> CloseTicketWithNotification(
    AppUser currentUser,
    int ticketI3D,
    CloseTicketNotificationSettings request)
{
    Guard.NotNull(currentUser, nameof(currentUser));
    Guard.NotNull(request, nameof(request));

    var closeInformation = new CloseTicketInformation()
    {
        InternalNotificationSuccessful = true,
        ExternalNotificationSuccessful = true,
    };

    Helpdesk helpdesk;
    bool transactionCommited = false;

    Session.StartTransaction();
    try
    {
        // 1. Load Helpdesk
        var helpdeskResult = _helpdeskBL.GetHelpdeskRequest(ticketI3D, filter: null, new LoggedInUser(currentUser));
        if (helpdeskResult.Status == ResultStatus.Error)
            return Result<CloseTicketInformation>.AsError(helpdeskResult.Message);

        helpdesk = helpdeskResult.Data;

        // 2. Validate Can Close
        var canCloseResult = CanCloseHelpdesk(ticketI3D);
        if (canCloseResult.Status != ResultStatus.Success)
            return Result<CloseTicketInformation>.AsError(canCloseResult.Message);

        // 3. Update Helpdesk Status
        helpdesk.HelpdeskState = _helpdeskSettingsBL.GetClosedHelpdeskState();
        helpdesk.ClosedAt = DateTime.Now;
        helpdesk.ClosedByI3D = currentUser.EmployeeI3D;

        closeInformation.NewStatusI3D = helpdesk.HelpdeskState.I3D;
        closeInformation.NewStatusCaption = helpdesk.HelpdeskState.Name;
        closeInformation.ClosedDate = helpdesk.ClosedAt.Value;

        // 4. Delete Open ToDos
        var deleteToDoResult = _toDoBL.DeleteHelpdeskToDos(helpdesk.I3D);
        if (deleteToDoResult.Status != ResultStatus.Success)
            return Result<CloseTicketInformation>.FromResult(deleteToDoResult);

        // 5. Save Helpdesk
        var saveResult = _helpdeskBL.Save(helpdesk, new LoggedInUser(currentUser));
        if (saveResult.Status != ResultStatus.Success)
            return Result<CloseTicketInformation>.FromResult(saveResult);

        // 6. Create History
        _helpdeskHistoryBL.CreateHistory("Beendet", currentUser.Fullname, null, null, null,
            true, false, false, false, currentUser, helpdesk, "Ticket abgeschlossen");
        _helpdeskHistoryBL.CreateHistoryForActionType(currentUser, HelpdeskHistoryType.Close, ticketI3D);

        // 7. SignalR Notification
        _nexusNotificationsBL.SaveClosedTicketNotifications(helpdesk, new LoggedInUser(currentUser));

        // 8. Commit Transaction
        Session.CommitTransaction();
        transactionCommited = true;
    }
    finally
    {
        if (!transactionCommited)
            Session.RollbackTransaction();
    }

    // 9. Send External Email (after transaction)
    if (!string.IsNullOrWhiteSpace(request.ExternalToAddresses))
    {
        var sendMailResult = SendCloseTicketMail(
            currentUser, helpdesk, request.ExternalSubject, request.ExternalBody,
            request.ExternalToAddresses, request.ExternalCcAddresses, request.ExternalBccAddresses,
            request.AttachServiceReport, ReportGroupConstants.HELPDESK_MAIL_EXTERN,
            AppSettingsDataConst.HelpdeskExternalSignature, externemail: true,
            request.ExternalAttachments, request.HelpdeskTimerI3Ds, request.Sender);

        if (sendMailResult.Status != ResultStatus.Success)
        {
            closeInformation.ExternalNotificationSuccessful = false;
            closeInformation.ExternalNotificationMessage = sendMailResult.Message;
        }
    }

    // 10. Send Internal Email (after transaction)
    if (!string.IsNullOrWhiteSpace(request.InternalToAddresses))
    {
        var sendMailResult = SendCloseTicketMail(
            currentUser, helpdesk, request.InternalSubject, request.InternalBody,
            request.InternalToAddresses, request.InternalCcAddresses, request.InternalBccAddresses,
            request.AttachServiceReport, ReportGroupConstants.HELPDESK_MAIL_INTERN,
            AppSettingsDataConst.HelpdeskInternalSignature, externemail: false,
            request.InternalAttachments, request.HelpdeskTimerI3Ds, request.InternalSender);

        if (sendMailResult.Status != ResultStatus.Success)
        {
            closeInformation.InternalNotificationSuccessful = false;
            closeInformation.InternalNotificationMessage = sendMailResult.Message;
        }
    }

    return Result<CloseTicketInformation>.AsSuccess(closeInformation);
}
```

---

### Example 2: Close Ticket Component (Blazor Frontend)

```razor
@* CloseTicket.razor *@
@inject ICentronService CentronService
@inject IAlertService AlertService

<DxTabs>
    <DxTabPage Text="1. Schließungs-Grund">
        <DxComboBox Data="@closingReasons"
                    @bind-Value="@selectedClosingReason"
                    ValueFieldName="Value"
                    TextFieldName="Text"/>
    </DxTabPage>

    <DxTabPage Text="2. Notizen" Enabled="@(selectedClosingReason != null)">
        <DxRichEdit @ref="richEditSolution" DocumentFormat="DocumentFormat.Html"/>
        <DxMemo @bind-Text="@internalNotes" Rows="5" Label="Interne Notizen"/>
        <DxCheckBox @bind-Checked="@includeServiceReport" Text="Service-Report anhängen"/>
    </DxTabPage>

    <DxTabPage Text="3. Benachrichtigungen" Enabled="@(solutionSummary != null)">
        <h4>Externe Benachrichtigung (Kunde)</h4>
        <DxCheckBox @bind-Checked="@sendExternalMail" Text="Kunde benachrichtigen"/>
        <DxComboBox Data="@externalTemplates"
                    @bind-Value="@selectedExternalTemplate"
                    Enabled="@sendExternalMail"/>

        <h4>Interne Benachrichtigung (Team)</h4>
        <DxCheckBox @bind-Checked="@sendInternalMail" Text="Team benachrichtigen"/>
        <DxComboBox Data="@internalTemplates"
                    @bind-Value="@selectedInternalTemplate"
                    Enabled="@sendInternalMail"/>
    </DxTabPage>

    <DxTabPage Text="4. Bestätigung">
        <h3>Zusammenfassung</h3>
        <p>Ticket #@HelpdeskI3D wird geschlossen</p>
        <p>Grund: @selectedClosingReason</p>
        <p>Benachrichtigungen: @(sendExternalMail ? "1 Kunde" : "0") + @(sendInternalMail ? " Team" : "")</p>

        <DxButton Text="Ticket schließen und senden"
                  RenderStyle="ButtonRenderStyle.Primary"
                  Click="@OnCloseTicketClick"/>
    </DxTabPage>
</DxTabs>

@code {
    [Parameter] public int HelpdeskI3D { get; set; }
    [Parameter] public EventCallback OnTicketClosed { get; set; }

    private string selectedClosingReason;
    private string solutionSummary;
    private string internalNotes;
    private bool includeServiceReport = true;
    private bool sendExternalMail = true;
    private bool sendInternalMail = true;

    private async Task OnCloseTicketClick()
    {
        var request = new CloseTicketWithNotificationRequest
        {
            HelpdeskI3D = HelpdeskI3D,
            ClosingReasonKind = selectedClosingReason,
            SolutionSummary = await richEditSolution.GetDocumentAsync(),
            InternalNotes = internalNotes,
            SendExternalMail = sendExternalMail,
            SendInternalMail = sendInternalMail,
            AttachServiceReport = includeServiceReport,
            // ... more properties
        };

        var response = await CentronService.CloseTicketWithNotification(request);

        if (response.IsSuccess)
        {
            await AlertService.ShowSuccess($"Ticket #{HelpdeskI3D} erfolgreich geschlossen.");
            await OnTicketClosed.InvokeAsync();
        }
        else
        {
            await AlertService.ShowError($"Fehler: {response.Message}");
        }
    }
}
```

---

