Finanzmanager/public/static/buchungsmaske.js
fds2610-bot 619e427a30 Fix #96: Splitbuchungen zeigen Restbetrag, Speichern nur bei Null
Die Summenzeile der Splitbuchungen (Buchungsmaske und Splitdialog im
Kontoblatt) zeigt jetzt den noch offenen Restbetrag zum Buchungswert statt
der Summe der Zeilen. „Speichern" bleibt gesperrt, solange Splitzeilen
befüllt sind und der Restbetrag nicht Null ist; ohne Splitzeilen (auch mit
direkter Kategorie/Gegenkonto) bleibt der Knopf wie bisher an.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 23:34:27 +02:00

116 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Buchungsmaske: Restbetrag der Splitzeilen mitrechnen und direkte Zuordnung
* und Splitbuchung gegeneinander sperren.
*
* Solange Splitzeilen befüllt sind, aber noch ein Restbetrag zum
* Buchungswert offen ist, bleibt „Speichern“ gesperrt – das Model wiese die
* Buchung sonst ohnehin ab, hier spart es die Rundreise zum Server.
*
* Nur Komfort – die Maske kommt fertig vom Server. Ohne JavaScript bleibt alles
* bedienbar: Die Splitsumme prüft ohnehin das Model.
*
* Eine Buchung ist entweder direkt zugeordnet (Kategorie / Gegenkonto, Klasse)
* oder eine Splitbuchung. Sobald in einer Splitzeile etwas steht, sind die
* beiden Felder oben gesperrt; steht oben eine Kategorie oder Klasse, sind es
* die Splitzeilen. Gesperrte Felder schickt der Browser nicht mit – was dort
* noch stehen mag, geht also nicht in die Buchung ein. Der Knopf
* „Splitbuchungen löschen“ leert alle Splitzeilen und gibt oben wieder frei.
*
* Kategorie und Gegenkonto stehen in einem Feld, deshalb gibt es hier nichts
* mehr ein- und auszublenden; die Art bestimmt nur noch die Richtung.
*/
(function () {
'use strict';
const art = document.getElementById('art');
const maske = document.querySelector('.buchungsmaske');
if (!art || !maske || !window.FmBetrag) {
return;
}
const summenfeld = document.getElementById('splitsumme');
const betragsfeld = document.getElementById('betrag');
const speichernknopf = maske.querySelector('button[type="submit"]');
function rechneSumme() {
if (!summenfeld) {
return;
}
// Der Buchungsbetrag wird positiv eingegeben; das Vorzeichen ergibt
// sich aus der Art. Für den Vergleich zählt der Betrag mit Richtung.
let ziel = window.FmBetrag.zahl(betragsfeld ? betragsfeld.value : '0');
if (art.value !== 'einnahme') {
ziel = -ziel;
}
const stand = window.FmBetrag.splitsumme(
Array.from(maske.querySelectorAll('.splitbetrag')),
ziel
);
summenfeld.textContent = stand.text;
summenfeld.classList.toggle('negativ', stand.belegt && stand.rest !== 0);
// Gesperrt nur, solange tatsächlich Splitzeilen mit Beträgen stehen und
// deren Summe den Buchungswert verfehlt. Eine direkte Zuordnung (oder
// gar keine Eingabe) deckt den ganzen Betrag stets ab.
if (speichernknopf) {
speichernknopf.disabled = stand.belegt && stand.rest !== 0;
}
}
const direktfelder = ['zuordnung', 'klasse_id']
.map(function (id) { return document.getElementById(id); })
.filter(Boolean);
const splitkasten = document.getElementById('splitzeilen');
const leerenknopf = document.getElementById('splitsleeren');
function splitfelder() {
return splitkasten ? Array.from(splitkasten.querySelectorAll('input, select')) : [];
}
function belegt(felder) {
return felder.some(function (feld) { return feld.value.trim() !== ''; });
}
function sperrenAktualisieren() {
const splits = splitfelder();
const splitBelegt = belegt(splits);
// Stehen beim Öffnen beide Seiten voll (ältere Daten), gilt wie im
// Model die Splitbuchung.
const direktBelegt = !splitBelegt && belegt(direktfelder);
direktfelder.forEach(function (feld) { feld.disabled = splitBelegt; });
splits.forEach(function (feld) { feld.disabled = direktBelegt; });
if (leerenknopf) {
leerenknopf.hidden = !splitBelegt;
}
}
function splitsLeeren() {
splitfelder().forEach(function (feld) {
feld.value = '';
});
sperrenAktualisieren();
rechneSumme();
}
art.addEventListener('change', rechneSumme);
maske.addEventListener('input', function (ereignis) {
if (ereignis.target.classList.contains('splitbetrag') || ereignis.target.id === 'betrag') {
rechneSumme();
}
sperrenAktualisieren();
});
maske.addEventListener('change', sperrenAktualisieren);
if (leerenknopf) {
leerenknopf.addEventListener('click', splitsLeeren);
}
sperrenAktualisieren();
rechneSumme();
})();