diff --git a/Documentation-develop/CHAT.md b/Documentation-develop/CHAT.md new file mode 100644 index 0000000000000000000000000000000000000000..5be916eced407e4466b7a3a346588aef607e32ca --- /dev/null +++ b/Documentation-develop/CHAT.md @@ -0,0 +1,525 @@ +# QFQ Chat + +## Motivation + +* QFQ Chat soll kein RocketChat Ersatz sein (das sollten wir im Hinterkopf behalten). +* Hoffnung: mit ein paar gut ueberlegten Kernfeatures könnte 80/20 moeglich sein (80% aller Features, mit 20% Aufwand) +* Bei diversen QFQ Tools waere ein Chat hilfreich - typischerweise sind beide Chat Akteure bekannt, wobei eine Seite ( + z.B. Dekanat, IT Support) eine Gruppe von Personen sein koennte. +* Wenn der Chat Content in einer QFQ Tabelle ist bieten sich viele Moeglichkeiten, die bei einer geschlossenen Chat + Software Loesung nicht so einfach zu erreichen sind: + + * Automatisch erzeugte Chat-Rooms + * Anfragen koennen durch Personen Gruppen beantwortet werden + * Dashboard + * Reminder + * Chatverlauf in einem Public Chat kann auf Private gesestzt werden - spezifische Messages koennen als Public + markiert werden (z.B. die initiale Anfrage, die finale Antwort), ...) + +## Features + +### V1.0 + +* Chat-Rooms +* Anonymous: User kann eingeloggt sein oder nicht. + * Hintergrund 'Anonym': wenig Mehraufwand, es kann direkt auf einem Tool ein Chatfenster angezeigt werden um Kontakt + aufzunehmen. + * Ziel: Support Channel (I-MATH) in RC aufloesen und ein Chat Fenster auf MY anbieten. Gleiches fuer diverse andere + QFQ Tools. +* Anzeige Nachrichten: + * By default nur die 10 neuesten Nachrichten. + * Suche in Nachrichten moeglich. + +### V1.1 + +* Thread Support. +* Anzeige Nachrichten: Lazy Loading. + +### V1.2 + +* Tag _'Done'_ pro Chat-Room / Thread. +* Dashboard Ideen (ist vermutlich keine PHP Implementierung sondern nur Best Practice SQL Queries): + * Anzahl offener Anfragen. + * Anzahl offener Anfragen aelter als 8 Arbeitsstunden (critical). + * Anzahl neu eingetroffener Anfragen seit dem die eigene Seite (typischerweise stellt die eine Seite Fragen und die + andere Seite antwortet) das letzte mal im Chat geschrieben hat. + +### V1.3 + +* Chat: Public/Private +* *Edit Message* : Kann der Creator einer Message / Admin diese nachtraeglich veraendern? +* *Delete Message* : Kann der Creator einer Message / Admin diese nachtraeglich veraendern? +* *Make Semi-Private* : Nur interessant bei 'Public' Chats. By default ist ein Chat Verlauf in einem Public Chat fuer + alle + lesbar. Mit einem Click koennen alle Nachrichten eines Thread auf 'private' gestellt werden. Damit kann nur noch + der Creator und die 'andere' Seite die Chat's lesen. Das wird benoetigt fuer Anfragen via IT Support Chat, wenn + z.B. ein Prof eine allgmeine Frage hat und ploetzlich einen Studentennamen nennt. + +### V1.4 + +* Link zu einem Video Call anbieten (Jitsi, Teams, Zoom). + +### V1.5 + +* Status 'Read' +* Reminder + +### V1.6 + +* Screenshots / Attachments +* Emoticons + +### V1.7 + +* Split Thread: Teilnehmer (Admin oder Creator) kann einen bestehenden Thread teilen. Bsp: Die Diskussion beginnt ein + weiteres + Topic (oder User stellt mehrere Fragen) die einzeln behandelt werden sollen. +* Option: optionTagDoneResetOnNewMessage +* Tags pro Messages vergeben. + +## Konzept + +### Table 'Chat' + +| Column | Type | Comment | +|-------------|-----------------------------------------------------|---------------------------------------------------------------------------------------------------------------| +| id | \<int> | - | +| cIdTopic | \<int> | - | +| xId | \<int> | Antrag.id, Person.id, ... | +| cIdThread | \<int> | =0 bei Thread Start. Pointer auf die erste Messages eines Thread | +| message | \<text> | Chat Nachricht | +| type | enum('message', 'topic', 'tag', 'read', 'reminder') | type='Message,tag,reminder' wird von Chat Teilnehmern angelegt. type='Topic' wird i.d.R. von QFQ Dev angelegt | +| reference | <varchar> | Aktuell nur bei type='Topic'. Wird i.d.R. von QFQ Dev gesetzt | +| cIdLastRead | \<int> | Bei type=read: Pointer auf die zuletzt gelesene Message des pIdCreator. | +| username | \<varchar> | Angezeigter Name im Chat Fenster | +| pIdCreator | \<int> | Eindeutige Zuordnung zu einer Person | +| emoticon | \<varchar> | 'thumbsup:enured,crose;smiley:crose,jhaller,bbaer | + +* Records mit einer 'reference' werden gesynct. + + * Im aktuellen Konzept haben nur Chat.type='Topic' Records eine Reference. + * Damit werden Chat-Rooms implizit Applikationslogik, denn diese werden bei einem Sync angelegt (falls die noch + nicht existieren). + * Werden neue Chat-Rooms auf 'prod' angelegt, bleiben sie bei einem Sync unberuehrt (ok). D.h. es + Applikations-Chat-Rooms und Custom-Chat-Rooms. + +### FormElement.parameter + +<pre> +# Thread Support +thread=0|1 + +# Chat-Room: cIcTopic + xId bilden jeweils einen Chat-Room. +cIdTopic= +xId= + +# Person die die Message erstellt hat. +pIdCreator={{pIdFeUser}} + +# Mapping das FE.Chat auch mit anderen Tabellen, Spaltennamen arbeiten kann. +columnMap=[apId:xId,pId:pIdCreator,grIdStatus:xGrIdStatus,...] +tableName=history[, note] + +# Displayname des aktuellen Users. Bei Teams koennte das z.B. die reale Person sein, pIdCreator zeigt auf das Team. +username = {{SELECT p.firstname, ' ', p.lastname FROM Person WHERE p.account='{{feUser:UT}}' }} + +# TBD: wo genau brauchen wir den noch? +xGrIdStatus (optional) + +# Optional kann ein Teilnehmer pro Chat / Thread Tag(s) setzen. +# `off` - Es kann kein Tag gesetzt werden. +# `all` - Jeder Teilnehmer kann Tag(s) setzen/loeschen. Im Chat wird angezeigt welche Personen welches Tag gesetzt haben. +# 'my' - Jeder Teilnehmer kann Tag(s) setzen/loeschen. Im Chat wird nur die eigenen Tags angezeigt. +optionTag=*off|all|my +optionTagDone=*off|all|my + +# 0: Tag Done wird vollstaendig manuell kontrolliert. +# 1: Ist der Tag _'Done'_ gesetzt und wird eine neue Message gesendet, wird der Tag _'Done'_ automatisch geloescht. +optionTagDoneResetOnNewMessage=0|1 + +# Pro Teilnehmer kann auf einem Chat / Thread ein Reminder gesetzt werden. +optionReminder=0|1 +</pre> + +Beispiel `FE.type=chat`: + +<pre> +fillStoreVar={{!SELECT CONCAT(p.firstname, ' ', p.lastname) AS username, p.id AS pId FROM Person WHERE p.account='{{feUser:UT}}' }} +cIdTopic={{SELECT c.id FROM Chat AS c WHERE c.reference='RueckfrageAntragsteller'}} +username={{username:V}} +pIdCreator={{pId:V}} +xId={{id:R}} +thread=0|1 +optionTagDone=all +</pre> + +### Chat-Room + +Es gibt zwei Sorten von Chat-Rooms: + +* General Topic : z.B. Support Anfrage. +* Topic per Request : Pro Topic (Rueckfrage an Antragsteller, Rueckfrage bei Stv.Dean, ...) und pro User (oder pro + Antrag). + +Bei einen `General Topic` kann `per Request` einfach leer gelassen werden (xId=0). + +Die Chat-Rooms (name, id) werden in der Tabelle `Chat` gespeichert mit `Chat.type='topic'`. + +* Tabelle Chat: + * type='topic' + * cIdTopic, xId: keine Bedeutung + * reference: <topic name> + * message=<...> - Optional, beschreibt den Chat-Room. + +### Thread + +Motivation: In einem Chat-Room, in dem viele Personen Anfragen stellen koennen, geht schnell die Übersicht verloren wer +mit wem was diskutiert. _Threads_ innerhalb eines Chats helfen die Uebersicht zu behalten. + +* FE.parameter: + * threads=0|1 - Threads sind erlaubt ja/nein. + +Umsetzung: + +* Column: Chat.cIdThread +* Bei dem ersten Message-Record eines Threads ist `Chat.cIdThreadId=0` - das ist implizit das Flag das bei diesem + Record ein Thread startet. +* Alle weiteren Message-Records des Threads zeigen (id) mit `Chat.cIdThreadId=...` auf den ersten Thread Message-Record. + +UI: + +* By default ist der neueste Thread ausgeklappt und _aktiv_, alle anderen Threads sind zusammen geklappt (vergleichbar + RC). +* Durch anklicken eines Threads wird dieser aktiv und auf-/zugeklappt. Die anderen Threads sind inaktiv/gemuted. +* Die Message Eingabe Textbox (sticky, unten) sendet immer in den zuletzt aktivierten Thread (in RC ist das anders + geloest). +* New Message: + * zu einem _bestehenden_ Thread: die threadId in der SIP. + * zu einem _neuem_ Thread: keine threadId in der SIP (also 0). +* Beispiel ('+' bedeutet eingeklappt): +<pre> ++Dekanat: cxxxxdgsdg ++Dekanat: cxxxxdgsdg + +skdafhsdkafhsdakf: Antragsteller ++Dekanat: cxxxxdgsdg +Dekanat: cxxxxdgsdg + skdafhsdkafhsdakf: Antragsteller + skdafhsdkafhsdakf: Antragsteller +Dekanat: cxxxxdgsdg + skdafhsdkafhsdakf: Antragsteller ++Dekanat: cxxxxdgsdg + +------------------------------------------ +| |send| +------------------------------------------ +</pre> + +### Tag + +* Motivation: + * User koennen so die Chats/Threads mit eigenen Tags markieren. + * Optional werden Tags vorgegeben die z.B. auch auf Antraegen liegen koennen. + * Auf einem Dashboard anzeigen, in welchen Chat-Room welche Tags zugeordnet sind. +* Optional koennen Tags gesetzt werden pro Chat Teilnehmer und Chat/Thread (nicht pro Message). +* Darstellung: + * FE.thread=0: Oben rechts '<tag>: John, Jane' + * FE.thread=1: Im Thread Rahmen oben rechts '<tag>: John, Jane' +* Soll ein Tag wieder geloescht werden, wird der Record geloescht. + * Hintergrund: nur wenige User werden den Tag-Mechanismus nutzen, damit werden dann nur die wirklich noetigen + Records angelegt. + * Mit dem loeschen muss nicht zusaetzlich ein value on/off gemanaged werden. +* Pro Chat/Thread koennen beliebig viele `Chat.type=tag` Records angelegt werden. D.h. jeder User kann fuer sich + Tags vergeben. +* Change: User klickt auf das Tag Icon. + * Es oeffnet sich ein Typeahead Input Feld in dem die bestehende Tags ausgewaehlt oder neue erfasst werden. + * API Aufruf save.php + * Das Tag wird gesetzt oder geloescht, je nachdem was vorher war. + * Es wird ein 'Refresh' an den Websocket gesendet, um alle registrierten Clients zu informieren die Messages, inkl. + Tags, neu zu laden. + +* Beschreibung 'Update Tag' + + * FE.type=Chat wird geladen + * Value: 0|1 + * Data: Tag Name + * Check ob es bereits einen Tag Record von dem User in dem Chat / Thread mit dem Tag gibt. + * Nein & value==1: es wird ein Record angelegt. + * Ja & value==0: der Record wird geloescht. + +* Parameter + * optionTag=*off|all|my + +* Tabelle Chat: + * type='tag' + * cIdTopic, xId: pro Chat-Room + * cIdThread: pro Thread moeglich + * pIdCreator: Person die den Tag gesetzt hat. + * message=<Tag Name> + +### Tag _'Done'_ + +* Motivation: + * Die Übersicht behalten in Chat-Rooms mit vielen Anfragen welche Anfragen noch offen sind. + * Auf einem Dashboard anzeigen, in welchen Chat-Rooms offene Anfragen sind (typischerweise gab es bei einem Antrag + eine Rückfrage). + +* Ientisch mit 'Tag' - hat aber eine besondere Bedeutung. +* Der Tag Name ist '\__done' (Tags deren Name mit '__' beginnt sind System Tags) +* Im Chat gibt es ein eigenes Icon um 'done' zu setzen/loeschen. Es muss kein 'Tag Name' angegeben werden. +* Darstellung: + * FE.thread=0: Oben rechts 'done: John, Jane' + * FE.thread=1: Im Thread Rahmen oben rechts 'done: John, Jane' +* Ein `Chat.type='tag', Chat.message='__done'` Record symbolisiert _'Done'_ fuer den Chat/Thread und den User + `Chat.pIdCreator`. +* Parameter: + * optionTagDone=*off|all|my + * optionTagDoneResetOnNewMessage + +### Status 'read' + +* Pro User und Chat-Room gibt es einen Pointer der auf die letzte gelesene Nachricht zeigt: `Chat.cIdLastRead` +* Alle Chat.id die groesser sind als dieser Wert, werden als 'ungelesen' dargestellt: Strich 'unread' hinter der letzten + gelesenen Nachricht. +* Update Chat.cIdLastRead: + * Sobald man selbst eine Nachrict sendet. + * Wenn man den Chat einmal aufgerufen hat und die Message angezeigt (geladen) wurde: beim naechsten oeffnen des + Chats ist die Nachricht 'gelesen'. +* Speicherort: aktuelles Konzept ist das der Chat Record mit Chat.mode='read' pro User und Chat-Room angelegt wird + (sobald der User das erste mal den Chat aufmacht). +* Tabelle Chat: + * type='read' + * cIdTopic, xId: pro Chat-Room + * cIdLastRead: Pointer auf die zuletzt gelesene Message des pIdCreator. + * pIdCreator: Chat Teilnehmer + +### Reminder + +* Synonym: Reminder=Wiedervorlage=Deadline +* Motivation: + * Pro 'Chat / Thread / User|Team' waere es gut wenn Reminder gesetzt werden koennte. Ob das noch feiner sein soll (pro + Message) ist nicht klar. +* Da Reminder ein allgemeines Thema sind, sollte das ggfs. eine Tabelle sein die nicht nur auf den Chat hindeutet (Wahl + des Namens). +* Es waere auch gut wenn das fuer Gruppen moeglich waere. Bsp: Im IT Support ist ein User Request, ein Mitarbeiter + stellt eine Rueckfrage an den User (mit Deadline=24h) und ist am naechsten Tag aber nicht da, der User antwortet + nicht bis zur Deadline und der IT Support vom Folgetag sollte aktiv werden (=Reminder poppt beim Support auf). +* Die Reminder: + * Werden auf dem Dashboard angezeigt. + * Koennen eine Email ausloesen. + * Koennen gemuted werden (.z.B. 24h) + +* Tabelle Chat: + * type='reminder' + * cIdTopic, xId: pro Chat-Room + * cIdThread: pro Thread moeglich + * reminder: timestamp wann der Reminder aufpoppen soll + * message: Eigene Custom Nachricht die man angeben kann wenn man den Remider setzt. + * pIdCreator: Person die den Reminder angelegt hat. + +### Attachments + +TBD + +### Emoticon + +Pro Message gibt es einen String `emoticon`. Das ist leicht zu implementieren und erzeugt keinen zusaetzlichen SQL +Aufwand. + +* Tabelle Chat: + * type='message' + * emoticon = 'thumbsup:john,jane;smiley:jimmy' - Es wird der 'username' abgelegt. + * Alle weiteren Spalten wie bei einer Message. + +### Team + +* Gerade bei Support Chats kommt es haeufig vor, dass Anfragen von einem Team von Personen bearbeitet werden. +* Um die Übersicht zu behalten welche Anfragen offen/in progress/done sind, koennte z.B. als pIdCreator nicht eine + reale Person eingetragen werden, sondern eine Fake Person die ein Team representiert. +* Mit dieser Variante koennte mit wenig Aufwand eine 'Team'-Applikationslogik implementiert werden. +* Die eine Seite des Chats (Antragsteller) kann eine andere Konfiguration haben als die andere Seite (Dekanat). + Welche Person eingetragen wird kontrolliert das FE.Chat Element. Damit ist es einfach moeglich auf der einen Seite + ein _Team_ als Creator einzutragen. + + * Hinweis: Damit der Autor einer Chat Nachricht immer auf der rechten Seite im Chat erscheint sollte fuer die + fuer die Seitenerkennung nicht `pIdCreator` sondern `pIdCreator+username` verwendet werden. + +Frage: brauchen wir wirklich diese Art von Teams? + +* Koennte das gleiche nicht implizit erreicht werden? Ein 'Team' kennt alle 'seine' + Chat-Rooms, das reicht aus um die offenen Anfrage fuer die man zustaendig ist anzuzeigen. +* Auch die Existenz eines Tags _'Done'_ muesste ausreichen um zu erkennen das eine Anfrage _'Done'_ ist, egal wer als + 'Creator' eingetragen ist. +* Die Diskussion ist aber eigentlich egal, denn wenn FE.pidCreator={{pIdFeUser:Y}} ist, werden keine Teams verwendet. + +## API + +### Load Messages + +TBD: was liefert der AJAX Call load.php zurueck wenn die 10 neuesten Messages, inkl. Done Status, geliefert werden + +* API: load.php +* FormElement.id (zeigt direkt auf das FE.type=Chat Element) +* Evtl. keine formId + +### Save Message + +* API: save.php +* FormElement.id (zeigt direkt auf das FE.type=Chat Element) +* Mode: 'Save Chat Message' +* Wird eine Message gespeichert und existiert zu dem Chat/Thread ein Tag _'Done'_ und ist + optionTagDoneResetOnNewMessage=1: + dann wird das Tag Done automatisch geloescht. + +### Toggle Done Status + +* API: save.php +* FormElement.id (zeigt direkt auf das FE.type=Chat Element) +* Mode: 'Set Status Done' + +## Umsetzung + +QFQ interne Query fuer alle Messages (FE.thread=0): +<pre> +# +# FE.thread=0 +# + +# Alle DONEs (falls mehrere ein Done gesetzt haben) +SELECT c.pIdCreator, c.username FROM Chat AS c WHERE c.cIdTopic=$FE.cIdTopic AND c.xId=$FE.xId AND cDone.type=_'Done'_ ORDER BY c.id + +# Alle Messages +SELECT c.* + FROM Chat AS c + WHERE c.cIdTopic=$FE.cIdTopic AND c.xId=$FE.xId ORDER BY c.id +</pre> + + +QFQ interne Query fuer alle Messages (FE.thread=1): +<pre> +# +# FE.thread=1 +# + +# Alle Messages, inkl. done +SELECT c.*, GROUP_CONCAT(cDone.username) AS 'doneUsername' + FROM Chat AS c + LEFT JOIN Chat AS cDone + ON cDone.cIdTopic=$FE.cIdTopic AND cDone.xId=$FE.xId + AND (cDone.cIdThread=c.id OR $FE.thread=0 ) + AND cDone.type=_'Done'_ + AND cDone.pIdCreator=c.pIdCreator + AND $FE.optionTagDone='all' OR ($FE.optionTagDone='my' AND cDone.pIdCreator=$FE.pIdCreator) + WHERE c.cIdTopic=$FE.cIdTopic AND c.xId=$FE.xId ORDER BY c.id + GROUP BY c.id +</pre> + + +Problem: das Tag _'Done'_ soll gesetzt werden koennen fuer ein Team von Personen (=angenommen Dekant BEF hat mehrere +Personen). In so einem Fall wird nicht die p.id einer realen Person im FE definiert, sondern die p.id einer anzulegen +Pseudo Person (=Dekanat BEF). + +### threadId + +Innerhalb eines Chat-Rooms (Beispiel: Antrag 123 & 'Frage von Antragsteller an Dekanat') soll es moeglich sein mehrere +Threads zu fuehren. + +* Die `threadId` gruppiert Records eines Threads. +* Bsp: Antrag 123 hat a) eine Rueckfrage an den Antragsteller und b) eine Rueckfrage zum Vize-Dean. Die threadId waere + in dem Fall eine unterschiedliche Nummer. +* Die threadId koennte z.B. die ID des ersten Chat-Records in einem Thread sein. +* BTW: Eine grIdChatType (='Chat-Room' ... aber ich finde der Begriff 'room' passt hier nicht so gut) waere z.B. 'Frage + von Antragsteller', 'Frage an Antragsteller' oder 'Frage an Vize Dean', ... - also eine uebergeordnete + Kategorisierung. +* Pro Antragsteller und grIdChatType koennen theoretisch mehrere Threads existieren - z.B. wenn der Antragsteller am + Montag eine Frage hat und am Mittwoch eine weitere. Dann waere es gut wenn Olga sieht das sie zwei Anfragen hat. + +Wann und wie wird eine threadId gebildet? + +* Variante 'statisch'. Z.B. '<application.id>-<grIdThread>' + + * + + gut lesbar + * + + beim form.load bereits verfuegbar + * + - Bei fehlerhaftem Design kann es passieren das die gleiche threadId fuer unterschiedliche Threads vergeben + werden. + * + - Das obige Beispiel mit mehreren Fragen vom Antragsteller kann nicht umgesetzt werden, pro Chat-Room gibt es + nur einen Thread. + +* Variante 'dynamisch'. Z.B. 'record id' des chat Records + + * + - schlecht lesbar + * + - beim form.load noch nicht bekannt (z.B. weil es noch keinen Chatverlauf gibt) + * + + sicher uniq. + * + + Implementierung mit wenig Aufwand machbar! (s.o. 'Konzept Thread') + +### submitReference= (default: form) + +* Diese Angabe ist nur fuer das Logging/Debugging. +* Nicht sicher ob wir das als expliziten Parameter (QFQ Keyword) brauchen. +* In der Tabelle FormSubmitLog (FSL) werden die Eingaben bei jedem QFQ-Form-Submit gespeichert. +* Da das Chat Element Records speichert, ohne das 'Save' geklickt wird, wuerden solche Records nicht im FSL + protokolliert. +* Wenn wir diese Chat Protokoll Records im FSL speichern (bin nicht sicher ob das eine schlaue Entscheidung waere), kann + direkt der Form.name, Form.id des QFQ Forms genommen werden - dann braeuchte es kein `submitReference`. +* Das Logging der Chat-Eingaben sollten wir nochmal diskutieren. + +## Outdated + +Folgende Features werden aktuell nicht benoetigt: + +### formParam + +* Mit dem FormElement `Chat` haben wir ein 'Form im Form'. +* Die Chat Nachrichten landen in einer anderen Tabelle als das Hauptform als Primary-Table hat. +* Damit die Chat Records einfach customized werden koennen, waere es gut wenn weitere Values (mit dem der Chat Record + gespeichert wird) gesetzt werden koennen. +* Diese Values haben auch Aehnlichkeit mit 'FE.parameter.detail' bei den Subrecords. +* Ich bin nicht sicher welche Notation sinnvoller ist: + + * a) wie bei Form: `grId=123&threadId={{threadIdAntragsteller:V}}` + * b) wie bei subrecord FE.parameter.detail: `#123:grId,#{{threadIdAntragsteller:V}}:threadId` + +* Falls (b), dann muesste der Parameter `detail` o.ä. heissen. + +### customJson + +* Konfigurationsoptionen fuer die wir keine eigenen FormElement.parameter Keywords machen wollen. JSON ist nicht gedacht + damit es in den QFQ Client (=JS) geht, sondern damit wir eine Moeglichkeit haben das der QFQ Applikationsentwickler + Key/Value Pairs im Form / FE definieren kann. + +### Teams (Gruppen) + +* Die Teamzugehoerigkeit regelt den Zugriff auf den Chat. +* (Fuer CR): Die Teamzugehoerigkeit hat keinen Einfluss darauf auf welcher Seite man im Chat man angezeigt wird - man + selbst wird immer rechts im Chat angezeigt. + +A= Team 1 (Alle Antragsteller) +B= Team 2 (Dekanat) + +A +B +B +A +---- + +* Nur zwei Seiten (entweder A oder B) + +---- + +* grIdSetGroupList: 120,121 - grIdSetGroupAccess +* grIdCreatorGroupList + +### Tag done + +* Falls ein thread=_'Done'_, ist das Textmessage Eingabe Feld muted und der 'Send' Button zeigt 'Reopen' an. + +### FormElement.parameter + +pIdTeam=123 (=Dekanat BEF)