feat(tickets): Rückfrage am Ticket + Antwort durch die Züchterin
Tickets können jetzt eine Rückfrage (Question) tragen und die Züchterin kann
in-app antworten. Feedback um FK-freie Felder Question/Answer/AnsweredAt erweitert
(überlebt Ingest-Wipe); Status-Lebenszyklus Open → NeedsInfo (Rückfrage gestellt)
→ Answered (beantwortet) → Resolved. Migration AddFeedbackQuestionAnswer.
PUT /feedback/{id}: question → NeedsInfo, answer → Answered+AnsweredAt; DTO gibt
die Felder zurück. Tickets-Seite (/hilfe/tickets) zeigt die Rückfrage hervorgehoben
und bietet ein Antwort-Feld + „Antworten"; Status-Badges Offen/Rückfrage offen/
Beantwortet/Gelöst.
Tests: FeedbackEndpointTests (Frage→NeedsInfo, Antwort→Answered, übersteht Ingest),
e2e tickets.spec.ts. dotnet/vitest/playwright grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -409,6 +409,9 @@ export async function installMockApi(page: Page): Promise<MockDb> {
|
||||
createdAt: new Date().toISOString(),
|
||||
status: 'Open',
|
||||
resolvedAt: null,
|
||||
question: null,
|
||||
answer: null,
|
||||
answeredAt: null,
|
||||
}
|
||||
db.feedback.push(created)
|
||||
return json(route, 201, created)
|
||||
@@ -425,15 +428,47 @@ export async function installMockApi(page: Page): Promise<MockDb> {
|
||||
const idx = db.feedback.findIndex((f) => f.id === fid)
|
||||
if (idx < 0) return json(route, 404, { title: 'Not Found' })
|
||||
if (method === 'PUT') {
|
||||
const body = request.postDataJSON() as { message?: string; status?: string }
|
||||
const body = request.postDataJSON() as {
|
||||
message?: string
|
||||
status?: string
|
||||
question?: string
|
||||
answer?: string
|
||||
}
|
||||
const row = db.feedback[idx]
|
||||
if (typeof body.message === 'string') {
|
||||
if (!body.message.trim()) return json(route, 400, 'Message darf nicht leer sein.')
|
||||
row.message = body.message.trim()
|
||||
}
|
||||
// Rückfrage anhängen → NeedsInfo (außer bereits Resolved).
|
||||
if (typeof body.question === 'string') {
|
||||
const q = body.question.trim()
|
||||
row.question = q.length === 0 ? null : q
|
||||
if (row.question && row.status !== 'Resolved') {
|
||||
row.status = 'NeedsInfo'
|
||||
row.resolvedAt = null
|
||||
}
|
||||
}
|
||||
// Antwort der Züchterin → Answered + answeredAt.
|
||||
if (typeof body.answer === 'string') {
|
||||
const a = body.answer.trim()
|
||||
if (a.length === 0) {
|
||||
row.answer = null
|
||||
row.answeredAt = null
|
||||
} else {
|
||||
row.answer = a
|
||||
row.answeredAt = new Date().toISOString()
|
||||
row.status = 'Answered'
|
||||
row.resolvedAt = null
|
||||
}
|
||||
}
|
||||
if (typeof body.status === 'string') {
|
||||
const resolved = body.status.toLowerCase() === 'resolved'
|
||||
row.status = resolved ? 'Resolved' : 'Open'
|
||||
const s = body.status
|
||||
const resolved = s.toLowerCase() === 'resolved'
|
||||
row.status = resolved
|
||||
? 'Resolved'
|
||||
: s === 'NeedsInfo' || s === 'Answered'
|
||||
? s
|
||||
: 'Open'
|
||||
row.resolvedAt = resolved ? (row.resolvedAt ?? new Date().toISOString()) : null
|
||||
}
|
||||
return json(route, 200, row)
|
||||
|
||||
Reference in New Issue
Block a user