// @ts-check const { test, expect } = require('@playwright/test') /* * Der Weg, den die Wertpapierabrechnung nimmt: Sie kommt als Buchung aufs * Girokonto, in der Zuordnungsliste wird das Depot gewählt, und dann folgen die * beiden Dialoge – vorne das Geschäft, dahinter die Kosten. */ 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 WP Giro ${MARKE}` const DEPOT = `E2E WP Depot ${MARKE}` const KAT_GEBUEHR = `E2E Bankprovision ${MARKE}` const KAT_STEUER = `E2E Kapitalertragsteuer ${MARKE}` const ZWECK = 'Depot 0502022353 Wertp.Abrechn. 03.08.2026 WKN LYX01W Gesch.Art KV ' + 'MUL-AMUN ST600 BANK ETF A ISIN LU1834983477 Ihr Wertpapie' async function anmelden(page) { await page.goto('/login') if (page.url().includes('/einrichtung')) { throw new Error( 'Instanz ist noch nicht eingerichtet: ' + '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(/\/$/) } async function kontoAnlegen(page, name, kontoart, anfangssaldo) { await page.goto('/konten/neu') await page.fill('#name', name) await page.selectOption('#kontoart', kontoart) await page.fill('#anfangssaldo', anfangssaldo) await page.click('.formular button[type="submit"]') await expect(page).toHaveURL(/\/konten\/\d+\/bearbeiten/) const treffer = page.url().match(/\/konten\/(\d+)\//) return treffer ? treffer[1] : null } async function kategorieAnlegen(page, name) { await page.goto('/verwaltung/kategorien/neu') await page.fill('#name', name) await page.selectOption('#art', 'ausgabe') await page.click('.formular button[type="submit"]') } /** Eine unzugeordnete Buchung auf dem Girokonto – wie sie der Abruf hinterlässt. */ async function buchungAnlegen(page, giroId, betrag, empfaenger) { await page.goto(`/konten/${giroId}/buchung`) await page.selectOption('#art', betrag < 0 ? 'ausgabe' : 'einnahme') await page.fill('#datum', '2026-08-03') await page.fill('#betrag', String(Math.abs(betrag)).replace('.', ',')) await page.fill('#empfaenger', empfaenger) await page.fill('#buchungstext', ZWECK) await page.click('.buchungsmaske button[type="submit"]') await expect(page.locator('.meldung-erfolg')).toBeVisible() } test.describe('Wertpapiergeschäft aus dem Kontoblatt', () => { let giroId = null test.beforeAll(async ({ browser }) => { const page = await browser.newPage() await anmelden(page) giroId = await kontoAnlegen(page, GIRO, 'bank', '10.000,00') await kontoAnlegen(page, DEPOT, 'depot', '0,00') await kategorieAnlegen(page, KAT_GEBUEHR) await kategorieAnlegen(page, KAT_STEUER) // Die Kostenarten bekommen ihre feste Kategorie – danach fragt der // Kostendialog nur noch Beträge ab. await page.goto('/einstellungen?bereich=wertpapiere') await page.selectOption('#wertpapiere_kategorie_bankprovision', { label: KAT_GEBUEHR }) await page.selectOption('#wertpapiere_kategorie_kapitalertragsteuer', { label: KAT_STEUER }) await page.click('.formular button[type="submit"]') await expect(page.locator('.meldung-erfolg')).toBeVisible() await page.close() }) test.beforeEach(async ({ page }) => { await anmelden(page) }) test('Kauf mit Kosten wird zur Depotbuchung und zur Splitbuchung', async ({ page }) => { const empfaenger = `Wertpapierkauf ${MARKE}` await buchungAnlegen(page, giroId, -255, empfaenger) const zeile = page.locator('tr', { hasText: empfaenger }) await zeile.locator('a.zuordnungsfeld').click() await zeile.locator('#zuordnungsauswahl select').selectOption({ label: `[${DEPOT}] …` }) const dialog = page.locator('#wertpapierdialog') await expect(dialog).toBeVisible() await expect(dialog).toContainText('-255,00') await expect(dialog).toContainText(DEPOT) // Ein Abfluss kann nur ein Kauf sein. await expect(page.locator('#wp_vorgang')).toHaveValue('kauf') // ISIN und WKN stehen im Verwendungszweck und sind schon eingetragen. await expect(page.locator('#wp_isin')).toHaveValue('LU1834983477') await expect(page.locator('#wp_wkn')).toHaveValue('LYX01W') await page.fill('#wp_name', `LYXOR ETF ${MARKE}`) await page.selectOption('#wp_art', 'fonds') await page.fill('#wp_stueck', '3,3593') await page.fill('#wp_kurs', '74,42026613') // Ohne Kosten ist der ganze Betrag Einstand. await expect(page.locator('#wp_einstand')).toContainText('255,00') // Der zweite Dialog: die Nebenkosten. await page.click('#wp_kostenknopf') const kosten = page.locator('#kostendialog') await expect(kosten).toBeVisible() await expect(kosten).toContainText(KAT_GEBUEHR) await kosten.locator('#kosten_bankprovision').fill('4,00') await kosten.locator('#kosten_spesen').fill('1,00') await expect(page.locator('#wp_kostensumme')).toContainText('5,00') await page.click('#wp_kostenfertig') await expect(kosten).toBeHidden() // Was nach den Kosten übrig bleibt, ist der Einstandspreis. await expect(page.locator('#wp_einstand')).toContainText('250,00') await dialog.locator('button[type="submit"]').click() await expect(page.locator('.meldung-erfolg')).toContainText('Einstandspreis 250,00') // Im Kontoblatt steht jetzt eine Splitbuchung – die eine Buchung der // Bank, aufgeteilt, nicht eine zweite daneben. const danach = page.locator('tr', { hasText: empfaenger }).first() await expect(danach).toContainText('Splitbuchung') await expect(danach).toContainText('-255,00') // Die Depotbuchung führt Stück, Kurs und Nebenkosten. await page.goto('/wertpapiere/buchungen') const depotzeile = page.locator('tr', { hasText: `LYXOR ETF ${MARKE}` }) await expect(depotzeile).toContainText('250,00') await expect(depotzeile).toContainText('3,3593') }) test('Kauf ohne Kosten bleibt eine Umbuchung ins Depot', async ({ page }) => { const empfaenger = `Sparplan ${MARKE}` await buchungAnlegen(page, giroId, -1000, empfaenger) const zeile = page.locator('tr', { hasText: empfaenger }) await zeile.locator('a.zuordnungsfeld').click() await zeile.locator('#zuordnungsauswahl select').selectOption({ label: `[${DEPOT}] …` }) await expect(page.locator('#wertpapierdialog')).toBeVisible() await page.fill('#wp_name', `Sparplan-Fonds ${MARKE}`) await page.fill('#wp_stueck', '10') await page.locator('#wertpapierdialog button[type="submit"]').click() await expect(page.locator('.meldung-erfolg')).toContainText('Einstandspreis 1.000,00') // Keine Splitbuchung mit einer einzigen Zeile, sondern eine Umbuchung. await expect(page.locator('tr', { hasText: empfaenger }).first()) .toContainText(`[${DEPOT}]`) }) })