Feature: Bank-Zuordnung öffnet Buchungsmaske mit vorausgefüllten Feldern

This commit is contained in:
fds2610-bot 2026-05-14 20:36:09 +02:00
parent b12f4c9b2b
commit e4a6995a31
4 changed files with 125 additions and 16 deletions

View file

@ -11,6 +11,7 @@ use Steuerkram\Models\Asset;
use Steuerkram\Models\Supplier; use Steuerkram\Models\Supplier;
use Steuerkram\View; use Steuerkram\View;
use Steuerkram\Services\ImageCropService; use Steuerkram\Services\ImageCropService;
use Steuerkram\Models\BankTransaction;
use Steuerkram\Services\KontierungsvorschlagService; use Steuerkram\Services\KontierungsvorschlagService;
class BelegController class BelegController
@ -205,22 +206,60 @@ class BelegController
$kiVorschlag = $beleg['ki_vorschlag'] !== null $kiVorschlag = $beleg['ki_vorschlag'] !== null
? json_decode($beleg['ki_vorschlag'], true) ? json_decode($beleg['ki_vorschlag'], true)
: null; : null;
// KI-Sektion anzeigen wenn Ergebnis vorhanden ODER LLM gerade verfügbar
$kiSektion = $kiAktiv || $kiVorschlag !== null; $kiSektion = $kiAktiv || $kiVorschlag !== null;
// Bank-Kontext: Vorausfüllen aus Bankbuchung
$bankTx = null;
$bankTxSupplier = '';
$bankTxBrutto = 0.0;
$bankFormData = [];
if (!empty($_GET['bank_tx'])) {
$bankTx = BankTransaction::find((int)$_GET['bank_tx']);
}
if ($bankTx) {
// Auftraggeber extrahieren: "Auftraggeber: X Buchungstext" → X
if (preg_match('/Auftraggeber:\s*(.*?)(?:\s*Buchungstext|\s*$)/si', $bankTx['buchungstext'], $m)) {
$bankTxSupplier = trim($m[1]);
}
$bankTxBrutto = abs((float)$bankTx['wert']);
$netto = round($bankTxBrutto / 1.19, 2);
$bankFormData = [
'buchungs_datum' => $bankTx['wertstellung'],
'betrag_netto' => number_format($netto, 2, '.', ''),
'zahlungs_kontonummer'=> '1200',
];
// Lieferant am Beleg setzen, wenn noch keiner hinterlegt ist
if (empty($beleg['supplier_name']) && $bankTxSupplier !== '') {
$supplierId = \Steuerkram\Models\Supplier::findOrCreate($bankTxSupplier);
\Steuerkram\Database::execute(
'UPDATE belege SET supplier_id=?, updated_at=NOW() WHERE id=?',
[$supplierId, (int)$params['id']]
);
$beleg['supplier_name'] = $bankTxSupplier;
}
}
$view = new View(); $view = new View();
$view->render('layout/base.php', [ $view->render('layout/base.php', [
'pageTitle' => 'Beleg ' . $beleg['beleg_nr'], 'pageTitle' => 'Beleg ' . $beleg['beleg_nr'],
'content' => $this->renderView('belege/detail', [ 'content' => $this->renderView('belege/detail', [
'beleg' => $beleg, 'beleg' => $beleg,
'suppliers' => $suppliers, 'suppliers' => $suppliers,
'bookings' => $bookings, 'bookings' => $bookings,
'integrity' => $integrity, 'integrity' => $integrity,
'accounts' => $accounts, 'accounts' => $accounts,
'assets' => $assets, 'assets' => $assets,
'kiAktiv' => $kiAktiv, 'kiAktiv' => $kiAktiv,
'kiSektion' => $kiSektion, 'kiSektion' => $kiSektion,
'kiVorschlag' => $kiVorschlag, 'kiVorschlag' => $kiVorschlag,
'bankTx' => $bankTx,
'bankTxSupplier' => $bankTxSupplier,
'bankTxBrutto' => $bankTxBrutto,
'formData' => $bankFormData,
]), ]),
]); ]);
} }

