Formular-Abstaende korrigiert und Anleitungsseite ergaenzt

Abstaende:
Im Template hat das Label 1em Abstand nach unten, das Eingabefeld aber gar
keinen, und die Formular-Grids nutzen keinen vertikalen Gutter (gtr-uniform).
Dadurch war der Abstand vom Feld zum naechsten Label 0, waehrend Label zu
eigenem Feld 1em betrug - das Label wirkte, als gehoere es zum Feld darueber.
Jetzt sitzt das Label eng an seinem eigenen Feld (0,5em) und die Felder haben
1,5em Abstand nach unten.

Ausserdem stylt main.css die Typen number, date, datetime-local und file gar
nicht; diese Felder erschienen als kleine native Kaestchen zwischen den sonst
einheitlichen Feldern (z. B. "Listenfenster in Tagen" und "Zeilenhoehe im
Ausdruck"). Sie bekommen jetzt dasselbe Aussehen wie die Textfelder.

Die Anpassungen liegen in einer eigenen assets/css/app.css, damit main.css als
Template-Datei unveraendert bleibt. Nebenbei: .hint-box.error war nirgends
definiert, obwohl es die haeufigste Variante ist - Fehlermeldungen erschienen
ungefaerbt wie neutrale Hinweise.

In faq.php, hinweise.php, namenanpassen.php und stricheintragen.php standen
<br>/<br><br> als Abstandsersatz hinter Labels und Feldern; mit den neuen
Abstaenden waere daraus doppelter Leerraum geworden. Entfernt, das Layout
kommt jetzt aus dem CSS.

Anleitung:
Neue Seite anleitung.php mit den wichtigsten Funktionen, gegliedert nach
Zielgruppe. Die Abschnitte fuer Kassenwart bzw. Administration werden nur
eingeblendet, wenn die Rolle sie auch nutzen kann - sonst stuenden dort
Hinweise auf Menuepunkte, die es fuer den Nutzer nicht gibt. Verlinkt im Menue
unter "Kaffeeliste" und im Smoke-Test abgedeckt.

Geprueft: alle 12 Formularseiten rendern fehlerfrei, http-smoke 34/0,
role-matrix 55/0, tenant-isolation 12/0, settings-flow 15/0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 11:51:14 +02:00
co-authored by Claude Opus 4.8
parent 064c872c30
commit d18c55eef5
9 changed files with 401 additions and 15 deletions
+243
View File
@@ -0,0 +1,243 @@
<?php
include "functions.php";
require_once __DIR__ . "/app/ledger.php";
$pdo = app_db_pdo();
$saasUser = saas_current_user($pdo);
// Die Anleitung ist bewusst fuer alle angemeldeten Rollen lesbar - auch
// Mitglieder sollen nachschlagen koennen. Die Verwaltungsabschnitte werden
// nur eingeblendet, wenn die Rolle sie auch nutzen kann; sonst waere die
// Seite voller Hinweise auf Menuepunkte, die es fuer diesen Nutzer nicht gibt.
$istKassenwart = $saasUser !== null && saas_user_has_role(['owner', 'admin', 'treasurer'], $saasUser);
$istAdmin = $saasUser !== null && saas_user_has_role(['owner', 'admin'], $saasUser);
// Einstellungen fuer die Hinweise zu den aktivierten Zahlungswegen.
$settings = $saasUser !== null ? saas_fetch_tenant_settings($pdo, (int)$saasUser['tenant_id']) : null;
include "header.php";
include "headerline.php";
include "nav.php";
?>
<section id="banner">
<div class="content">
<h2>Anleitung</h2>
<p>Diese Seite erklärt die wichtigsten Funktionen der Kaffeeliste. Oben stehen
die Punkte, die jedes Mitglied betreffen, darunter die Verwaltungsfunktionen.</p>
<?php if ($saasUser === null): ?>
<div class="hint-box">
<p>Du bist nicht angemeldet. Nach der Anmeldung siehst du hier zusätzlich die
Anleitung zu den Funktionen deiner Rolle.</p>
</div>
<?php endif; ?>
<hr>
<h2>Für alle Mitglieder</h2>
<h3>Meine Kaffeeliste</h3>
<p>Das ist deine Startseite. Sie zeigt deinen <b>aktuellen Stand</b>, deine
Jahresübersicht sowie die letzten Einzahlungen und Striche.</p>
<ul>
<li><b>Guthaben</b> (positiver Betrag): Du hast mehr eingezahlt, als du verbraucht hast.</li>
<li><b>Schulden</b> (negativer Betrag): Es ist Zeit für eine Einzahlung.</li>
</ul>
<p>In der <b>Monatsübersicht</b> siehst du, wie viele Striche in welchem Monat
zusammengekommen sind.</p>
<h3>Striche selbst eintragen</h3>
<p>Wenn die Verwaltung das freigeschaltet hat, kannst du deinen Kaffee direkt auf
der Startseite eintragen ein oder zwei Striche pro Vorgang.</p>
<div class="hint-box">
<p><b>Vertippt?</b> Direkt darunter kannst du deinen <b>zuletzt selbst
eingetragenen</b> Strich wieder stornieren. Striche, die der Kassenwart über die
Sammelerfassung eingetragen hat, kannst du nicht selbst stornieren wende dich
dafür an die Verwaltung.</p>
</div>
<h3>Bezahlen</h3>
<p>Welche Wege zur Verfügung stehen, legt die Verwaltung fest. Möglich sind:</p>
<ul>
<li><b>Barzahlung</b> beim angegebenen Ansprechpartner.</li>
<li><b>PayPal</b> über die Schaltflächen auf der Startseite.</li>
<li><b>Überweisung</b> auf die angegebene IBAN.</li>
</ul>
<?php if ($settings !== null): ?>
<p><small>In eurem Mandanten sind aktuell aktiv:
<?php
$wege = [];
if ((int)$settings['cash_enabled'] === 1) { $wege[] = 'Barzahlung'; }
if ((int)$settings['paypal_enabled'] === 1) { $wege[] = 'PayPal'; }
if ((int)$settings['bank_transfer_enabled'] === 1) { $wege[] = 'Überweisung'; }
echo $wege === [] ? '<b>noch kein Zahlungsweg</b>' : '<b>' . saas_html(implode(', ', $wege)) . '</b>';
?>.</small></p>
<?php endif; ?>
<div class="hint-box">
<p><b>Wichtig bei PayPal:</b> Nutzt die Funktion „Geld an Freunde senden“. Bei
Zahlungen für „Waren und Dienstleistungen“ zieht PayPal Gebühren ab gutgeschrieben
wird dann nur der Betrag, der tatsächlich angekommen ist.</p>
</div>
<h3>Anzeigenamen ändern</h3>
<p>Unter <b>Namensanpassung</b> kannst du festlegen, wie dein Name auf der
Kaffeeliste und im Ausdruck erscheint (maximal 20 Zeichen).</p>
<h3>Kundenkonto</h3>
<p>Unter <b>Kundenkonto</b> findest du deine Zugangsdaten, kannst deine E-Mail-Adresse
bestätigen und dein Passwort ändern.</p>
<?php if ($istKassenwart): ?>
<hr>
<h2>Für Kassenwart und Verwaltung</h2>
<p><small>Sichtbar für die Rollen <b>Kassenwart</b>, <b>Administrator</b> und
<b>Inhaber</b>.</small></p>
<h3>Einzahlung eintragen</h3>
<p>Trage hier ein, wer wie viel eingezahlt hat. Du kannst mehrere Mitglieder in
einem Durchgang erfassen; leere Felder werden übersprungen.</p>
<ul>
<li><b>Bemerkung</b>: optionale Notiz, damit später nachvollziehbar bleibt, worum es ging.</li>
<li><b>Negativer Betrag</b> = Abzug (z.&nbsp;B. Auszahlung bei Austritt oder eine
Erstattung). Dabei ist eine Bemerkung <b>Pflicht</b>.</li>
</ul>
<div class="hint-box">
<p><b>Falsch eingetragen ist kein Abzug.</b> Wenn eine Buchung schlicht falsch war,
storniere sie unter <b>Letzte Einträge</b>. Ein Gegen-Abzug würde die Auswertung
verfälschen, weil dann beide Buchungen als Umsatz zählen.</p>
<p>Zum Schutz vor Vertippern werden Beträge über 1.000&nbsp;€ abgelehnt. Schlägt eine
Zeile fehl, wird <b>nichts</b> gebucht und deine Eingaben bleiben stehen.</p>
</div>
<h3>Striche eintragen</h3>
<p>Sammelerfassung für die Papierliste. Über die Schaltflächen wählst du:</p>
<ul>
<li><b>Vorderseite</b>: Vieltrinker Mitglieder mit mindestens 10 Strichen im Listenfenster.</li>
<li><b>Rückseite</b>: alle übrigen aktiven Mitglieder.</li>
<li><b>Alle</b>: die komplette Liste.</li>
</ul>
<p>Der <b>Preis pro Strich</b> wird aus den Mandant-Einstellungen vorbelegt und kann
für den einzelnen Vorgang überschrieben werden.</p>
<h3>Letzte Einträge stornieren</h3>
<p>Zeigt die letzten 100 Einzahlungen und Striche. Fehlerhafte Buchungen werden
<b>storniert</b>, nicht gelöscht: Sie verschwinden aus der Auswertung, bleiben aber
im Journal nachvollziehbar.</p>
<h3>PayPal-Zahlungen automatisch verbuchen</h3>
<p>Die Kaffeeliste kann PayPal-Zahlungen selbstständig gutschreiben:</p>
<ol>
<li>Auf der Seite <b>PayPal-Zahlungen</b> steht eure persönliche Eingangsadresse.</li>
<li>Richtet in eurem Mailprogramm eine <b>automatische Weiterleitung</b> der
PayPal-Benachrichtigungen an diese Adresse ein.</li>
<li>Erkannte Zahlungen werden dem passenden Mitglied automatisch gutgeschrieben.</li>
</ol>
<p>Gutgeschrieben wird immer der Betrag, der <b>tatsächlich angekommen</b> ist bei
Waren&nbsp;&amp;&nbsp;Dienstleistungen also nach Abzug der PayPal-Gebühr.</p>
<div class="hint-box">
<p>Zahlungen, die sich nicht eindeutig zuordnen lassen, landen in der Warteschlange
„Offene Zahlungen“ und können dort von Hand einem Mitglied zugeordnet oder ignoriert
werden. Damit die automatische Zuordnung möglichst oft greift, hinterlege den
<b>PayPal-Namen</b> beim Mitglied.</p>
</div>
<h3>CSV-Import</h3>
<p>Über <b>CSV Upload</b> lassen sich Einzahlungen aus einer Datei einlesen. Der Import
zeigt zuerst eine <b>Vorschau</b>; erst danach wird gebucht. Zeilen ohne eindeutige
Zuordnung werden markiert und nicht verbucht.</p>
<h3>Kaffeeliste, Ausdruck und Auswertung</h3>
<ul>
<li><b>Kaffeeliste anzeigen</b>: Gesamtübersicht aller aktiven Mitglieder. Ein Klick
auf den Namen öffnet die Einzelauswertung.</li>
<li><b>PDF-Export</b>: die Liste zum Ausdrucken und Aufhängen.</li>
<li><b>Journal-Vorschau</b>: alle Buchungen mit Herkunft nützlich zur Kontrolle.</li>
</ul>
<h3>Info-Mails versenden</h3>
<p>Unter <b>Mailversand</b> lassen sich Mitglieder über ihren Stand informieren. Der
<b>Dry-Run</b> zeigt zuerst, wer eine Mail bekäme, ohne etwas zu verschicken. Das
Versandlog hält fest, was tatsächlich rausging.</p>
<h3>Jahresabschluss</h3>
<p>Verteilt einen frei wählbaren Gesamtbetrag als Guthaben auf alle Mitglieder
anteilig zu ihren Strichen im laufenden Jahr. Auch hier gilt: erst <b>Dry-Run</b>
zur Vorschau, dann buchen. Beim echten Lauf werden die Mitglieder per Mail informiert.</p>
<?php endif; ?>
<?php if ($istAdmin): ?>
<hr>
<h2>Für Administratoren</h2>
<p><small>Sichtbar für die Rollen <b>Administrator</b> und <b>Inhaber</b>.</small></p>
<h3>Mitglieder verwalten</h3>
<ul>
<li><b>Anlegen und bearbeiten</b> von Mitgliedern (Name, E-Mail, PayPal-Name).</li>
<li><b>PayPal-Name</b>: entscheidend für die automatische Zuordnung von Zahlungen.</li>
<li><b>Deaktivieren</b> statt löschen: Das Mitglied verschwindet aus den Listen,
die Buchungshistorie bleibt erhalten.</li>
<li><b>Anonymisieren</b>: ersetzt Name und E-Mail durch einen Platzhalter (DSGVO-Auskunft),
die Buchungen der Kasse bleiben für die Abrechnung bestehen.</li>
<li><b>Zugang gewähren / entziehen</b>: steuert, ob ein Mitglied sich anmelden kann.</li>
<li><b>CSV-Import</b> für viele Mitglieder auf einmal eine passende Vorlage gibt es
zum Herunterladen.</li>
</ul>
<h3>Hinweise verwalten</h3>
<p>Ein Hinweis erscheint als gelber Balken oben auf jeder Seite z.&nbsp;B. „Kaffeekasse
ist leer“. Jeder Hinweis hat ein <b>Gültig bis</b>-Datum und verschwindet danach
automatisch.</p>
<h3>Mandant-Einstellungen</h3>
<p>Hier stellst du das Verhalten der Kaffeeliste ein:</p>
<ul>
<li><b>Preis pro Strich</b> Grundlage jeder Verbrauchsbuchung.</li>
<li><b>Listenfenster in Tagen</b> Zeitraum für die Vorder-/Rückseiten-Aufteilung.</li>
<li><b>Schulden-Warnschwelle</b> ab welchem Minus gewarnt bzw. erinnert wird.</li>
<li><b>Bezahloptionen</b> Barzahlung, PayPal und Überweisung samt Kontaktdaten.
Nur aktivierte Wege erscheinen bei den Mitgliedern.</li>
<li><b>Web-Striche erlauben</b> ob Mitglieder selbst eintragen dürfen.</li>
<li><b>Ausdruck</b> Sortierung, Zeilenhöhe, leere Zeilen, Wasserzeichen und Fußzeile.</li>
<li><b>Zahlungserinnerungen</b> automatische Mails an Mitglieder unterhalb der
Warnschwelle, samt Mindestabstand zwischen zwei Erinnerungen.</li>
</ul>
<p>Im <b>Protokoll</b> unten auf der Seite steht, wer wann welche Änderung vorgenommen hat.</p>
<h3>Konto, Abo und Daten</h3>
<ul>
<li><b>Kundenkonto</b>: Tarif, Rechnungen und Zahlungsdaten.</li>
<li><b>Datenexport</b>: alle Daten des Mandanten als Datei für ein Backup oder
einen Umzug.</li>
<li><b>Mandant löschen</b>: entfernt den Mandanten mit allen Daten endgültig.</li>
</ul>
<div class="hint-box error">
<p><b>Das Löschen eines Mandanten kann nicht rückgängig gemacht werden.</b> Lade dir
vorher über den Datenexport eine Kopie herunter.</p>
</div>
<h3>Rollen im Überblick</h3>
<table>
<tr><th>Rolle</th><th>Darf</th></tr>
<tr><td><b>Inhaber</b></td><td>alles, inklusive Abo, Datenexport und Löschen des Mandanten</td></tr>
<tr><td><b>Administrator</b></td><td>alle Verwaltungsfunktionen inklusive Mitglieder, Hinweise und Einstellungen</td></tr>
<tr><td><b>Kassenwart</b></td><td>Buchen: Einzahlungen, Striche, Stornos, Import, Ausdruck, Mails, Jahresabschluss</td></tr>
<tr><td><b>Mitglied</b></td><td>eigene Kaffeeliste, Striche selbst eintragen, eigenen Namen anpassen</td></tr>
<tr><td><b>Betrachter</b></td><td>nur die eigene Übersicht ansehen</td></tr>
</table>
<?php endif; ?>
<hr>
<h3>Noch Fragen?</h3>
<p>Häufige Fragen und die Antworten eurer Verwaltung stehen unter
<a href="faq.php">FAQ Kaffeeliste</a>.</p>
</div>
</section>
<?php include "footer.php"; ?>
+127
View File
@@ -0,0 +1,127 @@
/*
* Anpassungen der Kaffeeliste gegenueber dem HTML5-UP-Template (main.css).
* Bewusst eine eigene Datei, damit main.css unveraendert bleibt und bei einem
* Template-Update nicht mit eigenen Aenderungen kollidiert. Wird in header.php
* NACH main.css geladen.
*/
/* ---------------------------------------------------------------------------
* Formular-Abstaende
*
* Im Template hat das Label 1em Abstand nach unten, das Eingabefeld aber gar
* keinen. In einem Formular mit mehreren Feldern klebte das naechste Label
* dadurch direkt am Feld darueber und wirkte, als gehoere es zu diesem statt
* zum eigenen Feld. Jetzt gilt: Label eng am eigenen Feld, deutlicher Abstand
* zur naechsten Gruppe.
* ------------------------------------------------------------------------ */
label {
margin-bottom: 0.5em;
}
input[type="text"],
input[type="password"],
input[type="email"],
input[type="tel"],
input[type="search"],
input[type="url"],
input[type="number"],
input[type="date"],
input[type="datetime-local"],
input[type="file"],
select,
textarea {
margin-bottom: 1.5em;
}
/* Achtung: hier bewusst KEIN ".row > * > :last-child { margin-bottom: 0 }".
In einer Grid-Spalte ist das Eingabefeld das letzte Kind - genau dessen
unterer Abstand erzeugt die Trennung zur naechsten Formularzeile. */
/* In Tabellen und nebeneinanderliegenden Bedienelementen wuerde der
zusaetzliche Abstand die Zeilen auseinanderziehen. */
td label,
td input,
td select,
td textarea,
.form-inline label,
.form-inline input,
.form-inline select {
margin-bottom: 0;
}
/* ---------------------------------------------------------------------------
* Zahlen-, Datums- und Dateifelder
*
* main.css stylt diese Typen nicht, sie erschienen deshalb als kleine native
* Kaestchen zwischen den sonst einheitlich gestalteten Feldern. Hier wird das
* Aussehen der Textfelder uebernommen.
* ------------------------------------------------------------------------ */
input[type="number"],
input[type="date"],
input[type="datetime-local"] {
background: #ffffff;
border: solid 1px rgba(210, 215, 217, 0.75);
border-radius: 0.375em;
color: inherit;
display: block;
height: 2.75em;
outline: 0;
padding: 0 1em;
text-decoration: none;
width: 100%;
}
input[type="number"]:focus,
input[type="date"]:focus {
border-color: #38761d;
box-shadow: 0 0 0 1px #38761d;
}
/* In Tabellen (Striche/Einzahlungen erfassen) bleiben die Zahlenfelder
kompakt, sonst sprengen sie die Spaltenbreite. */
td input[type="number"] {
width: 100%;
min-width: 5em;
}
input[type="file"] {
display: block;
width: 100%;
}
/* ---------------------------------------------------------------------------
* Checkbox-Zeilen
*
* Checkbox und zugehoeriges Label stehen nebeneinander; ohne Ausgleich sitzt
* die Beschriftung nicht auf einer Linie mit den Feldern der Nachbarspalte.
* ------------------------------------------------------------------------ */
input[type="checkbox"] + label,
input[type="radio"] + label {
margin-bottom: 1.5em;
}
/* ---------------------------------------------------------------------------
* Hinweisboxen
*
* main.css definiert den Grundstil und die Varianten .info/.warning/.success,
* aber keine .error-Variante - obwohl sie in der App am haeufigsten verwendet
* wird. Ohne sie erscheinen Fehlermeldungen ungefaerbt wie neutrale Hinweise.
* Farbgebung analog zu den vorhandenen Varianten.
* ------------------------------------------------------------------------ */
.hint-box.error {
background: #fdecea;
border-left: 5px solid #d32f2f;
color: #7f1d1d;
}
/* Der Grundstil ist ein Flex-Container; Absaetze darin sollen unten keinen
zusaetzlichen Abstand erzeugen. */
.hint-box > :last-child {
margin-bottom: 0;
}
/* Mehrere Absaetze/Listen in einer Box sollen untereinander stehen statt
nebeneinander (Flex-Grundstil). */
.hint-box {
flex-direction: column;
}
+8 -8
View File
@@ -105,10 +105,10 @@ if ($bearbeitenId !== null) {
<?php echo app_csrf_field(); ?>
<input type="hidden" name="aktion" value="speichern">
<input type="hidden" name="id" value="<?php echo (int)$eintrag['id']; ?>">
<label>Frage:</label><br>
<input type="text" name="question" value="<?php echo saas_html($eintrag['question']); ?>" required style="width:100%"><br><br>
<label>Antwort:</label><br>
<textarea name="answer" required style="width:100%"><?php echo saas_html($eintrag['answer']); ?></textarea><br><br>
<label>Frage:</label>
<input type="text" name="question" value="<?php echo saas_html($eintrag['question']); ?>" required>
<label>Antwort:</label>
<textarea name="answer" required><?php echo saas_html($eintrag['answer']); ?></textarea>
<button type="submit">Speichern</button>
<a href="faq.php" class="button">Abbrechen</a>
</form>
@@ -139,10 +139,10 @@ if ($bearbeitenId !== null) {
<form method="post" action="faq.php">
<?php echo app_csrf_field(); ?>
<input type="hidden" name="aktion" value="anlegen">
<label>Frage:</label><br>
<input type="text" name="question" required style="width:100%"><br><br>
<label>Antwort:</label><br>
<textarea name="answer" required style="width:100%"></textarea><br><br>
<label>Frage:</label>
<input type="text" name="question" required>
<label>Antwort:</label>
<textarea name="answer" required></textarea>
<button type="submit">Frage hinzufügen</button>
</form>
<?php endif; ?>
+1
View File
@@ -23,6 +23,7 @@ $canUseVerwaltungGroup = $saasCanUseLedgerNav;
<span class="menu-group-label">Kaffeeliste</span>
<ul class="menu-group-items">
<li><a href="index.php">Meine Kaffeeliste</a></li>
<li><a href="anleitung.php">Anleitung</a></li>
<li><a href="faq.php">FAQ Kaffeeliste</a></li>
</ul>
</li>
+1
View File
@@ -10,6 +10,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="assets/css/main.css" />
<link rel="stylesheet" href="assets/css/app.css" />
</head>
<body class="is-preload">
<?php
+4 -4
View File
@@ -66,10 +66,10 @@ if($hasAccess){
<form method="post">
<input type="hidden" name="aktion" value="speichern">
<?php echo app_csrf_field(); ?>
<label>Nachricht:</label><br>
<textarea name="nachricht" required></textarea><br><br>
<label>Gültig bis:</label><br>
<input type="datetime-local" name="gueltig_bis" required><br><br>
<label>Nachricht:</label>
<textarea name="nachricht" required></textarea>
<label>Gültig bis:</label>
<input type="datetime-local" name="gueltig_bis" required>
<button type="submit">Speichern</button>
</form>
+1 -2
View File
@@ -98,9 +98,8 @@ $fehler = null;
<?php endif; ?>
<br>
<label for="neuerName">Neuer Anzeigename: (maximal 20 Zeichen)</label><br>
<label for="neuerName">Neuer Anzeigename: (maximal 20 Zeichen)</label>
<input type="text" name="neuerName" id="neuerName" maxlength="20" required>
<br><br>
<input type="hidden" name="aktion" value="aktualisieren">
<button type="submit">Anzeigename aktualisieren</button>
+14
View File
@@ -118,6 +118,20 @@ $checks = [
'auth' => true,
'contains' => ['FAQ - Kaffeeliste'],
],
[
// Der Smoke-Login ist Owner, deshalb muessen auch die
// Verwaltungsabschnitte erscheinen.
'label' => 'Anleitung',
'path' => 'anleitung.php',
'auth' => true,
'contains' => [
'Anleitung',
'Für alle Mitglieder',
'Für Kassenwart und Verwaltung',
'Für Administratoren',
'Rollen im Überblick',
],
],
[
'label' => 'Login',
'path' => 'login.php',
+2 -1
View File
@@ -141,8 +141,9 @@ $kostenproStrichAnzeige = number_format(($settings['mark_price_cents'] ?? 20) /
<?php echo app_csrf_field(); ?>
<?php
// Kein <br> noetig - das Feld ist ein Blockelement mit eigenem Abstand.
echo "<label for='kostenproStrich'>Kosten pro Strich:</label>
<input type='number' name='kostenproStrich' step='0.01' value='" . saas_html($kostenproStrichAnzeige) . "'><br>";
<input type='number' name='kostenproStrich' step='0.01' value='" . saas_html($kostenproStrichAnzeige) . "'>";
echo "<table>";
echo " <tr>