Nicht jeder bekommt alle 68 Punkte -- DogFather und die rechte Hand suchen je Person aus
Filipe: "ich will dass die rechte hand und ich diese aufgaben die es da
gibt, individuel aussuchen koennen wer welche aufgabe bekommt. bis
dahin sollen die keine aufgaben sehen. nur die, die die rechte hand
oder dogfather ihnen zutragen."
VORHER GEFRAGT, UND ES WAR NOETIG. Auf entwicklung.html stehen zwei
Listen untereinander -- das Vorlagenbrett (Aufgaben zum Verteilen) und
der Beobachtungskatalog (die 68 Punkte je Person). Sein Text passte auf
das eine, sein Bildschirmfoto zeigte das andere. Am 24.09. habe ich in
genau dieser Lage geraten und an der falschen Stelle gebaut. Seine
Antwort: der 68-Punkte-Katalog, das Vorlagenbrett "nicht anfassen".
BIS HEUTE GALTEN ALLE 68 FUER JEDEN. Das war bequem und in der Sache
falsch: "Clips, Schnitt und Kommentare" gehoert nicht zu jemandem, der
nur im Chat moderiert -- und ein Punkt, der nie zutrifft, steht
trotzdem in der Zaehlung. "0 von 68" bei jemandem, fuer den zwoelf
gelten, ist keine Auskunft, sondern eine Entmutigung.
EINE TABELLE, EINE ZEILE JE PAAR. Eine kommagetrennte Liste in der
Personenzeile waere schneller gebaut und liesse sich nicht abfragen
("wer hat diesen Punkt?"), nicht zaehlen und nicht absichern. Wer und
wann stehen mit drin -- fuer die Frage "seit wann gilt das eigentlich
fuer ihn", die erfahrungsgemaess dann kommt, wenn sie niemand mehr
beantworten kann.
EINE STELLE FUER DIE ANTWORT, drei Aufrufer: die Karte der Leitung, die
Uebersicht mit den Zahlen und der Auswahl-Dialog. Drei Abschriften
waeren drei Gelegenheiten, dass eine nicht mitzieht -- und dann stuende
in der Uebersicht "von 68", waehrend in der Karte zwoelf Punkte stehen.
DIE ZAHLEN ZIEHEN MIT. "X von Y" zaehlt jetzt das Zugeteilte. Auch die
linke Zahl musste nachgezogen werden: Wer frueher zu einem Punkt
gesetzt hat, der ihm inzwischen nicht mehr zugeteilt ist, haette sonst
"13 von 12" bekommen.
GESPEICHERT WIRD DIE GANZE LISTE AUF EINMAL, in einer Transaktion. Wer
zwoelf Haken setzt und dabei die Verbindung verliert, haette sonst
sieben gesetzte und fuenf verlorene -- und saehe nicht, welche.
EINMALIG WIRD UEBERNOMMEN, wozu es schon eine Einschaetzung gibt. Ohne
das waere der Umbau Datenverlust auf dem Bildschirm: Wer zwanzig Punkte
gesetzt hat, saehe am naechsten Morgen eine leere Karte -- die Daten
liegen noch da, man kommt nur nicht mehr hin. Ein Flag verhindert, dass
die Uebernahme wiederkommt, nachdem jemand bewusst abgewaehlt hat; an
einer Wegwerf-Datenbank durchgespielt, beide Laeufe wie erwartet.
NOCH NICHTS AUSGESUCHT HEISST NICHT "KAPUTT". Die Karte sagt es dann
mit einem Satz und einem Knopf. Ein leerer Bildschirm ohne Erklaerung
ist die schlechteste Antwort von allen -- man weiss nicht, ob es laedt,
ob etwas kaputt ist oder ob schlicht noch niemand ausgesucht hat.
Geprueft: pruef-entwicklung 48 -> 61 Punkte. Zuerst das Wichtigste an
seinem Satz ("bis dahin sollen die keine aufgaben sehen"): ohne
Zuteilung ist die Karte leer -- ohne diese Zeile waere alles Folgende
auch dann gruen, wenn weiterhin alles fuer jeden gilt. Dazu vier
Gegenproben: erfundene Schluessel fallen weg, ein Modi sucht nicht aus
(404) und nach seinem Versuch steht der Bestand unveraendert da, und
alles wieder wegzunehmen geht ebenfalls (eine Auswahl, die man nur
erweitern kann, waere eine Falle). pruef-css-klassen und
pruef-tippziele (11) unveraendert gruen.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -207,6 +207,76 @@ export function tabellen() {
|
||||
entstand, wie ein Tag aus, an dem 43 Dinge gleichzeitig passiert
|
||||
sind.
|
||||
============================================================= */
|
||||
/* =================================================================
|
||||
WELCHE PUNKTE GELTEN FUER DIESEN MENSCHEN? (25.09.2026)
|
||||
=================================================================
|
||||
|
||||
Filipe: „ich will dass die rechte hand und ich diese aufgaben die
|
||||
es da gibt, individuel aussuchen koennen wer welche aufgabe
|
||||
bekommt. bis dahin sollen die keine aufgaben sehen. nur die, die
|
||||
die rechte hand oder dogfather ihnen zutragen."
|
||||
|
||||
BIS HEUTE GALTEN ALLE 68 FUER JEDEN. Das war bequem und in der
|
||||
Sache falsch: „Clips, Schnitt und Kommentare" gehoert nicht zu
|
||||
jemandem, der nur im Chat moderiert -- und ein Punkt, der nie
|
||||
zutrifft, steht trotzdem in der Zaehlung („0 von 68"). Wer eine
|
||||
Liste sieht, in der ein Drittel nie passt, liest die ganze Liste
|
||||
weniger ernst.
|
||||
|
||||
EINE ZEILE JE PAAR, KEINE LISTE IN EINER SPALTE: Eine
|
||||
kommagetrennte Liste in personen waere schneller gebaut und
|
||||
liesse sich nicht abfragen („wer hat diesen Punkt?"), nicht
|
||||
zaehlen und nicht mit einem Fremdschluessel absichern.
|
||||
|
||||
WER UND WANN stehen mit drin. Nicht fuer die Anzeige -- fuer die
|
||||
Frage „seit wann gilt das eigentlich fuer ihn", die
|
||||
erfahrungsgemaess in dem Moment kommt, in dem jemand sie nicht
|
||||
mehr beantworten kann. */
|
||||
CREATE TABLE IF NOT EXISTS entwicklung_zuteilung (
|
||||
person_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE,
|
||||
schluessel TEXT NOT NULL,
|
||||
seit TEXT NOT NULL,
|
||||
von_id INTEGER REFERENCES personen(id) ON DELETE SET NULL,
|
||||
PRIMARY KEY (person_id, schluessel)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_entw_zuteilung_person
|
||||
ON entwicklung_zuteilung (person_id);
|
||||
|
||||
/* =================================================================
|
||||
EINMALIG: WAS SCHON BEURTEILT WURDE, BLEIBT SICHTBAR
|
||||
=================================================================
|
||||
|
||||
Ab heute zeigt die Karte nur noch zugeteilte Punkte. Fuer jemanden,
|
||||
bei dem noch nichts ausgesucht ist, heisst das „leer" -- und genau
|
||||
das hat Filipe gewollt („bis dahin sollen die keine aufgaben
|
||||
sehen").
|
||||
|
||||
FUER BESTEHENDE BEURTEILUNGEN WAERE ES ABER DATENVERLUST AUF DEM
|
||||
BILDSCHIRM: Wer zwanzig Punkte gesetzt hat, saehe am naechsten
|
||||
Morgen eine leere Karte -- die Daten liegen noch da, man kommt nur
|
||||
nicht mehr hin. Das sieht aus wie ein Fehler und ist einer.
|
||||
|
||||
DESHALB WIRD EINMALIG UEBERNOMMEN, wozu es schon eine
|
||||
Einschaetzung gibt. Nicht mehr: Ein Punkt, den nie jemand gesetzt
|
||||
hat, war auch vorher nur Papier.
|
||||
|
||||
Ein INSERT, das Vorhandenes stehen laesst, UND EIN FLAG: Das Flag verhindert, dass die
|
||||
Uebernahme wiederkommt, nachdem jemand bewusst alles abgewaehlt
|
||||
hat -- sonst waere „ich will diesen Punkt nicht mehr" nach dem
|
||||
naechsten Neustart rueckgaengig gemacht, und niemand wuesste,
|
||||
warum. */
|
||||
INSERT INTO entwicklung_zuteilung (person_id, schluessel, seit, von_id)
|
||||
SELECT DISTINCT s.person_id, s.schluessel, MIN(s.am), NULL
|
||||
FROM entwicklung_stand s
|
||||
WHERE NOT EXISTS (SELECT 1 FROM einstellungen
|
||||
WHERE schluessel = 'entwicklung_zuteilung_uebernommen')
|
||||
GROUP BY s.person_id, s.schluessel
|
||||
ON CONFLICT(person_id, schluessel) DO NOTHING;
|
||||
|
||||
INSERT INTO einstellungen (schluessel, wert, geaendert, von)
|
||||
VALUES ('entwicklung_zuteilung_uebernommen', 'ja', datetime('now'), NULL)
|
||||
ON CONFLICT(schluessel) DO NOTHING;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS entwicklung_lauf (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
person_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE,
|
||||
@@ -488,6 +558,40 @@ function katalogFuer(person, ueberMichSelbst) {
|
||||
.map((b) => ({ ...b, punkte: ENTWICKLUNG_PUNKTE[b.schluessel] || [] }));
|
||||
}
|
||||
|
||||
/* ===== WELCHE PUNKTE GELTEN FUER DIESEN MENSCHEN? (25.09.2026) ======
|
||||
|
||||
Filipe: „ich will dass die rechte hand und ich diese aufgaben die es
|
||||
da gibt, individuel aussuchen koennen wer welche aufgabe bekommt.
|
||||
bis dahin sollen die keine aufgaben sehen."
|
||||
|
||||
EINE STELLE FUER DIE ANTWORT, drei Aufrufer: die Karte der Leitung,
|
||||
die Karte des Menschen selbst und die Uebersicht mit den Zahlen.
|
||||
Drei Abschriften waeren drei Gelegenheiten, dass eine davon beim
|
||||
naechsten Umbau nicht mitzieht -- und dann stuende in der Uebersicht
|
||||
„12 von 68", waehrend in der Karte elf Punkte stehen.
|
||||
|
||||
EIN SET UND KEINE LISTE: Gefragt wird 68-mal je Person („gehoert
|
||||
dieser Punkt dazu?"). In einer Liste ist das 68 x N Vergleiche, im
|
||||
Set 68 Nachschlagevorgaenge. */
|
||||
function zugeteiltFuer(personId) {
|
||||
return new Set(db().prepare(
|
||||
"SELECT schluessel FROM entwicklung_zuteilung WHERE person_id = ?")
|
||||
.all(personId).map((z) => z.schluessel));
|
||||
}
|
||||
|
||||
/* Der Katalog, auf das eingegrenzt, was fuer DIESEN Menschen gilt.
|
||||
`nurSelbst`-Bloecke bleiben unberuehrt: Das ist der Block, den die
|
||||
Person selbst beantwortet (warum Moderatoren aufhoeren), er gehoert
|
||||
niemandem sonst und ist nicht Teil der 68. */
|
||||
function katalogFuerPerson(person, personId, ueberMichSelbst) {
|
||||
const dabei = zugeteiltFuer(personId);
|
||||
return katalogFuer(person, ueberMichSelbst)
|
||||
.map((b) => (b.nurSelbst
|
||||
? b
|
||||
: { ...b, punkte: b.punkte.filter((pt) => dabei.has(pt.schluessel)) }))
|
||||
.filter((b) => b.nurSelbst || b.punkte.length);
|
||||
}
|
||||
|
||||
entwicklungRouter.get("/workspace/api/entwicklung/lage", (req, res) => {
|
||||
try {
|
||||
const leitung = fuehrtTeamDogi(req.person);
|
||||
@@ -514,6 +618,15 @@ entwicklungRouter.get("/workspace/api/entwicklung/lage", (req, res) => {
|
||||
.all(req.person.id, new Date(Date.now() - VERBLASST_NACH_TAGEN * TAG).toISOString());
|
||||
const alteZahl = new Map(alt.map((z) => [z.person_id, z.n]));
|
||||
|
||||
/* WIE VIELE PUNKTE GELTEN JE PERSON (25.09.2026). Ohne diese Zahl
|
||||
stuende in der Uebersicht weiter „von 68" -- und in der Karte
|
||||
daneben „von 12". Zwei Zahlen fuer dieselbe Sache, und die
|
||||
Uebersicht ist die, die man zuerst sieht. */
|
||||
const zugeteilt = db().prepare(
|
||||
"SELECT person_id, COUNT(*) AS n FROM entwicklung_zuteilung GROUP BY person_id")
|
||||
.all();
|
||||
const zuteilZahl = new Map(zugeteilt.map((z) => [z.person_id, z.n]));
|
||||
|
||||
const anders = db().prepare(
|
||||
`SELECT a.person_id, COUNT(*) AS n
|
||||
FROM entwicklung_stand a JOIN entwicklung_stand b
|
||||
@@ -544,6 +657,10 @@ entwicklungRouter.get("/workspace/api/entwicklung/lage", (req, res) => {
|
||||
gesetzt: meineZahl.get(p.id) || 0,
|
||||
veraltet: alteZahl.get(p.id) || 0,
|
||||
abweichungen: andersZahl.get(p.id) || 0,
|
||||
/* Wie viele Punkte fuer sie ausgesucht sind. `0` heisst
|
||||
ausdruecklich „noch keine" und nicht „keine Angabe" -- die
|
||||
Oberflaeche sagt dann „Punkte auswaehlen", nicht „0 von 68". */
|
||||
zugeteilt: zuteilZahl.get(p.id) || 0,
|
||||
})),
|
||||
/* Die eigene Karte -- fuer jeden, der im Team ist. */
|
||||
ich: { id: req.person.id, name: req.person.name },
|
||||
@@ -576,7 +693,9 @@ entwicklungRouter.get("/workspace/api/entwicklung/person/:id", nurLeitung, (req,
|
||||
|
||||
const grenze = Date.now() - VERBLASST_NACH_TAGEN * TAG;
|
||||
const schritte = schritteVon(p.id);
|
||||
const bloecke = katalogFuer(req.person, false).map((b) => ({
|
||||
/* NUR, WAS FUER IHN GILT (25.09.2026). Vorher stand hier
|
||||
`katalogFuer` -- alle 68 fuer jeden. */
|
||||
const bloecke = katalogFuerPerson(req.person, id, false).map((b) => ({
|
||||
...b,
|
||||
punkte: b.punkte.map((pt) => {
|
||||
const m = meine.get(pt.schluessel) || null;
|
||||
@@ -662,8 +781,24 @@ entwicklungRouter.get("/workspace/api/entwicklung/person/:id", nurLeitung, (req,
|
||||
erwartung: ENTWICKLUNG_ERWARTUNG,
|
||||
bloecke,
|
||||
abweichungen,
|
||||
gesetzt: meine.size,
|
||||
gesamt: ENTWICKLUNG_FREMD.length,
|
||||
/* „X VON Y" ZAEHLT AB HEUTE DAS ZUGETEILTE (25.09.2026).
|
||||
|
||||
`ENTWICKLUNG_FREMD.length` sind alle 68 -- die Zahl war damit
|
||||
fuer jeden gleich, egal wie viele Punkte ihn ueberhaupt
|
||||
betreffen. „0 von 68" bei jemandem, fuer den zwoelf gelten,
|
||||
ist keine Auskunft, sondern eine Entmutigung.
|
||||
|
||||
`meine.size` musste ebenfalls nachgezogen werden: Wer frueher
|
||||
zu einem Punkt gesetzt hat, der ihm inzwischen nicht mehr
|
||||
zugeteilt ist, haette sonst „13 von 12" bekommen. Gezaehlt
|
||||
wird deshalb nur, was auch in der Liste steht. */
|
||||
gesetzt: bloecke.reduce((n, b) => n
|
||||
+ b.punkte.filter((pt) => pt.meins).length, 0),
|
||||
gesamt: bloecke.reduce((n, b) => n + b.punkte.length, 0),
|
||||
/* Wie viele es insgesamt GAEBE -- die Oberflaeche sagt damit
|
||||
„12 von 68 ausgewaehlt", ohne die Liste selbst zu kennen. */
|
||||
gesamt_katalog: ENTWICKLUNG_FREMD.length,
|
||||
darf_zuteilen: true,
|
||||
});
|
||||
} catch (f) {
|
||||
console.error("[entwicklung] Person:", f?.message);
|
||||
@@ -729,6 +864,93 @@ function schritteVon(personId) {
|
||||
return nach;
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
WELCHE PUNKTE GELTEN FUER DIESEN MENSCHEN -- LESEN UND SETZEN
|
||||
=====================================================================
|
||||
|
||||
Filipe: „ich will dass die rechte hand und ich diese aufgaben die es
|
||||
da gibt, individuel aussuchen koennen wer welche aufgabe bekommt."
|
||||
|
||||
ZWEI WEGE UND NICHT EINER MIT SCHALTER: Lesen liefert den GANZEN
|
||||
Katalog mit einem Haken an dem, was gilt -- man waehlt nur aus, was
|
||||
man sieht. Die Karte selbst liefert weiterhin nur das Zugeteilte;
|
||||
die zwei Fragen sind verschieden und bleiben getrennt.
|
||||
|
||||
GESETZT WIRD DIE GANZE LISTE AUF EINMAL, nicht Punkt fuer Punkt. Wer
|
||||
zwoelf Haken setzt und dabei die Verbindung verliert, haette sonst
|
||||
sieben gesetzte und fuenf verlorene -- und saehe nicht, welche. So
|
||||
ist es eine Entscheidung, und sie gilt ganz oder gar nicht.
|
||||
===================================================================== */
|
||||
entwicklungRouter.get("/workspace/api/entwicklung/person/:id/punkte",
|
||||
nurLeitung, (req, res) => {
|
||||
try {
|
||||
const id = nummer(req.params.id);
|
||||
const p = id && db().prepare("SELECT id, name, rolle FROM personen WHERE id = ?").get(id);
|
||||
if (!p || !KARTEN_ROLLEN.includes(p.rolle)) {
|
||||
return res.status(404).json({ fehler: "nicht_gefunden" });
|
||||
}
|
||||
const dabei = zugeteiltFuer(id);
|
||||
res.json({
|
||||
person: { id: p.id, name: p.name, rolle_name: ROLLEN_NAME[p.rolle] ?? p.rolle },
|
||||
bloecke: katalogFuer(req.person, false).map((b) => ({
|
||||
schluessel: b.schluessel, name: b.name, text: b.text,
|
||||
punkte: b.punkte.map((pt) => ({
|
||||
schluessel: pt.schluessel, titel: pt.titel, text: pt.text || null,
|
||||
stufe: pt.stufe, dabei: dabei.has(pt.schluessel),
|
||||
})),
|
||||
})),
|
||||
});
|
||||
} catch (f) {
|
||||
console.error("[entwicklung] Punkte lesen:", f?.message);
|
||||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||||
}
|
||||
});
|
||||
|
||||
entwicklungRouter.put("/workspace/api/entwicklung/person/:id/punkte",
|
||||
gleicheHerkunft, nurLeitung, express.json({ limit: "32kb" }), (req, res) => {
|
||||
try {
|
||||
const id = nummer(req.params.id);
|
||||
const p = id && db().prepare("SELECT id, name, rolle FROM personen WHERE id = ?").get(id);
|
||||
if (!p || !KARTEN_ROLLEN.includes(p.rolle)) {
|
||||
return res.status(404).json({ fehler: "nicht_gefunden" });
|
||||
}
|
||||
/* NUR SCHLUESSEL, DIE ES WIRKLICH GIBT. Ohne diesen Abgleich
|
||||
liesse sich ueber diesen Weg beliebiger Text in die Tabelle
|
||||
schreiben -- und er stuende danach in keiner Liste, waere aber
|
||||
auch nicht wegzubekommen. */
|
||||
const erlaubt = new Set(ENTWICKLUNG_FREMD.map((pt) => pt.schluessel));
|
||||
const wunsch = Array.isArray(req.body?.punkte) ? req.body.punkte : null;
|
||||
if (!wunsch) return res.status(400).json({ fehler: "ungueltig" });
|
||||
const neu = [...new Set(wunsch.map(String).filter((s) => erlaubt.has(s)))];
|
||||
|
||||
const nun = jetzt();
|
||||
/* EINE TRANSAKTION. Zwischen „alles weg" und „neu eintragen"
|
||||
darf es keinen Moment geben, in dem die Person gar nichts hat
|
||||
-- sonst saehe ein gleichzeitiger Abruf eine leere Karte, und
|
||||
bei einem Fehler dazwischen bliebe sie leer. */
|
||||
db().exec("BEGIN");
|
||||
try {
|
||||
db().prepare("DELETE FROM entwicklung_zuteilung WHERE person_id = ?").run(id);
|
||||
const rein = db().prepare(`INSERT INTO entwicklung_zuteilung
|
||||
(person_id, schluessel, seit, von_id) VALUES (?,?,?,?)`);
|
||||
for (const s of neu) rein.run(id, s, nun, req.person.id);
|
||||
db().exec("COMMIT");
|
||||
} catch (f) {
|
||||
db().exec("ROLLBACK");
|
||||
throw f;
|
||||
}
|
||||
|
||||
protokolliere("entwicklung_punkte_zugeteilt", {
|
||||
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
|
||||
detail: `${p.name}: ${neu.length} Punkte`.slice(0, 120),
|
||||
});
|
||||
res.json({ ok: true, zugeteilt: neu.length });
|
||||
} catch (f) {
|
||||
console.error("[entwicklung] Punkte setzen:", f?.message);
|
||||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||||
}
|
||||
});
|
||||
|
||||
entwicklungRouter.post("/workspace/api/entwicklung/person/:id/:schluessel/schritt",
|
||||
gleicheHerkunft, nurLeitung, express.json({ limit: "8kb" }), (req, res) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user