680 lines
35 KiB
PHP
680 lines
35 KiB
PHP
<?php
|
||
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;
|
||
?>
|
||
|
||
<div class="card" style="max-width:620px">
|
||
<div class="card-body">
|
||
<form method="post" action="<?= $h($action) ?>">
|
||
|
||
<div class="form-group">
|
||
<label for="account_id">Anlageart (Konto)</label>
|
||
<select id="account_id" name="account_id">
|
||
<option value="">– bitte wählen –</option>
|
||
<?php foreach ($anlagearten as $kto): ?>
|
||
<option value="<?= $h($kto['id']) ?>"
|
||
<?= (string)($asset['account_id'] ?? '') === (string)$kto['id'] ? 'selected' : '' ?>>
|
||
<?= $h($kto['kontonummer']) ?> – <?= $h($kto['bezeichnung']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="bezeichnung">Bezeichnung des Wirtschaftsguts *</label>
|
||
<input type="text" id="bezeichnung" name="bezeichnung"
|
||
value="<?= $h($asset['bezeichnung'] ?? '') ?>"
|
||
class="<?= isset($errors['bezeichnung']) ? 'input-error' : '' ?>"
|
||
required autofocus>
|
||
<?php if (isset($errors['bezeichnung'])): ?>
|
||
<div class="error-text"><?= $h($errors['bezeichnung']) ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="anschaffungsdatum">Anschaffungsdatum *</label>
|
||
<input type="date" id="anschaffungsdatum" name="anschaffungsdatum"
|
||
value="<?= $h($asset['anschaffungsdatum'] ?? '') ?>"
|
||
class="<?= isset($errors['anschaffungsdatum']) ? 'input-error' : '' ?>"
|
||
required>
|
||
<?php if (isset($errors['anschaffungsdatum'])): ?>
|
||
<div class="error-text"><?= $h($errors['anschaffungsdatum']) ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="anschaffungskosten">Anschaffungskosten (€) *</label>
|
||
<input type="number" id="anschaffungskosten" name="anschaffungskosten"
|
||
value="<?= $h($asset['anschaffungskosten'] ?? '0') ?>"
|
||
class="<?= isset($errors['anschaffungskosten']) ? 'input-error' : '' ?>"
|
||
min="0" step="0.01" required>
|
||
<?php if (isset($errors['anschaffungskosten'])): ?>
|
||
<div class="error-text"><?= $h($errors['anschaffungskosten']) ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem">
|
||
<div class="form-group">
|
||
<label for="zugaenge">Zugänge (€)</label>
|
||
<input type="number" id="zugaenge" name="zugaenge"
|
||
value="<?= $h($asset['zugaenge'] ?? '0') ?>"
|
||
min="0" step="0.01">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="abgaenge">Abgänge (€)</label>
|
||
<input type="number" id="abgaenge" name="abgaenge"
|
||
value="<?= $h($asset['abgaenge'] ?? '0') ?>"
|
||
min="0" step="0.01">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="abschreibung_jahr">Abschreibung lfd. Jahr (€)</label>
|
||
<input type="number" id="abschreibung_jahr" name="abschreibung_jahr"
|
||
value="<?= $h($asset['abschreibung_jahr'] ?? '0') ?>"
|
||
min="0" step="0.01">
|
||
</div>
|
||
|
||
<!-- Nutzungsdauer + Abschreibungstabelle -->
|
||
<div style="display:grid;grid-template-columns:1fr auto;gap:.75rem;align-items:flex-end">
|
||
<div class="form-group" style="margin-bottom:0">
|
||
<label for="nutzungsdauer_select">Nutzungsdauer (AfA)</label>
|
||
<div style="display:flex;gap:.5rem">
|
||
<select id="nutzungsdauer_select" style="flex:1">
|
||
<option value="" <?= $nutzung === null ? 'selected' : '' ?>>– keine –</option>
|
||
<option value="0" <?= $nutzung === 0 ? 'selected' : '' ?>>sofort</option>
|
||
<?php foreach ([1,2,3,4,5,6,7,8,9,10,12,13,15,20,25,50] as $j): ?>
|
||
<option value="<?= $j ?>" <?= $nutzung === $j ? 'selected' : '' ?>>
|
||
<?= $j ?> <?= $j === 1 ? 'Jahr' : 'Jahre' ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
<option value="custom" <?= $isCustom ? 'selected' : '' ?>>Individuell …</option>
|
||
</select>
|
||
<input type="number" id="nutzungsdauer_custom" name="nutzungsdauer"
|
||
value="<?= $h($nutzung !== null ? $nutzung : '') ?>"
|
||
min="1" step="1" placeholder="Jahre"
|
||
style="width:90px;<?= !$isCustom ? 'display:none' : '' ?>">
|
||
<input type="hidden" id="nutzungsdauer_hidden" name="nutzungsdauer"
|
||
value="<?= $h($nutzung !== null ? $nutzung : '') ?>"
|
||
<?= $isCustom ? 'disabled' : '' ?>>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<?php if ($isEdit): ?>
|
||
<button type="button" class="btn btn-secondary" data-modal-open="modal-afa"
|
||
style="white-space:nowrap" id="btn-show-afa">
|
||
AfA-Tabelle
|
||
</button>
|
||
<?php endif; ?>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group" style="margin-top:.75rem">
|
||
<label style="display:flex;align-items:center;gap:.5rem;cursor:pointer;font-weight:normal">
|
||
<input type="checkbox" name="cent_im_ersten_jahr" id="cent_im_ersten_jahr" value="1"
|
||
<?= $centFlag ? 'checked' : '' ?>>
|
||
Centbeträge im ersten Jahr abschreiben
|
||
</label>
|
||
<div class="text-muted text-sm" style="margin-top:.25rem;margin-left:1.5rem">
|
||
Wenn aktiv: Restwert nach Jahr 1 auf volle Euro gerundet; Folgejahre gleichmäßig ohne Centabweichungen.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label for="notizen">Notizen</label>
|
||
<textarea id="notizen" name="notizen" rows="3"><?= $h($asset['notizen'] ?? '') ?></textarea>
|
||
</div>
|
||
|
||
<!-- Automatisch-Flag (versteckt, wird per JS aktualisiert) -->
|
||
<input type="hidden" name="automatisch_berechnet" id="automatisch_berechnet_input"
|
||
value="<?= $autoFlag ? '1' : '0' ?>">
|
||
|
||
<div style="display:flex;gap:.75rem;margin-top:1rem">
|
||
<button type="submit" class="btn btn-primary">Speichern</button>
|
||
<a href="/assets" class="btn btn-secondary">Abbrechen</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<?php if ($isEdit): ?>
|
||
<?php
|
||
$gwgKontonummer = '0480';
|
||
$isGwg = ($asset['anlageart_kontonummer'] ?? '') === $gwgKontonummer;
|
||
$abschreibungskonten = $abschreibungskonten ?? [];
|
||
$defaultKontoNr = \Steuerkram\Constants::KONTO_AFA_GWG;
|
||
$defaultKontoId = 0;
|
||
foreach ($abschreibungskonten as $k) {
|
||
if ($k['kontonummer'] === $defaultKontoNr) { $defaultKontoId = $k['id']; break; }
|
||
}
|
||
if (!$isGwg) { $defaultKontoId = 0; } // nur bei GWG vorauswählen
|
||
// Früher genutztes Konto hat Vorrang vor dem GWG-Standard
|
||
if (!empty($previousAfaKontoId)) { $defaultKontoId = (int)$previousAfaKontoId; }
|
||
$testMode = $testMode ?? false;
|
||
?>
|
||
<!-- ── AfA-Tabellen-Modal ─────────────────────────────────── -->
|
||
<div class="modal-overlay" id="modal-afa">
|
||
<div class="modal" style="width:min(760px,95vw);max-height:85vh;overflow-y:auto">
|
||
<div class="modal-title">Abschreibungstabelle</div>
|
||
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem;flex-wrap:wrap;gap:.5rem">
|
||
<div style="display:flex;align-items:center;gap:.75rem">
|
||
<span class="text-sm text-muted" id="afa-mode-label">Modus:</span>
|
||
<label style="display:flex;align-items:center;gap:.4rem;cursor:pointer;font-size:.875rem;font-weight:normal">
|
||
<input type="checkbox" id="afa-manual-toggle">
|
||
Manuell bearbeiten
|
||
</label>
|
||
</div>
|
||
<button type="button" class="btn btn-secondary btn-sm" id="afa-recalc-btn"
|
||
title="Tabelle neu berechnen (überschreibt manuelle Änderungen)">
|
||
Neu berechnen
|
||
</button>
|
||
</div>
|
||
|
||
<div id="afa-warning" class="flash flash-warning text-sm" style="display:none;margin-bottom:.75rem">
|
||
Achtung: Die Abschreibungstabelle wurde manuell bearbeitet. Neu berechnen überschreibt alle manuellen Änderungen.
|
||
</div>
|
||
|
||
<div id="afa-table-wrap">
|
||
<table style="width:100%;border-collapse:collapse">
|
||
<thead>
|
||
<tr>
|
||
<th style="text-align:left;padding:.5rem .75rem;border-bottom:2px solid var(--color-border)">Jahr</th>
|
||
<th style="text-align:right;padding:.5rem .75rem;border-bottom:2px solid var(--color-border)">Abschreibung (€)</th>
|
||
<th style="text-align:right;padding:.5rem .75rem;border-bottom:2px solid var(--color-border)">Restwert (€)</th>
|
||
<th style="text-align:center;padding:.5rem .75rem;border-bottom:2px solid var(--color-border);width:2.5rem" title="Buchungsstatus">Buchung</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="afa-table-body">
|
||
<?php if (empty($scheduleRows)): ?>
|
||
<tr id="afa-empty-row">
|
||
<td colspan="4" class="text-center text-muted" style="padding:1.5rem">
|
||
Noch keine Abschreibungstabelle vorhanden. Klicke „Neu berechnen".
|
||
</td>
|
||
</tr>
|
||
<?php else: ?>
|
||
<?php foreach ($scheduleRows as $row): ?>
|
||
<?php $gebucht = !empty($row['afa_booking_id']); ?>
|
||
<tr>
|
||
<td style="padding:.4rem .75rem;border-bottom:1px solid var(--color-border)">
|
||
<?= $h($row['jahr']) ?>
|
||
</td>
|
||
<td class="td-num afa-betrag" style="padding:.4rem .75rem;border-bottom:1px solid var(--color-border)">
|
||
<?= number_format((float)$row['abschreibungsbetrag'], 2, ',', '.') ?>
|
||
</td>
|
||
<td class="td-num afa-restwert" style="padding:.4rem .75rem;border-bottom:1px solid var(--color-border)">
|
||
<?= number_format((float)$row['restwert'], 2, ',', '.') ?>
|
||
</td>
|
||
<td style="padding:.4rem .75rem;border-bottom:1px solid var(--color-border);text-align:center">
|
||
<?php if ($gebucht): ?>
|
||
<div style="display:inline-flex;gap:.2rem;align-items:center;justify-content:center">
|
||
<span title="Buchung vorhanden" style="color:#22c55e;display:inline-flex;align-items: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.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="20 6 9 17 4 12"/>
|
||
</svg>
|
||
</span>
|
||
<button type="button"
|
||
class="afa-reverse-btn"
|
||
data-row-id="<?= (int)$row['id'] ?>"
|
||
title="AFA-Buchung stornieren"
|
||
style="background:none;border:none;cursor:pointer;color:var(--color-muted);padding:.2rem;line-height:1;display:inline-flex;align-items:center">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 7v6h6"/>
|
||
<path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>
|
||
</svg>
|
||
</button>
|
||
<?php if ($testMode): ?>
|
||
<button type="button"
|
||
class="afa-delete-btn"
|
||
data-row-id="<?= (int)$row['id'] ?>"
|
||
title="AFA-Buchung löschen (Testmodus)"
|
||
style="background:none;border:none;cursor:pointer;color:var(--color-danger);padding:.2rem;line-height:1;display:inline-flex;align-items:center">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="3,6 5,6 21,6"/>
|
||
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
|
||
<path d="M10 11v6M14 11v6"/>
|
||
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
|
||
</svg>
|
||
</button>
|
||
<?php endif; ?>
|
||
</div>
|
||
<?php else: ?>
|
||
<button type="button"
|
||
class="afa-book-btn"
|
||
data-row-id="<?= (int)$row['id'] ?>"
|
||
data-jahr="<?= (int)$row['jahr'] ?>"
|
||
data-betrag="<?= number_format((float)$row['abschreibungsbetrag'], 2, ',', '.') ?>"
|
||
data-default-konto="<?= $defaultKontoId ?>"
|
||
title="AFA-Buchung für <?= (int)$row['jahr'] ?> anlegen"
|
||
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 endif; ?>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="modal-actions">
|
||
<button type="button" class="btn btn-secondary" data-modal-close>Schließen</button>
|
||
<button type="button" class="btn btn-primary" id="afa-save-btn">Speichern</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── AFA-Buchungs-Dialog ──────────────────────────────────── -->
|
||
<div class="modal-overlay" id="modal-afa-book">
|
||
<div class="modal" style="width:min(460px,95vw)">
|
||
<div class="modal-title">AFA-Buchung anlegen</div>
|
||
<p class="text-sm text-muted" style="margin-bottom:1rem">
|
||
Jahr <strong id="afabook-jahr"></strong> · Betrag <strong id="afabook-betrag"></strong> €
|
||
</p>
|
||
<div class="form-group">
|
||
<label for="afabook-konto">Abschreibungskonto (Aufwand)</label>
|
||
<select id="afabook-konto">
|
||
<?php foreach ($abschreibungskonten as $k): ?>
|
||
<option value="<?= (int)$k['id'] ?>"><?= $h($k['kontonummer']) ?> – <?= $h($k['bezeichnung']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</div>
|
||
<div class="modal-actions">
|
||
<button type="button" class="btn btn-secondary" data-modal-close>Abbrechen</button>
|
||
<button type="button" class="btn btn-primary" id="afabook-confirm-btn">Buchung anlegen</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<?php endif; ?>
|
||
|
||
<script>
|
||
(function() {
|
||
// ── Nutzungsdauer Dropdown ──────────────────────────────
|
||
const sel = document.getElementById('nutzungsdauer_select');
|
||
const custom = document.getElementById('nutzungsdauer_custom');
|
||
const hidden = document.getElementById('nutzungsdauer_hidden');
|
||
|
||
if (sel) {
|
||
sel.addEventListener('change', () => {
|
||
const val = sel.value;
|
||
if (val === 'custom') {
|
||
custom.style.display = '';
|
||
custom.removeAttribute('disabled');
|
||
hidden.setAttribute('disabled', '');
|
||
custom.focus();
|
||
} else {
|
||
custom.style.display = 'none';
|
||
custom.setAttribute('disabled', '');
|
||
hidden.removeAttribute('disabled');
|
||
hidden.value = val;
|
||
}
|
||
// Automatisch-Flag zurücksetzen bei Parameteränderung
|
||
scheduleMarkAuto();
|
||
});
|
||
|
||
custom.addEventListener('input', () => {
|
||
scheduleMarkAuto();
|
||
});
|
||
}
|
||
|
||
function getNutzungsdauer() {
|
||
if (!sel) return null;
|
||
const val = sel.value;
|
||
if (val === 'custom') return parseInt(custom.value, 10) || null;
|
||
if (val === '') return null;
|
||
return parseInt(val, 10);
|
||
}
|
||
|
||
function scheduleMarkAuto() {
|
||
// Wenn Tabelle war manuell und Parameter ändert sich: Rückfrage
|
||
const autoInput = document.getElementById('automatisch_berechnet_input');
|
||
if (autoInput && autoInput.value === '0') {
|
||
const confirm = window.confirm(
|
||
'Manuell geänderte Werte vorhanden. Sollen diese überschrieben werden?\n\n' +
|
||
'OK = Neu berechnen, Abbrechen = Manuelle Werte behalten'
|
||
);
|
||
if (confirm) {
|
||
autoInput.value = '1';
|
||
updateAfaWarning(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── AfA-Tabellen-Modal ──────────────────────────────────
|
||
const manualToggle = document.getElementById('afa-manual-toggle');
|
||
const recalcBtn = document.getElementById('afa-recalc-btn');
|
||
const saveBtn = document.getElementById('afa-save-btn');
|
||
const tbody = document.getElementById('afa-table-body');
|
||
const autoInput = document.getElementById('automatisch_berechnet_input');
|
||
const assetId = <?= $assetId ?>;
|
||
|
||
// Spiegele aktuellen Auto-Status in Modal-Toggle
|
||
function syncToggle() {
|
||
if (!manualToggle || !autoInput) return;
|
||
manualToggle.checked = autoInput.value === '0';
|
||
updateAfaWarning(autoInput.value === '0');
|
||
}
|
||
|
||
function updateAfaWarning(isManual) {
|
||
const warn = document.getElementById('afa-warning');
|
||
if (warn) warn.style.display = isManual ? '' : 'none';
|
||
if (recalcBtn) recalcBtn.style.display = isManual ? '' : 'none';
|
||
}
|
||
|
||
// Modal öffnen → Toggle syncen
|
||
document.addEventListener('click', (e) => {
|
||
if (e.target.closest('[data-modal-open="modal-afa"]')) {
|
||
syncToggle();
|
||
setTableEditable(manualToggle?.checked ?? false);
|
||
}
|
||
});
|
||
|
||
if (manualToggle) {
|
||
manualToggle.addEventListener('change', () => {
|
||
const isManual = manualToggle.checked;
|
||
if (autoInput) autoInput.value = isManual ? '0' : '1';
|
||
updateAfaWarning(isManual);
|
||
setTableEditable(isManual);
|
||
});
|
||
}
|
||
|
||
function setTableEditable(editable) {
|
||
if (!tbody) return;
|
||
tbody.querySelectorAll('tr').forEach(tr => {
|
||
tr.querySelectorAll('.afa-betrag, .afa-restwert').forEach(cell => {
|
||
cell.contentEditable = editable ? 'true' : 'false';
|
||
cell.style.background = editable ? 'var(--color-input-bg, #fff)' : '';
|
||
cell.style.outline = editable ? '1px solid var(--color-border)' : '';
|
||
cell.style.borderRadius = editable ? '3px' : '';
|
||
});
|
||
});
|
||
}
|
||
|
||
function fmtNum(v) {
|
||
return parseFloat(String(v).replace(',', '.')).toLocaleString('de-DE', {
|
||
minimumFractionDigits: 2, maximumFractionDigits: 2
|
||
});
|
||
}
|
||
|
||
function parseNum(s) {
|
||
return parseFloat(String(s).replace(/\./g, '').replace(',', '.')) || 0;
|
||
}
|
||
|
||
// Neu berechnen
|
||
if (recalcBtn) {
|
||
recalcBtn.addEventListener('click', async () => {
|
||
if (!window.confirm('Abschreibungstabelle neu berechnen? Manuelle Änderungen gehen verloren.')) return;
|
||
await loadPreview();
|
||
if (autoInput) autoInput.value = '1';
|
||
if (manualToggle) manualToggle.checked = false;
|
||
updateAfaWarning(false);
|
||
setTableEditable(false);
|
||
});
|
||
}
|
||
|
||
async function loadPreview() {
|
||
const aks = document.getElementById('anschaffungskosten')?.value ?? '0';
|
||
const zug = document.getElementById('zugaenge')?.value ?? '0';
|
||
const abg = document.getElementById('abgaenge')?.value ?? '0';
|
||
const dat = document.getElementById('anschaffungsdatum')?.value ?? '';
|
||
const nd = getNutzungsdauer();
|
||
const cent = document.getElementById('cent_im_ersten_jahr')?.checked ? '1' : '0';
|
||
|
||
if (nd === null) {
|
||
if (tbody) tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted" style="padding:1.5rem">Keine Nutzungsdauer angegeben.</td></tr>';
|
||
return;
|
||
}
|
||
|
||
const qs = new URLSearchParams({
|
||
anschaffungskosten: aks, zugaenge: zug, abgaenge: abg,
|
||
nutzungsdauer: nd, cent_im_ersten_jahr: cent,
|
||
anschaffungsdatum: dat
|
||
});
|
||
|
||
try {
|
||
const res = await fetch('/assets/depreciation-preview?' + qs);
|
||
const data = await res.json();
|
||
renderTable(data.rows ?? []);
|
||
} catch (_) {
|
||
if (tbody) tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted" style="padding:1rem">Fehler beim Laden.</td></tr>';
|
||
}
|
||
}
|
||
|
||
function renderTable(rows) {
|
||
if (!tbody) return;
|
||
if (!rows.length) {
|
||
tbody.innerHTML = '<tr><td colspan="4" class="text-center text-muted" style="padding:1.5rem">Keine Daten.</td></tr>';
|
||
return;
|
||
}
|
||
const isManual = manualToggle?.checked ?? false;
|
||
tbody.innerHTML = rows.map(r => `
|
||
<tr>
|
||
<td style="padding:.4rem .75rem;border-bottom:1px solid var(--color-border)">${r.jahr}</td>
|
||
<td class="td-num afa-betrag" contenteditable="${isManual}"
|
||
style="padding:.4rem .75rem;border-bottom:1px solid var(--color-border)${isManual ? ';background:var(--color-input-bg,#fff);outline:1px solid var(--color-border);border-radius:3px' : ''}">
|
||
${fmtNum(r.abschreibungsbetrag)}
|
||
</td>
|
||
<td class="td-num afa-restwert" contenteditable="${isManual}"
|
||
style="padding:.4rem .75rem;border-bottom:1px solid var(--color-border)${isManual ? ';background:var(--color-input-bg,#fff);outline:1px solid var(--color-border);border-radius:3px' : ''}">
|
||
${fmtNum(r.restwert)}
|
||
</td>
|
||
<td style="padding:.4rem .75rem;border-bottom:1px solid var(--color-border);text-align:center;width:2.5rem">
|
||
<span title="Tabelle neu speichern, um Buchungen anzulegen" style="color:var(--color-muted);font-size:.7rem">–</span>
|
||
</td>
|
||
</tr>`).join('');
|
||
}
|
||
|
||
// Speichern
|
||
if (saveBtn) {
|
||
saveBtn.addEventListener('click', async () => {
|
||
const rows = [];
|
||
if (tbody) {
|
||
tbody.querySelectorAll('tr').forEach((tr, i) => {
|
||
const cells = tr.querySelectorAll('td');
|
||
if (cells.length < 3) return;
|
||
rows.push({
|
||
jahr: parseInt(cells[0].textContent.trim(), 10),
|
||
abschreibungsbetrag: cells[1].textContent.trim(),
|
||
restwert: cells[2].textContent.trim(),
|
||
});
|
||
});
|
||
}
|
||
|
||
const automatisch = !(manualToggle?.checked ?? false) ? 1 : 0;
|
||
|
||
try {
|
||
const res = await fetch(`/assets/${assetId}/depreciation`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ rows, automatisch }),
|
||
});
|
||
const data = await res.json();
|
||
if (data.ok) {
|
||
if (autoInput) autoInput.value = automatisch ? '1' : '0';
|
||
document.getElementById('modal-afa')?.classList.remove('open');
|
||
} else {
|
||
alert('Fehler beim Speichern: ' + (data.error ?? 'Unbekannt'));
|
||
}
|
||
} catch (_) {
|
||
alert('Netzwerkfehler beim Speichern.');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Init: Neu-Berechnen-Button nur bei manueller Tabelle zeigen
|
||
syncToggle();
|
||
|
||
// ── AFA-Buchungs-Dialog ──────────────────────────────────────
|
||
let afabookRowId = 0;
|
||
const afabookJahr = document.getElementById('afabook-jahr');
|
||
const afabookBetrag = document.getElementById('afabook-betrag');
|
||
const afabookKonto = document.getElementById('afabook-konto');
|
||
const afabookModal = document.getElementById('modal-afa-book');
|
||
const afabookBtn = document.getElementById('afabook-confirm-btn');
|
||
|
||
document.addEventListener('click', (e) => {
|
||
const btn = e.target.closest('.afa-book-btn');
|
||
if (!btn) return;
|
||
afabookRowId = parseInt(btn.dataset.rowId, 10);
|
||
if (afabookJahr) afabookJahr.textContent = btn.dataset.jahr;
|
||
if (afabookBetrag) afabookBetrag.textContent = btn.dataset.betrag;
|
||
const defaultId = parseInt(btn.dataset.defaultKonto, 10);
|
||
if (afabookKonto && defaultId) {
|
||
afabookKonto.value = defaultId;
|
||
}
|
||
if (afabookModal) afabookModal.classList.add('open');
|
||
});
|
||
|
||
const isTestMode = <?= $testMode ? 'true' : 'false' ?>;
|
||
|
||
function bookedCellHtml(rowId, buchungsNr) {
|
||
const title = buchungsNr ? `Buchung vorhanden (${buchungsNr})` : 'Buchung vorhanden';
|
||
const deleteBtn = isTestMode
|
||
? `<button type="button" class="afa-delete-btn" data-row-id="${rowId}"
|
||
title="AFA-Buchung löschen (Testmodus)"
|
||
style="background:none;border:none;cursor:pointer;color:var(--color-danger);padding:.2rem;line-height:1;display:inline-flex;align-items:center">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="3,6 5,6 21,6"/>
|
||
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/>
|
||
<path d="M10 11v6M14 11v6"/>
|
||
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
|
||
</svg>
|
||
</button>`
|
||
: '';
|
||
return `<div style="display:inline-flex;gap:.2rem;align-items:center;justify-content:center">
|
||
<span title="${title}" style="color:#22c55e;display:inline-flex;align-items: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.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<polyline points="20 6 9 17 4 12"/>
|
||
</svg>
|
||
</span>
|
||
<button type="button" class="afa-reverse-btn" data-row-id="${rowId}"
|
||
title="AFA-Buchung stornieren"
|
||
style="background:none;border:none;cursor:pointer;color:var(--color-muted);padding:.2rem;line-height:1;display:inline-flex;align-items:center">
|
||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 7v6h6"/>
|
||
<path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/>
|
||
</svg>
|
||
</button>
|
||
${deleteBtn}
|
||
</div>`;
|
||
}
|
||
|
||
// SVG für "noch nicht gebucht" (Pfeil aus Box)
|
||
function unbookedCellHtml(rowId) {
|
||
return `<button type="button"
|
||
class="afa-book-btn"
|
||
data-row-id="${rowId}"
|
||
data-jahr=""
|
||
data-betrag=""
|
||
data-default-konto="${<?= $defaultKontoId ?>}"
|
||
title="AFA-Buchung anlegen"
|
||
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>`;
|
||
}
|
||
|
||
// Storno: Buchung stornieren, danach Haken → Pfeil-Button
|
||
document.addEventListener('click', async (e) => {
|
||
const btn = e.target.closest('.afa-reverse-btn');
|
||
if (!btn) return;
|
||
if (!confirm('AFA-Buchung wirklich stornieren? Es wird eine Gegenbuchung angelegt.')) return;
|
||
const rowId = parseInt(btn.dataset.rowId, 10);
|
||
try {
|
||
const res = await fetch(`/assets/${assetId}/depreciation/${rowId}/reverse`, { method: 'POST' });
|
||
const data = await res.json();
|
||
if (data.ok) {
|
||
const cell = btn.closest('td');
|
||
cell.innerHTML = unbookedCellHtml(rowId);
|
||
// Jahr/Betrag aus Tabellenzeile ermitteln und in data-* setzen
|
||
const tr = cell.closest('tr');
|
||
const tds = tr.querySelectorAll('td');
|
||
const newBtn = cell.querySelector('.afa-book-btn');
|
||
if (newBtn && tds.length >= 2) {
|
||
newBtn.dataset.jahr = tds[0].textContent.trim();
|
||
newBtn.dataset.betrag = tds[1].textContent.trim();
|
||
}
|
||
} else {
|
||
alert('Fehler: ' + (data.error ?? 'Unbekannt'));
|
||
}
|
||
} catch (_) { alert('Netzwerkfehler beim Stornieren.'); }
|
||
});
|
||
|
||
// Löschen (Testmodus): Buchung löschen, danach Haken → Pfeil-Button
|
||
document.addEventListener('click', async (e) => {
|
||
const btn = e.target.closest('.afa-delete-btn');
|
||
if (!btn) return;
|
||
if (!confirm('AFA-Buchung wirklich löschen? (Nur im Testmodus – nicht GoBD-konform!)')) return;
|
||
const rowId = parseInt(btn.dataset.rowId, 10);
|
||
try {
|
||
const res = await fetch(`/assets/${assetId}/depreciation/${rowId}/delete`, { method: 'POST' });
|
||
const data = await res.json();
|
||
if (data.ok) {
|
||
const cell = btn.closest('td');
|
||
cell.innerHTML = unbookedCellHtml(rowId);
|
||
const tr = cell.closest('tr');
|
||
const tds = tr.querySelectorAll('td');
|
||
const newBtn = cell.querySelector('.afa-book-btn');
|
||
if (newBtn && tds.length >= 2) {
|
||
newBtn.dataset.jahr = tds[0].textContent.trim();
|
||
newBtn.dataset.betrag = tds[1].textContent.trim();
|
||
}
|
||
} else {
|
||
alert('Fehler: ' + (data.error ?? 'Unbekannt'));
|
||
}
|
||
} catch (_) { alert('Netzwerkfehler beim Löschen.'); }
|
||
});
|
||
|
||
if (afabookBtn) {
|
||
afabookBtn.addEventListener('click', async () => {
|
||
const kontoId = afabookKonto ? parseInt(afabookKonto.value, 10) : 0;
|
||
if (!kontoId) { alert('Bitte ein Abschreibungskonto wählen.'); return; }
|
||
|
||
try {
|
||
const res = await fetch(`/assets/${assetId}/depreciation/${afabookRowId}/book`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ ausgabe_konto_id: kontoId }),
|
||
});
|
||
const data = await res.json();
|
||
if (data.ok) {
|
||
if (afabookModal) afabookModal.classList.remove('open');
|
||
const bookBtn = tbody?.querySelector(`.afa-book-btn[data-row-id="${afabookRowId}"]`);
|
||
if (bookBtn) {
|
||
const cell = bookBtn.closest('td');
|
||
cell.innerHTML = bookedCellHtml(afabookRowId, data.buchungs_nr);
|
||
}
|
||
} else {
|
||
alert('Fehler: ' + (data.error ?? 'Unbekannt'));
|
||
}
|
||
} catch (_) {
|
||
alert('Netzwerkfehler beim Buchen.');
|
||
}
|
||
});
|
||
}
|
||
})();
|
||
</script>
|