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>
171 lines
7 KiB
JavaScript
171 lines
7 KiB
JavaScript
// @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)
|
||
})
|
||
})
|