Filipe (screen22), nachdem Managerin Schulle "2 Creator" angezeigt bekam, obwohl sie einen hat: "die zahl die da angezeigt wird soll bitte immer jedem genau zutreffend sein." Und dazu (screen23), wer was sehen soll: "dogfather und cigdem haben die zahl vom insgesamten. manager sehen nur die gesamte zahl ihrer scouts und ihren creator die ihnen zugeteilt sind, die scout sehen die zahl nur von ihren creator und die creator da termine vom tag selber" DIE ZAHL WAR NICHT FALSCH GERECHNET, SIE BEANTWORTETE DIE FALSCHE FRAGE. Hier stand `SELECT ... FROM personen WHERE aktiv = 1` -- alle, fuer jeden aus der Leitung gleich. Ein Manager sah damit das ganze Haus als "sein Team", einschliesslich Leuten, mit denen er nichts zu tun hat. Jetzt je Rolle: admin/spicy alle aktiven Personen, ohne sich selbst manager seine Scouts UND die Creator (eigene wie die der Scouts) scout nur seine Creator creator unveraendert der eigene Tag SCOUTS BEKOMMEN DIESEN RING NEU. Sie zaehlen nicht zur Leitung und sahen deshalb den Stundenring des eigenen Tages -- aber ein Scout hat ein Team, naemlich seine Creator. Genau danach hat Filipe gefragt. Der Ring heisst bei ihm "Creator versorgt" statt "Team versorgt": Sonst liest ein Scout "Team" und sucht die anderen vier Rollen darin. OHNE SICH SELBST: Wer den Ring ansieht, ist die Person, die ihn liest. Sich selbst als Segment im eigenen Team mitzuzaehlen verschiebt jede Prozentangabe um einen Platz. KEINE ZWEITE RECHENVORSCHRIFT. Die Zuordnung wird nicht hier nachgebaut: `betreuteIds` kennt die Kette Manager -> Scouts -> deren Creator bereits, `scoutsVon` die Scouts. Eine eigene Fassung derselben Frage waere genau der Weg, auf dem zwei Wahrheiten entstehen. Dazu screen26: "liegt liegen, keine ahnung was das bedeuten soll aber das soll viel besser sein bitte." Gezaehlt werden Personen mit unerledigten Terminen aus der VERGANGENHEIT -- das Schild heisst jetzt "ueberfaellig". GEMESSEN an einer Lage, die den Fehler enthaelt: fuenf aktive Personen, darunter ein Creator, der zu niemandem gehoert. DogFather 4 im Team [Fremder, Tili, Schulle, Patrick] Manager Schulle 2 im Team [Tili, Patrick] <- ohne "Fremder" Scout Patrick 1 meine Creator [Tili] Creator Tili eigener Tag pruef-start-ansicht EXIT=0 (140), pruef-rollen EXIT=0 (97). Co-Authored-By: Claude Opus 5 <[email protected]>
246 lines
11 KiB
JavaScript
246 lines
11 KiB
JavaScript
/* =====================================================================
|
|
workspace-zentrale.js — die Zahlen fuer den Ring auf der Startseite.
|
|
|
|
Wunsch Filipe (08.09.2026): die Begruessungskachel nach dem Vorbild
|
|
von VanVans Business Hub umbauen -- grosser Ring links, Text in der
|
|
Mitte, Uhr rechts. Dort zeigt der Ring "91 % deiner Produktakten":
|
|
ein Segment je Produkt, eingefaerbt nach Vollstaendigkeit.
|
|
|
|
---------------------------------------------------------------------
|
|
WARUM ES DIESELBE ZAHL HIER NICHT GIBT
|
|
|
|
Nachgemessen an der echten Datenbank am 08.09.2026:
|
|
|
|
Termine 31 · Wissen 7 · Schulungen 4 · Personen 9 · AUFGABEN 1
|
|
Leistung, Leads, Profile, Content-Saeulen, Dateien: jeweils 0
|
|
|
|
VanVans Kranz funktioniert, weil 253 Produkte ein BILD ergeben. Ein
|
|
Kranz aus einer Aufgabe ergibt keins. Die naheliegende Uebertragung
|
|
("dann eben Aufgaben") haette eine leere Scheibe erzeugt, die aussieht
|
|
wie ein Ladefehler.
|
|
|
|
Filipe hat deshalb DAS TEAM gewaehlt: ein Segment je Person, gefaerbt
|
|
danach, wie es um sie steht. Neun Segmente sind wenige, aber sie sind
|
|
ECHT und aendern sich taeglich -- und die Zahl waechst mit dem Team.
|
|
|
|
---------------------------------------------------------------------
|
|
UND WARUM DAS NICHT FUER JEDEN GEHT
|
|
|
|
Ein Creator darf die Teamliste nicht sehen. Das ist keine
|
|
Feinheit, sondern die Regel, die diesen ganzen Arbeitsplatz traegt
|
|
(Wunsch Filipe, 07.09.2026: "jeder soll und darf im kalender immer nur
|
|
seine eigenen eintraege nur sehen"). Ein Ring, der neun Namen
|
|
einfaerbt, waere fuer einen Creator ein Leck -- er koennte daran
|
|
ablesen, wer heute Termine hat und wer nicht.
|
|
|
|
Deshalb liefert diese Route ZWEI VERSCHIEDENE RINGE, je nach Rolle:
|
|
|
|
Leitung (DogFather, Manager, Scout, Spicy)
|
|
-> das Team: ein Segment je aktiver Person
|
|
Creator -> der eigene Tag: ein Segment je Stunde, plus die
|
|
eigenen Termine als Marken
|
|
|
|
Das ist keine Notloesung, sondern die richtige Antwort auf dieselbe
|
|
Frage: "Wie weit ist das, wofuer ich zustaendig bin?" Fuer DogFather
|
|
ist das sein Team, fuer eine Creatorin ihr Tag.
|
|
|
|
KEINE EIGENEN DATEN, wie bei den Hinweisen: Alles hier ist eine SICHT
|
|
auf Zeilen, die ohnehin existieren. Es gibt nichts zu pflegen und
|
|
nichts, was veralten kann.
|
|
===================================================================== */
|
|
|
|
import express from "express";
|
|
import { db, sitzungLesen, istLeitung, betreuteIds, scoutsVon } from "./workspace.js";
|
|
|
|
export const zentraleRouter = express.Router();
|
|
|
|
function angemeldet(req, res, next) {
|
|
const person = sitzungLesen(req);
|
|
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
|
|
req.person = person;
|
|
next();
|
|
}
|
|
zentraleRouter.use("/workspace/api/zentrale", angemeldet);
|
|
|
|
const p2 = (n) => String(n).padStart(2, "0");
|
|
/* ORTSZEIT, NICHT UTC. toISOString() liefert zwischen Mitternacht und
|
|
zwei Uhr noch den Vortag -- der Ring stuende dann auf den Terminen
|
|
von gestern. Derselbe Fehler ist mir am 08.09. zweimal an einem Abend
|
|
passiert; pruef-struktur.mjs sucht ihn inzwischen im ganzen Repo. */
|
|
function heuteLokal() {
|
|
const d = new Date();
|
|
return `${d.getFullYear()}-${p2(d.getMonth() + 1)}-${p2(d.getDate())}`;
|
|
}
|
|
|
|
zentraleRouter.get("/workspace/api/zentrale", (req, res) => {
|
|
try {
|
|
const d = db();
|
|
const heute = heuteLokal();
|
|
const ich = req.person;
|
|
|
|
/* Wessen Lage wird gezeigt? Bei aktiver Fremdsicht die der Person,
|
|
in die man hineinsieht -- sonst zeigte die Kachel die eigene Lage
|
|
und alles darunter eine fremde. Genau diese Uneinheitlichkeit hat
|
|
auf der Startseite schon einmal zu "ist das jetzt meins oder
|
|
ihrs?" gefuehrt. */
|
|
const zeigt = req.person.sicht || req.person;
|
|
|
|
/* ================================================================
|
|
WESSEN TEAM? JEDE ROLLE SIEHT IHR EIGENES (08.09.2026)
|
|
|
|
Filipe, nachdem Managerin Schulle "2 Creator" angezeigt bekam,
|
|
obwohl sie einen hat: "die zahl die da angezeigt wird soll bitte
|
|
immer jedem genau zutreffend sein" -- und dazu, wer was sieht:
|
|
|
|
"dogfather und cigdem haben die zahl vom insgesamten. manager
|
|
sehen nur die gesamte zahl ihrer scouts und ihren creator die
|
|
ihnen zugeteilt sind, die scout sehen die zahl nur von ihren
|
|
creator und die creator da termine vom tag selber"
|
|
|
|
Vorher stand hier `SELECT ... FROM personen WHERE aktiv = 1` --
|
|
ALLE, fuer jeden aus der Leitung gleich. Deshalb sah Schulle das
|
|
ganze Haus statt ihres Teams. Die Zahl war nicht falsch berechnet,
|
|
sie beantwortete die falsche Frage.
|
|
|
|
Die Zuordnung selbst wird NICHT hier nachgebaut: `betreuteIds`
|
|
kennt die Kette Manager -> Scouts -> deren Creator bereits, und
|
|
`scoutsVon` die Scouts. Eine zweite Rechenvorschrift fuer
|
|
dieselbe Frage waere genau der Weg, auf dem zwei Wahrheiten
|
|
entstehen.
|
|
|
|
SCOUTS BEKOMMEN JETZT AUCH DIESEN RING. Sie zaehlen nicht zur
|
|
Leitung und sahen deshalb den Stundenring des eigenen Tages --
|
|
aber ein Scout hat ein Team, naemlich seine Creator. Genau danach
|
|
hat Filipe gefragt. */
|
|
const istTeamsicht = istLeitung(ich) || ich.rolle === "scout";
|
|
|
|
if (istTeamsicht) {
|
|
/* ---------- DAS TEAM ----------
|
|
Ein Segment je Person im eigenen Verantwortungsbereich. Der
|
|
Zustand einer Person ist bewusst GROB in drei Stufen -- mehr
|
|
taeuschte Genauigkeit vor, die es nicht gibt (dieselbe
|
|
Ueberlegung steht in VanVans Kranz).
|
|
|
|
frei - nichts Offenes, keine Termine heute -> ruhig
|
|
dran - hat heute Termine -> die sind versorgt
|
|
offen - hat unerledigte Termine aus der VERGANGENHEIT
|
|
-> das ist das, was liegen bleibt
|
|
*/
|
|
let leute;
|
|
if (ich.rolle === "admin" || ich.rolle === "spicy") {
|
|
/* Das ganze Haus -- und zwar ohne sich selbst: Wer den Ring
|
|
ansieht, ist die Person, die ihn liest; sich selbst als
|
|
Segment im eigenen Team zu zaehlen, verschiebt jede Prozent-
|
|
angabe um einen Platz. */
|
|
leute = d.prepare(
|
|
`SELECT id, name, rolle FROM personen
|
|
WHERE aktiv = 1 AND id <> ? ORDER BY rolle, name`).all(ich.id);
|
|
} else {
|
|
/* Manager: seine Scouts UND die Creator (eigene wie die seiner
|
|
Scouts). Scout: nur seine Creator. */
|
|
const ids = [...new Set([
|
|
...(ich.rolle === "manager" ? scoutsVon(ich.id) : []),
|
|
...betreuteIds(ich),
|
|
])].filter((id) => id !== ich.id);
|
|
|
|
leute = ids.length
|
|
? d.prepare(
|
|
`SELECT id, name, rolle FROM personen
|
|
WHERE aktiv = 1 AND id IN (${ids.map(() => "?").join(",")})
|
|
ORDER BY rolle, name`).all(...ids)
|
|
: [];
|
|
}
|
|
|
|
const heuteZaehler = d.prepare(
|
|
`SELECT COUNT(*) AS n FROM termine t
|
|
WHERE t.erledigt = 0 AND substr(t.beginn, 1, 10) = ?
|
|
AND (t.creator_id = ? OR t.erstellt_von = ?
|
|
OR EXISTS (SELECT 1 FROM termin_teilnehmer x
|
|
WHERE x.termin_id = t.id AND x.person_id = ?))`);
|
|
const altZaehler = d.prepare(
|
|
`SELECT COUNT(*) AS n FROM termine t
|
|
WHERE t.erledigt = 0 AND substr(t.beginn, 1, 10) < ?
|
|
AND (t.creator_id = ? OR t.erstellt_von = ?
|
|
OR EXISTS (SELECT 1 FROM termin_teilnehmer x
|
|
WHERE x.termin_id = t.id AND x.person_id = ?))`);
|
|
|
|
const segmente = leute.map((p) => {
|
|
const heuteN = heuteZaehler.get(heute, p.id, p.id, p.id).n;
|
|
const altN = altZaehler.get(heute, p.id, p.id, p.id).n;
|
|
const stufe = altN > 0 ? "offen" : (heuteN > 0 ? "dran" : "frei");
|
|
/* NUR DER VORNAME nach aussen. Der Ring braucht ein Kuerzel fuer
|
|
die Sprechblase, nicht den Datensatz einer Person. */
|
|
return { name: String(p.name || "").trim().split(/\s+/)[0] || "?",
|
|
rolle: p.rolle, stufe, heute: heuteN, alt: altN };
|
|
});
|
|
|
|
const versorgt = segmente.filter((s) => s.stufe !== "offen").length;
|
|
|
|
/* Ein Scout fuehrt Creator, kein "Team" im Sinne des Hauses. Das
|
|
Wort auf dem Ring sagt deshalb, WEN er zaehlt -- sonst liest ein
|
|
Scout "Team versorgt" und sucht die anderen vier Rollen darin. */
|
|
const nurCreator = ich.rolle === "scout";
|
|
|
|
return res.json({
|
|
art: "team",
|
|
titel: nurCreator ? "Creator versorgt" : "Team versorgt",
|
|
prozent: segmente.length ? Math.round((versorgt / segmente.length) * 100) : 100,
|
|
segmente,
|
|
zahlen: [
|
|
{ wert: segmente.length, schild: nurCreator ? "meine Creator" : "im Team" },
|
|
{ wert: segmente.filter((s) => s.stufe === "dran").length, schild: "heute dran" },
|
|
/* "liegt liegen" stand hier und war nicht zu verstehen -- Filipe
|
|
wortwoertlich: "keine ahnung was das bedeuten soll". Gezaehlt
|
|
werden Personen mit unerledigten Terminen aus der
|
|
VERGANGENHEIT. "Ueberfaellig" sagt das in einem Wort und ist
|
|
der Begriff, der auch sonst im Haus dafuer steht. */
|
|
{ wert: segmente.filter((s) => s.stufe === "offen").length, schild: "überfällig" },
|
|
],
|
|
});
|
|
}
|
|
|
|
/* ---------- DER EIGENE TAG ----------
|
|
Fuer Creator. Ein Segment je Stunde von 6 bis 24 -- der Teil des
|
|
Tages, in dem gearbeitet wird. Nachts einen leeren Halbkreis zu
|
|
zeigen sagt nichts; 18 Segmente fuellen den Ring ordentlich.
|
|
|
|
Gefuellt sind die vergangenen Stunden, markiert die mit einem
|
|
eigenen Termin. Der Ring beantwortet damit "wie weit ist mein
|
|
Tag" -- dieselbe Frage wie bei VanVan, nur auf das bezogen,
|
|
wofuer eine Creatorin zustaendig ist. */
|
|
const VON = 6, BIS = 24;
|
|
const jetzt = new Date();
|
|
const stundeJetzt = jetzt.getHours();
|
|
|
|
const meine = d.prepare(
|
|
`SELECT t.beginn FROM termine t
|
|
WHERE substr(t.beginn, 1, 10) = ?
|
|
AND (t.creator_id = ? OR t.erstellt_von = ?
|
|
OR EXISTS (SELECT 1 FROM termin_teilnehmer x
|
|
WHERE x.termin_id = t.id AND x.person_id = ?))`)
|
|
.all(heute, zeigt.id, zeigt.id, zeigt.id);
|
|
const mitTermin = new Set(meine.map((t) => Number(String(t.beginn).slice(11, 13))));
|
|
|
|
const segmente = [];
|
|
for (let s = VON; s < BIS; s++) {
|
|
segmente.push({ name: `${p2(s)} Uhr`, rolle: "stunde",
|
|
stufe: mitTermin.has(s) ? "dran" : (s < stundeJetzt ? "frei" : "offen") });
|
|
}
|
|
const durch = Math.min(Math.max(stundeJetzt - VON, 0), BIS - VON);
|
|
return res.json({
|
|
art: "tag",
|
|
titel: "Tag geschafft",
|
|
prozent: Math.round((durch / (BIS - VON)) * 100),
|
|
segmente,
|
|
zahlen: [
|
|
{ wert: meine.length, schild: "heute" },
|
|
{ wert: [...mitTermin].filter((s) => s >= stundeJetzt).length, schild: "kommt noch" },
|
|
{ wert: betreuteIds(zeigt).length, schild: "betreut" },
|
|
],
|
|
});
|
|
} catch (fehler) {
|
|
console.error("[workspace] Zentrale:", fehler?.message);
|
|
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
|
}
|
|
});
|