/* Kontoblatt: Kategorie oder Gegenkonto an Ort und Stelle zuordnen. * * Ein Klick auf die Kategoriespalte öffnet die Auswahlliste in der Zelle. Sie * gibt es genau einmal und wandert in die angeklickte Zelle – wie das * Zahnradmenü im Budgetplaner. Eine Liste je Zeile wären bei hundert Zeilen * ebenso viele vollständige Kategoriebäume im HTML. * * Ohne JavaScript führt derselbe Klick in die Buchungsmaske; die Zuordnung ist * dort wie bisher zu ändern. */ (function () { 'use strict'; const tabelle = document.querySelector('table.kontoblatt'); const auswahl = document.getElementById('zuordnungsauswahl'); if (!tabelle || !auswahl) { return; } const abfrage = tabelle.dataset.abfrage || ''; const liste = auswahl.querySelector('select'); const dialog = document.getElementById('splitdialog'); const splitformular = document.getElementById('splitformular'); // Das Feld, das gerade durch die Auswahlliste ersetzt ist. let offenesFeld = null; function ziel(feld, aktion) { return '/buchungen/' + feld.dataset.buchung + '/' + aktion + abfrage; } function schliessen() { if (!offenesFeld) { return; } offenesFeld.hidden = false; offenesFeld = null; auswahl.hidden = true; document.body.appendChild(auswahl); } /* Der Splitdialog zu einer Zeile – oder, wo es kein gibt, die * Buchungsmaske. Die Weiche steht hier oben, weil beide Wege hierher * führen: der Klick auf eine Splitbuchung und die Wahl „Splitbuchung“. */ function splitOeffnen(feld) { if (dialog && splitformular && window.FmBetrag) { splitdialogOeffnen(feld); } else { window.location.href = '/buchungen/' + feld.dataset.buchung; } } function oeffnen(feld) { schliessen(); offenesFeld = feld; feld.hidden = true; auswahl.action = ziel(feld, 'zuordnung'); liste.value = feld.dataset.wert || ''; feld.parentNode.appendChild(auswahl); auswahl.hidden = false; liste.focus(); // Die Liste soll sofort aufklappen; showPicker gibt es nicht überall. try { liste.showPicker(); } catch (fehler) { /* Dann klappt sie beim ersten Klick auf – kein Verlust. */ } } tabelle.addEventListener('click', function (ereignis) { // Die Gegenseite einer fremden Umbuchung führt zu ihrer Buchung, statt // hier die Auswahl zu öffnen: Geändert wird sie auf ihrem Konto. const feld = ereignis.target.closest('a.zuordnungsfeld:not(.gegenseite)'); if (!feld || ereignis.metaKey || ereignis.ctrlKey || ereignis.shiftKey) { return; } ereignis.preventDefault(); // Eine schon aufgeteilte Buchung führt geradewegs in den Splitdialog: // Die Auswahlliste hätte für sie nichts anzubieten außer dem Weg dorthin. if (feld.dataset.wert === 'split') { splitOeffnen(feld); return; } oeffnen(feld); }); liste.addEventListener('change', function () { if (!offenesFeld) { return; } const feld = offenesFeld; if (liste.value === 'split') { schliessen(); splitOeffnen(feld); return; } // Eine Rückfrage wegen verlorener Splitzeilen braucht es hier nicht mehr: // Für eine aufgeteilte Buchung öffnet sich diese Liste gar nicht erst. auswahl.requestSubmit(); }); // Escape und der Klick daneben verwerfen die Auswahl. document.addEventListener('keydown', function (ereignis) { if (ereignis.key === 'Escape' && offenesFeld) { schliessen(); } }); document.addEventListener('click', function (ereignis) { if (!(ereignis.target instanceof Element) || !offenesFeld) { return; } if (!ereignis.target.closest('.zuordnungszelle')) { schliessen(); } }); /* ── Splitdialog ─────────────────────────────────────────────────────── */ if (!dialog || !splitformular || !window.FmBetrag) { return; } const zeilenkasten = document.getElementById('splitdialogzeilen'); const summenfeld = document.getElementById('splitdialogsumme'); const loeschknopf = document.getElementById('splitloeschen'); const vorlage = zeilenkasten.innerHTML; let buchungsbetrag = 0; function felder() { return Array.from(zeilenkasten.querySelectorAll('.splitbetrag')); } function rechneSumme() { const stand = window.FmBetrag.splitsumme(felder(), buchungsbetrag); summenfeld.textContent = stand.text; summenfeld.classList.toggle('negativ', stand.belegt && stand.rest !== 0); } /* Eine weitere Zeile: die letzte kopieren, leeren und neu durchnummerieren. * Die Namen tragen den Index (`split[3][betrag]`) – ohne Umnummerierung * überschriebe die Kopie ihre Vorlage. */ function zeileAnhaengen() { const letzte = zeilenkasten.lastElementChild; const kopie = letzte.cloneNode(true); const nr = zeilenkasten.children.length; kopie.querySelectorAll('[name]').forEach(function (feld) { feld.name = feld.name.replace(/^split\[\d+\]/, 'split[' + nr + ']'); if (feld.tagName === 'SELECT') { feld.selectedIndex = 0; } else { feld.value = ''; } }); zeilenkasten.appendChild(kopie); return kopie; } function splitdialogOeffnen(feld) { splitformular.action = ziel(feld, 'splitbuchung'); buchungsbetrag = Number(feld.dataset.betrag || '0'); document.getElementById('splitbezeichnung').textContent = feld.dataset.bezeichnung || ''; document.getElementById('splitbuchungsbetrag').textContent = window.FmBetrag.formatiere(buchungsbetrag); // Immer mit leeren Zeilen beginnen: Der Dialog gilt für alle Buchungen // der Seite, die Zeilen der vorigen dürfen nicht stehen bleiben. zeilenkasten.innerHTML = vorlage; const vorhandene = JSON.parse(feld.dataset.splits || '[]'); // Zu löschen gibt es nur, was schon aufgeteilt ist. Beim erstmaligen // Aufteilen wäre der Knopf ohne Wirkung – und ohne Sinn. loeschknopf.hidden = vorhandene.length === 0; vorhandene.forEach(function (zeile, nr) { const tr = zeilenkasten.children[nr] || zeileAnhaengen(); tr.querySelector('select[name$="[zuordnung]"]').value = zeile.zuordnung || ''; tr.querySelector('select[name$="[klasse_id]"]').value = zeile.klasse_id ? String(zeile.klasse_id) : ''; tr.querySelector('input[name$="[notiz]"]').value = zeile.notiz || ''; tr.querySelector('input[name$="[betrag]"]').value = zeile.betrag || ''; }); rechneSumme(); dialog.showModal(); } zeilenkasten.addEventListener('input', function (ereignis) { if (ereignis.target.classList.contains('splitbetrag')) { rechneSumme(); } }); document.getElementById('splitzeileMehr').addEventListener('click', function () { zeileAnhaengen().querySelector('select').focus(); }); document.getElementById('splitabbrechen').addEventListener('click', function () { dialog.close(); }); })();