feat(gehege): Reinigungszyklus (Maße, Kapazität, letzte/nächste Reinigung)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,35 @@ test.describe('Becken', () => {
|
||||
await expect(page.getByText(tb.delete.conflict)).toBeVisible()
|
||||
})
|
||||
|
||||
test('Reinigungszyklus: überfälliges Becken zeigt Hinweis + Als gereinigt markieren', async ({ page }) => {
|
||||
skipUnlessMock()
|
||||
await page.goto('/gehege')
|
||||
await page.getByRole('link', { name: /Großbecken/ }).click()
|
||||
await expect(page.getByRole('heading', { name: 'Großbecken' })).toBeVisible()
|
||||
// Maße + Kapazität werden angezeigt
|
||||
await expect(page.getByText('120×50 cm')).toBeVisible()
|
||||
// Reinigung ist überfällig (nextCleaningDate in der Vergangenheit) -> Warn-Hinweis
|
||||
await expect(page.getByText(/Reinigung fällig/)).toBeVisible()
|
||||
// Als gereinigt markieren -> letzte Reinigung = heute, Hinweis verschwindet
|
||||
await page.getByRole('button', { name: tb.cleaning.markCleaned }).click()
|
||||
await expect(page.getByText(/Reinigung fällig/)).toBeHidden()
|
||||
})
|
||||
|
||||
test('Reinigungszyklus: Becken mit Zyklus anlegen zeigt nächste Reinigung', async ({ page }) => {
|
||||
await gotoSection(page, de.nav.enclosures)
|
||||
await page.getByRole('link', { name: tb.newButton }).click()
|
||||
const name = uniqueName('Zyklusbecken')
|
||||
await page.getByLabel(`${tb.fields.name} *`).fill(name)
|
||||
await page.getByLabel(tb.fields.size).fill('60×40 cm')
|
||||
await page.getByLabel(tb.fields.capacity).fill('4')
|
||||
await page.getByLabel(tb.fields.lastCleanedDate).fill('2026-06-01')
|
||||
await page.getByLabel(tb.fields.cleaningCycleDays).fill('14')
|
||||
await page.getByRole('button', { name: tb.form.save, exact: true }).click()
|
||||
await expect(page.getByRole('heading', { name })).toBeVisible()
|
||||
// nächste Reinigung = 2026-06-01 + 14 Tage = 15.06.2026 (in der def-list-Zeile)
|
||||
await expect(page.getByText('15.06.2026', { exact: true })).toBeVisible()
|
||||
})
|
||||
|
||||
test('Becken anlegen, bearbeiten und (leer) löschen', async ({ page }) => {
|
||||
await gotoSection(page, de.nav.enclosures)
|
||||
await page.getByRole('link', { name: tb.newButton }).click()
|
||||
|
||||
@@ -261,6 +261,22 @@ export async function installMockApi(page: Page): Promise<MockDb> {
|
||||
return json(route, 405)
|
||||
}
|
||||
if (path.match(/^\/enclosure-photos\/[^/]+$/) && method === 'DELETE') return json(route, 204)
|
||||
// Gehege-Reinigungszyklus: nächste fällige Reinigung = letzte Reinigung + Zyklus (Tage).
|
||||
const nextCleaning = (lastCleaned?: string | null, cycleDays?: number | null): string | null => {
|
||||
if (!lastCleaned || cycleDays == null || cycleDays <= 0) return null
|
||||
const d = new Date(`${lastCleaned}T00:00:00Z`)
|
||||
d.setUTCDate(d.getUTCDate() + cycleDays)
|
||||
return d.toISOString().slice(0, 10)
|
||||
}
|
||||
// "Als gereinigt markieren": setzt letzte Reinigung auf heute (wie das echte Backend).
|
||||
m = path.match(/^\/enclosures\/([^/]+)\/mark-cleaned$/)
|
||||
if (m && method === 'POST') {
|
||||
const enc = db.enclosures.find((x) => x.id === m![1])
|
||||
if (!enc) return json(route, 404, { title: 'Not Found' })
|
||||
enc.lastCleanedDate = new Date().toISOString().slice(0, 10)
|
||||
enc.nextCleaningDate = nextCleaning(enc.lastCleanedDate, enc.cleaningCycleDays)
|
||||
return json(route, 200, enc)
|
||||
}
|
||||
m = path.match(/^\/gerbils\/([^/]+)\/inbreeding-coefficient$/)
|
||||
if (m) {
|
||||
const isKruemel = m[1] === 'kruemel'
|
||||
@@ -427,6 +443,13 @@ export async function installMockApi(page: Page): Promise<MockDb> {
|
||||
if (method === 'POST') {
|
||||
const body = request.postDataJSON() as Row
|
||||
const created = { id: newId(col.idPrefix), ...body }
|
||||
// Gehege: berechnetes Feld nextCleaningDate wie das echte Backend ableiten.
|
||||
if (m[1] === 'enclosures') {
|
||||
created.nextCleaningDate = nextCleaning(
|
||||
created.lastCleanedDate as string | null,
|
||||
created.cleaningCycleDays as number | null,
|
||||
)
|
||||
}
|
||||
col.rows.push(created)
|
||||
return json(route, 201, created)
|
||||
}
|
||||
@@ -440,6 +463,12 @@ export async function installMockApi(page: Page): Promise<MockDb> {
|
||||
if (method === 'PUT') {
|
||||
if (idx < 0) return json(route, 404, { title: 'Not Found' })
|
||||
Object.assign(col.rows[idx], request.postDataJSON() as Row)
|
||||
if (m[1] === 'enclosures') {
|
||||
col.rows[idx].nextCleaningDate = nextCleaning(
|
||||
col.rows[idx].lastCleanedDate as string | null,
|
||||
col.rows[idx].cleaningCycleDays as number | null,
|
||||
)
|
||||
}
|
||||
return json(route, 200, col.rows[idx])
|
||||
}
|
||||
if (method === 'DELETE') {
|
||||
|
||||
@@ -232,8 +232,28 @@ export function seedDb(): MockDb {
|
||||
]
|
||||
|
||||
const enclosures: Enclosure[] = [
|
||||
{ id: 'enc-gross', name: 'Großbecken', notes: '120×50 cm' },
|
||||
{ id: 'enc-leer', name: 'Quarantänebecken', notes: null },
|
||||
// Reinigung überfällig: letzte Reinigung lange her + Zyklus -> nextCleaningDate in der Vergangenheit.
|
||||
{
|
||||
id: 'enc-gross',
|
||||
name: 'Großbecken',
|
||||
notes: null,
|
||||
size: '120×50 cm',
|
||||
capacity: 6,
|
||||
lastCleanedDate: '2020-01-01',
|
||||
cleaningCycleDays: 14,
|
||||
nextCleaningDate: '2020-01-15',
|
||||
},
|
||||
// Kein Reinigungszyklus hinterlegt.
|
||||
{
|
||||
id: 'enc-leer',
|
||||
name: 'Quarantänebecken',
|
||||
notes: null,
|
||||
size: null,
|
||||
capacity: null,
|
||||
lastCleanedDate: null,
|
||||
cleaningCycleDays: null,
|
||||
nextCleaningDate: null,
|
||||
},
|
||||
]
|
||||
|
||||
// FEAT-13: contactInfo (Freitext) wurde durch strukturierte Felder ersetzt.
|
||||
|
||||
Reference in New Issue
Block a user