Kalender: mehrere Teilnehmer je Termin und Serie

Wunsch Filipe: "wenn ich im kalender was eintrage will ich dass ich
auch 2 leute markieren kann mit denen der call ist." -- und auf
Rueckfrage: beliebig viele, und auch fuer wiederkehrende Termine.

Bis hierher hatte ein Termin GENAU EIN Gegenueber. Fuer ein Gespraech
zu dritt musste man zwei Termine anlegen und hatte zwei Wahrheiten
ueber dieselbe halbe Stunde.

DAS IST KEINE ANZEIGE, SONDERN EINE RECHTEREGEL. An der
Teilnehmerliste haengt die Sichtbarkeit: Wer eingetragen ist, sieht den
Termin. Deshalb zwei Grenzen, beide serverseitig:
  * Eintragen darf man nur, wen man ohnehin sehen darf (Leitung jeden,
    ein Scout seine betreuten Creator, ein Creator sich selbst).
  * Zuordnungen verschiebt nur die Leitung -- sonst koennte sich jemand
    selbst in fremde Termine eintragen und sie sich damit sichtbar
    machen.

Gebaut nach dem Muster von datei_personen: eigene Tabellen
termin_teilnehmer und serie_teilnehmer statt weiterer Spalten.
teilnehmer_id bleibt das Haupt-Gegenueber und wird beim Speichern immer
in die Liste mit aufgenommen.

Vorhandene Termine werden beim Start uebernommen. Zusaetzlich liest die
Abfrage das Haupt-Gegenueber IMMER mit dazu -- die Liste stimmt damit
auch dann, wenn die Uebernahme nicht gelaufen ist (Sicherung
zurueckgespielt, Neustart ausgeblieben). Genau das ist beim Bauen
aufgefallen: Ein alter Termin zeigte "niemand dabei", obwohl ein
Gegenueber eingetragen war.

Serien vererben ihre Teilnehmer an jede erzeugte Auspraegung -- sonst
saehe der zweite Mensch den woechentlichen Call einmal und danach nie
wieder.

