Fix #166: Buchungszeilen ohne Anlage-Spalte, Typ in der ersten Spalte
This commit is contained in:
parent
fc65fcb9c5
commit
ed58490fc2
5 changed files with 55 additions and 29 deletions
|
|
@ -23,8 +23,10 @@
|
|||
|
||||
### Buchungszeilen brauchen weniger Breite (#166)
|
||||
|
||||
- **`src/Views/belege/detail.php`:** Buchungsnummer und Buchungsdatum teilen sich in der Belegansicht eine Spalte (Nummer oben, Datum darunter) – wie bereits bei Kategorie- und Zahlungskonto. Die Tabelle ist damit eine Spalte schmaler und ohne waagerechtes Scrollen lesbar.
|
||||
- **`tests/e2e/specs/beleg-buchung.spec.js`:** Neuer Test auf die zusammengelegte Spalte; die Betragsprüfungen der bestehenden Tests greifen jetzt auf die verschobene Zelle zu.
|
||||
- **`src/Views/belege/detail.php`:** Buchungsnummer, Buchungsdatum und Buchungstyp teilen sich in der Belegansicht eine Spalte (Nummer oben, Datum darunter, Typ-Badge zuunterst) – wie bereits bei Kategorie- und Zahlungskonto. Die Spalte **Anlage** entfällt ersatzlos; das Wirtschaftsgut ist weiterhin über Journal, UStVA- und EÜR-Drill-down sowie die Anlagenliste erreichbar. Von acht Spalten bleiben sechs, die Tabelle ist ohne waagerechtes Scrollen lesbar.
|
||||
- **`src/View.php`:** `formatMoney()` setzt zwischen Betrag und `€` ein geschütztes Leerzeichen. In schmalen Spalten rutschte das Währungszeichen sonst allein in die nächste Zeile. Wirkt in allen HTML-Ansichten; die PDF-Dienste haben eigene Formatierer und bleiben unberührt.
|
||||
- **`docs/anlagevermögen.md`:** Der Abschnitt „Anzeige in Buchungstabellen" nennt die Beleg-Detailansicht nicht mehr unter den Übersichten mit Anlage-Spalte.
|
||||
- **`tests/e2e/specs/beleg-buchung.spec.js`:** Tests auf die zusammengelegte Spalte (inklusive Typ und entfallener Anlage-Spalte) und darauf, dass Betrag und Währungszeichen in einer Zeile bleiben; die Betragsprüfungen der bestehenden Tests greifen jetzt auf die verschobene Zelle zu.
|
||||
|
||||
### Nebenbei
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,9 @@ In beiden Fällen wird die `asset_id` in der Buchungszeile gespeichert.
|
|||
|
||||
## Anzeige in Buchungstabellen
|
||||
|
||||
In allen Buchungsübersichten (Beleg-Detail, Journal, UStVA-Drill-down, EÜR-Drill-down) erscheint eine **Anlage**-Spalte. Sie zeigt die Anlagen-Nummer als direkten Link zur Bearbeitungsmaske des Wirtschaftsguts. Falls für ein älteres Wirtschaftsgut keine Anlagen-Nummer hinterlegt ist, wird stattdessen die Bezeichnung angezeigt.
|
||||
In den Buchungsübersichten Journal, UStVA-Drill-down und EÜR-Drill-down erscheint eine **Anlage**-Spalte. Sie zeigt die Anlagen-Nummer als direkten Link zur Bearbeitungsmaske des Wirtschaftsguts. Falls für ein älteres Wirtschaftsgut keine Anlagen-Nummer hinterlegt ist, wird stattdessen die Bezeichnung angezeigt.
|
||||
|
||||
In der **Beleg-Detailansicht** entfällt die Spalte zugunsten einer schmaleren Tabelle; das zugehörige Wirtschaftsgut ist dort über das Journal oder die Anlagenliste erreichbar.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,9 @@ class View
|
|||
|
||||
public static function formatMoney(mixed $value): string
|
||||
{
|
||||
return number_format((float)$value, 2, ',', '.') . ' €';
|
||||
// Geschütztes Leerzeichen: in schmalen Tabellenspalten rutscht das
|
||||
// Währungszeichen sonst allein in die nächste Zeile.
|
||||
return number_format((float)$value, 2, ',', '.') . "\u{00A0}€";
|
||||
}
|
||||
|
||||
public static function formatDate(?string $date): string
|
||||
|
|
|
|||
|
|
@ -218,13 +218,11 @@ $suppliers = $suppliers ?? [];
|
|||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Buchungsnr.<br>Datum</th>
|
||||
<th>Typ</th>
|
||||
<th>Buchungsnr.<br>Datum<br>Typ</th>
|
||||
<th>Kategorie</th>
|
||||
<th>Zahlungskonto</th>
|
||||
<th class="td-right">Wert</th>
|
||||
<th>Text</th>
|
||||
<th>Anlage</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -236,9 +234,9 @@ $suppliers = $suppliers ?? [];
|
|||
<tr style="<?= ($isReversed || $isReversal) ? 'opacity:.55;background:#fff5f5' : '' ?>">
|
||||
<td class="td-mono text-sm">
|
||||
<?= $h($bk['buchungs_nr']) ?><br>
|
||||
<span class="td-muted"><?= View::formatDate($bk['buchungs_datum']) ?></span>
|
||||
<span class="td-muted"><?= View::formatDate($bk['buchungs_datum']) ?></span><br>
|
||||
<span class="badge <?= $bk['buchungstyp'] === 'Einnahme' ? 'badge-green' : 'badge-red' ?>"><?= $h($bk['buchungstyp']) ?></span>
|
||||
</td>
|
||||
<td><span class="badge <?= $bk['buchungstyp'] === 'Einnahme' ? 'badge-green' : 'badge-red' ?>"><?= $h($bk['buchungstyp']) ?></span></td>
|
||||
<td class="td-mono text-sm"><?= $h($bk['kategorie_nr']) ?><br><span class="td-muted"><?= $h($bk['kategorie_name']) ?></span></td>
|
||||
<td class="td-mono text-sm"><?= $h($bk['zahlungs_nr']) ?><br><span class="td-muted"><?= $h($bk['zahlungs_name']) ?></span></td>
|
||||
<td class="td-right td-mono"><?= $fmt($bk['betrag_netto']) ?></td>
|
||||
|
|
@ -250,15 +248,6 @@ $suppliers = $suppliers ?? [];
|
|||
<span class="badge badge-red">Storniert</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td class="td-mono text-sm">
|
||||
<?php if (!empty($bk['anlage_nr'])): ?>
|
||||
<a href="/assets/<?= (int)$bk['asset_id'] ?>/edit" title="<?= $h($bk['anlage_bezeichnung']) ?>"><?= $h($bk['anlage_nr']) ?></a>
|
||||
<?php elseif (!empty($bk['anlage_bezeichnung'])): ?>
|
||||
<a href="/assets/<?= (int)$bk['asset_id'] ?>/edit"><?= $h($bk['anlage_bezeichnung']) ?></a>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">–</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!$bk['locked'] && !$isReversal && !$isReversed): ?>
|
||||
<div style="display:flex;gap:.35rem">
|
||||
|
|
@ -300,7 +289,7 @@ $suppliers = $suppliers ?? [];
|
|||
</tr>
|
||||
<?php endforeach; ?>
|
||||
<?php if (empty($bookings)): ?>
|
||||
<tr><td colspan="8" class="text-center text-muted" style="padding:1rem">Noch keine Buchungen.</td></tr>
|
||||
<tr><td colspan="6" class="text-center text-muted" style="padding:1rem">Noch keine Buchungen.</td></tr>
|
||||
<?php endif; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ test.describe('Beleg-Upload und Buchung', () => {
|
|||
|
||||
const zeile = page.locator('table tbody tr', { hasText: tag }).first()
|
||||
await expect(zeile).toHaveCount(1)
|
||||
expect(parseMoney(await zeile.locator('td').nth(4).textContent())).toBeCloseTo(100.0, 2)
|
||||
expect(parseMoney(await zeile.locator('td').nth(3).textContent())).toBeCloseTo(100.0, 2)
|
||||
})
|
||||
|
||||
test('Buchung im BRUTTO-Modus wird vor dem Absenden in Netto umgerechnet', async ({ page }) => {
|
||||
|
|
@ -98,7 +98,7 @@ test.describe('Beleg-Upload und Buchung', () => {
|
|||
|
||||
// Kern der Regression: Gespeichert werden muss der Nettobetrag 100,00 —
|
||||
// nicht die eingegebenen 119,00.
|
||||
const gespeichert = parseMoney(await zeile.locator('td').nth(4).textContent())
|
||||
const gespeichert = parseMoney(await zeile.locator('td').nth(3).textContent())
|
||||
expect(gespeichert, 'Bruttoeingabe wurde nicht in Netto umgerechnet').toBeCloseTo(100.0, 2)
|
||||
})
|
||||
|
||||
|
|
@ -131,13 +131,14 @@ test.describe('Beleg-Upload und Buchung', () => {
|
|||
|
||||
const zeile = page.locator('table tbody tr', { hasText: tag }).first()
|
||||
await expect(zeile).toHaveCount(1)
|
||||
expect(parseMoney(await zeile.locator('td').nth(4).textContent())).toBeCloseTo(100.0, 2)
|
||||
expect(parseMoney(await zeile.locator('td').nth(3).textContent())).toBeCloseTo(100.0, 2)
|
||||
})
|
||||
|
||||
// Issue #166: Buchungsnummer und Buchungsdatum teilen sich eine Spalte,
|
||||
// damit die Tabelle ohne waagerechtes Scrollen lesbar bleibt. Prüfbar nur
|
||||
// am gerenderten DOM — die Zellaufteilung existiert nur in der View.
|
||||
test('Buchungsnummer und Datum stehen in einer gemeinsamen Spalte', async ({ page }) => {
|
||||
// Issue #166: Buchungsnummer, Buchungsdatum und Typ teilen sich eine Spalte,
|
||||
// die Spalte „Anlage" entfällt — damit die Tabelle ohne waagerechtes Scrollen
|
||||
// lesbar bleibt. Prüfbar nur am gerenderten DOM: die Zellaufteilung existiert
|
||||
// nur in der View.
|
||||
test('Buchungsnummer, Datum und Typ stehen in einer gemeinsamen Spalte', async ({ page }) => {
|
||||
const { id, tag } = await uploadBeleg(page)
|
||||
belegId = id
|
||||
|
||||
|
|
@ -149,17 +150,47 @@ test.describe('Beleg-Upload und Buchung', () => {
|
|||
const zeile = page.locator('table tbody tr', { hasText: tag }).first()
|
||||
await expect(zeile).toHaveCount(1)
|
||||
|
||||
// Erste Zelle trägt beides: Buchungsnummer (BU-JAHR-NNNNNN) und Datum.
|
||||
// Erste Zelle trägt alle drei Angaben: Buchungsnummer (BU-JAHR-NNNNNN),
|
||||
// Datum und Buchungstyp.
|
||||
const ersteZelle = await zeile.locator('td').nth(0).textContent()
|
||||
expect(ersteZelle).toMatch(/BU-\d{4}-\d+/)
|
||||
expect(ersteZelle).toMatch(/\d{2}\.\d{2}\.\d{4}/)
|
||||
expect(ersteZelle).toMatch(/Einnahme|Ausgabe/)
|
||||
|
||||
// Die Kopfzeile führt beide Bezeichnungen in einer einzigen Spalte.
|
||||
// Die Kopfzeile führt alle drei Bezeichnungen in einer einzigen Spalte;
|
||||
// „Anlage" gibt es nicht mehr.
|
||||
const kopf = page.locator('table thead tr').first()
|
||||
const ersteKopfzelle = await kopf.locator('th').nth(0).textContent()
|
||||
expect(ersteKopfzelle).toContain('Buchungsnr.')
|
||||
expect(ersteKopfzelle).toContain('Datum')
|
||||
await expect(kopf.locator('th')).toHaveCount(8)
|
||||
expect(ersteKopfzelle).toContain('Typ')
|
||||
await expect(kopf.locator('th')).toHaveCount(6)
|
||||
await expect(kopf.locator('th', { hasText: 'Anlage' })).toHaveCount(0)
|
||||
})
|
||||
|
||||
// Der Betrag darf nicht zwischen Zahl und Währungszeichen umbrechen.
|
||||
test('Betrag und Währungszeichen bleiben zusammen', async ({ page }) => {
|
||||
const { id, tag } = await uploadBeleg(page)
|
||||
belegId = id
|
||||
|
||||
await buchungsmaskeVorbereiten(page, { buchungstext: tag })
|
||||
await page.fill('#betrag_netto_input', '1234.56')
|
||||
await page.click('#buchung_form button[type="submit"]')
|
||||
await page.waitForLoadState('networkidle')
|
||||
|
||||
const wert = page.locator('table tbody tr', { hasText: tag }).first().locator('td').nth(3)
|
||||
const text = await wert.textContent()
|
||||
// Geschütztes Leerzeichen (U+00A0) statt eines gewöhnlichen Leerzeichens.
|
||||
expect(text).toBe('1.234,56\u00A0€')
|
||||
|
||||
// Und der Betrag steht auch gerendert auf einer einzigen Zeile — ein
|
||||
// Umbruch zwischen Zahl und Währungszeichen ergäbe zwei Rechtecke.
|
||||
const fragmente = await wert.evaluate((el) => {
|
||||
const r = document.createRange()
|
||||
r.selectNodeContents(el)
|
||||
return r.getClientRects().length
|
||||
})
|
||||
expect(fragmente).toBe(1)
|
||||
})
|
||||
|
||||
// Hinweis: Dass ein Beleg mit Buchungen nicht löschbar ist, ist reine
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue