Scouts gehoeren zu einem Manager ODER zu DogFather -- wie bei den Creatorn

Wunsch vom 02.09.2026: "ich will die Rollen, welcher Scout welchem
Manager oder DogFather gehoert, wie es bei den Creator ist, drunter."

Die Auswahl stand bisher nur dann unter einem Scout, wenn es ueberhaupt
einen Manager gab -- und es gibt zurzeit keinen. In der Personenliste war
davon also nichts zu sehen. Jetzt steht sie immer da, mit Managern UND
DogFather zur Wahl, genau wie "Betreut von" bei den Creatorn.

DIE BEIDEN FAELLE BEWIRKEN VERSCHIEDENES, und das ist Absicht:
  MANAGER    Der Eintrag entscheidet ueber SICHTBARKEIT -- er sieht
             danach die Leads dieses Scouts und dessen Creator.
  DOGFATHER  Der Eintrag haelt nur die ZUSTAENDIGKEIT fest. An den
             Rechten aendert er nichts; DogFather sieht ohnehin alles.

Genau diese Unterscheidung gilt bei den Creatorn seit dem 31.08. auch.
Vorher hatte ich einen Scout unter DogFather abgewiesen mit der
Begruendung, der Eintrag bewirke nichts. Das war zu eng gedacht: Er
beantwortet die Frage "wen frage ich?", und das ist der Zweck dieser
ganzen Liste.

Ein Scout unter einem Scout bleibt ausgeschlossen -- eine Ordnung, die
es nicht gibt.

ZWEI DINGE MITGEZOGEN, damit die Liste nicht zwei Sprachen spricht:
  * Der Leerwert heisst wieder "— niemand —" wie bei den Creatorn.
    "— direkt bei DogFather —" sah aus wie eine Zuordnung und war
    keine -- derselbe Fehler war bei den Creatorn schon einmal behoben
    worden, weil dieselben Leute dadurch gleichzeitig als "ohne
    zustaendige Person" gezaehlt wurden.
  * Die Rolle steht nur dann in Klammern, wenn sie etwas hinzufuegt.
    "Dogfather (DogFather)" waere zweimal dasselbe Wort.

Die Pruefung haelt jetzt BEIDES fest: dass die Zuteilung an DogFather
geht -- und dass sie niemandem mehr Sicht gibt. Verschwimmt dieser
Unterschied je, faellt es dort auf.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-02 02:16:42 +02:00
co-authored by Claude Opus 5
parent d7cf598b00
commit 9134b30d06
21 changed files with 79 additions and 18 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 921 KiB

After

Width:  |  Height:  |  Size: 921 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 KiB

After

Width:  |  Height:  |  Size: 760 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 890 KiB

After

Width:  |  Height:  |  Size: 890 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 KiB

After

Width:  |  Height:  |  Size: 858 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 939 KiB

After

Width:  |  Height:  |  Size: 939 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1007 KiB

After

Width:  |  Height:  |  Size: 1014 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 830 KiB

After

Width:  |  Height:  |  Size: 834 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 924 KiB

After

Width:  |  Height:  |  Size: 924 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 246 KiB

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 325 KiB

After

