Steuerkram/public/static/js/beleg-form.js

312 lines
13 KiB
JavaScript

'use strict';
// ── Netto-Neuberechnung bei verknüpfter Bankbuchung ─────────────
(function () {
const form = document.getElementById('buchung_form');
if (!form || form.dataset.bankTxBrutto === undefined) return;
const brutto = parseFloat(form.dataset.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;
if (typeof getBetragModus === 'function' && getBetragModus() !== 'netto') 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);
const _origAutoUst = updateAutoUst;
window.updateAutoUst = function () {
_origAutoUst.apply(this, arguments);
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
if (form.dataset.openMetaForm) {
const metaForm = document.getElementById('beleg-meta-form');
if (metaForm) metaForm.style.display = 'block';
}
})();
function filterKategorieKonten(buchungstyp) {
const select = document.getElementById('kategorie_konto_id');
const groups = select.querySelectorAll('optgroup');
const zielArt = buchungstyp === 'Einnahme' ? 'Betriebseinnahme' : 'Betriebsausgabe';
groups.forEach(function(g) {
const art = g.getAttribute('data-kontenart');
if (!art) return;
// Privat, Verbindlichkeit und Anlagevermögen immer anzeigen, Kategorie nach Typ filtern
g.style.display = (art === zielArt || art === 'Privat' || art === 'Verbindlichkeit' || art === 'Anlagevermögen') ? '' : 'none';
});
// Auswahl zurücksetzen wenn aktuell gewähltes Konto nicht mehr sichtbar
if (select.selectedIndex > 0) {
const selected = select.options[select.selectedIndex];
if (selected && selected.closest('optgroup') && selected.closest('optgroup').style.display === 'none') {
select.selectedIndex = 0;
}
}
updateEigenanteilState();
}
function updateAutoUst() {
const kategorieEl = document.getElementById('kategorie_konto_id');
const ustSatzEl = document.getElementById('ust_satz_select');
if (!kategorieEl || !ustSatzEl) return;
const selectedOpt = kategorieEl.options[kategorieEl.selectedIndex];
const steuercode = selectedOpt ? (selectedOpt.getAttribute('data-steuercode') || '') : '';
const kontonummer = selectedOpt ? (selectedOpt.getAttribute('data-kontonummer') || '') : '';
const ustMap = { 'VSt19': '19', 'USt19': '19', 'VSt7': '7', 'USt7': '7' };
// 4652/4657 immer 0 % (keine Vorsteuer bei pauschalen Bewirtungskosten ohne USt-Ausweis)
const autoUst = (['4652','4657'].includes(kontonummer)) ? '0' : (ustMap[steuercode] || null);
const hiddenEl = document.getElementById('ust_satz_hidden');
if (autoUst !== null) {
ustSatzEl.value = autoUst;
if (hiddenEl) hiddenEl.value = autoUst;
}
ustSatzEl.disabled = false;
ustSatzEl.title = '';
if (hiddenEl && autoUst === null) hiddenEl.value = ustSatzEl.value;
updateSteuerartVisibility();
updateNettoAnzeige();
}
function updateBewirtungsState() {
const kategorieEl = document.getElementById('kategorie_konto_id');
const felder = document.getElementById('bewirtungsfelder');
if (!kategorieEl || !felder) return;
const selectedOpt = kategorieEl.options[kategorieEl.selectedIndex];
const kontonummer = selectedOpt ? (selectedOpt.getAttribute('data-kontonummer') || '') : '';
const istBewirtung = ['4650','4651','4652'].includes(kontonummer);
felder.style.display = istBewirtung ? '' : 'none';
const grundEl = document.getElementById('bewirtungsgrund_input');
const teilnEl = document.getElementById('teilnehmerliste_input');
if (grundEl) grundEl.required = istBewirtung;
if (teilnEl) teilnEl.required = istBewirtung;
}
function updateEigenanteilState() {
const group = document.getElementById('eigenanteil_group');
const cb = document.getElementById('eigenanteil_30_cb');
if (!group || !cb) return;
const buchungstyp = document.querySelector('input[name="buchungstyp"]:checked');
const kategorieEl = document.getElementById('kategorie_konto_id');
const ustSatzEl = document.getElementById('ust_satz_select');
const istAusgabe = buchungstyp && buchungstyp.value === 'Ausgabe';
const selectedOpt = kategorieEl && kategorieEl.options[kategorieEl.selectedIndex];
const kontonummer = selectedOpt ? (selectedOpt.getAttribute('data-search') || '').split(' ')[0] : '';
const istTelInternet = kontonummer === '4920' || kontonummer === '4925';
const ist19 = ustSatzEl && ustSatzEl.value === '19';
const aktiv = istAusgabe && istTelInternet && ist19;
group.style.opacity = aktiv ? '1' : '.35';
group.style.pointerEvents = aktiv ? '' : 'none';
group.style.cursor = aktiv ? 'pointer' : 'not-allowed';
cb.disabled = !aktiv;
if (!aktiv) cb.checked = false;
}
// ── Info-Bar: Zuschneiden-Button, Metadaten-Formular ein-/ausblenden ──
const _cropOpenBtn = document.getElementById('beleg-crop-open-btn');
if (_cropOpenBtn) _cropOpenBtn.addEventListener('click', function () { openCropModal(); });
const _metaToggleBtn = document.getElementById('beleg-meta-toggle-btn');
if (_metaToggleBtn) {
_metaToggleBtn.addEventListener('click', function () {
const metaForm = document.getElementById('beleg-meta-form');
if (metaForm) metaForm.style.display = metaForm.style.display === 'none' ? 'block' : 'none';
});
}
const _metaCancelBtn = document.getElementById('beleg-meta-cancel-btn');
if (_metaCancelBtn) {
_metaCancelBtn.addEventListener('click', function () {
const metaForm = document.getElementById('beleg-meta-form');
if (metaForm) metaForm.style.display = 'none';
});
}
// ── Inline-Handler der Buchungszeile: zentrale Listener statt onchange/onclick/oninput ──
document.querySelectorAll('input[name="buchungstyp"]').forEach(function (radio) {
radio.addEventListener('change', function () { filterKategorieKonten(this.value); });
});
const _ustSatzSelect = document.getElementById('ust_satz_select');
if (_ustSatzSelect) {
_ustSatzSelect.addEventListener('change', function () {
updateSteuerartVisibility();
updateNettoAnzeige();
const hidden = document.getElementById('ust_satz_hidden');
if (hidden) hidden.value = this.value;
});
}
const _kategorieKontoSelect = document.getElementById('kategorie_konto_id');
if (_kategorieKontoSelect) {
_kategorieKontoSelect.addEventListener('change', function () {
showKontoArt('kategorie_konto_id', 'kategorie_konto_art', this);
updateEigenanteilState();
updateBewirtungsState();
updateAutoUst();
checkAnlagevermoegen();
});
}
const _zahlungskontoSelect = document.getElementById('zahlungskonto_id');
if (_zahlungskontoSelect) {
_zahlungskontoSelect.addEventListener('change', function () {
showKontoArt('zahlungskonto_id', 'zahlungskonto_art', this);
});
}
const _betragModusNettoBtn = document.getElementById('betrag_modus_netto_btn');
const _betragModusBruttoBtn = document.getElementById('betrag_modus_brutto_btn');
if (_betragModusNettoBtn) _betragModusNettoBtn.addEventListener('click', function () { setBetragModus('netto'); });
if (_betragModusBruttoBtn) _betragModusBruttoBtn.addEventListener('click', function () { setBetragModus('brutto'); });
const _betragNettoInput = document.getElementById('betrag_netto_input');
if (_betragNettoInput) _betragNettoInput.addEventListener('input', updateNettoAnzeige);
// Initial filtern
document.addEventListener('DOMContentLoaded', function() {
const checked = document.querySelector('input[name="buchungstyp"]:checked');
if (checked) filterKategorieKonten(checked.value);
updateSteuerartVisibility();
updateEigenanteilState();
updateBewirtungsState();
updateAutoUst();
updateNettoAnzeige();
// Leeres Lieferantenfeld fokussieren wenn kein KI-Block aktiv
const sup = document.getElementById('supplier-persistent-input');
if (sup && !document.getElementById('ki-badge')) sup.focus();
});
function updateSteuerartVisibility() {
const ustSatz = document.getElementById('ust_satz_select');
const steuerartGroup = document.getElementById('steuerart_group');
const steuerartSelect = document.getElementById('steuerart_select');
if (!ustSatz || !steuerartGroup) return;
if (ustSatz.value === '0') {
steuerartGroup.style.display = '';
} else {
steuerartGroup.style.display = 'none';
if (steuerartSelect) steuerartSelect.value = 'normal';
}
updateEigenanteilState();
}
// ── Netto/Brutto-Eingabemodus ──────────────────────────────────
// Solange Modus "netto" ist, entspricht der Feldwert dem Nettobetrag (wie bisher).
// Im Modus "brutto" wird der eingegebene Wert als Bruttobetrag interpretiert;
// vor dem Absenden wird er in den tatsächlichen Nettobetrag umgerechnet (siehe Submit-Handler unten).
let _betragModus = 'netto';
function getBetragModus() { return _betragModus; }
function setBetragModus(modus) {
if (modus === _betragModus) return;
const input = document.getElementById('betrag_netto_input');
const ustEl = document.getElementById('ust_satz_select');
const ustSatz = ustEl ? (parseFloat(ustEl.value) || 0) : 0;
const alt = parseFloat(input.value);
if (!isNaN(alt)) {
const neu = (modus === 'brutto')
? Math.round(alt * (1 + ustSatz / 100) * 100) / 100 // Netto → Brutto
: Math.round(alt / (1 + ustSatz / 100) * 100) / 100; // Brutto → Netto
input.value = neu.toFixed(2);
}
_betragModus = modus;
const nettoBtn = document.getElementById('betrag_modus_netto_btn');
const bruttoBtn = document.getElementById('betrag_modus_brutto_btn');
nettoBtn.classList.toggle('active', modus === 'netto');
nettoBtn.setAttribute('aria-pressed', modus === 'netto' ? 'true' : 'false');
bruttoBtn.classList.toggle('active', modus === 'brutto');
bruttoBtn.setAttribute('aria-pressed', modus === 'brutto' ? 'true' : 'false');
updateNettoAnzeige();
}
// Übernimmt einen Netto-Betrag (z.B. aus KI-Vorschlag) und schaltet dafür in den Netto-Modus zurück.
function setBetragNettoWert(wert) {
if (_betragModus !== 'netto') setBetragModus('netto');
const input = document.getElementById('betrag_netto_input');
input.value = wert;
updateNettoAnzeige();
}
function updateNettoAnzeige() {
const eingabeEl = document.getElementById('betrag_netto_input');
const ustEl = document.getElementById('ust_satz_select');
const anzeige = document.getElementById('ust_brutto_anzeige');
if (!eingabeEl || !ustEl || !anzeige) return;
const eingabe = parseFloat(eingabeEl.value);
if (isNaN(eingabe) || eingabeEl.value === '') {
anzeige.textContent = '';
return;
}
const ustSatz = parseFloat(ustEl.value) || 0;
const fmt = v => v.toLocaleString('de-DE', {minimumFractionDigits: 2, maximumFractionDigits: 2}) + ' €';
if (_betragModus === 'brutto') {
const netto = Math.round(eingabe / (1 + ustSatz / 100) * 100) / 100;
const ust = Math.round((eingabe - netto) * 100) / 100;
anzeige.innerHTML = 'Netto ' + fmt(netto) + '  ·  USt. ' + fmt(ust);
} else {
const ust = Math.round(eingabe * ustSatz / 100 * 100) / 100;
const brutto = Math.round((eingabe + ust) * 100) / 100;
anzeige.innerHTML = 'USt. ' + fmt(ust) + '  ·  Brutto ' + fmt(brutto);
}
}
// Vor dem Absenden: Im Brutto-Modus enthält das Feld einen Bruttobetrag,
// das Backend erwartet aber immer den Nettobetrag (Feldname betrag_netto).
const _buchungFormEl = document.getElementById('buchung_form');
if (_buchungFormEl) {
_buchungFormEl.addEventListener('submit', function () {
if (_betragModus !== 'brutto') return;
const input = document.getElementById('betrag_netto_input');
const ustEl = document.getElementById('ust_satz_select');
const ustSatz = ustEl ? (parseFloat(ustEl.value) || 0) : 0;
const brutto = parseFloat(input.value);
if (!isNaN(brutto)) {
input.value = (Math.round(brutto / (1 + ustSatz / 100) * 100) / 100).toFixed(2);
}
});
}