Feature: Anlagevermögen-Modal beim Buchen auf AV-Konto

This commit is contained in:
fds2610-bot 2026-04-12 11:40:51 +02:00
parent f77b228fdb
commit 05a964edbb
2 changed files with 186 additions and 1 deletions

View file

@ -7,6 +7,7 @@ namespace Steuerkram\Controllers;
use Steuerkram\Models\Beleg; use Steuerkram\Models\Beleg;
use Steuerkram\Models\Booking; use Steuerkram\Models\Booking;
use Steuerkram\Models\Account; use Steuerkram\Models\Account;
use Steuerkram\Models\Asset;
use Steuerkram\Models\Supplier; use Steuerkram\Models\Supplier;
use Steuerkram\Config; use Steuerkram\Config;
use Steuerkram\View; use Steuerkram\View;
@ -198,6 +199,7 @@ class BelegController
$integrity = Beleg::verifyIntegrity((int)$params['id']); $integrity = Beleg::verifyIntegrity((int)$params['id']);
$accounts = Account::all(['aktiv' => 1]); $accounts = Account::all(['aktiv' => 1]);
$suppliers = Supplier::all(); $suppliers = Supplier::all();
$assets = Asset::all();
$svc = new KontierungsvorschlagService(); $svc = new KontierungsvorschlagService();
$kiAktiv = $svc->isEnabled(); $kiAktiv = $svc->isEnabled();
@ -214,6 +216,7 @@ class BelegController
'bookings' => $bookings, 'bookings' => $bookings,
'integrity' => $integrity, 'integrity' => $integrity,
'accounts' => $accounts, 'accounts' => $accounts,
'assets' => $assets,
'kiAktiv' => $kiAktiv, 'kiAktiv' => $kiAktiv,
'kiVorschlag' => $kiVorschlag, 'kiVorschlag' => $kiVorschlag,
]), ]),

View file

