Beim Zuweisen einer Kategorie fragt der Finanzmanager beim Bargeld-Tool (FSCash) nach, ob es zum Betrag dort schon eine Buchung gibt. Eingerichtet wird das unter Verwaltung → Bargeld-Tool: Adresse, Zugangsdaten (Passwort AES-verschlüsselt), Suchzeitraum und die Auswahl der abzugleichenden Kategorien. Die Vormerkung steht als `bargeld_abgleich` an der Kategorie selbst, nicht als Nummernliste in den Einstellungen – so verschwindet sie mit der Kategorie. Geprüft wird nur, was geprüft werden soll: Kategorie an der Buchung, Kategorie vorgemerkt, Adresse konfiguriert. Fällt eines weg, verschwindet der Status. Gesucht wird rückwärts ab dem Buchungsdatum, weil Bargeld ausgegeben wird, bevor der Umsatz auf dem Konto ankommt. Im Kontoblatt steht das Ergebnis zwischen Klasse und Betrag: grauer Kreis, grüner Haken, rotes X, Ausrufezeichen. Das Ausrufezeichen behält die Textfarbe des Farbschemas – der Fehler liegt beim Abruf, nicht bei der Buchung. Ohne hinterlegte Adresse entfällt die Spalte. Ein Fehlschlag ist ein Ergebnis, kein Abbruch: Die Kategorie wird auch dann gespeichert, wenn das Bargeld-Tool schweigt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
180 lines
6.9 KiB
JavaScript
180 lines
6.9 KiB
JavaScript
// @ts-check
|
||
const { test, expect } = require('@playwright/test')
|
||
|
||
/*
|
||
* Der Abgleich mit dem Bargeld-Tool im Browser: die Kachel in der Verwaltung,
|
||
* das Formular dahinter und die Wirkung im Kontoblatt.
|
||
*
|
||
* Als Gegenstelle dient eine Adresse, die es nicht gibt. Das genügt: Der Abruf
|
||
* selbst steckt in der PHP-Suite, wo ein Testserver antwortet – hier geht es um
|
||
* den Weg und darum, dass ein gescheiterter Abruf die Zuordnung nicht aufhält.
|
||
*/
|
||
|
||
const BENUTZER = process.env.E2E_USER || 'e2e'
|
||
const PASSWORT = process.env.E2E_PASSWORD || 'e2e-testpasswort'
|
||
|
||
const MARKE = Date.now().toString().slice(-6)
|
||
const GIRO = `E2E Bargeld Giro ${MARKE}`
|
||
const KATEGORIE = `E2E Bargeld Haushalt ${MARKE}`
|
||
const ADRESSE = 'https://kasse.beispiel.invalid'
|
||
|
||
async function anmelden(page) {
|
||
await page.goto('/login')
|
||
|
||
if (page.url().includes('/einrichtung')) {
|
||
throw new Error(
|
||
'Instanz ist noch nicht eingerichtet. Zuerst einen Benutzer anlegen: ' +
|
||
'docker compose exec -u www-data php php scripts/e2e-benutzer.php'
|
||
)
|
||
}
|
||
|
||
await page.fill('#benutzername', BENUTZER)
|
||
await page.fill('#passwort', PASSWORT)
|
||
await page.click('button[type="submit"]')
|
||
await expect(page).toHaveURL(/\/$/)
|
||
}
|
||
|
||
/** Adresse und Kategorienauswahl zurücksetzen – die Instanz gehört nicht dem Test. */
|
||
async function zuruecksetzen(page) {
|
||
await page.goto('/verwaltung/bargeld')
|
||
await page.fill('#bargeld_url', '')
|
||
for (const feld of await page.locator('input[name="kategorien[]"]:checked').all()) {
|
||
await feld.uncheck()
|
||
}
|
||
await page.getByRole('button', { name: 'Speichern' }).click()
|
||
await expect(page.locator('.meldung')).toContainText('gespeichert')
|
||
}
|
||
|
||
test.describe('Bargeld-Tool', () => {
|
||
let giroId = null
|
||
|
||
test.beforeAll(async ({ browser }) => {
|
||
const page = await browser.newPage()
|
||
await anmelden(page)
|
||
|
||
await page.goto('/konten/neu')
|
||
await page.fill('#name', GIRO)
|
||
await page.selectOption('#kontoart', 'bank')
|
||
await page.fill('#anfangssaldo', '1.000,00')
|
||
await page.click('.formular button[type="submit"]')
|
||
await expect(page).toHaveURL(/\/konten\/\d+\/bearbeiten/)
|
||
const treffer = page.url().match(/\/konten\/(\d+)\//)
|
||
giroId = treffer ? treffer[1] : null
|
||
|
||
await page.goto('/verwaltung/kategorien/neu')
|
||
await page.fill('#name', KATEGORIE)
|
||
await page.selectOption('#art', 'ausgabe')
|
||
await page.click('.formular button[type="submit"]')
|
||
|
||
await page.close()
|
||
})
|
||
|
||
test.beforeEach(async ({ page }) => {
|
||
await anmelden(page)
|
||
})
|
||
|
||
test.afterEach(async ({ page }) => {
|
||
await zuruecksetzen(page)
|
||
})
|
||
|
||
test('Die Verwaltung führt zur Konfiguration', async ({ page }) => {
|
||
await page.goto('/verwaltung')
|
||
|
||
const kachel = page.locator('.kachel', { hasText: 'Bargeld-Tool' })
|
||
await kachel.getByRole('link', { name: /einrichten/ }).click()
|
||
|
||
await expect(page).toHaveURL(/\/verwaltung\/bargeld$/)
|
||
await expect(page.locator('h1')).toContainText('Bargeld-Tool')
|
||
await expect(page.locator('#bargeld_url')).toBeVisible()
|
||
await expect(page.locator('#bargeld_benutzer')).toBeVisible()
|
||
await expect(page.locator('#bargeld_passwort')).toHaveAttribute('type', 'password')
|
||
await expect(page.getByRole('checkbox', { name: KATEGORIE })).not.toBeChecked()
|
||
})
|
||
|
||
test('Eine Adresse ohne Schema wird abgewiesen', async ({ page }) => {
|
||
await page.goto('/verwaltung/bargeld')
|
||
|
||
await page.fill('#bargeld_url', 'kasse.beispiel.invalid')
|
||
await page.getByRole('button', { name: 'Speichern' }).click()
|
||
|
||
await expect(page.locator('.meldung')).toContainText('http://')
|
||
await expect(page.locator('#bargeld_url')).toHaveValue('')
|
||
})
|
||
|
||
test('Der Verbindungstest meldet eine unerreichbare Gegenstelle', async ({ page }) => {
|
||
await page.goto('/verwaltung/bargeld')
|
||
await page.fill('#bargeld_url', ADRESSE)
|
||
await page.getByRole('button', { name: 'Speichern' }).click()
|
||
await expect(page.locator('.meldung')).toContainText('gespeichert')
|
||
|
||
await page.getByRole('button', { name: 'Verbindung testen' }).click()
|
||
await expect(page.locator('.meldung-fehler')).toContainText('nicht erreichbar')
|
||
})
|
||
|
||
test('Erst mit Adresse zeigt das Kontoblatt die Abgleichspalte', async ({ page }) => {
|
||
const empfaenger = `Ohne Bargeld ${MARKE}`
|
||
|
||
await page.goto(`/konten/${giroId}/buchung`)
|
||
await page.selectOption('#art', 'ausgabe')
|
||
await page.fill('#datum', '2026-04-03')
|
||
await page.fill('#betrag', '12,30')
|
||
await page.fill('#empfaenger', empfaenger)
|
||
await page.click('.buchungsmaske button[type="submit"]')
|
||
await expect(page.locator('.meldung-erfolg')).toBeVisible()
|
||
|
||
// Ohne Adresse gibt es die Spalte nicht – sie zeigte nur graue Kreise.
|
||
await expect(page.locator('th.bargeldspalte')).toHaveCount(0)
|
||
|
||
await page.goto('/verwaltung/bargeld')
|
||
await page.fill('#bargeld_url', ADRESSE)
|
||
await page.fill('#bargeld_tage', '10')
|
||
await page.getByRole('button', { name: 'Speichern' }).click()
|
||
await expect(page.locator('.meldung')).toContainText('gespeichert')
|
||
|
||
await page.goto(`/konten/${giroId}`)
|
||
await expect(page.locator('th.bargeldspalte')).toHaveCount(1)
|
||
|
||
// Die Spalte steht zwischen Klasse und Betrag.
|
||
const spalten = await page.locator('table.kontoblatt thead th').allTextContents()
|
||
expect(spalten.indexOf('Bar')).toBe(spalten.indexOf('Klasse') + 1)
|
||
expect(spalten.indexOf('Betrag')).toBe(spalten.indexOf('Bar') + 1)
|
||
|
||
// Ohne vorgemerkte Kategorie steht dort der graue Kreis.
|
||
const zeile = page.locator('tr', { hasText: empfaenger })
|
||
await expect(zeile.locator('td.bargeldspalte .bargeld-neutral')).toBeVisible()
|
||
})
|
||
|
||
test('Eine vorgemerkte Kategorie wird beim Zuordnen geprüft', async ({ page }) => {
|
||
// Der Abruf läuft ins Leere; genau das soll das Ausrufezeichen zeigen –
|
||
// und die Zuordnung muss trotzdem stehen.
|
||
test.setTimeout(60_000)
|
||
|
||
const empfaenger = `Bargeldabhebung ${MARKE}`
|
||
|
||
await page.goto('/verwaltung/bargeld')
|
||
await page.fill('#bargeld_url', ADRESSE)
|
||
await page.getByRole('checkbox', { name: KATEGORIE }).check()
|
||
await page.getByRole('button', { name: 'Speichern' }).click()
|
||
await expect(page.locator('.meldung')).toContainText('gespeichert')
|
||
|
||
await page.goto(`/konten/${giroId}/buchung`)
|
||
await page.selectOption('#art', 'ausgabe')
|
||
await page.fill('#datum', '2026-04-04')
|
||
await page.fill('#betrag', '50,00')
|
||
await page.fill('#empfaenger', empfaenger)
|
||
await page.click('.buchungsmaske button[type="submit"]')
|
||
await expect(page.locator('.meldung-erfolg')).toBeVisible()
|
||
|
||
const zeile = page.locator('tr', { hasText: empfaenger })
|
||
await zeile.locator('a.zuordnungsfeld').click()
|
||
await zeile.locator('#zuordnungsauswahl select').selectOption({ label: KATEGORIE })
|
||
|
||
// Die Zuordnung steht, der gescheiterte Abruf hat sie nicht aufgehalten.
|
||
await expect(page.locator('.meldung-erfolg')).toContainText('Zuordnung gespeichert')
|
||
await expect(page.locator('.meldung-erfolg')).toContainText('fehlgeschlagen')
|
||
|
||
const danach = page.locator('tr', { hasText: empfaenger })
|
||
await expect(danach).toContainText(KATEGORIE)
|
||
await expect(danach.locator('td.bargeldspalte .bargeld-fehler')).toBeVisible()
|
||
})
|
||
})
|