- Suche: Volltext über Nachricht/Frage/Antwort/Changelog/Name/Kategorie, plus Kategorie-Filter (KI-gesetzte Kategorie als Chip + Auswahlmenü), je innerhalb der aktiven Ansicht. - 👍/👎 auf gelösten Tickets: 👍 speichert die Rückmeldung, 👎 öffnet das Ticket wieder (zurück in die Rückfragen) und bittet um die fehlende Info. - Melde-Fenster: schlägt beim Tippen ähnliche bereits gelöste Tickets vor (Wort-Überlappung + Bonus bei gleichem Tier/Wurf/Kontakt) — Self-Service. Backend: Feedback.Category + Feedback.Helpful (+ PUT-Handling), Migration FeedbackCategoryAndHelpful (beide nullable). Tests: 260 Backend grün, e2e Tickets Desktop +4 (Suche/Kategorie/👍/👎), vitest 149. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
5.8 KiB
C#
118 lines
5.8 KiB
C#
using System.ComponentModel.DataAnnotations;
|
|
|
|
namespace GerbilManagerWebAPI.Models
|
|
{
|
|
/// <summary>
|
|
/// FEEDBACK: a user-submitted "Fehler melden" report. Decoupled from the rest of the
|
|
/// model on purpose — GerbilId/LitterId are plain nullable Guid columns (NOT enforced
|
|
/// foreign keys), so the import re-ingest wipe (IngestResolvedService) can delete
|
|
/// gerbils/litters without deleting or breaking feedback rows. The captured EntityName
|
|
/// keeps the report human-readable even after the referenced animal is gone.
|
|
/// </summary>
|
|
public class Feedback
|
|
{
|
|
[Key]
|
|
public Guid Id { get; set; }
|
|
|
|
/// <summary>The user's free-text description of the problem.</summary>
|
|
public required string Message { get; set; }
|
|
|
|
/// <summary>Which view the report came from: stammbaum | gerbil-detail | litter-detail.</summary>
|
|
public required string Context { get; set; }
|
|
|
|
/// <summary>Loose reference (no FK) to the gerbil the report is about, if any.</summary>
|
|
public Guid? GerbilId { get; set; }
|
|
|
|
/// <summary>Loose reference (no FK) to the litter the report is about, if any.</summary>
|
|
public Guid? LitterId { get; set; }
|
|
|
|
/// <summary>Loose reference (no FK) to the contact the report is about, if any.</summary>
|
|
public Guid? ContactId { get; set; }
|
|
|
|
/// <summary>Captured name of the referenced animal/litter (survives an ingest wipe).</summary>
|
|
public string? EntityName { get; set; }
|
|
|
|
/// <summary>The client URL/route the report was filed from.</summary>
|
|
public string? Url { get; set; }
|
|
|
|
/// <summary>Client-supplied timestamp (when the user submitted, in their browser).</summary>
|
|
public DateTimeOffset? ClientTimestamp { get; set; }
|
|
|
|
/// <summary>Optional browser user-agent for diagnostics.</summary>
|
|
public string? UserAgent { get; set; }
|
|
|
|
/// <summary>Server-side creation time.</summary>
|
|
public DateTimeOffset CreatedAt { get; set; }
|
|
|
|
/// <summary>
|
|
/// Ticket lifecycle status: "Open" | "NeedsInfo" | "Answered" | "Resolved" (default "Open").
|
|
/// "NeedsInfo" = a maintainer attached a clarifying question (Rückfrage) and is waiting on
|
|
/// the breeder; "Answered" = the breeder replied. Plain string, no FK — keeps feedback
|
|
/// decoupled and ingest-surviving like the rest of the row.
|
|
/// </summary>
|
|
public string Status { get; set; } = "Open";
|
|
|
|
/// <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>
|
|
/// Optionale Kategorie/Thema (von der KI gesetzt) für Filter + Übersicht, z. B.
|
|
/// "Genetik", "Import", "Stammbaum", "Daten", "Foto". Frei wählbar (kein FK), null = ohne.
|
|
/// </summary>
|
|
public string? Category { get; set; }
|
|
|
|
/// <summary>
|
|
/// War die Lösung hilfreich? (Daumen hoch/runter auf gelösten Tickets.) true = 👍,
|
|
/// false = 👎 (löst i. d. R. ein Wiederöffnen aus), null = noch keine Rückmeldung.
|
|
/// </summary>
|
|
public bool? Helpful { get; set; }
|
|
|
|
/// <summary>A clarifying question (Rückfrage) a maintainer attaches to the ticket; null if none.</summary>
|
|
public string? Question { get; set; }
|
|
|
|
/// <summary>The breeder's (Züchterin) reply to the clarifying question; null until answered.</summary>
|
|
public string? Answer { get; set; }
|
|
|
|
/// <summary>When the breeder answered the clarifying question; null until answered.</summary>
|
|
public DateTimeOffset? AnsweredAt { get; set; }
|
|
|
|
/// <summary>
|
|
/// FIX-NOTE: a breeder-friendly changelog written when the ticket is resolved — plain
|
|
/// language ("was sich für SIE sichtbar geändert hat"), no technical jargon. Shown in the
|
|
/// "Geschlossen" view. null while the ticket is open / unresolved.
|
|
/// </summary>
|
|
public string? FixNote { get; set; }
|
|
|
|
/// <summary>
|
|
/// AGENT-CONTEXT (INTERNAL): the maintainer/agent's working memory — findings, suspected
|
|
/// cause, files/data touched, the plan, what it is waiting on. MAY contain ids/filenames.
|
|
/// Returned by the DTO for tooling, but NEVER rendered in the breeder UI. null if unused.
|
|
/// </summary>
|
|
public string? AgentContext { get; set; }
|
|
|
|
/// <summary>
|
|
/// THREAD/HISTORY: a JSON array of past Q&A turns so earlier rounds aren't lost when a new
|
|
/// question is asked. Each entry: { "role": "maintainer" | "breeder", "text": string,
|
|
/// "at": ISO-8601 string }. The current open exchange stays in Question/Answer; when a NEW
|
|
/// question is set while a previous Q&A exists, the previous (question, answer) is appended
|
|
/// here first. Plain string column (no FK) so it survives the ingest wipe. null/empty = no history.
|
|
/// </summary>
|
|
public string? Thread { get; set; }
|
|
}
|
|
}
|