Bugfixes und Kleinigkeiten Anlagegüter

This commit is contained in:
fds2610-bot 2026-05-24 15:22:18 +02:00
parent b637b7b5ab
commit 5546c1a9e7
5 changed files with 407 additions and 35 deletions

View file

@ -17,13 +17,17 @@ class AssetController extends BaseController
{
public function index(array $params): void
{
$assets = Asset::all();
$assets = Asset::all();
$nurUnzugeordnet = ($_GET['filter'] ?? 'unzugeordnet') !== 'alle';
$anlageBookings = Asset::anlagekontoBookings($nurUnzugeordnet);
$view = new View();
$view->render('layout/base.php', [
'pageTitle' => 'Anlagevermögen',
'content' => $this->renderView('assets/list', [
'assets' => $assets,
'assets' => $assets,
'anlageBookings' => $anlageBookings,
'nurUnzugeordnet' => $nurUnzugeordnet,
]),
'topbarActions' => '<a href="/assets/new" class="btn btn-primary" title="Wirtschaftsgut anlegen" style="display:inline-flex;align-items:center;justify-content:center;padding:.36rem;border-radius:50%;width:1.8rem;height:1.8rem">'
. '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">'
@ -35,11 +39,25 @@ class AssetController extends BaseController
public function create(array $params): void
{
$prefill = [];
if (!empty($_GET['bk_booking_id'])) {
$prefill['bk_booking_id'] = (int)$_GET['bk_booking_id'];
}
if (!empty($_GET['bk_account_id'])) {
$prefill['account_id'] = (int)$_GET['bk_account_id'];
}
if (!empty($_GET['bk_datum'])) {
$prefill['anschaffungsdatum'] = $_GET['bk_datum'];
}
if (isset($_GET['bk_betrag']) && $_GET['bk_betrag'] !== '') {
$prefill['anschaffungskosten'] = number_format((float)$_GET['bk_betrag'], 2, '.', '');
}
$view = new View();
$view->render('layout/base.php', [
'pageTitle' => 'Wirtschaftsgut anlegen',
'content' => $this->renderView('assets/form', [
'asset' => [],
'asset' => $prefill,
'errors' => [],
'action' => '/assets',
'anlagearten' => Asset::anlagearten(),
@ -74,6 +92,14 @@ class AssetController extends BaseController
AssetDepreciation::generateAndSave($asset);
}
$bkBookingId = (int)($_POST['bk_booking_id'] ?? 0);
if ($bkBookingId > 0) {
\Steuerkram\Database::execute(
'UPDATE bookings SET asset_id = ? WHERE id = ? AND asset_id IS NULL',
[$id, $bkBookingId]
);
}
View::redirect('/assets/' . $id . '/edit', 'Wirtschaftsgut erfolgreich angelegt.');
}
@ -93,6 +119,8 @@ class AssetController extends BaseController
);
$previousAfaKontoId = $prevAfaKonto ? (int)$prevAfaKonto['kategorie_konto_id'] : null;
$bookingHints = $this->buildBookingHints($asset);
$view = new View();
$view->render('layout/base.php', [
'pageTitle' => 'Wirtschaftsgut bearbeiten',
@ -105,10 +133,33 @@ class AssetController extends BaseController
'abschreibungskonten' => $abschreibungskonten,
'previousAfaKontoId' => $previousAfaKontoId,
'testMode' => Setting::isTestMode(),
'bookingHints' => $bookingHints,
]),
]);
}
private function buildBookingHints(array $asset): array
{
$hints = [];
$bkAccountId = isset($_GET['bk_account_id']) && $_GET['bk_account_id'] !== ''
? (int)$_GET['bk_account_id'] : null;
$bkDatum = $_GET['bk_datum'] ?? '';
if ($bkAccountId !== null && (int)($asset['account_id'] ?? 0) !== $bkAccountId) {
$bkAccount = Account::find($bkAccountId);
$hints['account_id'] = $bkAccount
? $bkAccount['kontonummer'] . ' – ' . $bkAccount['bezeichnung']
: 'Konto-ID ' . $bkAccountId;
}
if ($bkDatum !== '' && ($asset['anschaffungsdatum'] ?? '') !== $bkDatum) {
$hints['anschaffungsdatum'] = View::formatDate($bkDatum);
}
return $hints;
}
public function update(array $params): void
{
$id = (int)$params['id'];
@ -150,9 +201,14 @@ class AssetController extends BaseController
public function destroy(array $params): void
{
$id = (int)$params['id'];
AssetDepreciation::deleteByAsset($id);
if (!Asset::delete($id)) {
$id = (int)$params['id'];
$result = Asset::delete($id);
if ($result === 'hat_buchungen') {
View::redirect('/assets', 'Das Wirtschaftsgut kann nicht gelöscht werden, da noch Buchungen damit verknüpft sind.', 'error');
return;
}
if (!$result) {
View::redirect('/assets', 'Wirtschaftsgut konnte nicht gelöscht werden.', 'error');
return;
}

View file

@ -203,11 +203,54 @@ class Asset
);
}
public static function delete(int $id): bool
public static function anlagekontoBookings(bool $nurUnzugeordnet = true): array
{
$extraWhere = $nurUnzugeordnet ? 'AND (b.asset_id IS NULL OR a.id IS NULL)' : '';
return Database::fetchAll(
"SELECT
b.id, b.buchungs_nr, b.buchungs_datum, b.buchungstext,
b.betrag_netto, b.betrag_brutto, b.is_reversal, b.reversed_by,
b.asset_id, b.beleg_id, b.kategorie_konto_id,
ac.kontonummer AS kategorie_kontonummer,
ac.bezeichnung AS kategorie_bezeichnung,
zk.kontonummer AS zahlungs_kontonummer,
zk.bezeichnung AS zahlungs_bezeichnung,
a.bezeichnung AS asset_bezeichnung,
a.anlage_nr AS asset_anlage_nr
FROM bookings b
JOIN accounts ac ON b.kategorie_konto_id = ac.id
AND ac.kontenart = 'Anlagevermögen'
JOIN accounts zk ON b.zahlungskonto_id = zk.id
LEFT JOIN assets a ON b.asset_id = a.id
WHERE b.buchungstyp != 'AfA'
AND b.is_reversal = 0
AND b.reversed_by IS NULL
$extraWhere
ORDER BY b.buchungs_datum DESC",
[]
);
}
/**
* Löscht ein Wirtschaftsgut inklusive AfA-Buchungen und Abschreibungstabelle.
* Gibt 'hat_buchungen' zurück, wenn noch reguläre Buchungen verknüpft sind.
*/
public static function delete(int $id): bool|string
{
$asset = self::find($id);
if (!$asset) return false;
$linked = Database::fetchOne(
"SELECT COUNT(*) AS cnt FROM bookings WHERE asset_id = ? AND buchungstyp != 'AfA'",
[$id]
);
if ($linked && (int)$linked['cnt'] > 0) {
return 'hat_buchungen';
}
Database::execute("DELETE FROM bookings WHERE asset_id = ? AND buchungstyp = 'AfA'", [$id]);
AssetDepreciation::deleteByAsset($id);
Database::execute('DELETE FROM assets WHERE id = ?', [$id]);
return true;
}

View file

@ -2,13 +2,27 @@
use Steuerkram\View;
$h = fn($v) => View::h($v);
$assetId = (int)($asset['id'] ?? 0);
$isEdit = $assetId > 0;
$nutzung = isset($asset['nutzungsdauer']) && $asset['nutzungsdauer'] !== '' ? (int)$asset['nutzungsdauer'] : null;
$predefined = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 20, 25, 50];
$isCustom = $nutzung !== null && !in_array($nutzung, $predefined, true);
$centFlag = !empty($asset['cent_im_ersten_jahr']);
$autoFlag = isset($asset['automatisch_berechnet']) ? (bool)(int)$asset['automatisch_berechnet'] : true;
$assetId = (int)($asset['id'] ?? 0);
$isEdit = $assetId > 0;
$nutzung = isset($asset['nutzungsdauer']) && $asset['nutzungsdauer'] !== '' ? (int)$asset['nutzungsdauer'] : null;
$predefined = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 20, 25, 50];
$isCustom = $nutzung !== null && !in_array($nutzung, $predefined, true);
$centFlag = !empty($asset['cent_im_ersten_jahr']);
$autoFlag = isset($asset['automatisch_berechnet']) ? (bool)(int)$asset['automatisch_berechnet'] : true;
$bkHints = $bookingHints ?? [];
$conflictBadge = function(string $field, string $label) use ($bkHints, $h): string {
if (!isset($bkHints[$field])) return '';
$val = $h($bkHints[$field]);
return '<span style="display:inline-flex;align-items:center;gap:.3rem;background:#fef2f2;color:#b91c1c;'
. 'border:1px solid #fecaca;border-radius:.35rem;font-size:.75rem;padding:.15rem .5rem;'
. 'margin-left:.5rem;font-weight:500" title="Abweichung zur verknüpften Buchung">'
. '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" '
. 'stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">'
. '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>'
. '<line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'
. 'Buchung: ' . $val . '</span>';
};
?>
<div class="card" style="max-width:620px">
@ -16,7 +30,7 @@ $autoFlag = isset($asset['automatisch_berechnet']) ? (bool)(int)$asset['automa
<form method="post" action="<?= $h($action) ?>">
<div class="form-group">
<label for="account_id">Anlageart (Konto)</label>
<label for="account_id">Anlageart (Konto)<?= $conflictBadge('account_id', 'Anlageart') ?></label>
<select id="account_id" name="account_id">
<option value="">– bitte wählen –</option>
<?php foreach ($anlagearten as $kto): ?>
@ -40,7 +54,7 @@ $autoFlag = isset($asset['automatisch_berechnet']) ? (bool)(int)$asset['automa
</div>
<div class="form-group">
<label for="anschaffungsdatum">Anschaffungsdatum *</label>
<label for="anschaffungsdatum">Anschaffungsdatum *<?= $conflictBadge('anschaffungsdatum', 'Anschaffungsdatum') ?></label>
<input type="date" id="anschaffungsdatum" name="anschaffungsdatum"
value="<?= $h($asset['anschaffungsdatum'] ?? '') ?>"
class="<?= isset($errors['anschaffungsdatum']) ? 'input-error' : '' ?>"
@ -133,6 +147,11 @@ $autoFlag = isset($asset['automatisch_berechnet']) ? (bool)(int)$asset['automa
<textarea id="notizen" name="notizen" rows="3"><?= $h($asset['notizen'] ?? '') ?></textarea>
</div>
<!-- Buchungs-Verknüpfung aus dem Zuordnungs-Workflow -->
<?php if (!empty($asset['bk_booking_id'])): ?>
<input type="hidden" name="bk_booking_id" value="<?= (int)$asset['bk_booking_id'] ?>">
<?php endif; ?>
<!-- Automatisch-Flag (versteckt, wird per JS aktualisiert) -->
<input type="hidden" name="automatisch_berechnet" id="automatisch_berechnet_input"
value="<?= $autoFlag ? '1' : '0' ?>">

View file

@ -1,4 +1,17 @@
<?php use Steuerkram\View; $h = fn($v) => View::h($v); ?>
<?php
use Steuerkram\View;
$h = fn($v) => View::h($v);
$assetsJson = json_encode(array_map(fn($a) => [
'id' => (int)$a['id'],
'lfd_nr' => (int)$a['lfd_nr'],
'anlage_nr' => $a['anlage_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 | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<div class="card">
<div class="table-wrap">
@ -67,3 +80,244 @@
</div>
</div>
<div style="margin-top:2rem">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem">
<h2 style="font-size:1rem;font-weight:600;margin:0">Buchungen auf Anlagevermögens-Konten</h2>
<div style="display:flex;align-items:center;gap:.5rem;font-size:.875rem;color:var(--color-muted)">
<label style="display:flex;align-items:center;gap:.35rem;cursor:pointer">
<input type="checkbox" id="filter-unzugeordnet"
<?= $nurUnzugeordnet ? 'checked' : '' ?>
onchange="location.href='/assets?filter='+(this.checked?'unzugeordnet':'alle')">
Nur nicht zugeordnete anzeigen
</label>
</div>
</div>
<div class="card">
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:8rem">Datum</th>
<th style="width:7rem">Beleg-Nr.</th>
<th>Konto</th>
<th>Buchungstext</th>
<th class="td-num">Betrag</th>
<th>Anlagegut</th>
<th style="width:2.5rem"></th>
</tr>
</thead>
<tbody>
<?php foreach ($anlageBookings as $b): ?>
<tr>
<td class="td-muted text-sm"><?= View::formatDate($b['buchungs_datum']) ?></td>
<td class="td-mono text-sm">
<?php if ($b['beleg_id']): ?>
<a href="/belege/<?= $h($b['beleg_id']) ?>" style="color:var(--color-link)">
<?= $h($b['buchungs_nr']) ?>
</a>
<?php else: ?>
<?= $h($b['buchungs_nr']) ?>
<?php endif; ?>
</td>
<td class="text-sm">
<span class="td-mono"><?= $h($b['kategorie_kontonummer']) ?></span>
<span class="td-muted"> <?= $h($b['kategorie_bezeichnung']) ?></span>
</td>
<td class="text-sm"><?= $h($b['buchungstext']) ?></td>
<td class="td-num text-sm"><?= number_format((float)$b['betrag_brutto'], 2, ',', '.') ?> €</td>
<td class="text-sm">
<?php if ($b['asset_id']): ?>
<a href="/assets/<?= $h($b['asset_id']) ?>/edit" style="color:var(--color-link)">
<?= $h($b['asset_anlage_nr']) ?> <?= $h($b['asset_bezeichnung']) ?>
</a>
<?php else: ?>
<span class="text-muted">–</span>
<?php endif; ?>
</td>
<td style="text-align:center">
<?php if (!$b['asset_id']): ?>
<button type="button"
class="anlage-assign-btn"
data-booking-id="<?= $h($b['id']) ?>"
data-account-id="<?= $h($b['kategorie_konto_id'] ?? '') ?>"
data-datum="<?= $h($b['buchungs_datum'] ?? '') ?>"
data-betrag="<?= $h($b['betrag_netto'] ?? '') ?>"
title="Anlagegut zuordnen"
style="background:none;border:none;cursor:pointer;color:var(--color-muted);padding:.2rem;line-height:1;display:inline-flex;align-items:center;justify-content:center">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="14" height="14" rx="2"/>
<polyline points="17 8 21 12 17 16"/>
<line x1="10" y1="12" x2="21" y2="12"/>
</svg>
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($anlageBookings)): ?>
<tr>
<td colspan="7" class="text-center text-muted" style="padding:2rem">
<?= $nurUnzugeordnet ? 'Alle Buchungen sind bereits einem Anlagegut zugeordnet.' : 'Keine Buchungen auf Anlagevermögens-Konten vorhanden.' ?>
</td>
</tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- ── Modal: Anlagegut zuordnen ──────────────────────────────── -->
<div id="modal-anlage-assign" 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">Anlagegut zuordnen</h3>
</div>
<div class="modal-body">
<p style="margin-bottom:1rem">Soll ein neues Wirtschaftsgut 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="assign_wahl" id="assign_radio_neu" value="neu" checked>
<span>Neues Wirtschaftsgut anlegen</span>
</label>
<label style="display:flex;align-items:center;gap:.6rem;cursor:pointer">
<input type="radio" name="assign_wahl" id="assign_radio_vorhanden" value="vorhanden">
<span>Bestehendes Wirtschaftsgut auswählen</span>
</label>
</div>
<div id="assign_liste_wrap" style="display:none">
<p id="assign_liste_leer" class="text-muted text-sm" style="display:none">
Für dieses Konto sind noch keine Wirtschaftsgüter vorhanden.
</p>
<div class="table-wrap" id="assign_liste_table_wrap">
<table>
<thead>
<tr>
<th style="width:6rem">Nr.</th>
<th>Wirtschaftsgut</th>
<th>Anschaffungsdatum</th>
<th class="td-num">Anschaffungskosten</th>
<th style="width:2.5rem"></th>
</tr>
</thead>
<tbody id="assign_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="assign_modal_ok">Weiter</button>
</div>
</div>
</div>
<script>
const _anlageAssets = <?= $assetsJson ?>;
let _assignAccountId = 0;
let _assignBookingId = 0;
let _assignDatum = '';
let _assignBetrag = '';
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}) + ' €';
}
function _escHtml(s) {
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
document.querySelectorAll('.anlage-assign-btn').forEach(btn => {
btn.addEventListener('click', () => {
_assignAccountId = parseInt(btn.dataset.accountId, 10) || 0;
_assignBookingId = parseInt(btn.dataset.bookingId, 10) || 0;
_assignDatum = btn.dataset.datum || '';
_assignBetrag = btn.dataset.betrag || '';
document.getElementById('assign_radio_neu').checked = true;
document.getElementById('assign_liste_wrap').style.display = 'none';
document.querySelectorAll('input[name="assign_auswahl"]').forEach(r => r.checked = false);
const passend = _anlageAssets.filter(a => a.account_id === _assignAccountId);
_renderAssignListe(passend);
document.getElementById('modal-anlage-assign').classList.add('open');
});
});
function _renderAssignListe(list) {
const tbody = document.getElementById('assign_liste_tbody');
const leer = document.getElementById('assign_liste_leer');
const table = document.getElementById('assign_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.innerHTML =
'<td class="td-mono text-sm">' + _escHtml(a.anlage_nr || a.lfd_nr) + '</td>' +
'<td>' + _escHtml(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="assign_auswahl" value="' + a.id + '"></td>';
tbody.appendChild(tr);
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');
});
});
}
document.addEventListener('change', e => {
if (e.target.name !== 'assign_wahl') return;
const wrap = document.getElementById('assign_liste_wrap');
wrap.style.display = e.target.value === 'vorhanden' ? '' : 'none';
if (e.target.value === 'neu') {
document.querySelectorAll('input[name="assign_auswahl"]').forEach(r => r.checked = false);
document.querySelectorAll('#assign_liste_tbody tr').forEach(r => r.classList.remove('row-selected'));
}
});
document.getElementById('assign_modal_ok').addEventListener('click', () => {
const wahl = document.querySelector('input[name="assign_wahl"]:checked')?.value;
const bkParams = new URLSearchParams();
if (_assignBookingId) bkParams.set('bk_booking_id', _assignBookingId);
if (_assignAccountId) bkParams.set('bk_account_id', _assignAccountId);
if (_assignDatum) bkParams.set('bk_datum', _assignDatum);
if (_assignBetrag) bkParams.set('bk_betrag', _assignBetrag);
const qs = bkParams.toString() ? '?' + bkParams.toString() : '';
if (wahl === 'vorhanden') {
const gewählt = document.querySelector('input[name="assign_auswahl"]:checked');
if (!gewählt) {
alert('Bitte ein Wirtschaftsgut aus der Liste auswählen.');
return;
}
document.getElementById('modal-anlage-assign').classList.remove('open');
location.href = '/assets/' + gewählt.value + '/edit' + qs;
} else {
document.getElementById('modal-anlage-assign').classList.remove('open');
location.href = '/assets/new' + qs;
}
});
</script>

View file

@ -142,15 +142,15 @@ $fmt = fn($v) => View::formatMoney($v);
<table>
<thead>
<tr>
<th>Anlage-ID</th>
<th>Bezeichnung</th>
<th>Anschaffungsdatum</th>
<th class="td-right">Anschaffungskosten</th>
<th class="td-right">Nutzungsdauer</th>
<th class="td-right">AfA Vorjahre</th>
<th class="td-right">Restbuchwert Vorjahr</th>
<th class="td-right">AfA <?= $h($year) ?></th>
<th class="td-right">Restbuchwert 31.12.<?= $h($year) ?></th>
<th style="white-space:normal">Anlage-ID</th>
<th style="white-space:normal">Bezeichnung</th>
<th style="white-space:normal">Anschaff.-datum</th>
<th class="td-right" style="white-space:normal">Anschaff.-kosten</th>
<th class="td-right" style="white-space:normal">Nutzungs-dauer</th>
<th class="td-right" style="white-space:normal">AfA Vorjahre</th>
<th class="td-right" style="white-space:normal">Restbuch-wert Vorjahr</th>
<th class="td-right" style="white-space:normal">AfA <?= $h($year) ?></th>
<th class="td-right" style="white-space:normal">Restbuch-wert 31.12.<?= $h($year) ?></th>
</tr>
</thead>
<tbody>
@ -170,17 +170,17 @@ $fmt = fn($v) => View::formatMoney($v);
if ($al['nutzungsdauer'] === '0' || $al['nutzungsdauer'] === 0) $nd = 'sofort';
?>
<tr>
<td class="td-mono text-sm">
<td class="td-mono text-sm" style="white-space:nowrap">
<a href="/assets/<?= (int)$al['id'] ?>/edit"><?= $h($al['anlage_nr'] ?: $al['lfd_nr']) ?></a>
</td>
<td><?= $h($al['bezeichnung']) ?></td>
<td class="td-muted text-sm"><?= View::formatDate($al['anschaffungsdatum']) ?></td>
<td class="td-right td-mono"><?= $fmt($al['basis']) ?></td>
<td class="td-right td-muted"><?= $h($nd) ?></td>
<td class="td-right td-mono"><?= $fmt($al['afa_vorjahre']) ?></td>
<td class="td-right td-mono"><?= $fmt($al['restbuchwert_vorjahr']) ?></td>
<td class="td-right td-mono"><?= $fmt($al['afa_jahr']) ?></td>
<td class="td-right td-mono"><?= $fmt($al['restbuchwert']) ?></td>
<td style="min-width:10rem;width:100%"><?= $h($al['bezeichnung']) ?></td>
<td class="td-muted text-sm" style="white-space:nowrap"><?= View::formatDate($al['anschaffungsdatum']) ?></td>
<td class="td-right td-mono" style="white-space:nowrap"><?= $fmt($al['basis']) ?></td>
<td class="td-right td-muted" style="white-space:nowrap"><?= $h($nd) ?></td>
<td class="td-right td-mono" style="white-space:nowrap"><?= $fmt($al['afa_vorjahre']) ?></td>
<td class="td-right td-mono" style="white-space:nowrap"><?= $fmt($al['restbuchwert_vorjahr']) ?></td>
<td class="td-right td-mono" style="white-space:nowrap"><?= $fmt($al['afa_jahr']) ?></td>
<td class="td-right td-mono" style="white-space:nowrap"><?= $fmt($al['restbuchwert']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>