View file

@ -7,6 +7,7 @@ namespace Steuerkram\Controllers;
use Steuerkram\Models\Booking; use Steuerkram\Models\Booking;
use Steuerkram\Models\Account; use Steuerkram\Models\Account;
use Steuerkram\Models\Beleg; use Steuerkram\Models\Beleg;
use Steuerkram\Models\BankTransaction;
use Steuerkram\Models\FiscalYear; use Steuerkram\Models\FiscalYear;
use Steuerkram\Models\Setting; use Steuerkram\Models\Setting;
use Steuerkram\View; use Steuerkram\View;
@ -182,8 +183,16 @@ class BookingController
} }
} }
// Bankbuchung verknüpfen wenn aus Bank-Kontext
$bankTxId = (int)($data['bank_tx_id'] ?? 0);
if ($bankTxId > 0) {
BankTransaction::linkBeleg($bankTxId, $belegId);
}
if ($newAssetId !== null) { if ($newAssetId !== null) {
View::redirect("/assets/{$newAssetId}/edit", 'Buchung erfasst. Bitte Wirtschaftsgut-Details ergänzen.'); View::redirect("/assets/{$newAssetId}/edit", 'Buchung erfasst. Bitte Wirtschaftsgut-Details ergänzen.');
} elseif ($bankTxId > 0) {
View::redirect('/bank', 'Buchung erfasst und Bankbuchung verknüpft.');
} else { } else {
View::redirect("/belege/{$belegId}", 'Buchung erfasst.'); View::redirect("/belege/{$belegId}", 'Buchung erfasst.');
} }

View file

@ -123,10 +123,8 @@ $vorschauHoehe = (int)($settings['beleg_vorschau_hoehe'] ?? 300);
<?= $b['betrag_brutto'] ? $fmt($b['betrag_brutto']) : '<span class="text-muted">–</span>' ?> <?= $b['betrag_brutto'] ? $fmt($b['betrag_brutto']) : '<span class="text-muted">–</span>' ?>
</td> </td>
<td onclick="event.stopPropagation()"> <td onclick="event.stopPropagation()">
<form method="post" action="/bank/<?= (int)$transaction['id'] ?>/link" style="display:inline"> <a href="/belege/<?= (int)$b['id'] ?>?bank_tx=<?= (int)$transaction['id'] ?>"
<input type="hidden" name="beleg_id" value="<?= (int)$b['id'] ?>"> class="btn btn-primary btn-sm">Verknüpfen</a>
<button type="submit" class="btn btn-primary btn-sm">Verknüpfen</button>
</form>
</td> </td>
</tr> </tr>
<?php endforeach; ?> <?php endforeach; ?>

View file

