Zum Inhalt

Tutorial: Mein erstes 1:n-Formular (Bestellung mit Positionen)

Diese Anleitung führt Formularentwickler ohne Vorerfahrung Schritt für Schritt durch den Bau eines Formulars mit einer 1:n-Beziehung: ein Kopf-Datensatz (eine Bestellung) mit beliebig vielen Detail-Zeilen (Bestellpositionen). Am Ende hast du ein funktionierendes Bestellformular mit Artikel-Auswahl und automatisch berechneten Summen.

Die vollständige Referenz steht unter Detail-Tabellen. Hier geht es um die „goldenen Pfade" — die häufigsten 80 %, ohne Sonderfälle. Wer lieber nur klickt, beginnt mit der bebilderten Kurzanleitung.


Was ist eine 1:n-Beziehung — und warum brauche ich sie?

„1:n" heißt: ein Hauptdatensatz hat viele Detail-Zeilen:

Hauptdatensatz Detail-Zeilen
eine Bestellung beliebig viele Bestellpositionen
eine Reisekostenabrechnung beliebig viele Belege
ein Kunde beliebig viele Ansprechpartner
eine Rechnung beliebig viele Rechnungspositionen

Du weißt beim Anlegen noch nicht, wie viele Zeilen es werden. In KCForms baust du das mit einem Feld vom Typ array und der Erweiterung kc:childTable (Detail-Tabelle). Beim Speichern legt KCForms dafür automatisch eine eigene Datenbank-Tabelle an und kümmert sich um Lesen und Schreiben.


Vorbereitung

Hilfreich, aber nicht zwingend (jeweils 5–10 Minuten):

  1. Formular-Grundlagen — Felder, Typen, Layout.
  2. Datenmodell — wie Felder zu Datenbank-Spalten werden.
  3. Picker — für Schritt 4.

Du brauchst Zugriff auf den Formular-Editor (Zahnrad oben rechts → Formular-Editor). Fehlt der Eintrag, hat dein Konto keine Rechte für den Formular-Editor — frag deinen Admin.


Das Ziel

flowchart TB
    K["<b>Kopf</b> — 1 Datensatz<br/>Name, Firma, Adresse, PLZ, Stadt, Telefon, E-Mail"]
    P["<b>Bestellpositionen</b> — n Zeilen<br/>Bestellnr (Picker) · Beschreibung · Menge · Einzelpreis · Gesamtpreis (berechnet)"]
    S["<b>Summen</b><br/>Zwischensumme (berechnet) · Versandart · Versandkosten · MwSt · Gesamt"]
    K --> P --> S

Das fertige Bestellformular im Ausfüllmodus Das mitgelieferte Beispiel „08 Bestellung" im Ausfüllmodus: Kopf-Felder oben, darunter die Positionen, am Ende die Summen.


