feat(tier): Erwerb/Kauf je Tier (Datum, Preis, Notiz)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 22:43:18 +02:00
parent 5e14124322
commit 55e79cda51
16 changed files with 2360 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
/** ERWERB: Erwerb/Kauf je Tier — Sektion in der Rennmausakte (anlegen / bearbeiten / löschen). */
import { de, expect, skipUnlessMock, test } from './fixtures'
const t = de.pages.tierTabs.acquisition
test('Tierakte: Erwerb erfassen, bearbeiten und löschen', async ({ page, mockDb }) => {
skipUnlessMock()
await page.goto('/rennmaeuse/kruemel')
const section = page.locator('section.ak-card', { hasText: t.sectionTitle })
await expect(section).toBeVisible()
await expect(section).toContainText(t.empty)
// Anlegen: Formular öffnen, Felder füllen, speichern.
await section.getByRole('button', { name: t.addButton }).click()
await section.getByLabel(t.fields.date).fill('2025-03-14')
await section.getByLabel(t.fields.price).fill('25.50')
await section.getByLabel(t.fields.note).fill('Auf der Börse gekauft.')
await section.getByRole('button', { name: t.addButton }).click()
// Eintrag erscheint in der Liste, im Mock gespeichert.
await expect(section).toContainText('25,50')
await expect(section).toContainText('Auf der Börse gekauft.')
expect(mockDb).not.toBeNull()
expect(mockDb!.acquisitions.length).toBe(1)
expect(mockDb!.acquisitions[0]).toMatchObject({
gerbilId: 'kruemel',
date: '2025-03-14',
price: 25.5,
note: 'Auf der Börse gekauft.',
})
// Bearbeiten: Preis ändern.
await section.getByRole('button', { name: t.edit }).click()
await section.getByLabel(t.fields.price).fill('30')
await section.getByRole('button', { name: t.saveButton }).click()
await expect(section).toContainText('30,00')
expect(mockDb!.acquisitions[0]).toMatchObject({ price: 30 })
// Löschen (window.confirm bestätigen).
page.once('dialog', (d) => d.accept())
await section.getByRole('button', { name: t.delete }).click()
await expect(section).toContainText(t.empty)
expect(mockDb!.acquisitions.length).toBe(0)
})

View File

@@ -416,6 +416,50 @@ export async function installMockApi(page: Page): Promise<MockDb> {
return json(route, 405)
}
// ERWERB: Erwerb/Kauf je Tier — GET ?gerbilId= (Array, kein Gridify-Paging),
// POST/PUT/DELETE. Vor den generischen Kollektionen, weil GET kein {items}-Objekt
// liefert und nach gerbilId statt Gridify-filter selektiert.
const acqMatch = path.match(/^\/acquisitions(?:\/([^/]+))?$/)
if (acqMatch) {
const acqId = acqMatch[1] ? decodeURIComponent(acqMatch[1]) : null
if (!acqId) {
if (method === 'GET') {
const gid = url.searchParams.get('gerbilId')
const rows = db.acquisitions.filter((a) => !gid || a.gerbilId === gid)
return json(route, 200, [...rows].reverse())
}
if (method === 'POST') {
const created = {
id: newId('acq'),
sourceContactId: null,
date: null,
price: null,
note: null,
...(request.postDataJSON() as Row),
createdAt: new Date().toISOString(),
}
db.acquisitions.push(created)
return json(route, 201, created)
}
return json(route, 405)
}
const ai = db.acquisitions.findIndex((a) => a.id === acqId)
if (method === 'GET') {
return ai >= 0 ? json(route, 200, db.acquisitions[ai]) : json(route, 404, { title: 'Not Found' })
}
if (method === 'PUT') {
if (ai < 0) return json(route, 404, { title: 'Not Found' })
Object.assign(db.acquisitions[ai], request.postDataJSON() as Row)
return json(route, 204)
}
if (method === 'DELETE') {
if (ai < 0) return json(route, 404, { title: 'Not Found' })
db.acquisitions.splice(ai, 1)
return json(route, 204)
}
return json(route, 405)
}
// Generische Kollektionen: /<resource> und /<resource>/<id>
m = path.match(/^\/([a-z-]+)(?:\/([^/]+))?$/)
const col = m ? collections[m[1]] : undefined

View File

@@ -78,6 +78,8 @@ export interface MockDb {
namesConfigured: boolean
// FEEDBACK: "Fehler melden" — gesammelte Berichte (POST /feedback)
feedback: Record<string, unknown>[]
// ERWERB: Erwerb/Kauf je Tier (Kaufdatum, Preis, Notiz) — /acquisitions
acquisitions: Record<string, unknown>[]
}
function gerbil(
@@ -376,5 +378,6 @@ export function seedDb(): MockDb {
saleAdConfigured: true,
namesConfigured: true,
feedback: [],
acquisitions: [],
}
}