Width:  |  Height:  |  Size: 324 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+34 -6
View File
@@ -150,16 +150,41 @@ console.log("\n=== Unsinnige Zuteilungen");
{
const a = await setzen(idPat, idBen, kDogi);
ok(a.status === 400, `Scout an Scout geht nicht (${a.status})`);
/* SEIT 02.09.2026 ERLAUBT: "ich will die Rollen, welcher Scout welchem
Manager ODER DOGFATHER gehoert, wie es bei den Creator ist."
Vorher wurde das abgewiesen mit der Begruendung, DogFather sehe
ohnehin alles -- der Eintrag bewirke also nichts. Das war zu eng
gedacht: Er haelt die ZUSTAENDIGKEIT fest, und das ist genau die
Frage "wen frage ich?". Bei den Creatorn steht DogFather aus
demselben Grund seit dem 31.08. in der Auswahl.
Geprueft wird deshalb beides: dass es geht -- und dass es an den
RECHTEN nichts aendert. */
const b = await setzen(idPat, idDogi, kDogi);
ok(b.status === 400,
`Scout an DogFather geht nicht (${b.status}) – er sieht ohnehin alles, `
+ "und ein Eintrag ohne Wirkung wird spaeter fuer eine Wirkung gehalten");
ok(b.status === 200, `Scout an DogFather geht jetzt (${b.status})`);
const c = await setzen(idTili, idMara, kDogi);
ok(c.status === 404, `einen Creator kann man nicht zuteilen (${c.status})`);
const e = await setzen(999999, idMara, kDogi);
ok(e.status === 404, `eine erfundene Nummer ebenfalls nicht (${e.status})`);
}
/* Eine Zuteilung an DogFather darf NIEMANDEM mehr zeigen. Das ist der
Unterschied zwischen "haelt die Zustaendigkeit fest" und "vergibt
Sichtbarkeit" -- und wenn er je verschwimmt, faellt es hier auf. */
console.log("\n=== Eine Zuteilung an DogFather vergibt keine Sicht");
{
const l = await holen("/workspace/api/leads", kMara);
const a = await holen("/workspace/api/aufgaben", kMara);
ok((l.daten?.leads || []).length === 0 && (a.daten?.aufgaben || []).length === 0,
`Mara sieht dadurch nichts (${(l.daten?.leads || []).length} Leads, `
+ `${(a.daten?.aufgaben || []).length} Aufgaben)`);
const lN = await holen("/workspace/api/leads", kNina);
ok((lN.daten?.leads || []).length === 0, "und Nina auch nicht");
/* Zuruecksetzen, damit die Kette darunter von vorn beginnt. */
await setzen(idPat, null, kDogi);
}
/* ================================================================
2. DIE KETTE: Manager -> Scout -> dessen Creator
================================================================ */
@@ -307,9 +332,12 @@ ok(r.personen === 7,
`aufgeklappt stehen alle sieben Personen da (${r.personen}) – `
+ "sonst misst die Pruefung eine zugeklappte Liste");
ok(r.wahlen.length === 2, `zwei Scouts, zwei Auswahlfelder (${r.wahlen.length})`);
ok(/direkt bei DogFather/.test(r.wahlen[0]?.erste || ""),
`der Leer-Wert heisst, was er ist ("${r.wahlen[0]?.erste}") – `
+ '"niemand" waere falsch, ein Scout ohne Manager haengt an dir');
ok(/niemand/.test(r.wahlen[0]?.erste || ""),
`der Leer-Wert heisst wie bei den Creatorn ("${r.wahlen[0]?.erste}")`);
/* DogFather MUSS in der Auswahl stehen -- das war der Wunsch. Und zwar
ohne die doppelte Klammer "Dogfather (DogFather)". */
ok(r.wahlen[0]?.anzahl >= 4,
`Manager UND DogFather stehen zur Wahl (${r.wahlen[0]?.anzahl} Eintraege)`);
ok(r.wahlen.some((w) => w.wert === String(idNina)),
"die bestehende Zuteilung ist vorausgewaehlt");
ok(r.texte.some((t) => /gehört zu Nina/.test(t)),
+22 -6
View File
@@ -225,12 +225,28 @@ personenRouter.put("/workspace/api/verwaltung/scout-zuteilung/:id", gleicheHerku
if (!Number.isInteger(z) || z < 1) return res.status(400).json({ fehler: "Ungültige Auswahl." });
const manager = db().prepare(
"SELECT id, name, rolle FROM personen WHERE id = ? AND aktiv = 1").get(z);
/* NUR ein Manager. Ein Scout unter einem Scout waere eine Ordnung,
die es nicht gibt; ein Scout unter DogFather ist ueberfluessig, da
er ohnehin alles sieht -- und ein Eintrag, der nichts bewirkt,
wird beim naechsten Mal fuer eine Wirkung gehalten. */
if (!manager || manager.rolle !== "manager") {
return res.status(400).json({ fehler: "Zuteilen kann man nur an einen aktiven Manager." });
/* MANAGER ODER DOGFATHER -- wie bei den Creatorn (02.09.2026:
"ich will die Rollen, welcher Scout welchem Manager oder DogFather
gehoert, wie es bei den Creator ist").
Ein Scout unter einem Scout bleibt ausgeschlossen: Das waere eine
Ordnung, die es nicht gibt. Ein Creator kann ohnehin niemanden
fuehren.
WICHTIG ZUM VERSTAENDNIS -- die beiden Faelle bewirken
Verschiedenes, und das ist Absicht:
MANAGER Der Eintrag entscheidet ueber SICHTBARKEIT. Er sieht
danach die Leads dieses Scouts und dessen Creator.
DOGFATHER Der Eintrag sagt nur, WER zustaendig ist. An den
Rechten aendert er nichts -- DogFather sieht ohnehin
alles.
Genau so ist es bei "Betreut von" der Creator auch. Ein Eintrag,
der die Zustaendigkeit festhaelt, ist kein Eintrag ohne Wirkung:
Er beantwortet die Frage "wen frage ich?". */
if (!manager || !["manager", "admin"].includes(manager.rolle)) {
return res.status(400).json({
fehler: "Zuteilen kann man nur an einen aktiven Manager oder DogFather.",
});
}
scoutZuteilungSetzen(id, z, req.person.id);
+23 -6
View File
@@ -222,7 +222,14 @@
Und nur, wenn es ueberhaupt einen Manager gibt -- eine leere
Auswahl sieht aus wie ein Fehler. */
const managerListe = betreuerListe.filter((x) => x.rolle === 'manager');
/* Manager UND DogFather -- genau wie bei "Betreut von" der Creator
(Wunsch 02.09.2026). Die beiden bewirken Verschiedenes: Bei einem
Manager entscheidet der Eintrag ueber die Sichtbarkeit, bei
DogFather haelt er nur die Zustaendigkeit fest. Beides ist eine
Antwort auf "wen frage ich?" -- und genau darum geht es in dieser
Liste. */
const managerListe = betreuerListe.filter(
(x) => x.rolle === 'manager' || x.rolle === 'admin');
if (p.rolle === 'scout' && ich.rolle === 'admin' && managerListe.length) {
const b = el('div', 'betreuung');
const s2 = el('label', 'betreuung__schild', 'Gehört zu');
@@ -231,12 +238,22 @@
w2.id = 'manager-' + p.id;
const leer2 = document.createElement('option');
leer2.value = '';
/* Nicht "— niemand —": Ein Scout ohne Manager ist nicht
unbetreut, er haengt direkt an dir. Das ist ein Zustand, kein
Mangel. */
leer2.textContent = '— direkt bei DogFather —';
/* Wie bei den Creatorn: "niemand" heisst wirklich niemand.
Vorher stand hier "— direkt bei DogFather —" -- das sah aus wie
eine Zuordnung und war keine, und derselbe Fehler war bei den
Creatorn schon einmal behoben worden. DogFather steht jetzt als
echte Person in der Liste, gleichrangig neben den Managern. */
leer2.textContent = '— niemand —';
w2.append(leer2);
for (const m of managerListe) w2.append(new Option(m.name, String(m.id)));
for (const m of managerListe) {
/* Dieselbe Regel wie bei den Creatorn: Die Rolle in Klammern nur
dann, wenn sie etwas hinzufuegt -- "Dogfather (DogFather)"
waere zweimal dasselbe Wort. */
const rn = ROLLENNAME[m.rolle] || m.rolle || '';
const gl = (a, b2) =>
a.trim().toLowerCase().replace(/\s+/g, '') === b2.trim().toLowerCase().replace(/\s+/g, '');
w2.append(new Option(gl(m.name, rn) ? m.name : `${m.name} (${rn})`, String(m.id)));
}
w2.value = p.manager_id ? String(p.manager_id) : '';
w2.addEventListener('change', async () => {
w2.disabled = true; melde('');