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>
455 lines
20 KiB
TypeScript
455 lines
20 KiB
TypeScript
/**
|
||
* QA-1: Seed-Daten für den Mock-Modus — geformt exakt nach dem DATA-2-Vertrag
|
||
* (camelCase-DTOs, Gridify-paged Listen). Datensatz angelehnt an Kellys
|
||
* FEAT-4-Verifikationsdaten (mehrgenerationiger Stammbaum inkl. gemeinsamem
|
||
* Vorfahren Anton), erweitert um Becken/Kontakte/Gesundheit/Gewicht für die
|
||
* FEAT-2/FEAT-6-Flüsse.
|
||
*/
|
||
import type { Contact, Enclosure, Gerbil, Litter } from '../src/api/types'
|
||
import type { InboxRequest } from '../src/api/requests'
|
||
|
||
export interface HealthRecordRow {
|
||
id: string
|
||
gerbilId: string
|
||
date: string
|
||
type: string
|
||
description: string
|
||
veterinarian: string | null
|
||
createdAt: string
|
||
}
|
||
|
||
export interface WeightRecordRow {
|
||
id: string
|
||
gerbilId: string
|
||
date: string
|
||
weightGrams: number
|
||
notes: string | null
|
||
}
|
||
|
||
/** WEB-0b: CMS-Block (data ist das typ-spezifische JSON-Objekt). */
|
||
export interface MockBlock {
|
||
id: string
|
||
order: number
|
||
type: string
|
||
data: Record<string, unknown>
|
||
}
|
||
|
||
/** WEB-0b: CMS-Seite der öffentlichen Webseite. */
|
||
export interface MockPage {
|
||
id: string
|
||
slug: string
|
||
title: string
|
||
seoDescription: string | null
|
||
status: 'Draft' | 'Published'
|
||
blocks: MockBlock[]
|
||
}
|
||
|
||
/** ABGABE: Abgabevertrag (mirrors SaleContract in src/api/contracts.ts). */
|
||
export interface MockContract {
|
||
id: string
|
||
contactId: string
|
||
price: number
|
||
handoverDate: string
|
||
contractDate: string
|
||
fileName: string
|
||
createdAt: string
|
||
gerbilIds: string[]
|
||
url: string
|
||
/** Ob die Word-Datei vorliegt. Importierte Verträge: false (kein Word-Button). */
|
||
hasFile: boolean
|
||
}
|
||
|
||
export interface MockDb {
|
||
gerbils: Gerbil[]
|
||
litters: Litter[]
|
||
enclosures: Enclosure[]
|
||
contacts: Contact[]
|
||
colorVarieties: { id: string; name: string; canonicalGenotype: string | null; sortOrder: number }[]
|
||
healthRecords: HealthRecordRow[]
|
||
weightRecords: WeightRecordRow[]
|
||
pages: MockPage[]
|
||
// INBOX-1: Anfragen-Posteingang. Die Flags steuern die Fehlerpfade des Mocks
|
||
// (Specs können sie pro Test umlegen): KI-Entwurf 503 / Sync+Send Mail-Fehler.
|
||
requests: InboxRequest[]
|
||
aiConfigured: boolean
|
||
mailConfigured: boolean
|
||
// ABGABE: Verträge + KI-Inserat-Flag
|
||
contracts: MockContract[]
|
||
saleAdConfigured: boolean
|
||
// FEAT-NAMEGEN: Namensvorschläge — false = 503 NamesKeyMissing simulieren
|
||
namesConfigured: boolean
|
||
// FEEDBACK: "Fehler melden" — gesammelte Berichte (POST /feedback)
|
||
feedback: Record<string, unknown>[]
|
||
}
|
||
|
||
function gerbil(
|
||
id: string,
|
||
name: string,
|
||
gender: 'male' | 'female',
|
||
dateOfBirth: string,
|
||
litterId: string | null,
|
||
colorVarietyId: string | null,
|
||
genotype: string | null = null,
|
||
): Gerbil {
|
||
return {
|
||
id,
|
||
name,
|
||
gender,
|
||
status: 'Breeding',
|
||
dateOfBirth,
|
||
dateOfDeath: null,
|
||
causeOfDeath: null,
|
||
goHomeDate: null,
|
||
litterId,
|
||
enclosureId: null,
|
||
colorVarietyId,
|
||
originContactId: null,
|
||
receiverContactId: null,
|
||
originBreeder: null,
|
||
// SEARCH-2b: backend stores a normalized name (lowercase, [\s._-] stripped).
|
||
nameSearch: name.toLowerCase().replace(/[\s._-]/g, ''),
|
||
genotype,
|
||
notes: null,
|
||
// BESTAND-FILTER: default = eigener Bestand; einzelne externe Ahnen unten gesetzt.
|
||
isResident: true,
|
||
profilePhotoUrl: null,
|
||
}
|
||
}
|
||
|
||
/** Frischer, unabhängiger Datenbestand pro Test. */
|
||
export function seedDb(): MockDb {
|
||
const gerbils: Gerbil[] = [
|
||
{
|
||
...gerbil('kruemel', 'Krümel', 'female', '2025-03-12', 'w-kruemel', 'cv-agouti', 'Aa CC DD EE GG Pp spsp rere'),
|
||
enclosureId: 'enc-gross',
|
||
originContactId: 'con-meier',
|
||
originBreeder: 'Clan-Kleine-Chaoten',
|
||
// NACHVERFOLGUNG: Datenherkunft (vom Import erzeugter JSON-String).
|
||
provenance: JSON.stringify({
|
||
sourceFiles: ['Stammbaum von Krümel.xlsx', 'Wurfchronik-Detail.docx'],
|
||
mergedRecordCount: 2,
|
||
fromWurfchronik: true,
|
||
parentMethod: 'chart-position',
|
||
parentConfidence: 'medium',
|
||
notes: ['aus 2 Datensätzen zusammengeführt'],
|
||
history: [
|
||
'In „Stammbaum von Krümel.xlsx“ gefunden.',
|
||
'Geburtsdatum (12.03.2025) aus „Stammbaum von Krümel.xlsx“.',
|
||
'Genotyp aus „Stammbaum von Krümel.xlsx“.',
|
||
'Auch in „Wurfchronik-Detail.docx“ gefunden → Datensätze zusammengeführt.',
|
||
// VERWORFEN: abweichender Wert aus einer Quelle wurde verworfen (Grund + Ersatz).
|
||
'⚠ Geburtsdatum 14.06.2015 aus „Wurfchronik-Detail.docx“ verworfen — abweichend; 12.03.2025 aus „Stammbaum von Krümel.xlsx“ verwendet (Mehrheit).',
|
||
'Eltern über Position im Stammbaum erkannt (Quelle: „Stammbaum von Krümel.xlsx“).',
|
||
// VERWORFEN: unplausibler Elternteil verworfen, kein Ersatz verwendet.
|
||
'⚠ Vater „Jayjay“ (*19.06.2013) verworfen — unplausibel (9 Jahre älter als das Kind); kein Ersatz.',
|
||
'Angaben aus der Wurfchronik übernommen.',
|
||
],
|
||
}),
|
||
},
|
||
{ ...gerbil('fridolin', 'Fridolin', 'male', '2023-05-01', 'w-fridolin', 'cv-schwarz', 'aa CC DD EE GG PP spsp rere'), enclosureId: 'enc-gross', originBreeder: 'Zoohandlung Meier', profilePhotoUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==' },
|
||
gerbil('luna', 'Luna', 'female', '2023-08-15', 'w-luna', 'cv-gold', 'AA CC DD EE GG pp spsp rere'),
|
||
gerbil('balu', 'Balu', 'male', '2021-04-20', 'w-balu', 'cv-agouti'),
|
||
gerbil('maja', 'Maja', 'female', '2021-06-11', null, 'cv-schwarz-schecke', 'aa CC DD EE GG PP Spsp rere'),
|
||
gerbil('karlsson', 'Karlsson', 'male', '2022-02-02', 'w-karlsson', 'cv-blau'),
|
||
gerbil('smilla', 'Smilla', 'female', '2022-09-30', null, 'cv-himalaya'),
|
||
gerbil('anton', 'Anton', 'male', '2019-07-07', 'w-anton', 'cv-agouti'),
|
||
gerbil('greta', 'Greta', 'female', '2019-05-23', null, 'cv-schwarz'),
|
||
gerbil('frieda', 'Frieda', 'female', '2020-01-18', null, 'cv-gold'),
|
||
gerbil('emil', 'Emil', 'male', '2017-03-03', 'w-emil', 'cv-agouti'),
|
||
// BESTAND-FILTER: externe Ahnen (Gründertiere fremder Zuchten, nur für den Stammbaum).
|
||
{ ...gerbil('hilde', 'Hilde', 'female', '2017-11-11', null, 'cv-schwarz'), isResident: false, originBreeder: 'Zoohandlung Meier' },
|
||
{ ...gerbil('max', 'Max', 'male', '2015-08-08', null, 'cv-agouti'), isResident: false, originBreeder: 'Zoohandlung Meier' },
|
||
// Statistik: Verstorbene + Abgegebene für Verluste/Bestandskurve
|
||
{ ...gerbil('willi', 'Willi', 'male', '2019-09-09', null, 'cv-schwarz'), status: 'Deceased', dateOfDeath: '2022-04-04' },
|
||
{ ...gerbil('pauli', 'Pauli', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-huber' },
|
||
{ ...gerbil('rosa', 'Rosa', 'female', '2020-02-02', null, 'cv-gold'), status: 'Deceased', dateOfDeath: '2023-08-15' },
|
||
{ ...gerbil('pauli-breeder', 'Pauli Breeder', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-meier' },
|
||
{ ...gerbil('pauli-neither', 'Pauli Neither', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-neither' },
|
||
// ABGABE: zwei Tiere zur Abgabe im Quarantänebecken (enc-leer ist leer, kein Konflikt
|
||
// mit dem Becken-Belegungstest der Großbecken-2-Tiere-Assertion).
|
||
{
|
||
...gerbil('sale-balu', 'Balu Abgabe', 'male', '2024-03-12', null, 'cv-agouti'),
|
||
status: 'ForSale' as const,
|
||
enclosureId: 'enc-leer',
|
||
notes: 'ruhig, nimmt Leckerlis aus der Hand',
|
||
},
|
||
{
|
||
...gerbil('sale-benny', 'Benny Abgabe', 'male', '2024-03-12', null, 'cv-schwarz-schecke'),
|
||
status: 'ForSale' as const,
|
||
enclosureId: 'enc-leer',
|
||
notes: 'neugieriger Entdecker',
|
||
},
|
||
// LITTER-JUVENILE-DETAILS: Jungtiere für Wurf K mit Zusatzinfos.
|
||
// JUVENILE-AVATARS: Pippa hat profilePhotoUrl (1×1-PNG data-URI) → Avatar-Img sichtbar.
|
||
{
|
||
...gerbil('pup-abgabe', 'Pippa', 'female', '2025-03-12', 'w-kruemel', 'cv-gold'),
|
||
status: 'GivenAway' as const,
|
||
goHomeDate: '2025-05-01',
|
||
receiverContactId: 'con-huber',
|
||
profilePhotoUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
|
||
},
|
||
{
|
||
...gerbil('pup-verstorben', 'Benny', 'male', '2025-03-12', 'w-kruemel', null),
|
||
status: 'Deceased' as const,
|
||
dateOfDeath: '2025-04-15',
|
||
causeOfDeath: 'Altersschwäche',
|
||
},
|
||
// UI-POLISH-1: Import-Stub ohne Namen — testet den '(ohne Namen)'-Platzhalter in Liste + Detail.
|
||
gerbil('nameless-stub', '', 'male', '2023-01-01', null, null),
|
||
// SIBLING-PAIRING: Vollgeschwister-Verpaarung (in der Zucht häufig). Beide
|
||
// Eltern von 'inzucht-kind' stammen aus DEMSELBEN Wurf 'w-zwillinge' → das
|
||
// Diagramm führt ihren Vorfahren-Ast zu einem Verweis-Knoten zusammen.
|
||
gerbil('zwilling-bock', 'Zwilling Bock', 'male', '2020-05-01', 'w-zwillinge', 'cv-agouti'),
|
||
gerbil('zwilling-maus', 'Zwilling Maus', 'female', '2020-05-01', 'w-zwillinge', 'cv-schwarz'),
|
||
{ ...gerbil('opa-w', 'Opa W', 'male', '2018-01-01', null, 'cv-agouti'), isResident: false },
|
||
{ ...gerbil('oma-u', 'Oma U', 'female', '2018-02-01', null, 'cv-gold'), isResident: false },
|
||
gerbil('inzucht-kind', 'Inzucht Kind', 'female', '2021-06-01', 'w-inzucht', 'cv-agouti'),
|
||
]
|
||
|
||
const litters: Litter[] = [
|
||
{ id: 'w-zwillinge', name: 'Wurf Z', date: '2020-05-01', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'opa-w', motherId: 'oma-u' },
|
||
{ id: 'w-inzucht', name: 'Wurf I', date: '2021-06-01', totalBorn: 3, expectedGoHomeDate: null, notes: null, fatherId: 'zwilling-bock', motherId: 'zwilling-maus' },
|
||
{
|
||
id: 'w-kruemel', name: 'Wurf K', date: '2025-03-12', totalBorn: 5, expectedGoHomeDate: '2025-04-16', notes: null, fatherId: 'fridolin', motherId: 'luna', deathsWithin8Weeks: 1,
|
||
// NACHVERFOLGUNG: Datenherkunft des Wurfs (vom Import erzeugter JSON-String).
|
||
provenance: JSON.stringify({
|
||
sourceFiles: ['Wurfchronik Teil 1_page_0009.md', 'Wurfchronik Teil 1_page_0028.md'],
|
||
mergedRecordCount: 2,
|
||
fromWurfchronik: true,
|
||
notes: ['aus Wurfchronik', 'aus 2 Datensätzen zusammengeführt', 'Geschwister-Würfe zusammengeführt'],
|
||
history: [
|
||
'Wurf aus Wurfchronik „Wurfchronik Teil 1_page_0009.md“.',
|
||
'Auch in „Wurfchronik Teil 1_page_0028.md“ gefunden → Datensätze zusammengeführt.',
|
||
'Geschwister-Würfe zusammengeführt.',
|
||
],
|
||
}),
|
||
},
|
||
{ id: 'w-fridolin', name: 'Wurf F', date: '2023-05-01', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'balu', motherId: 'maja' },
|
||
{ id: 'w-luna', name: 'Wurf L', date: '2023-08-15', totalBorn: 6, expectedGoHomeDate: null, notes: null, fatherId: 'karlsson', motherId: 'smilla' },
|
||
{ id: 'w-balu', name: 'Wurf B', date: '2021-04-20', totalBorn: 3, expectedGoHomeDate: null, notes: null, fatherId: 'anton', motherId: 'greta' },
|
||
// Anton ist auch Karlssons Vater -> gemeinsamer Vorfahre (Inzucht-Demo)
|
||
{ id: 'w-karlsson', name: 'Wurf C', date: '2022-02-02', totalBorn: 5, expectedGoHomeDate: null, notes: null, fatherId: 'anton', motherId: 'frieda' },
|
||
{ id: 'w-anton', name: 'Wurf A', date: '2019-07-07', totalBorn: 4, expectedGoHomeDate: null, notes: null, fatherId: 'emil', motherId: 'hilde' },
|
||
{ id: 'w-emil', name: 'Wurf E', date: '2017-03-03', totalBorn: 2, expectedGoHomeDate: null, notes: null, fatherId: 'max', motherId: null },
|
||
]
|
||
|
||
const enclosures: Enclosure[] = [
|
||
{ id: 'enc-gross', name: 'Großbecken', notes: '120×50 cm' },
|
||
{ id: 'enc-leer', name: 'Quarantänebecken', notes: null },
|
||
]
|
||
|
||
// FEAT-13: contactInfo (Freitext) wurde durch strukturierte Felder ersetzt.
|
||
const contacts: Contact[] = [
|
||
{
|
||
id: 'con-meier', name: 'Zoohandlung Meier', email: 'meier@example.de', phone: null, address: 'Hauptstraße 1, 12345 Musterstadt', notes: null, isBreeder: true, isReceiver: true,
|
||
// NACHVERFOLGUNG: Datenherkunft des Kontakts (vom Import erzeugter JSON-String).
|
||
provenance: JSON.stringify({
|
||
sourceFiles: ['Wurfchronik Teil 1_page_0001.md', 'Stammbaum von Krümel.xlsx'],
|
||
mergedRecordCount: 2,
|
||
fromWurfchronik: true,
|
||
notes: ['aus 2 Datensätzen zusammengeführt', 'als Züchter erkannt', 'als Abnehmer erkannt'],
|
||
history: [
|
||
'In „Stammbaum von Krümel.xlsx“ als Züchter erkannt.',
|
||
'Auch in „Wurfchronik Teil 1_page_0001.md“ gefunden → Datensätze zusammengeführt.',
|
||
'Sowohl als Züchter als auch als Abnehmer geführt.',
|
||
],
|
||
}),
|
||
},
|
||
{ id: 'con-huber', name: 'Familie Huber', email: null, phone: '0151 2345678', address: null, notes: null, isBreeder: false, isReceiver: true },
|
||
{ id: 'con-frei', name: 'Züchterin Frei', email: null, phone: null, address: null, notes: 'unverknüpft', isBreeder: true, isReceiver: false },
|
||
{ id: 'con-neither', name: 'Weder Noch', email: null, phone: null, address: null, notes: 'weder züchter noch abnehmer', isBreeder: false, isReceiver: false },
|
||
]
|
||
|
||
const colorVarieties = [
|
||
{ id: 'cv-agouti', name: 'Agouti', canonicalGenotype: 'AA CC DD EE GG PP spsp rere', sortOrder: 1 },
|
||
{ id: 'cv-schwarz', name: 'Schwarz', canonicalGenotype: 'aa CC DD EE GG PP spsp rere', sortOrder: 2 },
|
||
{ id: 'cv-gold', name: 'Gold', canonicalGenotype: 'AA CC DD EE GG pp spsp rere', sortOrder: 3 },
|
||
{ id: 'cv-blau', name: 'Blau', canonicalGenotype: 'aa CC dd EE GG PP spsp rere', sortOrder: 4 },
|
||
{ id: 'cv-himalaya', name: 'Himalaya', canonicalGenotype: 'AA chch DD EE GG PP spsp rere', sortOrder: 5 },
|
||
{ id: 'cv-schwarz-schecke', name: 'Schwarz Schecke', canonicalGenotype: 'aa CC DD EE GG PP Spsp rere', sortOrder: 6 },
|
||
]
|
||
|
||
const healthRecords: HealthRecordRow[] = [
|
||
{
|
||
id: 'hr-1',
|
||
gerbilId: 'kruemel',
|
||
date: '2026-01-15',
|
||
type: 'Vaccination',
|
||
description: 'Jahresimpfung',
|
||
veterinarian: 'Dr. Vogel',
|
||
createdAt: '2026-01-15T10:00:00Z',
|
||
},
|
||
]
|
||
|
||
const weightRecords: WeightRecordRow[] = [
|
||
{ id: 'wr-1', gerbilId: 'kruemel', date: '2026-05-01', weightGrams: 78, notes: null },
|
||
{ id: 'wr-2', gerbilId: 'kruemel', date: '2026-05-20', weightGrams: 82, notes: null },
|
||
]
|
||
|
||
// WEB-0b: die 6 Webseiten-Seiten (wie vom Backend geseedet), mit ein paar Blöcken.
|
||
const pages: MockPage[] = [
|
||
{
|
||
id: 'page-start',
|
||
slug: 'start',
|
||
title: 'Startseite',
|
||
seoDescription: 'Willkommen bei unserer Rennmauszucht.',
|
||
status: 'Published',
|
||
blocks: [
|
||
{ id: 'blk-start-1', order: 0, type: 'Heading', data: { text: 'Willkommen', level: 2 } },
|
||
{ id: 'blk-start-2', order: 1, type: 'RichText', data: { markdown: 'Schön, dass du da bist.' } },
|
||
],
|
||
},
|
||
{ id: 'page-zucht', slug: 'ueber-die-zucht', title: 'Über die Zucht', seoDescription: null, status: 'Draft', blocks: [] },
|
||
{
|
||
id: 'page-abgabe',
|
||
slug: 'abgabetiere',
|
||
title: 'Abgabetiere',
|
||
seoDescription: null,
|
||
status: 'Published',
|
||
blocks: [
|
||
{ id: 'blk-abgabe-1', order: 0, type: 'AbgabetiereList', data: { mode: 'auto', intro: 'Diese Tiere suchen ein Zuhause.' } },
|
||
],
|
||
},
|
||
{ id: 'page-bedingungen', slug: 'abgabebedingungen', title: 'Abgabebedingungen', seoDescription: null, status: 'Draft', blocks: [] },
|
||
{ id: 'page-farben', slug: 'farben-genetik', title: 'Farben & Genetik', seoDescription: null, status: 'Draft', blocks: [] },
|
||
{ id: 'page-kontakt', slug: 'kontakt', title: 'Kontakt', seoDescription: null, status: 'Draft', blocks: [] },
|
||
]
|
||
|
||
// INBOX-1: Anfragen (neueste zuerst nach receivedAt sortierbar)
|
||
const requests: InboxRequest[] = [
|
||
{
|
||
id: 'req-anna',
|
||
gmailMessageId: '<anna-1@mail.example>',
|
||
threadId: 'thr-1',
|
||
fromAddress: 'anna@example.de',
|
||
fromName: 'Anna Albrecht',
|
||
subject: 'Anfrage: Pärchen zur Abgabe?',
|
||
bodyText:
|
||
'Hallo,\n\nich habe euer Inserat gesehen — sind die beiden Agouti-Jungs noch zu haben?\n\nViele Grüße\nAnna',
|
||
receivedAt: '2026-06-05T18:30:00Z',
|
||
status: 'New',
|
||
assignedContactId: null,
|
||
draftReply: null,
|
||
answeredAt: null,
|
||
},
|
||
{
|
||
id: 'req-ben',
|
||
gmailMessageId: '<ben-1@mail.example>',
|
||
threadId: 'thr-2',
|
||
fromAddress: 'ben@example.org',
|
||
fromName: 'Ben Berger',
|
||
subject: 'Frage zur Haltung',
|
||
bodyText: 'Guten Tag, was für ein Becken empfehlt ihr für zwei Rennmäuse?',
|
||
receivedAt: '2026-06-04T09:00:00Z',
|
||
status: 'InProgress',
|
||
assignedContactId: null,
|
||
draftReply: 'Hallo Ben, wir empfehlen mindestens 100×50 cm …',
|
||
answeredAt: null,
|
||
},
|
||
{
|
||
id: 'req-clara',
|
||
gmailMessageId: '<clara-1@mail.example>',
|
||
threadId: null,
|
||
fromAddress: 'clara@example.com',
|
||
fromName: null,
|
||
subject: 'Danke!',
|
||
bodyText: 'Die beiden sind super angekommen — danke nochmal!',
|
||
receivedAt: '2026-06-01T12:00:00Z',
|
||
status: 'Answered',
|
||
assignedContactId: 'con-meier',
|
||
draftReply: null,
|
||
answeredAt: '2026-06-02T08:00:00Z',
|
||
},
|
||
]
|
||
|
||
return {
|
||
gerbils,
|
||
litters,
|
||
enclosures,
|
||
contacts,
|
||
colorVarieties,
|
||
healthRecords,
|
||
weightRecords,
|
||
pages,
|
||
requests,
|
||
aiConfigured: true,
|
||
mailConfigured: true,
|
||
// Ein importierter Vertrag (hasFile:false) prüft die Word-Button-Ausblendung.
|
||
contracts: [
|
||
{
|
||
id: 'contract-import-1',
|
||
contactId: 'con-meier',
|
||
price: 30,
|
||
handoverDate: '2023-09-29',
|
||
contractDate: '2023-09-29',
|
||
fileName: 'Zucht der kleinen Chaoten _ (Krümel) - Zoohandlung Meier_.docx',
|
||
createdAt: '2023-09-29T00:00:00Z',
|
||
gerbilIds: ['kruemel'],
|
||
url: '/contracts/contract-import-1/file',
|
||
hasFile: false,
|
||
},
|
||
],
|
||
saleAdConfigured: true,
|
||
namesConfigured: true,
|
||
// FEEDBACK-TICKETS: ein offenes + ein gelöstes Ticket (GET liefert neueste zuerst →
|
||
// db.feedback wird im Mock umgekehrt; das offene steht hier zuletzt = erscheint oben).
|
||
feedback: [
|
||
{
|
||
id: 'feedback-resolved',
|
||
message: 'Der Stammbaum zeigt den falschen Vater bei Krümel.',
|
||
context: 'stammbaum',
|
||
gerbilId: 'kruemel',
|
||
litterId: null,
|
||
contactId: null,
|
||
entityName: 'Krümel',
|
||
url: 'http://localhost:5173/rennmaeuse/kruemel/stammbaum',
|
||
clientTimestamp: '2026-06-10T09:00:00Z',
|
||
userAgent: null,
|
||
createdAt: '2026-06-10T09:00:00Z',
|
||
status: 'Resolved',
|
||
resolvedAt: '2026-06-12T14:00:00Z',
|
||
question: null,
|
||
answer: null,
|
||
answeredAt: null,
|
||
},
|
||
{
|
||
// Ticket mit offener Rückfrage (NeedsInfo): die Züchterin soll hier antworten können.
|
||
id: 'feedback-needsinfo',
|
||
message: 'Der Wurf B hat ein falsches Geburtsdatum.',
|
||
context: 'litter-detail',
|
||
gerbilId: null,
|
||
litterId: 'wurf-b',
|
||
contactId: null,
|
||
entityName: 'Wurf B',
|
||
url: 'http://localhost:5173/wuerfe/wurf-b',
|
||
clientTimestamp: '2026-06-14T08:00:00Z',
|
||
userAgent: null,
|
||
createdAt: '2026-06-14T08:00:00Z',
|
||
status: 'NeedsInfo',
|
||
resolvedAt: null,
|
||
question: 'Welches Geburtsdatum ist korrekt — der 1. oder der 2. Juni?',
|
||
answer: null,
|
||
answeredAt: null,
|
||
},
|
||
{
|
||
id: 'feedback-open',
|
||
message: 'Das Gewicht wird auf der Verlaufskurve falsch gerundet.',
|
||
context: 'gerbil-detail',
|
||
gerbilId: 'fridolin',
|
||
litterId: null,
|
||
contactId: null,
|
||
entityName: 'Fridolin',
|
||
url: 'http://localhost:5173/rennmaeuse/fridolin',
|
||
clientTimestamp: '2026-06-15T11:30:00Z',
|
||
userAgent: null,
|
||
createdAt: '2026-06-15T11:30:00Z',
|
||
status: 'Open',
|
||
resolvedAt: null,
|
||
question: null,
|
||
answer: null,
|
||
answeredAt: null,
|
||
},
|
||
],
|
||
}
|
||
}
|