@ -380,7 +380,7 @@ $suppliers = $suppliers ?? [];
data-target="kategorie_konto_id" autocomplete="off" style="margin-bottom:.3rem" data-target="kategorie_konto_id" autocomplete="off" style="margin-bottom:.3rem"
value="<?= $h($formData['kategorie_kontonummer'] ?? '') ?>"> value="<?= $h($formData['kategorie_kontonummer'] ?? '') ?>">
<select id="kategorie_konto_id" name="kategorie_konto_id" size="6" <select id="kategorie_konto_id" name="kategorie_konto_id" size="6"
onchange="showKontoArt('kategorie_konto_id','kategorie_konto_art',this);updateEigenanteilState();updateBewirtungsState();updateAutoUst()"> onchange="showKontoArt('kategorie_konto_id','kategorie_konto_art',this);updateEigenanteilState();updateBewirtungsState();updateAutoUst();checkAnlagevermoegen()">
<option value="">– Konto wählen –</option> <option value="">– Konto wählen –</option>
<?php foreach (['Betriebsausgabe', 'Betriebseinnahme', 'Anlagevermögen', 'Privat', 'Verbindlichkeit'] as $art): ?> <?php foreach (['Betriebsausgabe', 'Betriebseinnahme', 'Anlagevermögen', 'Privat', 'Verbindlichkeit'] as $art): ?>
<?php if (empty($kontenGruppen[$art])) continue; ?> <?php if (empty($kontenGruppen[$art])) continue; ?>
@ -477,6 +477,9 @@ $suppliers = $suppliers ?? [];
</label> </label>
</div> </div>
<!-- hidden: wird bei Auswahl eines bestehenden Anlageguts gesetzt -->
<input type="hidden" name="asset_id" id="asset_id_hidden" value="">
<button type="submit" class="btn btn-primary">Buchung erfassen</button> <button type="submit" class="btn btn-primary">Buchung erfassen</button>
</form> </form>
</div> </div>
@ -488,6 +491,185 @@ $suppliers = $suppliers ?? [];
</div> </div>
</div> </div>
<!-- ── Modal: Anlagevermögen ──────────────────────────────────── -->
<div id="modal-anlagevermoegen" class="modal-overlay" role="dialog" aria-modal="true">
<div class="modal" style="max-width:640px;width:100%">
<div class="modal-header">
<h3 class="modal-title">Anlagevermögen buchen</h3>
<button type="button" class="modal-close" data-modal-close aria-label="Schließen">&times;</button>
</div>
<div class="modal-body">
<p style="margin-bottom:1rem">Das gewählte Konto gehört zum Anlagevermögen. Soll ein neues Anlagegut angelegt oder ein bestehendes ausgewählt werden?</p>
<div style="display:flex;flex-direction:column;gap:.6rem;margin-bottom:1.25rem">
<label style="display:flex;align-items:center;gap:.6rem;cursor:pointer">
<input type="radio" name="anlage_wahl" id="anlage_radio_neu" value="neu" checked>
<span>Neues Anlagegut anlegen</span>
</label>
<label style="display:flex;align-items:center;gap:.6rem;cursor:pointer">
<input type="radio" name="anlage_wahl" id="anlage_radio_vorhanden" value="vorhanden">
<span>Bestehendes Anlagegut auswählen</span>
</label>
</div>
<!-- Liste bestehender Anlagegüter (nur bei "vorhanden") -->
<div id="anlage_liste_wrap" style="display:none">
<p id="anlage_liste_leer" class="text-muted text-sm" style="display:none">
Für dieses Konto sind noch keine Anlagegüter vorhanden.
</p>
<div class="table-wrap" id="anlage_liste_table_wrap">
<table>
<thead>
<tr>
<th style="width:3rem">Nr.</th>
<th>Wirtschaftsgut</th>
<th>Anschaffungsdatum</th>
<th class="td-num">Anschaffungskosten</th>
<th style="width:2.5rem"></th>
</tr>
</thead>
<tbody id="anlage_liste_tbody"></tbody>
</table>
</div>
</div>
</div>
<div class="modal-footer" style="display:flex;justify-content:flex-end;gap:.75rem;padding:1rem 1.5rem;border-top:1px solid var(--color-border)">
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
<button type="button" class="btn btn-primary" id="anlage_modal_ok">Weiter</button>
</div>
</div>
</div>
<?php
// Assets als JSON für JS vorbereiten
$assetsJson = json_encode(array_map(fn($a) => [
'id' => (int)$a['id'],
'lfd_nr' => (int)$a['lfd_nr'],
'bezeichnung' => $a['bezeichnung'],
'anschaffungsdatum' => $a['anschaffungsdatum'],
'anschaffungskosten' => (float)$a['anschaffungskosten'],
'account_id' => $a['account_id'] !== null ? (int)$a['account_id'] : null,
], $assets ?? []), JSON_HEX_TAG);
?>
<script>
const _assets = <?= $assetsJson ?>;
// Formatierung Datum de-DE
function _fmtDate(str) {
if (!str) return '–';
const [y, m, d] = str.split('-');
return d + '.' + m + '.' + y;
}
function _fmtEur(n) {
return n.toLocaleString('de-DE', {minimumFractionDigits:2, maximumFractionDigits:2}) + ' €';
}
// Wird aufgerufen wenn Kategorie-Konto geändert wird
function checkAnlagevermoegen() {
const sel = document.getElementById('kategorie_konto_id');
if (!sel) return;
const opt = sel.options[sel.selectedIndex];
if (!opt || opt.value === '') return;
const art = opt.getAttribute('data-art') || '';
if (art !== 'Anlagevermögen') {
// Kein AV-Konto – Asset-Auswahl zurücksetzen
document.getElementById('asset_id_hidden').value = '';
return;
}
// AV-Konto gewählt → Modal öffnen
const accountId = parseInt(opt.value, 10);
_openAnlageModal(accountId);
}
function _openAnlageModal(accountId) {
// Radio auf "neu" zurücksetzen
document.getElementById('anlage_radio_neu').checked = true;
document.getElementById('anlage_liste_wrap').style.display = 'none';
document.getElementById('asset_id_hidden').value = '';
// Liste für dieses Konto befüllen
const passend = _assets.filter(a => a.account_id === accountId);
_renderAnlageListe(passend);
document.getElementById('modal-anlagevermoegen').classList.add('open');
}
function _renderAnlageListe(list) {
const tbody = document.getElementById('anlage_liste_tbody');
const leer = document.getElementById('anlage_liste_leer');
const table = document.getElementById('anlage_liste_table_wrap');
tbody.innerHTML = '';
if (list.length === 0) {
leer.style.display = '';
table.style.display = 'none';
return;
}
leer.style.display = 'none';
table.style.display = '';
list.forEach(a => {
const tr = document.createElement('tr');
tr.style.cursor = 'pointer';
tr.dataset.assetId = a.id;
tr.innerHTML =
'<td class="td-mono text-sm">' + a.lfd_nr + '</td>' +
'<td>' + _esc(a.bezeichnung) + '</td>' +
'<td class="td-muted text-sm">' + _fmtDate(a.anschaffungsdatum) + '</td>' +
'<td class="td-num">' + _fmtEur(a.anschaffungskosten) + '</td>' +
'<td><input type="radio" name="anlage_auswahl" value="' + a.id + '"></td>';
tbody.appendChild(tr);
// Klick auf Zeile wählt Radio
tr.addEventListener('click', () => {
const radio = tr.querySelector('input[type="radio"]');
if (radio) radio.checked = true;
tbody.querySelectorAll('tr').forEach(r => r.classList.remove('row-selected'));
tr.classList.add('row-selected');
});
});
}
function _esc(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
// Radio-Wahl: neu / vorhanden
document.addEventListener('change', e => {
if (e.target.name !== 'anlage_wahl') return;
const wrap = document.getElementById('anlage_liste_wrap');
wrap.style.display = e.target.value === 'vorhanden' ? '' : 'none';
if (e.target.value === 'neu') {
document.getElementById('asset_id_hidden').value = '';
document.querySelectorAll('input[name="anlage_auswahl"]').forEach(r => r.checked = false);
document.querySelectorAll('#anlage_liste_tbody tr').forEach(r => r.classList.remove('row-selected'));
}
});
// OK-Button
document.getElementById('anlage_modal_ok').addEventListener('click', () => {
const wahl = document.querySelector('input[name="anlage_wahl"]:checked')?.value;
if (wahl === 'vorhanden') {
const gewählt = document.querySelector('input[name="anlage_auswahl"]:checked');
if (!gewählt) {
alert('Bitte ein Anlagegut aus der Liste auswählen.');
return;
}
document.getElementById('asset_id_hidden').value = gewählt.value;
} else {
// "neu" – asset_id bleibt leer, Backend legt neues Gut an
document.getElementById('asset_id_hidden').value = '';
}
document.getElementById('modal-anlagevermoegen').classList.remove('open');
});
</script>
<script> <script>
function filterKategorieKonten(buchungstyp) { function filterKategorieKonten(buchungstyp) {
const select = document.getElementById('kategorie_konto_id'); const select = document.getElementById('kategorie_konto_id');