Neue Pruefung pruef-teilnehmer.mjs mit 40 Punkten: beide sehen ihn,
Fremde nicht, kein Selbsteintragen, Hinzufuegen und Entfernen, Serien,
Unsinn in der Liste. Mit Gegenprobe, die nachweist, dass die Messung
"nicht sichtbar" ueberhaupt erkennt. Im echten Browser gegengeprueft:
Schalter da, zwei angehakt, gespeichert, beide zurueck, keine
Konsolenfehler.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-05 14:21:09 +02:00
co-authored by Claude Opus 5
parent 8759a46e5b
commit 529c814389
28 changed files with 842 additions and 151 deletions
+102 -2
View File
@@ -248,6 +248,38 @@ function umstellungen(d) {
console.error("[workspace] Index idx_termine_serie:", fehler?.message);
}
/* ---- Die vorhandenen Teilnehmer in die neue Tabelle uebernehmen ----
Ohne diesen Schritt haette jeder Termin, der vor dem 05.09.2026
angelegt wurde, eine LEERE Teilnehmerliste -- und weil an dieser
Liste die Sichtbarkeit haengt, faende die eingetragene Person ihren
eigenen Termin nicht mehr. Ein stiller Datenverlust, der erst
auffaellt, wenn jemand einen Call sucht, den es noch gibt.
`INSERT OR IGNORE` macht den Lauf wiederholbar: Beim zweiten Start
ist alles schon da und nichts passiert. Deshalb steht das hier bei
den Umstellungen und nicht in einem einmaligen Skript, das jemand
vergessen koennte. */
for (const [tabelle, quelle, ziel, schluessel] of [
["termin_teilnehmer", "termine", "termin_id", "id"],
["serie_teilnehmer", "termin_serien", "serie_id", "id"],
]) {
try {
const spalten = d.prepare(`PRAGMA table_info(${quelle})`).all().map((s) => s.name);
if (!spalten.includes("teilnehmer_id")) continue;
const vorher = d.prepare(`SELECT COUNT(*) AS n FROM ${tabelle}`).get().n;
d.exec(`INSERT OR IGNORE INTO ${tabelle} (${ziel}, person_id)
SELECT ${schluessel}, teilnehmer_id FROM ${quelle}
WHERE teilnehmer_id IS NOT NULL`);
const nachher = d.prepare(`SELECT COUNT(*) AS n FROM ${tabelle}`).get().n;
if (nachher > vorher) {
console.log(`[workspace] ${nachher - vorher} vorhandene Teilnehmer nach ${tabelle} uebernommen.`);
}
} catch (fehler) {
console.error(`[workspace] Uebernahme nach ${tabelle}:`, fehler?.message);
}
}
/* Content-Saeulen: die drei bis fuenf Themen, aus denen der Kanal
besteht. Aus der Recherche: 3-5 Saeulen nach der 70/20/10-Regel
(70 % Wert, 20 % Community, 10 % Eigenwerbung); eine Saeule wird
@@ -492,6 +524,52 @@ export function db() {
);
CREATE INDEX IF NOT EXISTS idx_termine_beginn ON termine (beginn);
/* MEHRERE TEILNEHMER AN EINEM TERMIN (05.09.2026) ------------------
Wunsch Filipe: *"wenn ich im kalender was eintrage will ich dass
ich auch 2 leute markieren kann mit denen der call ist."*
Bis hierher hatte ein Termin GENAU EIN Gegenueber
(termine.teilnehmer_id). Fuer ein Gespraech zu dritt musste man
zwei Termine anlegen -- und hatte damit zwei Wahrheiten ueber
dieselbe halbe Stunde.
Gebaut nach demselben Muster wie datei_personen weiter unten:
eine eigene kleine Tabelle statt weiterer Spalten. Zwei Spalten
"teilnehmer2_id", "teilnehmer3_id" waeren beim vierten Menschen
wieder am Ende, und jede Abfrage muesste alle einzeln
aufzaehlen.
WICHTIG -- teilnehmer_id BLEIBT und behaelt seine Bedeutung:
Es ist das HAUPT-Gegenueber, mit dem der Termin vereinbart
wurde. Diese Tabelle sagt, WER SONST NOCH dabei ist. Beim
Umstellen wird der vorhandene Wert mit uebernommen, damit die
Teilnehmerliste von Anfang an vollstaendig ist und keine
Abfrage zwei Stellen zusammensuchen muss.
An dieser Tabelle haengt die SICHTBARKEIT (siehe
termineSichtbar): Wer hier steht, sieht den Termin. Ein
vergessener Eintrag ist deshalb kein Schoenheitsfehler,
sondern ein Termin, den jemand nicht findet. */
CREATE TABLE IF NOT EXISTS termin_teilnehmer (
termin_id INTEGER NOT NULL REFERENCES termine(id) ON DELETE CASCADE,
person_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE,
PRIMARY KEY (termin_id, person_id)
);
CREATE INDEX IF NOT EXISTS idx_termin_teilnehmer ON termin_teilnehmer (person_id);
/* Dasselbe fuer die Wiederholungen. Eine Serie ist eine Regel --
die Teilnehmer gehoeren zur Regel, nicht zur einzelnen
Auspraegung, sonst muesste man sie jede Woche neu eintragen.
Der Nachfueller kopiert sie beim Anlegen jedes Termins hierher
hinueber (siehe workspace-serien.js). */
CREATE TABLE IF NOT EXISTS serie_teilnehmer (
serie_id INTEGER NOT NULL REFERENCES termin_serien(id) ON DELETE CASCADE,
person_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE,
PRIMARY KEY (serie_id, person_id)
);
CREATE INDEX IF NOT EXISTS idx_serie_teilnehmer ON serie_teilnehmer (person_id);
/* Wiederkehrende Termine (02.09.2026) --------------------------------
Eine Serie ist eine REGEL, kein Termin: "jeden Dienstag um 18:00
@@ -1373,8 +1451,30 @@ export const externSql = (personSpalte, externSpalte) =>
export function termineSichtbar(person, praefix = "t") {
if (istDogFather(person)) return { wo: "1=1", werte: [] };
const p = praefix;
const eigen = `(${p}.creator_id = ? OR ${p}.teilnehmer_id = ? OR ${p}.erstellt_von = ?)`;
const werte = [person.id, person.id, person.id];
/* MITGEZAEHLT WIRD AUCH DIE TEILNEHMERLISTE (05.09.2026).
Seit ein Termin mehrere Teilnehmer haben kann, reicht
`teilnehmer_id` nicht mehr: Das ist nur das Haupt-Gegenueber. Wer
als zweiter oder dritter dabei ist, steht in termin_teilnehmer --
und ohne diese Zeile saehe er den Termin nicht, an dem er
teilnimmt.
Die beiden Aufrufer arbeiten auf verschiedenen Tabellen: der
Kalender auf `termine` (praefix t), die Wiederholungen auf
`termin_serien` (praefix s). Deshalb wird hier die passende
Nebentabelle gewaehlt statt einer festen -- eine falsche Zuordnung
waere kein Fehler, den man sieht, sondern eine Liste, in der
jemandem etwas fehlt. */
const nebenTabelle = p === "s"
? { tabelle: "serie_teilnehmer", spalte: "serie_id" }
: { tabelle: "termin_teilnehmer", spalte: "termin_id" };
const dabei = `EXISTS (SELECT 1 FROM ${nebenTabelle.tabelle} tn`
+ ` WHERE tn.${nebenTabelle.spalte} = ${p}.id AND tn.person_id = ?)`;
const eigen = `(${p}.creator_id = ? OR ${p}.teilnehmer_id = ? OR ${p}.erstellt_von = ?`
+ ` OR ${dabei})`;
const werte = [person.id, person.id, person.id, person.id];
const b = betreutWo(person, `${p}.creator_id`);
if (!b) return { wo: eigen, werte };
return { wo: `(${eigen} OR ${b.wo})`, werte: [...werte, ...b.werte] };