From 60873bb6dfd53a25a507fbfd3de20bbf8e99bb76 Mon Sep 17 00:00:00 2001 From: fds2610-bot Date: Sat, 11 Apr 2026 16:19:04 +0200 Subject: [PATCH] =?UTF-8?q?Feature:=20Belegliste=20CSV-Export,=20Bewirtung?= =?UTF-8?q?sfelder,=20E=C3=9CR-Zeile=20im=20Kontenplan,=20Lade-Spinner=20Z?= =?UTF-8?q?uschnitt,=20Doppelklick-Fix=20Kontoauswahl,=20Backup-Script=20r?= =?UTF-8?q?epariert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/index.php | 1 + public/static/js/app.js | 17 ++++++++- scripts/backup.sh | 2 +- src/Controllers/BelegController.php | 58 ++++++++++++++++++++++++++++- src/Models/Account.php | 8 ++-- src/Views/accounts/form.php | 38 +++++++++++++++++++ src/Views/belege/detail.php | 11 ++++++ src/Views/belege/list.php | 16 ++++++-- 8 files changed, 141 insertions(+), 10 deletions(-) diff --git a/public/index.php b/public/index.php index 2b0915d..15d739f 100644 --- a/public/index.php +++ b/public/index.php @@ -45,6 +45,7 @@ $router->get('/accounts/search', fn($p) => (new AccountController())->search($p) // ── Belege ─────────────────────────────────────────────────── $router->get('/belege', fn($p) => (new BelegController())->index($p)); +$router->get('/belege/export', fn($p) => (new BelegController())->export($p)); $router->get('/belege/upload', fn($p) => (new BelegController())->uploadForm($p)); $router->post('/belege/upload', fn($p) => (new BelegController())->upload($p)); $router->get('/belege/{id}', fn($p) => (new BelegController())->show($p)); diff --git a/public/static/js/app.js b/public/static/js/app.js index f744567..bf5f372 100644 --- a/public/static/js/app.js +++ b/public/static/js/app.js @@ -341,10 +341,23 @@ document.addEventListener('click', (e) => { }); // ── Konto-Select: Doppelklick / Enter → Kontonr. ins Filterfeld ── +// selectedIndex ist beim click-Event garantiert korrekt; beim dblclick-Event +// kann er in manchen Browsern bei List-Boxes zurückgesetzt sein (kein Prior-Select), +// daher wird er beim click gecacht und als Fallback genutzt. +const _kontoNrCache = {}; +document.addEventListener('click', (e) => { + const sel = e.target.closest('select#kategorie_konto_id, select#zahlungskonto_id'); + if (!sel) return; + const opt = sel.options[sel.selectedIndex]; + if (opt && opt.value) _kontoNrCache[sel.id] = opt.textContent.split(' – ')[0].trim(); +}); + function transferKontoNr(select) { const opt = select.options[select.selectedIndex]; - if (!opt || !opt.value) return; - const kontoNr = opt.textContent.split(' – ')[0].trim(); + const kontoNr = (opt && opt.value) + ? opt.textContent.split(' – ')[0].trim() + : (_kontoNrCache[select.id] ?? null); + if (!kontoNr) return; const filterInput = document.querySelector(`.konto-filter[data-target="${select.id}"]`); if (filterInput) filterInput.value = kontoNr; } diff --git a/scripts/backup.sh b/scripts/backup.sh index 416c70f..4e49b98 100755 --- a/scripts/backup.sh +++ b/scripts/backup.sh @@ -12,7 +12,7 @@ mkdir -p "$BACKUP_DIR" echo "Erstelle Backup $TIMESTAMP..." # MariaDB Dump (aus dem Container) -docker compose exec -T db mysqldump \ +docker compose exec -T db mariadb-dump \ --user=steuerkram \ --password=steuerkram \ --single-transaction \ diff --git a/src/Controllers/BelegController.php b/src/Controllers/BelegController.php index 86b4dd4..8a4448d 100644 --- a/src/Controllers/BelegController.php +++ b/src/Controllers/BelegController.php @@ -44,10 +44,58 @@ class BelegController 'page' => $page, 'filter' => $filter, ]), - 'topbarActions' => '+ Beleg hochladen', + 'topbarActions' => 'CSV Export + Beleg hochladen', ]); } + public function export(array $params): void + { + $filter = $_GET; + unset($filter['page']); + $result = Beleg::all($filter, 1, 99999); + + header('Content-Type: text/csv; charset=utf-8'); + header('Content-Disposition: attachment; filename="belegliste-' . date('Ymd') . '.csv"'); + + $out = fopen('php://output', 'w'); + fprintf($out, chr(0xEF) . chr(0xBB) . chr(0xBF)); // UTF-8 BOM for Excel + + fputcsv($out, [ + 'Belegnr.', 'Belegdatum', 'Belegart', 'Lieferant', 'Rechnungsnr.', + 'Dateiname', 'Geschäftsjahr', 'Notizen', + 'Bewirtungsgrund', 'Teilnehmerliste', + 'Brutto (€)', 'Status', + ], ';'); + + foreach ($result['rows'] as $row) { + if ($row['locked']) { + $status = 'Gesperrt'; + } elseif ($row['booked']) { + $status = 'Gebucht'; + } else { + $status = 'Offen'; + } + + fputcsv($out, [ + $this->csvSanitize((string)($row['beleg_nr'] ?? '')), + $row['beleg_datum'] ?? '', + $row['belegtyp'] ?? '', + $this->csvSanitize((string)($row['supplier_name'] ?? '')), + $this->csvSanitize((string)($row['rechnungs_nr'] ?? '')), + $this->csvSanitize((string)($row['filename'] ?? '')), + (string)($row['fiscal_year_nr'] ?? ''), + $this->csvSanitize((string)($row['notes'] ?? '')), + $this->csvSanitize((string)($row['bewirtungsgrund'] ?? '')), + $this->csvSanitize((string)($row['teilnehmerliste'] ?? '')), + $row['betrag_brutto'] !== null + ? number_format((float)$row['betrag_brutto'], 2, ',', '') + : '', + $status, + ], ';'); + } + fclose($out); + } + public function uploadForm(array $params): void { $view = new View(); @@ -310,6 +358,14 @@ class BelegController echo json_encode(['ok' => true]); } + private function csvSanitize(string $value): string + { + if ($value !== '' && in_array($value[0], ['=', '+', '-', '@'], true)) { + return "'" . $value; + } + return $value; + } + private function renderView(string $template, array $data): string { extract($data, EXTR_SKIP); diff --git a/src/Models/Account.php b/src/Models/Account.php index b27a785..7ca1796 100644 --- a/src/Models/Account.php +++ b/src/Models/Account.php @@ -54,8 +54,8 @@ class Account public static function create(array $data): int { Database::execute( - 'INSERT INTO accounts (kontonummer, bezeichnung, kontenart, beschreibung, steuercode, ustva_kz) - VALUES (?, ?, ?, ?, ?, ?)', + 'INSERT INTO accounts (kontonummer, bezeichnung, kontenart, beschreibung, steuercode, ustva_kz, euer_zeile) + VALUES (?, ?, ?, ?, ?, ?, ?)', [ $data['kontonummer'], $data['bezeichnung'], @@ -63,6 +63,7 @@ class Account $data['beschreibung'] ?? null, $data['steuercode'] ?? null, $data['ustva_kz'] ?? null, + $data['euer_zeile'] !== '' ? ($data['euer_zeile'] ?? null) : null, ] ); return (int)Database::lastInsertId(); @@ -71,7 +72,7 @@ class Account public static function update(int $id, array $data): void { Database::execute( - 'UPDATE accounts SET kontonummer=?, bezeichnung=?, kontenart=?, beschreibung=?, steuercode=?, ustva_kz=?, aktiv=? + 'UPDATE accounts SET kontonummer=?, bezeichnung=?, kontenart=?, beschreibung=?, steuercode=?, ustva_kz=?, euer_zeile=?, aktiv=? WHERE id=?', [ $data['kontonummer'], @@ -80,6 +81,7 @@ class Account $data['beschreibung'] ?? null, $data['steuercode'] ?? null, $data['ustva_kz'] ?? null, + $data['euer_zeile'] !== '' ? ($data['euer_zeile'] ?? null) : null, (int)($data['aktiv'] ?? 1), $id, ] diff --git a/src/Views/accounts/form.php b/src/Views/accounts/form.php index edad660..2de49c6 100644 --- a/src/Views/accounts/form.php +++ b/src/Views/accounts/form.php @@ -68,6 +68,44 @@ +
+
+ + +
Zeile 51 = erscheint in EÜR unter KZ 165
+
+
+
diff --git a/src/Views/belege/detail.php b/src/Views/belege/detail.php index a060d1c..22823b4 100644 --- a/src/Views/belege/detail.php +++ b/src/Views/belege/detail.php @@ -754,7 +754,14 @@ document.addEventListener('DOMContentLoaded', function () {

+
+
@@ -779,10 +786,13 @@ document.addEventListener('DOMContentLoaded', function () { let dragging = false; let startX = 0, startY = 0; + const spinner = document.getElementById('crop-spinner'); + window.openCropModal = function() { overlay.style.display = 'flex'; if (img) { drawCanvas(); return; } + spinner.style.display = 'flex'; img = new Image(); img.onload = function() { // Canvas auf max. 90vw / 72vh skalieren @@ -794,6 +804,7 @@ document.addEventListener('DOMContentLoaded', function () { scaleX = img.naturalWidth / canvas.width; scaleY = img.naturalHeight / canvas.height; sel = null; + spinner.style.display = 'none'; drawCanvas(); }; img.src = FILE_URL + '?nocache=' + Date.now(); diff --git a/src/Views/belege/list.php b/src/Views/belege/list.php index 9ff3490..7f30d08 100644 --- a/src/Views/belege/list.php +++ b/src/Views/belege/list.php @@ -132,11 +132,11 @@ Belegnr. - Belegdatum Lieferant Rechnungsnr. Dateiname + Bewirtungsgrund / Teilnehmer Brutto Status @@ -146,11 +146,21 @@ - + + +
+ + +
+ + + – + + @@ -167,7 +177,7 @@ - Keine Belege gefunden. + Keine Belege gefunden.