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.
140 lines
5.7 KiB
JavaScript
140 lines
5.7 KiB
JavaScript
// @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.
|
||
})
|