Steuerkram/tests/e2e/specs/beleg-buchung.spec.js
fds2610-bot 7cea573ffc Fix #e2e: E2E-Tests ergänzt, /accounts/new und /assets/new lieferten HTTP 500
Steuerkram an den e2e-runner angebunden (tests/e2e/, 44 Playwright-Tests).
Der erste Smoke-Lauf fand zwei Seiten mit HTTP 500: AccountController::create
las $_GET['from'] ungeprüft, assets/form.php nutzte $testMode und
$defaultKontoId außerhalb des Bearbeiten-Zweigs. Beides war bis zur
Fehler-Härtung (#142) nur eine stille Warning.

Test 67 räumt sein Belegverzeichnis jetzt mit ab - es blieb als root-eigenes
Verzeichnis liegen und blockierte spätere echte Uploads mit HTTP 500.

Test 77 (neu) sichert die Löschsperre von Beleg::delete() ab.
2026-08-02 11:58:33 +02:00

140 lines
5.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
// Hotspot: Beleg-Upload → Buchungsmaske → gespeicherte Buchung.
//
// Der Netto/Brutto-Umschalter (Issue #137) ist reines JavaScript: Im Brutto-Modus
// steht im Feld `betrag_netto` ein Bruttowert, den ein submit-Handler kurz vor dem
// Absenden in den Nettobetrag umrechnet (src/Views/belege/detail.php).
// Ohne Browser ist dieser Pfad nicht prüfbar — ein PHP-Test sieht immer nur den
// bereits umgerechneten Wert und würde eine kaputte Umrechnung nie bemerken.
const { test, expect } = require('@playwright/test')
const { uploadBeleg, cleanupBeleg, gotoOk, parseMoney } = require('../helpers/app')
const KATEGORIE_KONTO = '4930' // Bürobedarf (Betriebsausgabe)
const ZAHLUNGS_KONTO = '1200' // Bank
/** Wählt im Konto-Select die Option mit der gegebenen Kontonummer. */
async function waehleKonto(page, selectId, kontonummer) {
const option = page.locator(`#${selectId} option`).filter({ hasText: new RegExp(`^\\s*${kontonummer}\\s+–`) }).first()
await expect(option, `Konto ${kontonummer} nicht in #${selectId} gefunden`).toHaveCount(1)
const value = await option.getAttribute('value')
await page.selectOption(`#${selectId}`, value)
return value
}
/**
* Füllt die Buchungsmaske bis auf den Betrag aus.
*
* Reihenfolge beachten: Die Auswahl des Kategorie-Kontos ruft updateAutoUst()
* auf und setzt den USt-Satz passend zum Steuercode des Kontos. Der Satz muss
* deshalb nach der Kontoauswahl gesetzt werden — sonst wird er überschrieben.
*/
async function buchungsmaskeVorbereiten(page, { ustSatz = '19', buchungstext }) {
await page.check('input[name="buchungstyp"][value="Ausgabe"]')
await waehleKonto(page, 'kategorie_konto_id', KATEGORIE_KONTO)
await waehleKonto(page, 'zahlungskonto_id', ZAHLUNGS_KONTO)
await page.selectOption('#ust_satz_select', ustSatz)
await page.fill('input[name="buchungstext"]', buchungstext)
}
test.describe('Beleg-Upload und Buchung', () => {
let belegId = 0
test.afterEach(async ({ page }) => {
await cleanupBeleg(page, belegId)
belegId = 0
})
test('Beleg wird hochgeladen und erscheint in der Belegliste', async ({ page }) => {
const { id, tag } = await uploadBeleg(page)
belegId = id
await expect(page.locator('body')).toContainText(tag)
await gotoOk(page, '/belege')
await expect(page.locator('body')).toContainText(tag)
})
test('Buchung im NETTO-Modus speichert den eingegebenen Wert unverändert', async ({ page }) => {
const { id, tag } = await uploadBeleg(page)
belegId = id
await buchungsmaskeVorbereiten(page, { buchungstext: tag })
// Netto ist der Default-Modus.
await expect(page.locator('#betrag_modus_netto_btn')).toHaveAttribute('aria-pressed', 'true')
await page.fill('#betrag_netto_input', '100.00')
// Live-Anzeige rechnet vorwärts: Netto 100 → USt 19 → Brutto 119.
await expect(page.locator('#ust_brutto_anzeige')).toContainText('19,00')
await expect(page.locator('#ust_brutto_anzeige')).toContainText('119,00')
await page.click('#buchung_form button[type="submit"]')
await page.waitForLoadState('networkidle')
const zeile = page.locator('table tbody tr', { hasText: tag }).first()
await expect(zeile).toHaveCount(1)
expect(parseMoney(await zeile.locator('td').nth(5).textContent())).toBeCloseTo(100.0, 2)
})
test('Buchung im BRUTTO-Modus wird vor dem Absenden in Netto umgerechnet', async ({ page }) => {
const { id, tag } = await uploadBeleg(page)
belegId = id
await buchungsmaskeVorbereiten(page, { buchungstext: tag })
await page.click('#betrag_modus_brutto_btn')
await expect(page.locator('#betrag_modus_brutto_btn')).toHaveAttribute('aria-pressed', 'true')
await expect(page.locator('#betrag_modus_netto_btn')).toHaveAttribute('aria-pressed', 'false')
// 119,00 brutto bei 19 % → 100,00 netto.
await page.fill('#betrag_netto_input', '119.00')
await expect(page.locator('#ust_brutto_anzeige')).toContainText('100,00')
await page.click('#buchung_form button[type="submit"]')
await page.waitForLoadState('networkidle')
const zeile = page.locator('table tbody tr', { hasText: tag }).first()
await expect(zeile, 'Buchung wurde nicht gespeichert').toHaveCount(1)
// Kern der Regression: Gespeichert werden muss der Nettobetrag 100,00 —
// nicht die eingegebenen 119,00.
const gespeichert = parseMoney(await zeile.locator('td').nth(5).textContent())
expect(gespeichert, 'Bruttoeingabe wurde nicht in Netto umgerechnet').toBeCloseTo(100.0, 2)
})
test('Moduswechsel rechnet den bereits eingegebenen Betrag um', async ({ page }) => {
const { id } = await uploadBeleg(page)
belegId = id
await page.selectOption('#ust_satz_select', '19')
await page.fill('#betrag_netto_input', '100.00')
// Netto → Brutto
await page.click('#betrag_modus_brutto_btn')
await expect(page.locator('#betrag_netto_input')).toHaveValue('119.00')
// und wieder zurück
await page.click('#betrag_modus_netto_btn')
await expect(page.locator('#betrag_netto_input')).toHaveValue('100.00')
})
test('Buchung mit 7 % USt rechnet im Brutto-Modus korrekt', async ({ page }) => {
const { id, tag } = await uploadBeleg(page)
belegId = id
await buchungsmaskeVorbereiten(page, { ustSatz: '7', buchungstext: tag })
await page.click('#betrag_modus_brutto_btn')
await page.fill('#betrag_netto_input', '107.00')
await page.click('#buchung_form button[type="submit"]')
await page.waitForLoadState('networkidle')
const zeile = page.locator('table tbody tr', { hasText: tag }).first()
await expect(zeile).toHaveCount(1)
expect(parseMoney(await zeile.locator('td').nth(5).textContent())).toBeCloseTo(100.0, 2)
})
// Hinweis: Dass ein Beleg mit Buchungen nicht löschbar ist, ist reine
// Model-Logik (Beleg::delete) und wird in tests/77_BelegLoeschsperreTest.php
// geprüft — dort schneller, isoliert und ohne Browser.
})