feat(tickets): Wiederöffnen→Rückfrage mit Zeitstempel + Soft-Delete-Papierkorb + Scroll-Erhalt

Wiederöffnen eines gelösten Tickets:
- landet jetzt in „Rückfragen" (NeedsInfo) statt „Offen".
- War es ein echt gelöstes Ticket OHNE Rückfrage, wird um mehr Infos gebeten und
  „Wieder geöffnet am" (ReopenedAt) gestempelt.
- Hatte es bereits eine offene Rückfrage (z. B. manuell gelöste Rückfrage), kehrt es
  ohne neuen Hinweis und ohne Zeitstempel in genau diese Frage zurück.

Löschen über die UI = Soft-Delete:
- Tickets wandern in die neue Kategorie „Gelöscht" (Papierkorb) statt entfernt zu werden.
- Von dort wiederherstellbar (POST /feedback/{id}/restore → DeletedAt = null).
- Countdown „Wird in N Tagen endgültig gelöscht"; nach 30 Tagen endgültige Löschung
  (lazy beim Abruf in GET /feedback).

Außerdem: Scrollposition bleibt beim Zurück-Navigieren (z. B. nach Klick auf einen
Tier-Link) erhalten, statt an den Seitenanfang zu springen.

EF-Migration FeedbackReopenedAndSoftDelete (ReopenedAt, DeletedAt — beide nullable).
Tests: 260 Backend grün (+2 Reopen-Fälle, CRUD auf Soft-Delete umgestellt), e2e Tickets
Desktop+Phone grün (+Soft-Delete/Restore/Reopen), vitest 149.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 11:02:04 +02:00
parent a0fa33da42
commit c0a7b1a5ee
13 changed files with 2237 additions and 43 deletions

View File

@@ -55,6 +55,21 @@ namespace GerbilManagerWebAPI.Models
/// <summary>When the ticket was marked resolved; null while open.</summary>
public DateTimeOffset? ResolvedAt { get; set; }
/// <summary>
/// When the ticket was reopened FROM a resolved state that had no pending Rückfrage —
/// i.e. a genuinely-fixed ticket the breeder wants reworked. Stays null for tickets that
/// merely had an open question, got marked resolved, and were reopened (those just return
/// to their existing Rückfrage). Shown as "Wieder geöffnet am" in the UI.
/// </summary>
public DateTimeOffset? ReopenedAt { get; set; }
/// <summary>
/// SOFT-DELETE: when the breeder deleted the ticket via the UI. Soft-deleted tickets are
/// NOT removed from the DB — they move into the "Gelöscht" category and can be restored
/// (DeletedAt → null) from there. null = not deleted.
/// </summary>
public DateTimeOffset? DeletedAt { get; set; }
/// <summary>A clarifying question (Rückfrage) a maintainer attaches to the ticket; null if none.</summary>
public string? Question { get; set; }