@ -147,7 +147,7 @@ $suppliers = $suppliers ?? [];
<div class="form-group" style="margin:0;min-width:180px"> <div class="form-group" style="margin:0;min-width:180px">
<label style="font-size:.8rem">Lieferant</label> <label style="font-size:.8rem">Lieferant</label>
<input type="text" name="supplier_name" id="supplier_name_input" <input type="text" name="supplier_name" id="supplier_name_input"
value="<?= $h($beleg['supplier_name'] ?? '') ?>" value="<?= $h($beleg['supplier_name'] ?? ($bankTxSupplier ?? '')) ?>"
placeholder="Name eingeben oder wählen…" placeholder="Name eingeben oder wählen…"
list="supplier-datalist" list="supplier-datalist"
autocomplete="off"> autocomplete="off">
@ -295,8 +295,25 @@ $suppliers = $suppliers ?? [];
</div> </div>
</div> </div>
<!-- BankBuchung-Badge (ersetzt KI-Badge wenn aus Bank-Kontext) -->
<?php if (!$beleg['locked'] && ($bankTx ?? null)): ?>
<div class="card mb-2" style="border-left:4px solid var(--color-primary,#3b82f6)">
<div class="card-body" style="padding:.75rem 1.25rem">
<div style="display:flex;align-items:center;gap:.75rem;flex-wrap:wrap">
<span class="badge badge-blue" style="font-size:.8rem">Bankbuchung</span>
<span class="td-mono text-sm"><?= View::formatDate($bankTx['wertstellung']) ?></span>
<span class="text-sm"><?= $h($bankTx['vorgang']) ?></span>
<span class="text-sm" style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
title="<?= $h($bankTx['buchungstext']) ?>"><?= $h($bankTx['buchungstext']) ?></span>
<?php $bwert = (float)$bankTx['wert']; ?>
<strong style="color:<?= $bwert >= 0 ? 'var(--color-success)' : 'var(--color-danger)' ?>;white-space:nowrap">
<?= $bwert >= 0 ? '+' : '' ?><?= number_format($bwert, 2, ',', '.') ?> €
</strong>
</div>
</div>
</div>
<?php elseif (!$beleg['locked'] && ($kiSektion ?? false)): ?>
<!-- KI-Kontierungsvorschlag --> <!-- KI-Kontierungsvorschlag -->
<?php if (!$beleg['locked'] && ($kiSektion ?? false)): ?>
<div id="ki-badge" style="margin-bottom:1rem"></div> <div id="ki-badge" style="margin-bottom:1rem"></div>
<?php endif; ?> <?php endif; ?>
@ -336,6 +353,9 @@ $suppliers = $suppliers ?? [];
</div> </div>
<?php endif; ?> <?php endif; ?>
<form method="post" action="/belege/<?= $h($beleg['id']) ?>/bookings"> <form method="post" action="/belege/<?= $h($beleg['id']) ?>/bookings">
<?php if ($bankTx ?? null): ?>
<input type="hidden" name="bank_tx_id" value="<?= (int)$bankTx['id'] ?>">
<?php endif; ?>
<!-- Buchungstyp --> <!-- Buchungstyp -->
<div class="form-group"> <div class="form-group">
@ -508,6 +528,49 @@ $suppliers = $suppliers ?? [];
</form> </form>
</div> </div>
</div> </div>
<?php if ($bankTx ?? null): ?>
<script>
(function () {
const brutto = <?= (float)($bankTxBrutto ?? 0) ?>;
let manualNetto = false;
const nettoInput = document.getElementById('betrag_netto_input');
const ustSelect = document.getElementById('ust_satz_select');
// Netto aus Brutto bei USt-Wechsel neu berechnen (solange nicht manuell geändert)
function recalcNetto() {
if (manualNetto || !brutto) return;
const ust = parseFloat(ustSelect.value) / 100;
const netto = Math.round(brutto / (1 + ust) * 100) / 100;
nettoInput.value = netto.toFixed(2);
if (typeof updateNettoAnzeige === 'function') updateNettoAnzeige();
}
nettoInput.addEventListener('input', function () { manualNetto = true; });
ustSelect.addEventListener('change', recalcNetto);
// Zahlungskonto 1200 vorauswählen
const zkSelect = document.getElementById('zahlungskonto_id');
if (zkSelect) {
for (const opt of zkSelect.options) {
if ((opt.dataset.search || '').startsWith('1200 ')) {
opt.selected = true;
zkSelect.dispatchEvent(new Event('change'));
break;
}
}
}
// Meta-Formular mit Lieferant aufklappen wenn noch nicht gespeichert
<?php if (!empty($bankTxSupplier) && empty($beleg['supplier_name'])): ?>
const metaForm = document.getElementById('beleg-meta-form');
if (metaForm) metaForm.style.display = 'block';
<?php endif; ?>
})();
</script>
<?php endif; ?>
<?php else: ?> <?php else: ?>
<div class="flash flash-info">Dieser Beleg ist gesperrt (GoBD). Keine weiteren Buchungen möglich.</div> <div class="flash flash-info">Dieser Beleg ist gesperrt (GoBD). Keine weiteren Buchungen möglich.</div>
<?php endif; ?> <?php endif; ?>