Finanzmanager/tests/e2e/specs/regelausbuchung.spec.js
fds2610-bot c58837dee8 Fix #124: Umbuchungskonten in jeder Kategorieauswahl
Unter den Kategorien stehen überall die aktiven Umbuchungskonten, je
Kontengruppe gruppiert und in deren Reihenfolge (Konto::umbuchungsgruppen()):
Regeln samt Splitzeilen, Umsatzabgleich, Buchungsvorlagen, Regelmäßige
Buchungen, Zahlungsverkehr, Verträge sowie die Filter von Buchungsbericht
und Export. Die Masken nutzen dafür die gemeinsame Liste Kategorie /
Gegenkonto der Buchungsmaske; das getrennte Gegenkonto-Feld der Regel
entfällt.

Regel-Splitzeilen können auf ein Umbuchungskonto zeigen. Zahlungsaufträge
und Verträge bekommen ein Gegenkonto (Migration 030). Ein schon gewähltes
inaktives Konto bleibt sichtbar, damit keine Zuordnung verloren geht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:30:44 +02:00

171 lines
7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// @ts-check
const { test, expect } = require('@playwright/test')
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 RegelBuchung ${MARKE}`
const KATEGORIE = `E2E RegelBuchung Kategorie ${MARKE}`
const TAGESGELD = `E2E RegelBuchung Tagesgeld ${MARKE}`
async function anmelden(page) {
await page.goto('/login')
await page.fill('#benutzername', BENUTZER)
await page.fill('#passwort', PASSWORT)
await page.click('button[type="submit"]')
await expect(page).toHaveURL(/\/$/)
}
// Fix #94: Aus der Buchungsmaske heraus lässt sich mit dem Toggle
// „Regel anlegen“ direkt eine Regel aus der gerade gespeicherten Buchung
// erzeugen; ein Dialog zeigt sie danach zum Prüfen.
test.describe('Regel aus einer Buchung anlegen', () => {
let giroId = null
let tagesgeldId = 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.fill('#anfangssaldo', '1.000,00')
await page.click('.formular button[type="submit"]')
await expect(page).toHaveURL(/\/konten\/\d+\/bearbeiten/)
giroId = page.url().match(/\/konten\/(\d+)\//)[1]
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.goto('/konten/neu')
await page.fill('#name', TAGESGELD)
await page.click('.formular button[type="submit"]')
await expect(page).toHaveURL(/\/konten\/\d+\/bearbeiten/)
tagesgeldId = page.url().match(/\/konten\/(\d+)\//)[1]
await page.close()
})
test.beforeEach(async ({ page }) => {
await anmelden(page)
})
test('Toggle ist standardmäßig aus – ohne ihn ändert sich nichts am bisherigen Ablauf', async ({ page }) => {
await page.goto(`/konten/${giroId}/buchung`)
await expect(page.locator('input[name="regel_anlegen"]')).not.toBeChecked()
await page.fill('#datum', '2026-05-01')
await page.fill('#betrag', '12,00')
await page.fill('#empfaenger', `Ohne Regel ${MARKE}`)
await page.click('.buchungsmaske button[type="submit"]')
await expect(page.locator('.meldung-erfolg')).toBeVisible()
await expect(page.locator('#regeldialog')).toHaveCount(0)
})
test('Regel anlegen öffnet den Dialog mit den Buchungsdaten; Abbruch verwirft die Regel wieder', async ({ page }) => {
const empfaenger = `Abbruchregel ${MARKE}`
await page.goto(`/konten/${giroId}/buchung`)
await page.fill('#datum', '2026-05-02')
await page.fill('#betrag', '20,00')
await page.fill('#empfaenger', empfaenger)
await page.selectOption('#zuordnung', { label: KATEGORIE })
await page.check('input[name="regel_anlegen"]')
await page.click('.buchungsmaske button[type="submit"]')
await expect(page.locator('.meldung-erfolg')).toBeVisible()
const dialog = page.locator('#regeldialog')
await expect(dialog).toBeVisible()
await expect(dialog.locator('input[name="name"]')).toHaveValue(empfaenger)
await expect(dialog.locator('input[name="muster"]')).toHaveValue(empfaenger)
await expect(dialog.locator('select[name="zuordnung"]')).toHaveValue(/^kategorie:\d+$/)
await dialog.getByRole('button', { name: 'Abbruch' }).click()
await expect(page).toHaveURL(new RegExp(`/konten/${giroId}$`))
await expect(page.locator('#regeldialog')).toHaveCount(0)
// Es darf keine neu angelegte Regel zurückbleiben. Der Name steht im
// Wert eines Eingabefelds, nicht im Text der Zeile – toContainText
// träfe ihn also gar nicht erst.
await page.goto('/banking/regeln')
await expect(page.locator(`input[name="name"][value="${empfaenger}"]`)).toHaveCount(0)
})
test('Speichern im Dialog übernimmt Änderungen und behält die Regel', async ({ page }) => {
const empfaenger = `Speicherregel ${MARKE}`
const angepasstesMuster = `Speicherregel ${MARKE} angepasst`
await page.goto(`/konten/${giroId}/buchung`)
await page.fill('#datum', '2026-05-03')
await page.fill('#betrag', '30,00')
await page.fill('#empfaenger', empfaenger)
await page.selectOption('#zuordnung', { label: KATEGORIE })
await page.check('input[name="regel_anlegen"]')
await page.click('.buchungsmaske button[type="submit"]')
const dialog = page.locator('#regeldialog')
await expect(dialog).toBeVisible()
await dialog.locator('input[name="muster"]').fill(angepasstesMuster)
await dialog.getByRole('button', { name: 'Speichern' }).click()
await expect(page).toHaveURL(new RegExp(`/konten/${giroId}$`))
await expect(page.locator('#regeldialog')).toHaveCount(0)
// Der Name blieb unverändert; über ihn findet sich die Zeile wieder –
// das Muster steckt wie der Name im Wert eines Eingabefelds.
await page.goto('/banking/regeln')
const zeile = page.locator('tr').filter({ has: page.locator(`input[name="name"][value="${empfaenger}"]`) })
await expect(zeile).toBeVisible()
await expect(zeile.locator('input[name="muster"]')).toHaveValue(angepasstesMuster)
})
// #124: Unter den Kategorien stehen auch die Umbuchungskonten – in der
// Regel selbst wie in ihren Splitzeilen.
test('Regel-Splitzeile bucht auf ein Umbuchungskonto', async ({ page }) => {
const name = `Sparregel ${MARKE}`
await page.goto('/banking/regeln')
await page.click('#neu-knopf')
const dialog = page.locator('#neudialog')
await expect(dialog).toBeVisible()
await expect(dialog.locator('#neu_zuordnung option', { hasText: `[${TAGESGELD}]` })).toHaveCount(1)
await dialog.locator('#neu_name').fill(name)
await dialog.locator('#neu_muster').fill(name)
await dialog.locator('select[name="split[0][zuordnung]"]').selectOption({ label: KATEGORIE })
await dialog.locator('input[name="split[0][prozent]"]').fill('25')
await dialog.locator('select[name="split[1][zuordnung]"]').selectOption({ label: `[${TAGESGELD}]` })
await dialog.locator('input[name="split[1][prozent]"]').fill('75')
await dialog.getByRole('button', { name: 'Regel anlegen' }).click()
await expect(page.locator('.meldung-erfolg')).toBeVisible()
const zeile = page.locator('tr').filter({ has: page.locator(`input[name="name"][value="${name}"]`) })
const splits = zeile.locator('xpath=following-sibling::tr[1]')
await expect(splits.locator('select[name="split[1][zuordnung]"]')).toHaveValue(`konto:${tagesgeldId}`)
})
test('Escape im Dialog verwirft die Regel wie ein Klick auf Abbruch', async ({ page }) => {
const empfaenger = `Escaperegel ${MARKE}`
await page.goto(`/konten/${giroId}/buchung`)
await page.fill('#datum', '2026-05-04')
await page.fill('#betrag', '40,00')
await page.fill('#empfaenger', empfaenger)
await page.check('input[name="regel_anlegen"]')
await page.click('.buchungsmaske button[type="submit"]')
await expect(page.locator('#regeldialog')).toBeVisible()
await page.keyboard.press('Escape')
await expect(page).toHaveURL(new RegExp(`/konten/${giroId}$`))
await page.goto('/banking/regeln')
await expect(page.locator(`input[name="name"][value="${empfaenger}"]`)).toHaveCount(0)
})
})