586 lines
24 KiB
JavaScript
586 lines
24 KiB
JavaScript
/**
|
||
* Steuerkram – App JavaScript
|
||
* Kein Framework. Nur native DOM-APIs.
|
||
*/
|
||
|
||
'use strict';
|
||
|
||
// ── Kontenart-Badge neben Label anzeigen ─────────────────────
|
||
function showKontoArt(selectId, badgeId, sel) {
|
||
const badge = document.getElementById(badgeId);
|
||
if (!badge) return;
|
||
const opt = sel.options[sel.selectedIndex];
|
||
const art = opt?.dataset?.art || '';
|
||
const classes = {
|
||
'Aufwand': 'badge-red',
|
||
'Ertrag': 'badge-green',
|
||
'Aktiv': 'badge-blue',
|
||
'Passiv': 'badge-yellow',
|
||
'Neutral': 'badge-gray',
|
||
};
|
||
badge.textContent = art;
|
||
badge.className = 'badge ' + (classes[art] || 'badge-gray');
|
||
badge.style.display = art ? '' : 'none';
|
||
}
|
||
|
||
// ── Konto-Filter (Select-Filterung per Sucheingabe) ───────────
|
||
document.addEventListener('input', (e) => {
|
||
if (!e.target.classList.contains('konto-filter')) return;
|
||
const q = e.target.value.toLowerCase();
|
||
const select = document.getElementById(e.target.dataset.target);
|
||
if (!select) return;
|
||
for (const opt of select.options) {
|
||
if (opt.value === '') { opt.hidden = false; continue; }
|
||
opt.hidden = !opt.dataset.search.toLowerCase().includes(q);
|
||
}
|
||
// Auto-select nur bei exaktem Kontonummer-Treffer; sonst Select leeren
|
||
// (verhindert, dass direkte Kontonummer-Eingabe durch einen Partial-Match überschrieben wird)
|
||
if (q.length > 0) {
|
||
const exact = [...select.options].find(
|
||
opt => opt.value !== '' && opt.dataset.search && opt.dataset.search.split(' ')[0] === q
|
||
);
|
||
select.value = exact ? exact.value : '';
|
||
select.dispatchEvent(new Event('change'));
|
||
}
|
||
});
|
||
|
||
// ── Modals ────────────────────────────────────────────────────
|
||
document.addEventListener('click', (e) => {
|
||
// Open modal
|
||
const openBtn = e.target.closest('[data-modal-open]');
|
||
if (openBtn) {
|
||
const id = openBtn.dataset.modalOpen;
|
||
document.getElementById(id)?.classList.add('open');
|
||
return;
|
||
}
|
||
|
||
// Close modal via backdrop click or close button
|
||
const closeBtn = e.target.closest('[data-modal-close]');
|
||
if (closeBtn) {
|
||
closeBtn.closest('.modal-overlay')?.classList.remove('open');
|
||
return;
|
||
}
|
||
|
||
if (e.target.classList.contains('modal-overlay')) {
|
||
e.target.classList.remove('open');
|
||
}
|
||
});
|
||
|
||
// Close modal on Escape key
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') {
|
||
document.querySelectorAll('.modal-overlay.open').forEach(m => m.classList.remove('open'));
|
||
}
|
||
});
|
||
|
||
// ── Klickbare Tabellenzeilen (Navigation) ──────────────────────
|
||
document.addEventListener('click', (e) => {
|
||
const row = e.target.closest('tr[data-row-href]');
|
||
if (!row || e.target.closest('[data-no-row-click]')) return;
|
||
window.location = row.dataset.rowHref;
|
||
});
|
||
|
||
// ── Confirm Delete ────────────────────────────────────────────
|
||
document.addEventListener('submit', (e) => {
|
||
const form = e.target;
|
||
const msg = form.dataset.confirm;
|
||
if (msg && !confirm(msg)) {
|
||
e.preventDefault();
|
||
}
|
||
});
|
||
|
||
// ── Rechnungszeilen – Live-Kalkulation ────────────────────────
|
||
function calcInvoiceLine(row) {
|
||
const menge = parseFloat(row.querySelector('[name$="[menge]"]')?.value) || 0;
|
||
const preis = parseFloat(row.querySelector('[name$="[einzelpreis]"]')?.value) || 0;
|
||
const ustSatz = parseFloat(row.querySelector('[name$="[ust_satz]"]')?.value) || 0;
|
||
|
||
const netto = menge * preis;
|
||
const ust = netto * ustSatz / 100;
|
||
const brutto = netto + ust;
|
||
|
||
const fmt = (v) => v.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||
|
||
const nettoEl = row.querySelector('.line-netto');
|
||
const bruttoEl = row.querySelector('.line-brutto');
|
||
if (nettoEl) nettoEl.textContent = fmt(netto) + ' €';
|
||
if (bruttoEl) bruttoEl.textContent = fmt(brutto) + ' €';
|
||
}
|
||
|
||
function calcInvoiceTotals() {
|
||
let totalNetto = 0;
|
||
let totalUst = 0;
|
||
let totalBrutto = 0;
|
||
|
||
document.querySelectorAll('.invoice-line-row').forEach(row => {
|
||
const menge = parseFloat(row.querySelector('[name$="[menge]"]')?.value) || 0;
|
||
const preis = parseFloat(row.querySelector('[name$="[einzelpreis]"]')?.value) || 0;
|
||
const ustSatz = parseFloat(row.querySelector('[name$="[ust_satz]"]')?.value) || 0;
|
||
const netto = menge * preis;
|
||
const ust = netto * ustSatz / 100;
|
||
totalNetto += netto;
|
||
totalUst += ust;
|
||
totalBrutto += netto + ust;
|
||
calcInvoiceLine(row);
|
||
});
|
||
|
||
const fmt = (v) => v.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) + ' €';
|
||
|
||
const elNetto = document.getElementById('total-netto');
|
||
const elUst = document.getElementById('total-ust');
|
||
const elBrutto = document.getElementById('total-brutto');
|
||
if (elNetto) elNetto.textContent = fmt(totalNetto);
|
||
if (elUst) elUst.textContent = fmt(totalUst);
|
||
if (elBrutto) elBrutto.textContent = fmt(totalBrutto);
|
||
}
|
||
|
||
// Live-Update on input change in invoice form
|
||
document.addEventListener('input', (e) => {
|
||
if (e.target.closest('.invoice-line-row')) {
|
||
calcInvoiceTotals();
|
||
}
|
||
if (e.target.closest('.invoice-line-row') && e.target.matches('[name$="[ust_satz]"]')) {
|
||
calcInvoiceTotals();
|
||
}
|
||
});
|
||
|
||
// ── Rechnungszeile / Überschrift hinzufügen ───────────────────
|
||
|
||
document.addEventListener('click', (e) => {
|
||
const el = e.target.closest('[data-action]');
|
||
if (!el) return;
|
||
switch (el.dataset.action) {
|
||
case 'show-add-line-dialog': showAddLineDialog(el); break;
|
||
case 'add-invoice-line': document.getElementById('add-line-popup')?.remove(); addInvoiceLine(); break;
|
||
case 'add-invoice-heading': document.getElementById('add-line-popup')?.remove(); addInvoiceHeading(); break;
|
||
case 'remove-invoice-line': removeInvoiceLine(el); break;
|
||
}
|
||
});
|
||
|
||
function showAddLineDialog(btn) {
|
||
document.getElementById('add-line-popup')?.remove();
|
||
|
||
const popup = document.createElement('div');
|
||
popup.id = 'add-line-popup';
|
||
popup.style.cssText = 'position:absolute;z-index:200;background:#fff;border:1px solid #ccc;border-radius:5px;box-shadow:0 2px 8px rgba(0,0,0,.12);overflow:hidden;font-size:.875rem';
|
||
popup.innerHTML = `
|
||
<button class="add-line-opt" data-action="add-invoice-heading">Überschrift</button>
|
||
<button class="add-line-opt" data-action="add-invoice-line">Neue Position</button>`;
|
||
document.body.appendChild(popup);
|
||
|
||
const r = btn.getBoundingClientRect();
|
||
popup.style.top = (window.scrollY + r.bottom + 3) + 'px';
|
||
popup.style.left = r.left + 'px';
|
||
popup.style.minWidth = r.width + 'px';
|
||
|
||
setTimeout(() => {
|
||
document.addEventListener('click', function close(e) {
|
||
if (!popup.contains(e.target) && e.target !== btn) {
|
||
popup.remove();
|
||
document.removeEventListener('click', close);
|
||
}
|
||
});
|
||
}, 0);
|
||
}
|
||
|
||
function _nextLineIdx() {
|
||
const tbody = document.getElementById('invoice-lines-body');
|
||
return tbody ? tbody.querySelectorAll('tr').length : 0;
|
||
}
|
||
|
||
function addInvoiceLine() {
|
||
const tbody = document.getElementById('invoice-lines-body');
|
||
if (!tbody) return;
|
||
|
||
const idx = _nextLineIdx();
|
||
const row = document.createElement('tr');
|
||
row.className = 'invoice-line-row';
|
||
row.innerHTML = `
|
||
<td><span class="line-pos text-muted"></span></td>
|
||
<td>
|
||
<input type="hidden" name="lines[${idx}][type]" value="position">
|
||
<textarea name="lines[${idx}][bezeichnung]" required placeholder="Leistungsbeschreibung" rows="1" class="line-bez-input" style="width:100%"></textarea>
|
||
</td>
|
||
<td><input type="number" name="lines[${idx}][menge]" value="1" min="0.001" step="0.001" style="width:100%" required></td>
|
||
<td>
|
||
<select name="lines[${idx}][einheit]" style="width:100%">
|
||
<option value="Stk.">Stk.</option>
|
||
<option value="h">h</option>
|
||
<option value="Tage">Tage</option>
|
||
<option value="Monat">Monat</option>
|
||
<option value="Pauschale">Pauschale</option>
|
||
<option value="km">km</option>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<select name="lines[${idx}][ust_satz]" style="width:100%">
|
||
<option value="19">19 %</option>
|
||
<option value="7">7 %</option>
|
||
<option value="0">0 %</option>
|
||
</select>
|
||
</td>
|
||
<td><input type="number" name="lines[${idx}][einzelpreis]" value="0.00" min="0" step="0.01" style="width:100%" required></td>
|
||
<td class="td-right line-netto text-muted">0,00 €</td>
|
||
<td class="td-right line-brutto font-bold">0,00 €</td>
|
||
<td>
|
||
<button type="button" class="btn btn-danger btn-sm" data-action="remove-invoice-line">✕</button>
|
||
</td>`;
|
||
tbody.appendChild(row);
|
||
renumberLines();
|
||
calcInvoiceTotals();
|
||
const ta = row.querySelector('.line-bez-input');
|
||
if (ta) { initBezTextarea(ta); ta.focus(); }
|
||
}
|
||
|
||
function addInvoiceHeading() {
|
||
const tbody = document.getElementById('invoice-lines-body');
|
||
if (!tbody) return;
|
||
|
||
const idx = _nextLineIdx();
|
||
const row = document.createElement('tr');
|
||
row.className = 'invoice-heading-row';
|
||
row.innerHTML = `
|
||
<td colspan="8">
|
||
<input type="hidden" name="lines[${idx}][type]" value="heading">
|
||
<textarea name="lines[${idx}][bezeichnung]" required placeholder="Überschrift" rows="1" class="line-bez-input" style="width:100%;font-weight:bold"></textarea>
|
||
</td>
|
||
<td><button type="button" class="btn btn-danger btn-sm" onclick="removeInvoiceLine(this)">✕</button></td>`;
|
||
tbody.appendChild(row);
|
||
renumberLines();
|
||
const ta = row.querySelector('.line-bez-input');
|
||
if (ta) { initBezTextarea(ta); ta.focus(); }
|
||
}
|
||
|
||
function removeInvoiceLine(btn) {
|
||
btn.closest('tr').remove();
|
||
renumberLines();
|
||
calcInvoiceTotals();
|
||
}
|
||
|
||
function renumberLines() {
|
||
const tbody = document.getElementById('invoice-lines-body');
|
||
if (!tbody) return;
|
||
let posNum = 0;
|
||
tbody.querySelectorAll('tr').forEach((row, i) => {
|
||
// Alle name-Attribute auf neuen Array-Index setzen
|
||
row.querySelectorAll('[name]').forEach(el => {
|
||
el.name = el.name.replace(/lines\[\d+\]/, `lines[${i}]`);
|
||
});
|
||
// Positions-Nummer nur bei echten Zeilen hochzählen
|
||
const posEl = row.querySelector('.line-pos');
|
||
if (posEl) posEl.textContent = ++posNum;
|
||
});
|
||
}
|
||
|
||
// Init totals on page load
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
if (document.getElementById('invoice-lines-body')) {
|
||
calcInvoiceTotals();
|
||
}
|
||
});
|
||
|
||
// ── Konto-Suche (Typeahead) ───────────────────────────────────
|
||
function setupAccountSearch(inputId, hiddenId) {
|
||
const input = document.getElementById(inputId);
|
||
const hidden = document.getElementById(hiddenId);
|
||
if (!input || !hidden) return;
|
||
|
||
let dropdown = null;
|
||
let debounce = null;
|
||
|
||
input.addEventListener('input', () => {
|
||
clearTimeout(debounce);
|
||
const q = input.value.trim();
|
||
if (q.length < 1) { closeDropdown(); return; }
|
||
|
||
debounce = setTimeout(async () => {
|
||
const res = await fetch(`/accounts/search?q=${encodeURIComponent(q)}`);
|
||
const data = await res.json();
|
||
renderDropdown(data);
|
||
}, 200);
|
||
});
|
||
|
||
function renderDropdown(items) {
|
||
closeDropdown();
|
||
if (!items.length) return;
|
||
|
||
dropdown = document.createElement('ul');
|
||
dropdown.className = 'account-dropdown';
|
||
dropdown.style.cssText = `
|
||
position:absolute; z-index:50; background:#fff; border:1px solid var(--color-border);
|
||
border-radius:var(--radius); width:100%; max-height:220px; overflow-y:auto;
|
||
list-style:none; padding:0; margin:0; box-shadow:var(--shadow);
|
||
`;
|
||
|
||
items.forEach(item => {
|
||
const li = document.createElement('li');
|
||
li.style.cssText = 'padding:.5rem .75rem; cursor:pointer; font-size:.875rem; border-bottom:1px solid var(--color-border)';
|
||
// Kontodaten nur als Text einfügen (Fix #213)
|
||
const nr = document.createElement('strong');
|
||
nr.textContent = item.kontonummer;
|
||
const art = document.createElement('small');
|
||
art.className = 'text-muted';
|
||
art.textContent = `(${item.kontenart})`;
|
||
li.append(nr, ` – ${item.bezeichnung} `, art);
|
||
li.addEventListener('mousedown', () => {
|
||
input.value = `${item.kontonummer} – ${item.bezeichnung}`;
|
||
hidden.value = item.id;
|
||
closeDropdown();
|
||
});
|
||
dropdown.appendChild(li);
|
||
});
|
||
|
||
const wrapper = input.closest('.account-search-wrap') || input.parentElement;
|
||
wrapper.style.position = 'relative';
|
||
wrapper.appendChild(dropdown);
|
||
}
|
||
|
||
function closeDropdown() {
|
||
dropdown?.remove();
|
||
dropdown = null;
|
||
}
|
||
|
||
input.addEventListener('blur', () => setTimeout(closeDropdown, 150));
|
||
}
|
||
|
||
// ── Inline-Edit (Belegdatum / Rechnungsnr. in der Info-Bar) ──────
|
||
document.addEventListener('click', (e) => {
|
||
const trigger = e.target.closest('.inline-edit-trigger');
|
||
if (!trigger || trigger.querySelector('input')) return;
|
||
|
||
const bar = trigger.closest('[data-beleg-id]');
|
||
if (!bar) return;
|
||
|
||
const field = trigger.dataset.field;
|
||
const type = trigger.dataset.type || 'text';
|
||
const rawValue = trigger.dataset.raw || '';
|
||
|
||
const input = document.createElement('input');
|
||
input.type = type;
|
||
input.value = rawValue;
|
||
input.style.cssText = 'font-size:inherit;padding:2px 5px;border:1px solid var(--color-border);border-radius:var(--radius);background:#fff;min-width:' + (type === 'date' ? '140px' : '180px');
|
||
if (field === 'supplier_name') {
|
||
input.setAttribute('list', 'supplier-datalist');
|
||
input.setAttribute('autocomplete', 'off');
|
||
}
|
||
|
||
const originalHTML = trigger.innerHTML;
|
||
trigger.innerHTML = '';
|
||
trigger.appendChild(input);
|
||
input.focus();
|
||
if (type === 'text') input.select();
|
||
|
||
let cancelled = false;
|
||
|
||
async function save() {
|
||
if (cancelled) return;
|
||
const newValue = input.value.trim();
|
||
|
||
const body = new URLSearchParams({
|
||
rechnungs_nr: field === 'rechnungs_nr' ? newValue : bar.dataset.rechnungsNr || '',
|
||
belegtyp: bar.dataset.belegtyp || '',
|
||
beleg_datum: field === 'beleg_datum' ? newValue : bar.dataset.belegDatum || '',
|
||
betrag_brutto: bar.dataset.betragBrutto || '',
|
||
supplier_name: field === 'supplier_name' ? newValue : bar.dataset.supplierName || '',
|
||
});
|
||
|
||
try {
|
||
const res = await fetch(`/belege/${bar.dataset.belegId}/meta`, {
|
||
method: 'POST',
|
||
headers: { 'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded' },
|
||
body: body.toString(),
|
||
});
|
||
if (!res.ok) throw new Error();
|
||
|
||
// Update stored data-attribute
|
||
if (field === 'beleg_datum') {
|
||
bar.dataset.belegDatum = newValue;
|
||
trigger.dataset.raw = newValue;
|
||
if (newValue) {
|
||
const [y, m, d] = newValue.split('-');
|
||
trigger.textContent = `${d}.${m}.${y}`;
|
||
} else {
|
||
trigger.innerHTML = '<span class="text-muted">– setzen –</span>';
|
||
}
|
||
} else if (field === 'supplier_name') {
|
||
bar.dataset.supplierName = newValue;
|
||
trigger.dataset.raw = newValue;
|
||
trigger.textContent = newValue || '';
|
||
if (!newValue) trigger.innerHTML = '<span class="text-muted">– setzen –</span>';
|
||
} else {
|
||
bar.dataset.rechnungsNr = newValue;
|
||
trigger.dataset.raw = newValue;
|
||
trigger.textContent = newValue || '';
|
||
if (!newValue) trigger.innerHTML = '<span class="text-muted">– setzen –</span>';
|
||
}
|
||
} catch (_) {
|
||
trigger.innerHTML = originalHTML;
|
||
}
|
||
}
|
||
|
||
input.addEventListener('blur', save);
|
||
input.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter') { e.preventDefault(); input.blur(); }
|
||
if (e.key === 'Escape') { cancelled = true; trigger.innerHTML = originalHTML; }
|
||
});
|
||
});
|
||
|
||
// ── 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];
|
||
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;
|
||
}
|
||
|
||
document.addEventListener('dblclick', (e) => {
|
||
const select = e.target.closest('select#kategorie_konto_id, select#zahlungskonto_id');
|
||
if (select) transferKontoNr(select);
|
||
});
|
||
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key !== 'Enter') return;
|
||
const select = e.target.closest('select#kategorie_konto_id, select#zahlungskonto_id');
|
||
if (select) {
|
||
e.preventDefault();
|
||
transferKontoNr(select);
|
||
}
|
||
});
|
||
|
||
// ── Rechnungsformular: Enter nicht als Submit, Textarea auto-resize ───────────
|
||
function initBezTextarea(el) {
|
||
el.style.height = 'auto';
|
||
el.style.height = el.scrollHeight + 'px';
|
||
}
|
||
|
||
document.addEventListener('input', (e) => {
|
||
if (e.target.classList.contains('line-bez-input')) initBezTextarea(e.target);
|
||
if (e.target.classList.contains('autosize-textarea')) initBezTextarea(e.target);
|
||
});
|
||
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key !== 'Enter') return;
|
||
if (e.target.tagName === 'TEXTAREA') return;
|
||
const form = e.target.closest('form.invoice-form');
|
||
if (form) e.preventDefault();
|
||
}, true);
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('.line-bez-input').forEach(initBezTextarea);
|
||
});
|
||
|
||
// ── Flash Message Auto-dismiss ────────────────────────────────
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('.flash').forEach(el => {
|
||
if (el.closest('#ki-badge')) return; // KI-Badge nie auto-dismissen
|
||
if (el.classList.contains('flash-persistent')) return; // z.B. Bewirtungshinweis
|
||
setTimeout(() => {
|
||
el.style.transition = 'opacity .4s';
|
||
el.style.opacity = '0';
|
||
setTimeout(() => el.remove(), 400);
|
||
}, 20000);
|
||
});
|
||
});
|
||
|
||
// ── Buchungsvorlagen ──────────────────────────────────────────
|
||
|
||
// Vorlage auswählen → Modal per AJAX laden
|
||
document.addEventListener('change', function(e) {
|
||
const sel = e.target.closest('#vorlage-select');
|
||
if (!sel) return;
|
||
const vorlageId = sel.value;
|
||
if (!vorlageId) return;
|
||
const belegId = sel.dataset.belegId || '';
|
||
const bankTxId = sel.dataset.bankTx || '';
|
||
let url = `/api/buchungsvorlagen/${vorlageId}/modal?beleg_id=${belegId}`;
|
||
if (bankTxId) url += `&bank_tx=${bankTxId}`;
|
||
fetch(url)
|
||
.then(r => r.ok ? r.text() : Promise.reject(r.status))
|
||
.then(html => {
|
||
const container = document.getElementById('vorlage-modal-container');
|
||
if (!container) return;
|
||
container.innerHTML = html;
|
||
// CSRF-Token in das dynamisch geladene Formular injizieren
|
||
const token = (document.querySelector('meta[name="csrf-token"]') || {}).content || '';
|
||
container.querySelectorAll('form').forEach(function(form) {
|
||
if (token && !form.querySelector('[name="csrf_token"]')) {
|
||
var inp = document.createElement('input');
|
||
inp.type = 'hidden'; inp.name = 'csrf_token'; inp.value = token;
|
||
form.appendChild(inp);
|
||
}
|
||
});
|
||
})
|
||
.catch(() => {
|
||
// Selektion zurücksetzen bei Fehler
|
||
sel.value = '';
|
||
});
|
||
});
|
||
|
||
// Modal schließen → Vorlage-Select zurücksetzen
|
||
document.addEventListener('click', function(e) {
|
||
const closeBtn = e.target.closest('[data-modal-close]');
|
||
if (!closeBtn) return;
|
||
const overlay = closeBtn.closest('.modal-overlay');
|
||
if (overlay && overlay.id === 'vorlage-modal') {
|
||
const sel = document.getElementById('vorlage-select');
|
||
if (sel) sel.value = '';
|
||
const container = document.getElementById('vorlage-modal-container');
|
||
if (container) setTimeout(() => { container.innerHTML = ''; }, 300);
|
||
}
|
||
});
|
||
|
||
// Live-Berechnungen in USt-Zeilen des Vorlage-Modals
|
||
document.addEventListener('input', function(e) {
|
||
const inp = e.target;
|
||
if (!inp.matches('#vorlage-modal .vorlage-netto, #vorlage-modal .vorlage-brutto')) return;
|
||
const row = inp.closest('.vorlage-ust-row');
|
||
if (!row) return;
|
||
const pDec = v => parseFloat(String(v).replace(',', '.')) || 0;
|
||
const satz = parseFloat(row.dataset.ustSatz) / 100;
|
||
const netto = row.querySelector('.vorlage-netto');
|
||
const brutto = row.querySelector('.vorlage-brutto');
|
||
const ust = row.querySelector('.vorlage-ust');
|
||
const ustAnzeige = row.querySelector('.vorlage-ust-anzeige');
|
||
if (inp.classList.contains('vorlage-netto')) {
|
||
const n = pDec(netto.value);
|
||
brutto.value = (n * (1 + satz)).toFixed(2);
|
||
ust.value = (n * satz).toFixed(2);
|
||
} else {
|
||
const b = pDec(brutto.value);
|
||
const n = b / (1 + satz);
|
||
netto.value = n.toFixed(2);
|
||
ust.value = (b - n).toFixed(2);
|
||
}
|
||
if (ustAnzeige) ustAnzeige.textContent = parseFloat(ust.value).toFixed(2).replace('.', ',');
|
||
vorlageUpdateBruttoGesamt();
|
||
});
|
||
|
||
function vorlageUpdateBruttoGesamt() {
|
||
const modal = document.getElementById('vorlage-modal');
|
||
if (!modal) return;
|
||
const bruttoFelder = modal.querySelectorAll('.vorlage-brutto');
|
||
const summe = Array.from(bruttoFelder).reduce((s, el) => s + (parseFloat(String(el.value).replace(',', '.')) || 0), 0);
|
||
const anzeige = modal.querySelector('[data-feldname="brutto_gesamt"]');
|
||
if (anzeige) anzeige.textContent = summe.toFixed(2).replace('.', ',') + ' €';
|
||
}
|
||
|
||
// Zahldatum-Feld Änderung → buchungs_datum-Hidden synchronisieren
|
||
document.addEventListener('change', function(e) {
|
||
if (!e.target.matches('#vorlage-modal #vorlage-zahldatum')) return;
|
||
const hidden = document.getElementById('vorlage-buchungs-datum');
|
||
if (hidden) hidden.value = e.target.value;
|
||
});
|