diff --git a/GerbilManager.Tests/SaleAdTests.cs b/GerbilManager.Tests/SaleAdTests.cs index 2134915..11c99bd 100644 --- a/GerbilManager.Tests/SaleAdTests.cs +++ b/GerbilManager.Tests/SaleAdTests.cs @@ -67,9 +67,9 @@ namespace GerbilManager.Tests Assert.Contains("Zucht der kleinen Chaoten", prompt); Assert.Contains("NIEMALS Preise", prompt); Assert.Contains("KEINE Fakten erfinden", prompt); - // few-shot: die beiden Beispiel-Inserate (Status-Zeilen der Vorlage) + // few-shot: verbatim listings from kleine-chaoten.jimdofree.com + Assert.Contains("Status: LOCKER RESERVIERT LEONIE", prompt); Assert.Contains("Status: FREI", prompt); - Assert.Contains("Status: LOCKER RESERVIERT Anna", prompt); Assert.Contains("Großer und kleiner Bruder Dynamik", prompt); } diff --git a/GerbilManagerWebAPI/SaleAd/SaleAdPromptBuilder.cs b/GerbilManagerWebAPI/SaleAd/SaleAdPromptBuilder.cs index 73f75b9..2c949f4 100644 --- a/GerbilManagerWebAPI/SaleAd/SaleAdPromptBuilder.cs +++ b/GerbilManagerWebAPI/SaleAd/SaleAdPromptBuilder.cs @@ -1,4 +1,4 @@ -using System.Globalization; +using System.Globalization; using System.Text; namespace GerbilManagerWebAPI.SaleAd @@ -13,37 +13,31 @@ namespace GerbilManagerWebAPI.SaleAd public static class SaleAdPromptBuilder { /// - /// Two few-shot examples derived from the documented listing template. - /// NOTE: the research file documents the format and one real tagline; - /// these examples are synthesized to that template. Swap in verbatim - /// listings from kleine-chaoten.jimdofree.com when available. + /// Two verbatim listings from kleine-chaoten.jimdofree.com (retrieved 2026-06-06). + /// These are real listings in the breeder's own voice — the strongest grounding source. /// private const string ExampleListing1 = """ - Status: FREI + Status: LOCKER RESERVIERT LEONIE - „Großer und kleiner Bruder Dynamik“ – Männliches Rennmaus-Duo sucht ein liebevolles Zuhause + „Großer und kleiner Bruder Dynamik” – Männliches Rennmaus Duo sucht ein liebevolles Zuhause - Balu – CP-Agouti, geboren am 12.03.2024 - Balu ist der ruhige Pol des Duos: Er beobachtet erst in aller Seelenruhe und buddelt sich dann zielstrebig durch jedes Einstreu-Projekt. Aus der Hand nimmt er Leckerlis schon ganz vorsichtig. + Hier steht ein außergewöhnlich harmonisches Rennmaus-Duo zur Abgabe frei, das nicht nur durch seinen Charakter, sondern auch durch seine besondere Geschichte überzeugt. Die zwei verhalten sich als ob sie Brüder wären und sind unzertrennlich. Das Duo kann ab sofort ausziehen. - Benny – Schwarz Schecke, geboren am 12.03.2024 - Benny ist der Entdecker: kein Röhrchen bleibt unerforscht, kein Häuschen unbewohnt. Mit seinem Bruder kuschelt er sich abends ins Nest — getrennt werden die beiden deshalb nicht. + Der Ältere von beiden trägt die Farbe CP-Agouti und ist am 11.11.2025 geboren. Er ist ein neugieriger, aktiver und sozialer kleiner Kerl. Er liebt es, seine Umgebung zu erkunden, ist gerne in Bewegung und bringt dabei eine angenehme Ausgeglichenheit mit. Er wirkt sicher, aufmerksam und zeigt ein rundum unkompliziertes Wesen. - Die zwei werden nur gemeinsam in ein rennmausgerechtes Zuhause abgegeben. + Sein, etwas jünger, Partner trägt die Farbe Schwarz Schecke und ist am 26.02.2026 geboren. Statt des in diesem Alter oft typischen Kräftemessens zeigt er ein erstaunlich sanftes, ruhiges und sehr soziales Verhalten. Man merkt ihm an, wie feinfühlig und verträglich er ist. """; private const string ExampleListing2 = """ - Status: LOCKER RESERVIERT Anna + Status: FREI - „Zwei Schwestern, ein Herz und ganz viel Neugier“ – Weibliches Duo sucht seine Menschen + „Unzertrennliches Mutter-Tochter Duo sucht gemeinsam neue Körnergeber” - Frieda – Gold, geboren am 28.06.2024 - Frieda ist die Mutige der beiden und steht beim Öffnen des Geheges sofort am Glas. Sie liebt Kolbenhirse und nimmt sie dir behutsam aus den Fingern. + Die Mama ist am 13.08.2025 geboren und trägt die Farbe Kohlfuchs. Sie ist eine eher ruhige und vorsichtige Rennmaus, die neuen Situationen zunächst aufmerksam begegnet. Gibt man ihr etwas Zeit, zeigt sich schnell ihre neugierige Seite und sie kommt gerne schauen, was im Gehege passiert. Sie ist ein echtes Energiebündel, liebt es zu buddeln, zu rennen und alles an Beschäftigungsmaterial – besonders Klorollen – kreativ umzugestalten. Charakterlich zeigt sie sich sehr sozial und friedlich; besonders hervorzuheben ist ihre fürsorgliche Art als Mutter. - Fine – Agouti, geboren am 28.06.2024 - Fine ist etwas zurückhaltender, taut aber neben ihrer Schwester schnell auf. Beim abendlichen Buddeln sind die zwei ein unschlagbares Team. + Die Tochter ist am 26.02.2026 geboren und trägt die Farbe Agouti-Schecke. Sie ist ihrer Mama sehr ähnlich, dabei aber schon ein kleines bisschen mutiger. Auch sie beobachtet erst gerne in Ruhe, bevor die Neugier siegt. Sie ist sehr anhänglich und orientiert sich stark an ihrer Mutter. - Die Schwestern ziehen selbstverständlich nur zusammen um. + Die zwei werden nur gemeinsam in ein rennmausgerechtes Zuhause abgegeben. """; /// System prompt: role, style description, hard rules, few-shot examples. diff --git a/gerbil-manager-web/e2e/abgabe.spec.ts b/gerbil-manager-web/e2e/abgabe.spec.ts new file mode 100644 index 0000000..b62487c --- /dev/null +++ b/gerbil-manager-web/e2e/abgabe.spec.ts @@ -0,0 +1,129 @@ +/** + * QA-2: Abgabe-Wizard (FEAT-12a + FEAT-13) — Abgabetiere gruppieren, + * KI-Inserat generieren, und Vertrag-Handoff via ?tiere= Query-Parameter. + * + * Mock-gebunden (Seed: 2 ForSale-Tiere im Quarantänebecken) → skipUnlessMock. + */ +import { de, expect, gotoSection, skipUnlessMock, test } from './fixtures' + +const ta = de.pages.abgabe +const tw = de.pages.vertraege.wizard + +test.describe('Abgabe', () => { + test('Abgabe-Seite zeigt ForSale-Tiere und Inserat-Vorschau', async ({ page }) => { + skipUnlessMock() + await gotoSection(page, de.nav.forSale) + await expect(page.getByRole('heading', { name: ta.title, exact: true })).toBeVisible() + await expect(page.getByText(ta.grouping.title)).toBeVisible() + + // Die beiden ForSale-Tiere müssen als Tiername (strong-Element im Komposer) sichtbar sein + await expect(page.locator('.group-composer__animal-head strong').filter({ hasText: 'Balu Abgabe' })).toBeVisible() + await expect(page.locator('.group-composer__animal-head strong').filter({ hasText: 'Benny Abgabe' })).toBeVisible() + + // Inserat-Vorschau (listing-preview) wird gerendert + await expect(page.locator('.listing-preview').first()).toBeVisible() + // Status-Dropdown standard: FREI + await expect( + page.locator('label.field').filter({ has: page.locator(`span:text-is("${ta.listing.status}")`) }).locator('select').first() + ).toHaveValue('free') + }) + + test('KI-Inserat: "Text mit KI verbessern" — Mock liefert Inserat-Text', async ({ page }) => { + skipUnlessMock() + await gotoSection(page, de.nav.forSale) + await expect(page.locator('.group-composer__animal-head strong').filter({ hasText: 'Balu Abgabe' })).toBeVisible() + + await page.getByRole('button', { name: ta.ai.improve }).first().click() + // Mock antwortet synchron; der generierte Text erscheint in der Vorschau + await expect(page.locator('.listing-preview').first()).toContainText('Generierter Inserat-Text') + }) + + test('KI-Inserat: 503 zeigt deutschen Hinweis (saleAdConfigured=false)', async ({ page, mockDb }) => { + skipUnlessMock() + mockDb!.saleAdConfigured = false + await gotoSection(page, de.nav.forSale) + await expect(page.locator('.group-composer__animal-head strong').filter({ hasText: 'Balu Abgabe' })).toBeVisible() + + await page.getByRole('button', { name: ta.ai.improve }).first().click() + await expect(page.getByText(ta.ai.notConfigured)).toBeVisible() + }) + + test('Vertrag-Handoff: "Abgabe abschließen" öffnet Wizard mit vorausgewählten Tieren', async ({ + page, + }) => { + skipUnlessMock() + await gotoSection(page, de.nav.forSale) + await expect(page.locator('.group-composer__animal-head strong').filter({ hasText: 'Balu Abgabe' })).toBeVisible() + + // Wizard öffnen (navigiert zu /vertraege/neu?tiere=sale-balu,sale-benny) + await page.getByRole('button', { name: ta.export.finish }).first().click() + await expect(page).toHaveURL(/\/vertraege\/neu\?tiere=/) + await expect(page.getByRole('heading', { name: tw.title })).toBeVisible() + // Schritt 1: Abnehmer auswählen + await expect(page.getByRole('heading', { name: tw.pickContact })).toBeVisible() + }) +}) + +test.describe('Vertrag-Wizard', () => { + test('4-Schritte-Wizard: Abnehmer → Tiere → Preis → Vertrag erzeugen', async ({ page }) => { + skipUnlessMock() + // Direkt mit vorausgewählten Tieren öffnen (wie über Abgabe-Seite) + await page.goto('/vertraege/neu?tiere=sale-balu,sale-benny') + await expect(page.getByRole('heading', { name: tw.title })).toBeVisible() + + // Schritt 1: Abnehmer wählen — Zoohandlung Meier aus dem Seed + await expect(page.getByRole('heading', { name: tw.pickContact })).toBeVisible() + await page.locator('.wizard-pick').filter({ hasText: 'Zoohandlung Meier' }).getByRole('radio').check() + await page.getByRole('button', { name: tw.next }).click() + + // Schritt 2: Tiere — Vorauswahl aus ?tiere= ist aktiv + await expect(page.getByRole('heading', { name: tw.pickAnimals })).toBeVisible() + // Beide ForSale-Tiere sind vorausgewählt (Checkboxen angehakt) + await expect( + page.locator('.wizard-pick').filter({ hasText: 'Balu Abgabe' }).getByRole('checkbox') + ).toBeChecked() + await expect( + page.locator('.wizard-pick').filter({ hasText: 'Benny Abgabe' }).getByRole('checkbox') + ).toBeChecked() + await page.getByRole('button', { name: tw.next }).click() + + // Schritt 3: Preis & Datum + await page.getByLabel(tw.priceLabel).fill('50,00') + await page.getByRole('button', { name: tw.next }).click() + + // Schritt 4: Zusammenfassung + Erzeugen + await expect(page.getByText(tw.summaryTitle)).toBeVisible() + await expect(page.getByText('Zoohandlung Meier')).toBeVisible() + await expect(page.getByText('Balu Abgabe')).toBeVisible() + await page.getByRole('button', { name: tw.generate }).click() + + // Erfolg: Vertrag erstellt, Download-Link vorhanden + await expect(page.getByRole('heading', { name: tw.successTitle })).toBeVisible() + await expect(page.getByRole('link', { name: tw.downloadDocx })).toBeVisible() + }) + + test('Schritt 1: fehlender Abnehmer zeigt Validierungsfehler', async ({ page }) => { + skipUnlessMock() + await page.goto('/vertraege/neu?tiere=sale-balu') + await expect(page.getByRole('heading', { name: tw.pickContact })).toBeVisible() + // Ohne Auswahl direkt auf Weiter klicken → Fehler-Alert erscheint + await page.getByRole('button', { name: tw.next }).click() + // Alert enthält den Hinweis (stepError), Schritt bleibt auf 1 + await expect(page.locator('.alert--error').filter({ hasText: tw.pickContact })).toBeVisible() + }) + + test('Schritt 3: ungültiger Preis zeigt Validierungsmeldung', async ({ page }) => { + skipUnlessMock() + await page.goto('/vertraege/neu?tiere=sale-balu') + // Schritt 1: Abnehmer wählen + await page.locator('.wizard-pick').filter({ hasText: 'Zoohandlung Meier' }).getByRole('radio').check() + await page.getByRole('button', { name: tw.next }).click() + // Schritt 2: Tiere bestätigen + await expect(page.getByRole('heading', { name: tw.pickAnimals })).toBeVisible() + await page.getByRole('button', { name: tw.next }).click() + // Schritt 3: leeres Preisfeld -> Fehler + await expect(page.getByLabel(tw.priceLabel)).toBeVisible() + await page.getByRole('button', { name: tw.next }).click() + await expect(page.locator('.alert--error').filter({ hasText: tw.priceInvalid })).toBeVisible() + }) +}) diff --git a/gerbil-manager-web/e2e/becken-kontakte.spec.ts b/gerbil-manager-web/e2e/becken-kontakte.spec.ts index be82dcf..7a8feb8 100644 --- a/gerbil-manager-web/e2e/becken-kontakte.spec.ts +++ b/gerbil-manager-web/e2e/becken-kontakte.spec.ts @@ -72,7 +72,13 @@ test.describe('Kontakte', () => { test('Kontakt anlegen und (unverknüpft) wieder löschen', async ({ page }) => { await gotoSection(page, de.nav.contacts) + // Explizit auf die Listenüberschrift warten, bevor wir klicken — verhindert den + // CI-Flake, der auftrat wenn der Vite-Server unter paralleler Last noch nicht + // alle Routen der Seite gerendert hatte (gotoSection klickt nur den Nav-Link, + // wartet aber nicht auf den vollständigen Seitenaufbau). + await expect(page.getByRole('heading', { name: tk.title, exact: true })).toBeVisible() await page.getByRole('link', { name: tk.newButton }).click() + await expect(page.getByRole('heading', { name: tk.form.createTitle })).toBeVisible() const name = uniqueName('Kontakt') await page.getByLabel(`${tk.fields.name} *`).fill(name) // FEAT-13: strukturierte Felder statt Freitext-Kontaktdaten diff --git a/gerbil-manager-web/e2e/charakter.spec.ts b/gerbil-manager-web/e2e/charakter.spec.ts index 1cfdc50..65bcfc1 100644 --- a/gerbil-manager-web/e2e/charakter.spec.ts +++ b/gerbil-manager-web/e2e/charakter.spec.ts @@ -17,5 +17,10 @@ test('Charakterbogen: Eigenschaft umschalten, speichern und im Abgabe-Komposer w // Tier zur Abgabe stellen -> erscheint im /abgabe-Komposer mit gesetzter Eigenschaft. await page.getByRole('button', { name: de.pages.abgabe.markAction }).click() await page.goto('/abgabe') - await expect(page.getByRole('checkbox', { name: 'neugierig' })).toBeChecked() + // Auf Krümels Tier-Abschnitt eingrenzen — andere ForSale-Tiere haben ebenfalls + // Charakterbögen, deshalb darf die Assertion nicht seitenbreit nach 'neugierig' suchen. + const kruemelSection = page.locator('.group-composer__animal').filter({ + has: page.locator('strong', { hasText: 'Krümel' }), + }) + await expect(kruemelSection.getByRole('checkbox', { name: 'neugierig' })).toBeChecked() }) diff --git a/gerbil-manager-web/e2e/fixtures.ts b/gerbil-manager-web/e2e/fixtures.ts index 6898651..40a33a6 100644 --- a/gerbil-manager-web/e2e/fixtures.ts +++ b/gerbil-manager-web/e2e/fixtures.ts @@ -39,6 +39,17 @@ export function skipUnlessMock() { test.skip(LIVE, 'benötigt die Mock-Seed-Daten (läuft nicht im LIVE-Modus)') } +/** + * Test überspringen im Mock-Modus — nur im LIVE-Modus sinnvoll. + * Live-Tests MÜSSEN Ergebnisse assertieren (z. B. Liste nicht leer, + * konkreter Wert sichtbar), nicht nur HTTP-200. Lesson: der Mock spiegelt + * den Frontend-Code, nicht das echte Backend — ein Bug bleibt unsichtbar, + * wenn wir nur testen, ob die Seite lädt. + */ +export function skipUnlessLive() { + test.skip(!LIVE, 'nur im LIVE-Modus sinnvoll (E2E_BASE_URL setzen)') +} + /** * Zu einem Navigationsziel wechseln — auf dem Smartphone liegen Becken, * Kontakte und Statistik hinter dem „Mehr“-Blatt. diff --git a/gerbil-manager-web/e2e/live.spec.ts b/gerbil-manager-web/e2e/live.spec.ts new file mode 100644 index 0000000..ce495eb --- /dev/null +++ b/gerbil-manager-web/e2e/live.spec.ts @@ -0,0 +1,57 @@ +/** + * QA-2: Live-Modus-Smoke — läuft NUR gegen das echte Backend (E2E_BASE_URL). + * + * Lektion (aus dem Such-Bug): Mock-Tests spiegeln nur den Frontend-Code. + * Ein Backend-Bug (falscher Filter, leere Antwort, fehlende Migration) bleibt + * unsichtbar, wenn Live-Tests nur "Seite lädt" prüfen. Diese Tests assertieren + * ERGEBNISSE: Liste nicht leer, Suche liefert Treffer, Detail zeigt Stammdaten + * und Eltern-Links. + * + * Voraussetzung für den Eltern-Link-Test: Re-Import #2 durchgeführt + * (322 Tiere, 882 Würfe live; mindestens ein Wurf hat beide Eltern). + */ +import { de, expect, gotoSection, skipUnlessLive, test } from './fixtures' + +const t = de.pages.gerbils +const tl = de.pages.litters + +test('Live: Tiere-Liste enthält nach Re-Import mindestens 10 Einträge', async ({ page }) => { + skipUnlessLive() + await gotoSection(page, de.nav.gerbils) + await expect(page.getByRole('heading', { name: t.title, exact: true })).toBeVisible() + // Leere Liste = Backend-Bug (falscher Filter, fehlende DB-Migration o. ä.) + const rows = page.locator('.gerbil-row') + await expect(rows.first()).toBeVisible() + expect(await rows.count()).toBeGreaterThanOrEqual(10) +}) + +test('Live: Namenssuche gibt Treffer zurück — nicht nur HTTP 200', async ({ page }) => { + skipUnlessLive() + await gotoSection(page, de.nav.gerbils) + await expect(page.getByRole('heading', { name: t.title, exact: true })).toBeVisible() + // Kurzer Prefix: bei 322 Tieren mindestens 1 Treffer. Kein Treffer = Gridify-Bug. + await page.getByPlaceholder(t.searchPlaceholder).first().fill('a') + const rows = page.locator('.gerbil-row') + await expect(rows.first()).toBeVisible({ timeout: 10_000 }) + expect(await rows.count()).toBeGreaterThanOrEqual(1) +}) + +test('Live: Wurf-Detail zeigt Elterntier-Links (mindestens Vater oder Mutter)', async ({ + page, +}) => { + skipUnlessLive() + // Nach Re-Import #2 hat mindestens Gerbil C (Victoria) beide Eltern. + // Der Wurf-Detail ist die einfachste Ansicht, die Eltern-Links rendert. + await gotoSection(page, de.nav.litters) + await expect(page.getByRole('heading', { name: tl.title, exact: true })).toBeVisible() + + // Mindestens 1 Wurf in der Liste; ersten öffnen. + const firstLink = page.getByRole('link').filter({ hasText: /.+/ }).nth(1) + await expect(firstLink).toBeVisible() + await firstLink.click() + + // Detailseite: Elterntier-Abschnitt ist sichtbar + await expect(page.getByText(tl.detail.parents)).toBeVisible() + // Mindestens ein Link führt zu einem Tier — bei fehlendem Import fehlt dieser Link + await expect(page.locator('a[href*="/rennmaeuse/"]').first()).toBeVisible({ timeout: 5_000 }) +}) diff --git a/gerbil-manager-web/e2e/mock-api.ts b/gerbil-manager-web/e2e/mock-api.ts index dbceadf..1e74a7a 100644 --- a/gerbil-manager-web/e2e/mock-api.ts +++ b/gerbil-manager-web/e2e/mock-api.ts @@ -101,6 +101,7 @@ export async function installMockApi(page: Page): Promise { 'color-varieties': collection(db.colorVarieties as unknown as Row[], 'cv'), 'health-records': collection(db.healthRecords as unknown as Row[], 'hr'), 'weight-records': collection(db.weightRecords as unknown as Row[], 'wr'), + contracts: collection(db.contracts as unknown as Row[], 'contract'), } const handler = async (route: Route) => { @@ -205,6 +206,25 @@ export async function installMockApi(page: Page): Promise { return json(route, 405) } + // ABGABE: KI-Inserat-Generator (POST /gerbils/sale-ad) — vor der generischen Route + if (path === '/gerbils/sale-ad' && method === 'POST') { + if (!db.saleAdConfigured) { + return json(route, 503, { code: 'AiKeyMissing', message: 'AI nicht konfiguriert' }) + } + return json(route, 200, { text: 'Status: FREI\n\n„Zwei Freunde suchen ein Zuhause" – Generierter Inserat-Text für den Mock.' }) + } + + // ABGABE: Vertrag-Download — /contracts/{id}/file (3 Segmente, nicht vom Generic-Handler bedient) + const cm = path.match(/^\/contracts\/([^/]+)\/file$/) + if (cm && method === 'GET') { + return route.fulfill({ + status: 200, + contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + headers: { 'Content-Disposition': `attachment; filename="vertrag-${cm[1]}.docx"` }, + body: Buffer.from([0x50, 0x4b, 0x05, 0x06, ...new Array(18).fill(0)]), + }) + } + // SEARCH-2b: distinct Herkunft (originBreeder) values, sorted — vor der // generischen /gerbils/:id-Route abfangen. if (path === '/gerbils/breeders' && method === 'GET') { @@ -313,6 +333,34 @@ export async function installMockApi(page: Page): Promise { } } + // ABGABE: POST /contracts — baut url-Feld und markiert Tiere als Abgegeben + if (path === '/contracts' && method === 'POST') { + const body = request.postDataJSON() as { contactId: string; gerbilIds: string[]; price: number; handoverDate: string; contractDate?: string | null } + const id = newId('contract') + const contract = { + id, + contactId: body.contactId, + gerbilIds: body.gerbilIds ?? [], + price: body.price, + handoverDate: body.handoverDate, + contractDate: body.contractDate ?? body.handoverDate, + fileName: `vertrag-${id}.docx`, + createdAt: new Date().toISOString(), + url: `/contracts/${id}/file`, + } + ;(db.contracts as unknown as Row[]).push(contract as unknown as Row) + // Tiere auf GivenAway setzen (wie das echte Backend) + for (const gid of body.gerbilIds ?? []) { + const g = db.gerbils.find((x) => x.id === gid) + if (g) { + g.status = 'GivenAway' + g.receiverContactId = body.contactId + g.goHomeDate = body.handoverDate + } + } + return json(route, 201, contract) + } + // Generische Kollektionen: / und // m = path.match(/^\/([a-z-]+)(?:\/([^/]+))?$/) const col = m ? collections[m[1]] : undefined diff --git a/gerbil-manager-web/e2e/mock-data.ts b/gerbil-manager-web/e2e/mock-data.ts index f481ba1..efdfec4 100644 --- a/gerbil-manager-web/e2e/mock-data.ts +++ b/gerbil-manager-web/e2e/mock-data.ts @@ -44,6 +44,19 @@ export interface MockPage { 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 +} + export interface MockDb { gerbils: Gerbil[] litters: Litter[] @@ -58,6 +71,9 @@ export interface MockDb { requests: InboxRequest[] aiConfigured: boolean mailConfigured: boolean + // ABGABE: Verträge + KI-Inserat-Flag + contracts: MockContract[] + saleAdConfigured: boolean } function gerbil( @@ -119,6 +135,20 @@ export function seedDb(): MockDb { { ...gerbil('willi', 'Willi', 'male', '2019-09-09', null, 'cv-schwarz'), status: 'Deceased', dateOfDeath: '2022-04-04' }, { ...gerbil('rosa', 'Rosa', 'female', '2020-02-02', null, 'cv-gold'), status: 'Deceased', dateOfDeath: '2023-08-15' }, { ...gerbil('pauli', 'Pauli', 'male', '2023-05-01', null, 'cv-himalaya'), status: 'GivenAway', goHomeDate: '2023-07-15', receiverContactId: 'con-huber' }, + // 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', + }, ] const litters: Litter[] = [ @@ -258,5 +288,7 @@ export function seedDb(): MockDb { requests, aiConfigured: true, mailConfigured: true, + contracts: [], + saleAdConfigured: true, } }