Liegen zwei Buchungen, die eigentlich derselbe Vorgang sind, zu weit auseinander oder gehörte eine ursprünglich einem anderen Ursprungskonto, findet der automatische Abgleich sie nicht – die Umbuchung und ihre einzeln erfasste Dublette stehen dann dauerhaft nebeneinander im Kontoblatt. Buchung::verknuepfen() führt eine Umbuchung (eigene Seite oder Gegenseite auf diesem Konto) und eine einzeln erfasste Buchung mit passendem Betrag zusammen: Die Umbuchung bleibt, die Dublette fällt weg, ihr Bankumsatz hängt sich an die Umbuchung. Im Kontoblatt lässt sich dafür jetzt auch die Gegenseite einer fremden Umbuchung ankreuzen; ein neuer Knopf „Verknüpfen“ nutzt dieselbe Auswahl wie „Auswahl löschen“. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
528 lines
26 KiB
PHP
528 lines
26 KiB
PHP
<?php
|
||
|
||
use Finanzmanager\Models\Wertpapier;
|
||
use Finanzmanager\View;
|
||
|
||
/** @var array<string,mixed> $konto */
|
||
/** @var list<array<string,mixed>> $zeilen */
|
||
/** @var int $gesamt */
|
||
/** @var int $seite */
|
||
/** @var int $seiten */
|
||
/** @var array<string,mixed> $filter */
|
||
/** @var list<array<string,mixed>> $kategorien */
|
||
/** @var list<array<string,mixed>> $klassen */
|
||
/** @var list<array<string,mixed>> $konten */
|
||
/** @var array<int, list<array<string,mixed>>> $splits */
|
||
/** @var bool $bargeld Ist ein Bargeld-Tool eingerichtet? */
|
||
|
||
$kategorieNamen = [];
|
||
foreach ($kategorien as $k) {
|
||
$kategorieNamen[(int)$k['id']] = $k['pfad'];
|
||
}
|
||
$klassenNamen = [];
|
||
foreach ($klassen as $k) {
|
||
$klassenNamen[(int)$k['id']] = $k['name'];
|
||
}
|
||
$kontenNamen = array_column($konten, 'name', 'id');
|
||
|
||
$eigenesKonto = (int)$konto['id'];
|
||
|
||
$gefiltert = ($filter['suche'] ?? '') !== '' || !empty($filter['von']) || !empty($filter['bis'])
|
||
|| !empty($filter['kategorie_id']) || !empty($filter['klasse_id']) || !empty($filter['nur_offene']);
|
||
|
||
$seitenPfad = static function (int $nr) use ($filter): string {
|
||
$p = array_filter([
|
||
'suche' => $filter['suche'] ?? '',
|
||
'von' => $filter['von'] ?? '',
|
||
'bis' => $filter['bis'] ?? '',
|
||
'kategorie_id' => $filter['kategorie_id'] ?? '',
|
||
'klasse_id' => $filter['klasse_id'] ?? '',
|
||
'nur_offene' => !empty($filter['nur_offene']) ? '1' : '',
|
||
'seite' => (string)$nr,
|
||
], static fn(string $v): bool => $v !== '');
|
||
|
||
return '?' . http_build_query($p);
|
||
};
|
||
|
||
// Nach dem Löschen soll dieselbe Ansicht wieder erscheinen – Filter und Seite
|
||
// hängen deshalb am Ziel des Löschformulars. Dasselbe gilt für die
|
||
// Schnellzuordnung: Ihre Ziele setzt kontoblatt.js aus dieser Abfrage zusammen.
|
||
$abfrage = $seitenPfad($seite);
|
||
$loeschZiel = '/konten/' . (int)$konto['id'] . '/buchungen/loeschen' . $abfrage;
|
||
$verknuepfenZiel = '/konten/' . (int)$konto['id'] . '/buchungen/verknuepfen' . $abfrage;
|
||
|
||
/**
|
||
* Die Zuordnung einer Zeile als Wert der gemeinsamen Auswahlliste – dieselben
|
||
* Präfixe wie in der Buchungsmaske.
|
||
*/
|
||
$zuordnungswert = static function (array $zeile): string {
|
||
if ((int)($zeile['gesplittet'] ?? 0) === 1) {
|
||
return 'split';
|
||
}
|
||
if (!empty($zeile['gegenkonto_id'])) {
|
||
return 'konto:' . (int)$zeile['gegenkonto_id'];
|
||
}
|
||
if (!empty($zeile['kategorie_id'])) {
|
||
return 'kategorie:' . (int)$zeile['kategorie_id'];
|
||
}
|
||
return '';
|
||
};
|
||
|
||
/**
|
||
* Die Splitzeilen einer Buchung, wie der Dialog sie braucht. Sie hängen als
|
||
* JSON an der Zelle statt nachgeladen zu werden: Es sind die Zeilen der wenigen
|
||
* aufgeteilten Buchungen dieser Seite, und ein zweiter Weg zu denselben Daten
|
||
* wäre auch ein zweiter Weg, sie falsch zu lesen.
|
||
*
|
||
* @param list<array<string,mixed>> $splitzeilen
|
||
*/
|
||
$splitdaten = static function (array $splitzeilen) use ($zuordnungswert): string {
|
||
return (string)json_encode(array_map(static fn(array $zeile): array => [
|
||
'zuordnung' => $zuordnungswert($zeile),
|
||
'klasse_id' => (int)($zeile['klasse_id'] ?? 0),
|
||
'notiz' => (string)$zeile['notiz'],
|
||
'betrag' => View::betrag($zeile['betrag'], false),
|
||
], $splitzeilen), JSON_UNESCAPED_UNICODE);
|
||
};
|
||
|
||
/**
|
||
* Der Tooltip einer Splitbuchung: je Zeile Kategorie und Betrag. In der Spalte
|
||
* steht nur „Splitbuchung“ – wohin das Geld geht, wäre sonst erst im Dialog zu
|
||
* sehen. Notiz und Klasse bleiben draußen; sie stünden im Weg.
|
||
*
|
||
* @param list<array<string,mixed>> $splitzeilen
|
||
*/
|
||
$splituebersicht = static function (array $splitzeilen) use ($kategorieNamen, $kontenNamen): string {
|
||
$texte = [];
|
||
foreach ($splitzeilen as $zeile) {
|
||
$name = match (true) {
|
||
!empty($zeile['gegenkonto_id']) => '[' . ($kontenNamen[(int)$zeile['gegenkonto_id']] ?? '?') . ']',
|
||
!empty($zeile['kategorie_id']) => (string)($kategorieNamen[(int)$zeile['kategorie_id']] ?? '?'),
|
||
default => '(ohne Kategorie)',
|
||
};
|
||
$texte[] = $name . ': ' . View::betrag($zeile['betrag']);
|
||
}
|
||
|
||
return implode("\n", $texte);
|
||
};
|
||
|
||
/**
|
||
* Das Symbol der Abgleichspalte, siehe View::bargeldMarke() – die Schnell-
|
||
* zuordnung (kontoblatt.js) braucht dasselbe Symbol für eine einzelne Zeile,
|
||
* ohne die Seite neu zu laden.
|
||
*
|
||
* @param array<string,mixed> $zeile
|
||
*/
|
||
$bargeldmarke = static fn(array $zeile): string
|
||
=> View::bargeldMarke($zeile['bargeld_status'] ?? null, $zeile['bargeld_hinweis'] ?? null);
|
||
|
||
include __DIR__ . '/../layout/kopf.php';
|
||
?>
|
||
|
||
<div class="tableiste">
|
||
<span class="tab aktiv">Kontoblatt</span>
|
||
<a href="/konten/<?= (int)$konto['id'] ?>/bearbeiten" class="tab">Kontodaten</a>
|
||
</div>
|
||
|
||
<div class="seitenaktionen">
|
||
<a href="/konten/<?= (int)$konto['id'] ?>/buchung" class="knopf primaer">Neue Buchung</a>
|
||
<a href="/konten" class="knopf">Kontenübersicht</a>
|
||
<?php if ($zeilen !== []): ?>
|
||
<!-- Das Formular steht hier, seine Ankreuzfelder stehen in der Tabelle;
|
||
verbunden sind sie über das form-Attribut. Ein Formular um die
|
||
ganze Tabelle würde das Filterformular verschachteln. -->
|
||
<form method="post" action="<?= View::h($loeschZiel) ?>" id="buchungsauswahl" class="inline"
|
||
data-bestaetigen="Die ausgewählten Buchungen wirklich löschen?"
|
||
data-frage-eine="Die ausgewählte Buchung wirklich löschen?"
|
||
data-frage-mehrere="{anzahl} ausgewählte Buchungen wirklich löschen?">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken ?? '') ?>">
|
||
<?php /* Der erste Knopf im Formular bleibt „Auswahl löschen“ –
|
||
kontoblatt.js/app.js greift sich per querySelector den
|
||
ersten Absende-Knopf, um ihn nach Anzahl der Auswahl zu
|
||
beschriften und zu sperren. Verknüpfen nutzt dasselbe
|
||
Formular und Ankreuzfeld, nur mit anderem Ziel: zwei
|
||
ausgewählte Buchungen, die denselben Vorgang doppelt
|
||
zeigen (eine Umbuchung und ihre einzeln erfasste
|
||
Dublette), führt es zusammen. */ ?>
|
||
<button type="submit" class="knopf gefahr">Auswahl löschen</button>
|
||
<button type="submit" formaction="<?= View::h($verknuepfenZiel) ?>" class="knopf"
|
||
data-bestaetigen="Beide ausgewählten Buchungen zu einer Umbuchung verknüpfen? Die einzeln erfasste Buchung wird dabei gelöscht.">
|
||
Verknüpfen
|
||
</button>
|
||
</form>
|
||
<?php endif; ?>
|
||
<span class="fuellung"></span>
|
||
<span class="saldoanzeige">
|
||
Aktueller Saldo
|
||
<strong class="<?= View::betragKlasse($konto['saldo_aktuell']) ?>">
|
||
<?= View::betrag($konto['saldo_aktuell']) ?>
|
||
</strong>
|
||
</span>
|
||
<?php if (abs((float)$konto['saldo_erwartet'] - (float)$konto['saldo_aktuell']) > 0.005): ?>
|
||
<span class="saldoanzeige">
|
||
Erwartet
|
||
<strong class="<?= View::betragKlasse($konto['saldo_erwartet']) ?>">
|
||
<?= View::betrag($konto['saldo_erwartet']) ?>
|
||
</strong>
|
||
</span>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<form method="get" action="/konten/<?= (int)$konto['id'] ?>" class="filterleiste">
|
||
<input type="search" name="suche" placeholder="Empfänger, Text, Nummer"
|
||
value="<?= View::h($filter['suche'] ?? '') ?>">
|
||
<label>von <input type="date" name="von" value="<?= View::h($filter['von'] ?? '') ?>"></label>
|
||
<label>bis <input type="date" name="bis" value="<?= View::h($filter['bis'] ?? '') ?>"></label>
|
||
|
||
<select name="kategorie_id" aria-label="Kategorie" data-kombobox>
|
||
<option value="">Alle Kategorien</option>
|
||
<?php foreach ($kategorien as $k): ?>
|
||
<option value="<?= (int)$k['id'] ?>"
|
||
<?= (int)($filter['kategorie_id'] ?? 0) === (int)$k['id'] ? 'selected' : '' ?>>
|
||
<?= View::h($k['pfad']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
|
||
<select name="klasse_id">
|
||
<option value="">Alle Klassen</option>
|
||
<?php foreach ($klassen as $k): ?>
|
||
<option value="<?= (int)$k['id'] ?>"
|
||
<?= (int)($filter['klasse_id'] ?? 0) === (int)$k['id'] ? 'selected' : '' ?>>
|
||
<?= View::h($k['name']) ?>
|
||
</option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
|
||
<label class="ankreuzfeld">
|
||
<input type="checkbox" name="nur_offene" value="1" <?= !empty($filter['nur_offene']) ? 'checked' : '' ?>>
|
||
nur nicht abgeglichene
|
||
</label>
|
||
|
||
<button type="submit" class="knopf klein">Filtern</button>
|
||
<?php if ($gefiltert): ?>
|
||
<a href="/konten/<?= (int)$konto['id'] ?>" class="knopf klein">Filter zurücksetzen</a>
|
||
<?php endif; ?>
|
||
</form>
|
||
|
||
<?php if ($zeilen === []): ?>
|
||
<p class="platzhalter">
|
||
<?= $gefiltert ? 'Keine Buchung passt zum Filter.' : 'Für dieses Konto ist noch nichts gebucht.' ?>
|
||
</p>
|
||
<?php else: ?>
|
||
<table class="liste kontoblatt" data-abfrage="<?= View::h($abfrage) ?>">
|
||
<thead>
|
||
<tr>
|
||
<th class="wahlspalte">
|
||
<input type="checkbox" class="alle-waehlen" data-waehlt="buchungsauswahl"
|
||
aria-label="Alle angezeigten Buchungen auswählen">
|
||
</th>
|
||
<th>Datum</th>
|
||
<th>Nr.</th>
|
||
<th>Empfänger</th>
|
||
<th>Buchungstext</th>
|
||
<th>Kategorie</th>
|
||
<th>Klasse</th>
|
||
<?php if ($bargeld): ?>
|
||
<th class="bargeldspalte" title="Abgleich mit dem Bargeld-Tool">Bar</th>
|
||
<?php endif; ?>
|
||
<th class="rechts">Betrag</th>
|
||
<th class="rechts">Saldo</th>
|
||
<th>✓</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<?php foreach ($zeilen as $zeile): ?>
|
||
<tr class="<?= $zeile['gegensicht'] ? 'gegensicht' : '' ?>">
|
||
<td class="wahlspalte">
|
||
<?php
|
||
// Auch die Gegenseite einer fremden Umbuchung lässt sich
|
||
// ankreuzen – gelöscht wird sie dadurch nicht (das prüft
|
||
// der Controller anhand ihres wirklichen Kontos), aber
|
||
// verknüpfen lässt sie sich mit einer einzeln erfassten
|
||
// Dublette auf diesem Konto.
|
||
$auswahlLabel = $zeile['gegensicht']
|
||
? 'Umbuchung vom ' . View::datum($zeile['datum']) . ' auswählen'
|
||
: 'Buchung vom ' . View::datum($zeile['datum']) . ' auswählen';
|
||
?>
|
||
<input type="checkbox" name="buchungen[]" value="<?= (int)$zeile['id'] ?>"
|
||
form="buchungsauswahl"
|
||
aria-label="<?= View::h($auswahlLabel) ?>">
|
||
</td>
|
||
<td class="nowrap"><?= View::datum($zeile['datum']) ?></td>
|
||
<td class="nebentext"><?= View::h($zeile['nummer']) ?></td>
|
||
<td>
|
||
<?php /* Der Text steht in einem eigenen Kasten, nicht direkt in
|
||
der Zelle: Abgeschnitten wird über zwei Zeilen, und das
|
||
kann nur ein Element, das kein Tabellenfeld ist. Die
|
||
Marke steht neben dem Kasten – im Kasten zählte sie als
|
||
eine der beiden Zeilen. */ ?>
|
||
<?php if ($zeile['gegensicht']): ?>
|
||
<div class="zellenreihe">
|
||
<div class="zellentext"><?= View::h($zeile['empfaenger']) ?></div>
|
||
<span class="marke" title="Gegenseite einer Umbuchung – bearbeitbar auf dem Ursprungskonto">
|
||
Umbuchung
|
||
</span>
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="zellentext">
|
||
<a href="/buchungen/<?= (int)$zeile['id'] ?>"><?= View::h($zeile['empfaenger'] ?: '(ohne Empfänger)') ?></a>
|
||
</div>
|
||
<?php endif; ?>
|
||
</td>
|
||
<?php /* Ungekürzt: Was nicht auf zwei Zeilen passt, schneidet das
|
||
Stylesheet ab – der vollständige Text bleibt für den
|
||
Tooltip erhalten. */ ?>
|
||
<td class="nebentext buchungstext"><div class="zellentext"><?= View::h($zeile['buchungstext']) ?></div></td>
|
||
<?php
|
||
// Die Gegenseite einer fremden Umbuchung nennt das Konto, von dem
|
||
// sie kommt – und zwar dort, wo sonst die Kategorie steht. Ohne
|
||
// diese Angabe stünde auf dem Gegenkonto eine Umbuchung ohne
|
||
// erkennbares Gegenüber.
|
||
$gegenkonto = empty($zeile['gegenkonto_id'])
|
||
? ''
|
||
: '[' . ($kontenNamen[(int)$zeile['gegenkonto_id']] ?? '?') . ']';
|
||
$beschriftung = match (true) {
|
||
(int)$zeile['gesplittet'] === 1 => 'Splitbuchung',
|
||
$gegenkonto !== '' => $gegenkonto,
|
||
default => (string)($kategorieNamen[(int)$zeile['kategorie_id']] ?? ''),
|
||
};
|
||
|
||
// Eine Splitbuchung nennt im Tooltip ihre Zeilen, alles andere
|
||
// seine eigene – womöglich abgeschnittene – Beschriftung.
|
||
$zuordnungstitel = match (true) {
|
||
isset($splits[(int)$zeile['id']]) => $splituebersicht($splits[(int)$zeile['id']]),
|
||
$beschriftung !== '' => $beschriftung,
|
||
default => 'Kategorie oder Gegenkonto zuordnen',
|
||
};
|
||
?>
|
||
<td class="zuordnungszelle">
|
||
<?php if ($zeile['gegensicht']): ?>
|
||
<?php /* Auch die Gegenseite führt zu ihrer Buchung – sie liegt
|
||
auf dem anderen Konto und wird nur dort geändert. */ ?>
|
||
<a href="/buchungen/<?= (int)$zeile['id'] ?>" class="zuordnungsfeld gegenseite"
|
||
title="Umbuchung von <?= View::h($kontenNamen[(int)$zeile['gegenkonto_id']] ?? '?') ?> – dort zu ändern">
|
||
<?= View::h($beschriftung) ?>
|
||
</a>
|
||
<?php else: ?>
|
||
<?php /* Ohne JavaScript führt der Klick in die Buchungsmaske;
|
||
kontoblatt.js macht daraus die Auswahl an Ort und Stelle. */ ?>
|
||
<?php
|
||
// Wertpapierabrechnungen führen ISIN und WKN im
|
||
// Verwendungszweck mit. Wer die Zeile ins Depot bucht,
|
||
// bekommt das Papier deshalb vorgeschlagen, statt es
|
||
// in einer langen Liste zu suchen.
|
||
$papier = Wertpapier::ausText(
|
||
(string)$zeile['buchungstext'] . ' ' . (string)$zeile['empfaenger']
|
||
);
|
||
?>
|
||
<a href="/buchungen/<?= (int)$zeile['id'] ?>" class="zuordnungsfeld"
|
||
data-buchung="<?= (int)$zeile['id'] ?>"
|
||
data-wert="<?= View::h($zuordnungswert($zeile)) ?>"
|
||
data-betrag="<?= View::h(number_format((float)$zeile['betrag'], 2, '.', '')) ?>"
|
||
data-datum="<?= View::h(substr((string)$zeile['datum'], 0, 10)) ?>"
|
||
data-zweck="<?= View::h((string)$zeile['buchungstext']) ?>"
|
||
data-isin="<?= View::h($papier['isin']) ?>"
|
||
data-wkn="<?= View::h($papier['wkn']) ?>"
|
||
data-bezeichnung="<?= View::h(View::datum($zeile['datum']) . ' · ' . ($zeile['empfaenger'] ?: '(ohne Empfänger)')) ?>"
|
||
<?php if (isset($splits[(int)$zeile['id']])): ?>
|
||
data-splits="<?= View::h($splitdaten($splits[(int)$zeile['id']])) ?>"
|
||
<?php endif; ?>
|
||
title="<?= View::h($zuordnungstitel) ?>">
|
||
<?php if ($beschriftung === 'Splitbuchung'): ?>
|
||
<span class="marke">Splitbuchung</span>
|
||
<?php else: ?>
|
||
<?= View::h($beschriftung) ?>
|
||
<?php endif; ?>
|
||
</a>
|
||
<?php endif; ?>
|
||
</td>
|
||
<?php $klassenname = (string)($klassenNamen[(int)$zeile['klasse_id']] ?? ''); ?>
|
||
<td class="klassenzelle nebentext">
|
||
<?php if ($zeile['gegensicht']): ?>
|
||
<?= View::h($klassenname) ?>
|
||
<?php else: ?>
|
||
<?php /* Wie bei der Zuordnung: Ohne JavaScript führt der Klick
|
||
in die Buchungsmaske, mit ihm öffnet sich die Auswahl
|
||
an Ort und Stelle. */ ?>
|
||
<a href="/buchungen/<?= (int)$zeile['id'] ?>" class="klassenfeld"
|
||
data-buchung="<?= (int)$zeile['id'] ?>"
|
||
data-klasse="<?= (int)$zeile['klasse_id'] ?>"
|
||
title="<?= View::h($klassenname !== '' ? $klassenname : 'Klasse zuordnen') ?>">
|
||
<?= View::h($klassenname) ?>
|
||
</a>
|
||
<?php endif; ?>
|
||
</td>
|
||
<?php if ($bargeld): ?>
|
||
<td class="bargeldspalte"><?= $bargeldmarke($zeile) ?></td>
|
||
<?php endif; ?>
|
||
<td class="rechts <?= View::betragKlasse($zeile['betrag']) ?>"><?= View::betrag($zeile['betrag']) ?></td>
|
||
<td class="rechts <?= $zeile['saldo'] === null ? 'nebentext' : View::betragKlasse($zeile['saldo']) ?>">
|
||
<?= $zeile['saldo'] === null ? '–' : View::betrag($zeile['saldo']) ?>
|
||
</td>
|
||
<td class="nebentext"><?= (int)$zeile['abgeglichen'] === 1 ? '✓' : '' ?></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="blaettern">
|
||
<span class="nebentext"><?= (int)$gesamt ?> Buchungen</span>
|
||
<?php if ($seiten > 1): ?>
|
||
<?php if ($seite > 1): ?>
|
||
<a href="<?= View::h($seitenPfad($seite - 1)) ?>" class="knopf klein">Neuer</a>
|
||
<?php endif; ?>
|
||
<span class="nebentext">Seite <?= (int)$seite ?> von <?= (int)$seiten ?></span>
|
||
<?php if ($seite < $seiten): ?>
|
||
<a href="<?= View::h($seitenPfad($seite + 1)) ?>" class="knopf klein">Älter</a>
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
|
||
<?php /* Die Auswahlliste gibt es einmal; kontoblatt.js hängt sie in die
|
||
angeklickte Zelle. Eine Liste je Zeile wären bei hundert Zeilen
|
||
ebenso viele vollständige Kategoriebäume. */ ?>
|
||
<form method="post" action="" id="zuordnungsauswahl" class="zuordnungsauswahl" hidden>
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<select name="zuordnung" aria-label="Kategorie oder Gegenkonto" data-kombobox>
|
||
<?php $gewaehlt = ''; $mitSplit = true; $mitDepot = true; ?>
|
||
<?php include __DIR__ . '/zuordnungsliste.php'; ?>
|
||
</select>
|
||
<noscript><button type="submit" class="knopf klein">Übernehmen</button></noscript>
|
||
</form>
|
||
|
||
<?php /* Dieselbe Bauart für die Klassenspalte: eine Liste, die in die
|
||
angeklickte Zelle wandert. Der erste Eintrag löscht die Klasse. */ ?>
|
||
<form method="post" action="" id="klassenauswahl" class="zuordnungsauswahl" hidden>
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<select name="klasse_id" aria-label="Klasse">
|
||
<option value="">— keine —</option>
|
||
<?php foreach ($klassen as $k): ?>
|
||
<option value="<?= (int)$k['id'] ?>"><?= View::h($k['name']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
<noscript><button type="submit" class="knopf klein">Übernehmen</button></noscript>
|
||
</form>
|
||
|
||
<dialog id="splitdialog" class="splitdialog">
|
||
<form method="post" action="" id="splitformular">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<h2>Splitbuchung</h2>
|
||
<p class="feldhinweis">
|
||
<span id="splitbezeichnung"></span> über
|
||
<output id="splitbuchungsbetrag" class="betrag">0,00</output>
|
||
</p>
|
||
|
||
<table class="liste splittabelle">
|
||
<thead>
|
||
<tr>
|
||
<th>Kategorie / Gegenkonto</th><th>Klasse</th><th>Notiz</th>
|
||
<th class="rechts">Betrag</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="splitdialogzeilen">
|
||
<?php for ($nr = 0; $nr < 4; $nr++): ?>
|
||
<tr>
|
||
<td>
|
||
<select name="split[<?= $nr ?>][zuordnung]">
|
||
<?php $gewaehlt = ''; $mitSplit = false; $mitDepot = false; ?>
|
||
<?php include __DIR__ . '/zuordnungsliste.php'; ?>
|
||
</select>
|
||
</td>
|
||
<td>
|
||
<select name="split[<?= $nr ?>][klasse_id]">
|
||
<option value="">— keine —</option>
|
||
<?php foreach ($klassen as $k): ?>
|
||
<option value="<?= (int)$k['id'] ?>"><?= View::h($k['name']) ?></option>
|
||
<?php endforeach; ?>
|
||
</select>
|
||
</td>
|
||
<td><input type="text" name="split[<?= $nr ?>][notiz]" value=""></td>
|
||
<td>
|
||
<input type="text" class="splitbetrag rechts" inputmode="decimal"
|
||
name="split[<?= $nr ?>][betrag]" value="">
|
||
</td>
|
||
</tr>
|
||
<?php endfor; ?>
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td colspan="3" class="rechts">Summe der Zeilen / Restbetrag</td>
|
||
<td class="rechts"><output id="splitdialogsumme" class="betrag">0,00</output></td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
|
||
<p class="feldhinweis">
|
||
Die Summe der Zeilen muss dem Buchungsbetrag entsprechen. Bleiben alle
|
||
Zeilen leer, wird die Splitbuchung aufgehoben.
|
||
</p>
|
||
|
||
<div class="knopfleiste">
|
||
<button type="submit" class="knopf primaer">Splitbuchung speichern</button>
|
||
<button type="button" class="knopf" id="splitzeileMehr">Zeile hinzufügen</button>
|
||
<button type="button" class="knopf" id="splitabbrechen">Abbrechen</button>
|
||
<?php /* Rechts außen, abseits der übrigen Knöpfe: hebt die Aufteilung
|
||
samt aller Zuordnungen auf. Nur sichtbar, wenn es etwas
|
||
aufzuheben gibt – kontoblatt.js blendet ihn ein. */ ?>
|
||
<button type="submit" class="knopf gefahr" id="splitloeschen"
|
||
name="aktion" value="loeschen" formnovalidate hidden>
|
||
Splitbuchung löschen
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<?php /* Ein Wertpapiergeschäft entsteht dort, wo seine Abrechnung ankommt:
|
||
am Girokonto. Die Auswahl eines Depots in der Zuordnungsliste öffnet
|
||
diesen Dialog; die Nebenkosten liegen dahinter im zweiten. */ ?>
|
||
<dialog id="wertpapierdialog" class="splitdialog wertpapierdialog">
|
||
<form method="post" action="" id="wertpapierformular" class="formular">
|
||
<input type="hidden" name="csrf_token" value="<?= View::h($csrfToken) ?>">
|
||
<input type="hidden" name="depot_id" id="wp_depot" value="">
|
||
|
||
<h2>Wertpapiergeschäft</h2>
|
||
<p class="feldhinweis">
|
||
<span id="wp_bezeichnung"></span> über
|
||
<output id="wp_buchungsbetrag" class="betrag">0,00</output> ·
|
||
Depot <strong id="wp_depotname"></strong>
|
||
</p>
|
||
<p class="feldhinweis verwendungszweck" id="wp_verwendungszweck"></p>
|
||
|
||
<?php $datumsvorgabe = date('Y-m-d'); ?>
|
||
<?php $vorschlag = ['isin' => '', 'wkn' => '']; ?>
|
||
<?php include __DIR__ . '/wertpapierfelder.php'; ?>
|
||
|
||
<div class="knopfleiste">
|
||
<button type="submit" class="knopf primaer">Geschäft buchen</button>
|
||
<button type="button" class="knopf" id="wp_kostenknopf">
|
||
Kosten … (<output id="wp_kostenknopfsumme" class="betrag">0,00</output>)
|
||
</button>
|
||
<button type="button" class="knopf" id="wp_abbrechen">Abbrechen</button>
|
||
<span class="fuellung"></span>
|
||
<span class="saldoanzeige">
|
||
Einstandspreis <strong><output id="wp_einstand" class="betrag">0,00</output></strong>
|
||
</span>
|
||
</div>
|
||
</form>
|
||
</dialog>
|
||
|
||
<?php /* Die Kostenfelder gehören zum Formular oben, stehen aber in einem
|
||
eigenen Dialog – verbunden über das form-Attribut, wie schon die
|
||
Ankreuzfelder der Sammellöschung. */ ?>
|
||
<dialog id="kostendialog" class="splitdialog">
|
||
<h2>Kosten</h2>
|
||
<?php $kostenFormular = 'wertpapierformular'; ?>
|
||
<?php include __DIR__ . '/wertpapierkosten.php'; ?>
|
||
<div class="knopfleiste">
|
||
<button type="button" class="knopf primaer" id="wp_kostenfertig">Übernehmen</button>
|
||
</div>
|
||
</dialog>
|
||
|
||
<script src="/static/betrag.js" defer></script>
|
||
<script src="/static/wertpapierbuchung.js" defer></script>
|
||
<script src="/static/kombobox.js" defer></script>
|
||
<script src="/static/kontoblatt.js" defer></script>
|
||
<?php endif; ?>
|
||
|
||
<?php include __DIR__ . '/../layout/fuss.php'; ?>
|