Schritt 1 — Neues Formular anlegen

  1. Formular-Editor öffnen (Zahnrad oben rechts → Formular-Editor).
  2. In der linken Spalte Formulare auf + Neu klicken.
  3. ID (URL-tauglich): meine-bestellung — klein, ohne Umlaute; daraus wird der Tabellenname form_meine_bestellung. (Die ID bestellung ist bereits vergeben: So heißt das mitgelieferte, fertige Beispiel „08 Bestellung".)
  4. Titel: „Bestellung".
  5. Katalog (optional): z. B. „Tutorial".
  6. Erstellen.

Der Formular-Editor öffnet das neue, leere Formular.

Neues Formular anlegen: ID, Titel und Katalog

Die ID bleibt

Die Formular-ID lässt sich nach dem Anlegen nicht mehr ändern — sie steckt im Tabellennamen. Lieber einmal kurz überlegen.


Schritt 2 — Die Kopf-Felder anlegen

Die einfachen Felder legst du komplett über die Oberfläche an. Zwei Wege:

  • Baukasten: Reiter Baukasten an der rechten Leiste öffnen und aus Eingabefelder z. B. „Textfeld" oder „E-Mailadressfeld" in die Spalte Felder ziehen. Noch schneller: die Feldgruppe Anschrift aus Feldgruppen legt mehrere Adressfelder auf einmal an.
  • + Neu in der Spalte Felder: Feldname, Typ, Spaltenbreite → Erstellen.

Anschließend per Doppelklick die Eigenschaften öffnen und Titel sowie Feld-ID einstellen:

Feld-ID Typ Titel Pflichtfeld?
name Textfeld Name ja
firma Textfeld Firma nein
adresse1 Textfeld Adresse 1 nein
adresse2 Textfeld Adresse 2 nein
stadt Textfeld Stadt nein
plz Textfeld (Maximallänge 10) PLZ nein
land Textfeld Land nein
telefon Textfeld Telefon nein
email E-Mailadressfeld E-Mail nein

In den Formular-Eigenschaften (Knopf Formulareigenschaften bearbeiten über der Vorschau) stellst du das Formularlayout auf Zwei Spalten — dann stehen die Felder paarweise nebeneinander.

Formular-Editor mit Kopf-Feldern und Baukasten

Eigenschaften-Dialog eines Feldes Doppelklick auf ein Feld öffnet die Eigenschaften mit Reitern: Titel, Feld-ID, Pflichtfeld, Länge usw.

Zwischendurch Speichern (oben rechts) nicht vergessen.


Schritt 3 — Die Detail-Tabelle anlegen (das Herzstück)

  1. Im Baukasten unter Eingabefelder einen der Einträge „Detail-Tabelle (1:n) · …" in die Felderliste ziehen, unterhalb der Kopf-Felder — oder über + Neu in der Spalte Felder den Typ Detail-Tabelle (1:n) wählen.
  2. Feld-ID positionen, Titel „Bestellpositionen", Spaltenbreite volle Breite.

Die Spalten festlegen

Wähle positionen aus. In den Eigenschaften gibt es für Detail-Tabellen den Bereich Felder mit der Liste Detailzeilen-Spalten. Über + Spalte legst du die Spalten einer Positionszeile an. Der Dialog fragt zuerst nach dem Titel (der Spaltenüberschrift) und schlägt die Spalten-ID daraus vor — überschreib sie mit dem kurzen Namen aus der Tabelle:

Die Spalten der Detail-Tabelle

Spalten-ID Typ Titel Optionen
bestellnr string Bestellnr Maximallänge 20
beschreibung string Beschreibung Maximallänge 200
menge number Menge 0 Nachkommastellen
preis number Einzelpreis 2 Nachkommastellen, rechtsbündig
summe number Gesamtpreis 2 Nachkommastellen, rechtsbündig, berechnet (Schritt 5)

Leere Zeilen vorgeben

In den Eigenschaften steht unter den Spalten initialRows (Vorbelegung leerer Zeilen). Mit 4 zeigt ein neuer Datensatz sofort vier leere Zeilen.

Leere Zeilen werden mitgespeichert

Eine Detail-Tabelle speichert jede Zeile, die beim Speichern existiert — auch eine komplett leere. Nicht benötigte Zeilen entfernt der Anwender mit ×; wähle initialRows deshalb eher knapp.

Speichern. KCForms legt im Hintergrund die Tabelle form_meine_bestellung_positionen an (Fremdschlüssel zur Bestellung plus je eine Spalte pro Positionsfeld). Der Reiter Modell an der rechten Leiste zeigt sie.


Schritt 4 — Die Bestellnummer mit einem Picker verbinden

Optional, aber beliebt: Der Anwender soll Artikelnummern nicht auswendig tippen, sondern aus einer Liste wählen. Dafür bekommt die Spalte bestellnr einen Picker. Doppelklick auf die Spalte in der Vorschau → Reiter Auswahl → Such-Dialog:

Picker-Einstellung auf der Spalte bestellnr

Im Schema sieht das so aus:

"kc:picker": {
  "title":  "Büromaterial auswählen",
  "table":  "bueromaterial",
  "search": ["artikelnr", "bezeichnung", "kategorie"],
  "columns": [
    { "column": "artikelnr",   "title": "Artikelnr" },
    { "column": "bezeichnung", "title": "Bezeichnung" },
    { "column": "kategorie",   "title": "Kategorie" },
    { "column": "preis",       "title": "Preis" }
  ],
  "orderBy":  "artikelnr ASC",
  "pageSize": 12,
  "set": {
    "bestellnr":    "artikelnr",
    "beschreibung": "bezeichnung",
    "preis":        "preis"
  }
}

Das Wichtigste ist set: Wählt der Anwender einen Artikel, landen die Werte in den Spalten derselben Zeile — artikelnr → bestellnr, bezeichnung → beschreibung, preis → preis. Die Menge gibt der Anwender selbst ein.

Zur Laufzeit öffnet das …-Symbol neben der Bestellnummer den Dialog:

Der Büromaterial-Picker zur Laufzeit

Mehr dazu unter Picker.

Die Tabelle muss existieren

kc:picker.table verweist auf eine Tabelle in der Datenbank. In der KCForms-Demo gibt es bueromaterial bereits; in einer eigenen Installation nimmst du eine vorhandene Tabelle — oder eine externe Datenquelle.


Schritt 5 — Gesamtpreis pro Zeile berechnen

summe soll automatisch menge × preis sein — dafür brauchst du kein Skript. Doppelklick auf die Spalte Gesamtpreis → Reiter Berechnung → Art Rechnung aus Feldern, Rechenart Produkt, Operanden menge und preis.

Im Schema:

"summe": {
  "type": "number", "title": "Gesamtpreis",
  "kc:decimals": 2, "kc:align": "right",
  "kc:calc": { "type": "arithmetic", "op": "multiply", "operands": ["menge", "preis"] }
}

Das Feld wird dadurch automatisch schreibgeschützt und rechnet live mit.


Schritt 6 — Zwischensumme über alle Zeilen

Lege ein Zahlenfeld zwischensumme (Titel „Zwischensumme", 2 Nachkommastellen) unter der Tabelle an. Reiter Berechnung → Art Summe einer Tabellenspalte, Tabelle positionen, Spalte summe:

"zwischensumme": {
  "type": "number", "title": "Zwischensumme",
  "kc:decimals": 2, "kc:format": "currency-eur",
  "kc:calc": { "type": "sum", "table": "positionen", "column": "summe" }
}

Details und Leerwert-Regeln: Berechnungsfelder.


Schritt 7 — Versand, MwSt und Gesamt

Berechnungsfelder kennen nur Felder als Operanden, keine festen Werte. Versandkosten nach Tarif und 19 % MwSt rechnest du deshalb in einem kleinen Skript:

  • versandart: Textfeld als Radio-Gruppe (kc:widget: "radio") mit fester Werteliste (kc:lookupOptions).
  • versandkosten, mwst, gesamt: Zahlenfelder, schreibgeschützt, 2 Nachkommastellen.

Im Formular-Editor: Reiter Skripte über der Vorschau → Hook onFieldChange:

Der Skripte-Reiter mit dem onFieldChange-Hook des mitgelieferten Beispiels — ausführlicher als die Fassung unten

// onFieldChange — läuft, sobald der Anwender ein Feld verlässt.
var VERSAND = {
  'post-inland-46':          5.95,
  'post-inland-uebernacht': 24.95,
  'post-inland-express-3':   7.95,
  'post-inland-express-2':  12.95,
  'international':          22.95,
  'international-dhl':      39.95
};
// String(...) macht aus dem Java-Wert sicher einen JS-String für den Nachschlag.
var art = String(ctx.form.versandart || '');
var vk  = VERSAND[art] || 0;
var zws = kc.num(ctx.form.zwischensumme);

ctx.form.versandkosten = vk;
ctx.form.mwst          = (zws + vk) * 0.19;
ctx.form.gesamt        = zws + vk + ctx.form.mwst;

kc.num(...) macht aus einem Feldwert sicher eine Zahl (leer → 0). Mehr zu Skripten: Scripting-Überblick.

Berechnet wird im Browser

kc:calc rechnet im Web-Formular und speichert das Ergebnis mit. Legt ein Skript oder ein REST-Service einen Datensatz ohne das Formular an, rechnet dort niemand nach — siehe Berechnungsfelder.


Schritt 8 — Testen

  1. Oben rechts Im Formular öffnen.
  2. Eine Position mit Menge 3 und Einzelpreis 5,00 eintragen.
  3. Feld verlassen → Gesamtpreis 15,00, Zwischensumme ebenfalls.
  4. Versandart wählen → Versandkosten, MwSt und Gesamt aktualisieren sich.
  5. Speichern (Disketten-Symbol oder Strg+S).
  6. Mit + in der Werkzeugleiste einen zweiten Datensatz anlegen — beide werden getrennt gespeichert.

Logs des Backends und der Skripte findest du unter System-Administration → Betrieb & Überwachung → System-Logs (kcforms-backend.log, script.log).


Komplettes Beispiel

Das vollständige Schema — z. B. über Formular-Eigenschaften → JSON-Editor einfügen, um Schritt für Schritt zu vergleichen:

{
  "type": "object",
  "title": "Bestellung",
  "kc:layout": "horizontal-2",
  "properties": {
    "name":     { "type": "string", "title": "Name", "kc:required": true },
    "firma":    { "type": "string", "title": "Firma" },
    "adresse1": { "type": "string", "title": "Adresse 1" },
    "adresse2": { "type": "string", "title": "Adresse 2" },
    "stadt":    { "type": "string", "title": "Stadt" },
    "plz":      { "type": "string", "title": "PLZ", "kc:maxLength": 10 },
    "land":     { "type": "string", "title": "Land" },
    "telefon":  { "type": "string", "title": "Telefon" },
    "email":    { "type": "string", "title": "E-Mail", "format": "email" },

    "positionen": {
      "type": "array",
      "title": "Bestellpositionen",
      "kc:colspan": "full",
      "kc:childTable": {
        "initialRows": 4,
        "properties": {
          "bestellnr": {
            "type": "string", "title": "Bestellnr", "kc:maxLength": 20,
            "kc:picker": {
              "title":  "Büromaterial auswählen",
              "table":  "bueromaterial",
              "search": ["artikelnr", "bezeichnung", "kategorie"],
              "columns": [
                { "column": "artikelnr",   "title": "Artikelnr" },
                { "column": "bezeichnung", "title": "Bezeichnung" },
                { "column": "kategorie",   "title": "Kategorie" },
                { "column": "preis",       "title": "Preis" }
              ],
              "orderBy":  "artikelnr ASC",
              "pageSize": 12,
              "set": { "bestellnr": "artikelnr", "beschreibung": "bezeichnung", "preis": "preis" }
            }
          },
          "beschreibung": { "type": "string", "title": "Beschreibung", "kc:maxLength": 200 },
          "menge":        { "type": "number", "title": "Menge",       "kc:decimals": 0 },
          "preis":        { "type": "number", "title": "Einzelpreis", "kc:decimals": 2, "kc:align": "right" },
          "summe":        { "type": "number", "title": "Gesamtpreis", "kc:decimals": 2, "kc:align": "right",
                            "kc:calc": { "type": "arithmetic", "op": "multiply", "operands": ["menge", "preis"] } }
        }
      }
    },

    "zwischensumme": { "type": "number", "title": "Zwischensumme",
                       "kc:decimals": 2, "kc:format": "currency-eur",
                       "kc:calc": { "type": "sum", "table": "positionen", "column": "summe" } },
    "versandart":    { "type": "string", "title": "Versandart", "kc:colspan": "full",
                       "kc:widget": "radio", "kc:lookupDisplay": "value",
                       "kc:lookupOptions": [
                         { "key": "post-inland-46",         "value": "Post Inland (4-6 Tage) — 5,95 €" },
                         { "key": "post-inland-uebernacht", "value": "Post Inland Übernacht — 24,95 €" },
                         { "key": "post-inland-express-3",  "value": "Post Inland Express Spar (3 Tage) — 7,95 €" },
                         { "key": "post-inland-express-2",  "value": "Post Inland Express (2 Tage) — 12,95 €" },
                         { "key": "international",          "value": "International — 22,95 €" },
                         { "key": "international-dhl",      "value": "International DHL Express — 39,95 €" }
                       ] },
    "versandkosten": { "type": "number", "title": "Versandkosten",
                       "kc:readonly": true, "kc:decimals": 2, "kc:format": "currency-eur" },
    "mwst":          { "type": "number", "title": "MwSt (19 %)",
                       "kc:readonly": true, "kc:decimals": 2, "kc:format": "currency-eur" },
    "gesamt":        { "type": "number", "title": "Gesamt",
                       "kc:readonly": true, "kc:decimals": 2, "kc:format": "currency-eur" },
    "bemerkungen":   { "type": "string", "title": "Bemerkungen",
                       "kc:widget": "textarea", "kc:colspan": "full" }
  }
}

Speichern und Drucken braucht das Schema nicht zu erwähnen — beide Knöpfe bekommt jedes Formular automatisch.


Checkliste — Fehlersuche

Symptom Wahrscheinliche Ursache
„Bestellpositionen" bleibt leer type ist nicht array oder kc:childTable fehlt
Tabelle nur eine Spalte breit kc:colspan fehlt oder ist nicht "full"
Gesamtpreis rechnet nicht kc:calc an summe fehlt, oder die Operanden-IDs stimmen nicht mit den Spalten-IDs überein
Zwischensumme bleibt leer kc:calc.table/column zeigen auf eine falsche Tabelle oder Spalte
Versandkosten bleiben 0 Schlüssel in VERSAND stimmen nicht mit den key-Werten der Werteliste überein
Picker meldet einen Tabellenfehler kc:picker.table verweist auf eine nicht vorhandene Tabelle
Fehler „column does not exist" beim Speichern Spalte umbenannt, alte DB-Spalte existiert noch → siehe Detail-Tabellen → Stolperfallen
Leere Zeilen im Ausdruck oder in der Liste Leere Zeilen werden mitgespeichert — mit × entfernen oder initialRows senken

Wie geht es weiter?

  • Picker — Such-Spalten, Abhängigkeiten, Skript-Picker.
  • Berechnungsfelder — alle Rechenarten und Verkettungen.
  • Validierung — Pflichtfelder, Muster, ctx.addError().
  • Druck — Detail-Tabellen erscheinen automatisch im PDF und laufen bei Bedarf über mehrere Seiten.
  • Detail-Tabellen (Referenz) — alle Einstellungen im Detail, inklusive Block-Ansicht und Verschachtelung.