From 5bde49e0cd7f1e0c204b33153b36630bd14a4332 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Tue, 22 Sep 2026 09:12:20 +0200 Subject: [PATCH] Was unten im Brett steht, steht jetzt auch oben im Band Filipe am 22.09.: "die aufgaben die man unten sieht soll man auch oben sehen." GEMESSEN an den echten Daten: aufgaben_zuteilung hatte NULL Zeilen, waehrend im Brett zwei Aufgaben standen (OFFEN 1, IN ARBEIT 1). Jede Person las oben "nichts zugeteilt". Die Aufgaben hingen am aelteren Feld aufgaben.verantwortlich_id -- genau dem Feld, nach dem das Brett gruppiert. Die Uebersicht las eine andere Quelle als die Liste darunter. resuemeeFuer() zaehlt jetzt BEIDE Wege und keinen doppelt: Gibt es zu einer Aufgabe eine Zuteilungszeile fuer die Person, gewinnt die Zuteilung (genauerer Zustand). Nur wo keine Zeile existiert, zaehlt das alte Feld. Die Zuordnung status->zustand steht an EINER Stelle; "review" zaehlt wie "arbeit", "abgebrochen" zaehlt nirgends -- so wie im Brett auch. Die Personenliste wird nicht mehr aufgezaehlt, sondern abgeleitet: die Rollen des Hauses (immer, auch mit null Aufgaben -- wer frei ist, ist die haeufigste Frage) PLUS jede aktive Person, der eine Aufgabe gehoert. Ohne den zweiten Teil koennte im Brett eine Aufgabe stehen, deren Mensch oben fehlt. pruef-zuteilung 71/0 (war 65). Der neue Abschnitt legt eine Aufgabe GENAU SO an wie die echten alten -- verantwortlich_id, keine Zuteilungszeile -- und misst Filipes Satz direkt: "unten im Brett stehen genauso viele wie oben (2 = 2)". Zahl in der Bedingung, nicht nur im Meldetext. Co-Authored-By: Claude Opus 5 --- server/pruef-zuteilung.mjs | 62 ++++++++++++++++++++++++++++++ server/workspace-zuteilung.js | 71 +++++++++++++++++++++++++++++++++-- 2 files changed, 130 insertions(+), 3 deletions(-) diff --git a/server/pruef-zuteilung.mjs b/server/pruef-zuteilung.mjs index 00ce4396..56f5f37b 100644 --- a/server/pruef-zuteilung.mjs +++ b/server/pruef-zuteilung.mjs @@ -333,6 +333,68 @@ melde("\n=== Das Resuemee ==="); ok(modiTeam.code === 403, `ein Modi bekommt es nicht (HTTP ${modiTeam.code})`); } +/* ===================================================================== + 5b. WAS UNTEN IM BRETT STEHT, STEHT AUCH OBEN IM BAND (22.09.2026) + ===================================================================== + Filipe: "die aufgaben die man unten sieht soll man auch oben sehen." + + Gemessen war es so: aufgaben_zuteilung leer, im Brett zwei Aufgaben, + oben bei jeder Person "nichts zugeteilt". Die Aufgaben hingen am + aelteren Feld verantwortlich_id -- genau dem, nach dem das Brett + gruppiert. + + Deshalb wird hier eine Aufgabe GENAU SO angelegt, wie die echten + alten liegen: mit verantwortlich_id und OHNE Zeile in + aufgaben_zuteilung. Eine Aufgabe ueber die Schnittstelle anzulegen + wuerde den Fall nicht treffen -- die legt heute beides an. */ +melde("\n=== Die alten Aufgaben zaehlen oben mit ==="); +{ + const vorher = await rufe("/workspace/api/aufgaben/team-resuemee", { keks: chef }); + const cemVor = (vorher.json?.leute || []).find((x) => x.name === "Cem") || {}; + + /* Die Verbindung von oben ist laengst zu (Zeile 115). Eine eigene, + kurz geoeffnete -- und gleich wieder geschlossen, damit sie dem + laufenden Server nicht im Weg steht. */ + { + const dd = new DatabaseSync(process.env.WORKSPACE_DB); + const setz = dd.prepare(`INSERT INTO aufgaben + (titel, status, verantwortlich_id, erstellt_von, erstellt) + VALUES (?,?,?,?,?)`); + const jetzt = new Date().toISOString(); + setz.run("Alte Aufgabe ohne Zuteilung", "arbeit", idCem, idChef, jetzt); + setz.run("Alte Aufgabe, noch offen", "offen", idCem, idChef, jetzt); + /* Eine abgebrochene darf NICHT mitzaehlen -- sie steht auch im + Brett in keiner der beiden Spalten. */ + setz.run("Alte Aufgabe, abgebrochen", "abgebrochen", idCem, idChef, jetzt); + dd.close(); + } + + const nachher = await rufe("/workspace/api/aufgaben/team-resuemee", { keks: chef }); + const cem = (nachher.json?.leute || []).find((x) => x.name === "Cem") || {}; + + /* DIE ZAHL GEHOERT IN DIE BEDINGUNG. Ein Vergleich von zwei Nullen + waere gruen, ohne irgendetwas bewiesen zu haben. */ + ok((cemVor.dran || 0) + 2 === (cem.dran || 0), + `Cems "dran" steigt um genau 2 (${cemVor.dran || 0} -> ${cem.dran || 0})`); + ok(cem.offen >= 1, `eine offene alte Aufgabe zaehlt (offen: ${cem.offen})`); + ok(cem.arbeit >= 1, `eine laufende alte Aufgabe zaehlt (arbeit: ${cem.arbeit})`); + ok((cemVor.gesamt || 0) + 2 === (cem.gesamt || 0), + `die abgebrochene zaehlt NICHT mit (gesamt ${cemVor.gesamt || 0} -> ${cem.gesamt || 0})`); + + /* GEGENPROBE AM BRETT: Genau so viele, wie oben stehen, stehen auch + unten -- das ist Filipes Satz, als Messung. */ + const brett = await rufe("/workspace/api/aufgaben", { keks: chef }); + const untenCem = (brett.json?.aufgaben || []) + .filter((a) => a.verantwortlich_id === idCem + && (a.status === "offen" || a.status === "arbeit" || a.status === "review")); + ok(untenCem.length >= 2 && untenCem.length === (cem.dran || 0), + `unten im Brett stehen genauso viele wie oben (${untenCem.length} = ${cem.dran})`); + + /* Und die Person darf nicht aus der Liste fallen, nur weil sie + ueber den alten Weg etwas hat. */ + ok(!!cem.id, "Cem steht in der Liste"); +} + /* ===================================================================== 6. EINZELN -- der haeufigste Fall, und die alte Spalte stimmt noch ===================================================================== */ diff --git a/server/workspace-zuteilung.js b/server/workspace-zuteilung.js index 8124f6b5..162fe0de 100644 --- a/server/workspace-zuteilung.js +++ b/server/workspace-zuteilung.js @@ -56,6 +56,7 @@ import { db, protokolliere, echteIp, darfAufgabenVerteilen, istDogFather, istHand, sitzungLesen, } from "./workspace.js"; +import { TEAM_DOGI_ROLLEN } from "./crew-adresse.js"; export const zuteilungRouter = express.Router(); @@ -450,13 +451,59 @@ zuteilungRouter.post("/workspace/api/aufgaben/:id/bewerten", (req, res) => { * ABGELEITET AUS DEN ZUSTÄNDEN, nicht aus einer zweiten Tabelle: Eine * mitgeführte Zahl ist eine Zahl von gestern, sobald jemand etwas * ändert und die Stelle vergisst. */ +/** Der Zustand einer Aufgabe, die jemandem auf dem ALTEN Weg gehoert. + * + * Die Aufgabentabelle kennt offen/arbeit/review/erledigt/abgebrochen, + * die Zuteilung kennt offen/angenommen/arbeit/erledigt/abgelehnt. + * "review" heisst: liegt zur Freigabe, ist also noch nicht vom Tisch -- + * deshalb zaehlt es wie "arbeit". "abgebrochen" ist weder dran noch + * erledigt und zaehlt gar nicht mit; es erscheint auch im Brett nicht + * in den Spalten OFFEN und IN ARBEIT. + * + * Die Zuordnung steht hier an EINER Stelle. Wer sie an zwei Stellen + * schreibt, hat beim naechsten Statuswert eine, die niemand nachzieht. */ +const STATUS_ALS_ZUSTAND = { + offen: "offen", arbeit: "arbeit", review: "arbeit", erledigt: "erledigt", +}; + export function resuemeeFuer(personId) { + /* ZWEI QUELLEN, EIN ERGEBNIS (22.09.2026). + ========================================================== + Filipe: "die aufgaben die man unten sieht soll man auch oben + sehen." + + Gemessen an den echten Daten: aufgaben_zuteilung hatte NULL + Zeilen, waehrend im Brett zwei Aufgaben standen (OFFEN 1, + IN ARBEIT 1). Jede Person las oben "nichts zugeteilt" -- weil + diese Zaehlung nur die neue Tabelle kannte. Die Aufgaben hingen + am aelteren Feld `aufgaben.verantwortlich_id`, und das ist genau + das Feld, nach dem das BRETT gruppiert. + + Eine Uebersicht, die eine andere Quelle liest als die Liste + darunter, widerspricht ihr frueher oder spaeter. Deshalb zaehlen + hier BEIDE Wege -- und keiner doppelt: Gibt es zu einer Aufgabe + eine Zuteilungszeile fuer diese Person, gewinnt die Zuteilung + (sie hat den genaueren Zustand: angenommen, abgelehnt, bewertet). + Nur wo keine Zeile existiert, zaehlt das alte Feld. */ const zeilen = db().prepare(` SELECT z.zustand, z.bewertung, a.frist, a.status AS aufgabe_status FROM aufgaben_zuteilung z JOIN aufgaben a ON a.id = z.aufgabe_id WHERE z.person_id = ?`).all(Number(personId)); + const alte = db().prepare(` + SELECT a.status, a.frist + FROM aufgaben a + WHERE a.verantwortlich_id = ? + AND NOT EXISTS (SELECT 1 FROM aufgaben_zuteilung z + WHERE z.aufgabe_id = a.id AND z.person_id = a.verantwortlich_id) + `).all(Number(personId)); + for (const a of alte) { + const zustand = STATUS_ALS_ZUSTAND[a.status]; + if (!zustand) continue; /* abgebrochen: zaehlt nirgends */ + zeilen.push({ zustand, bewertung: null, frist: a.frist, aufgabe_status: a.status }); + } + const heute = new Date().toISOString().slice(0, 10); const zaehl = { offen: 0, angenommen: 0, arbeit: 0, erledigt: 0, abgelehnt: 0 }; let ueberfaellig = 0, gelobt = 0, zuVerbessern = 0; @@ -510,11 +557,29 @@ zuteilungRouter.get("/workspace/api/aufgaben/team-resuemee", (req, res) => { Abgeleitet aus der Rollenliste dieses Hauses statt aus einer eigenen Aufzählung — wer morgen eine Rolle hinzufügt, muss hier nichts nachziehen. */ + /* WER IN DER LISTE STEHT -- ABGELEITET, NICHT AUFGEZAEHLT. + ========================================================== + Die Rollen des Hauses stehen immer da, auch mit null Aufgaben: + Wer frei ist, ist die haeufigste Frage beim Verteilen. + + Dazu kommt JEDE aktive Person, der im Brett eine Aufgabe + gehoert. Ohne diesen zweiten Teil koennte im Brett eine Aufgabe + stehen, deren Mensch oben fehlt -- genau der Widerspruch, den + Filipe am 22.09. gemeldet hat, nur an anderer Stelle. So kann + das nicht wieder entstehen: Die Liste folgt den Aufgaben. */ + const rollen = [...TEAM_DOGI_ROLLEN]; + const platz = rollen.map(() => "?").join(","); const leute = db().prepare(` SELECT id, name, rolle FROM personen - WHERE aktiv = 1 AND rolle IN ('modi','hand','linke') - ORDER BY CASE rolle WHEN 'hand' THEN 0 WHEN 'linke' THEN 1 ELSE 2 END, - name COLLATE NOCASE`).all(); + WHERE aktiv = 1 + AND (rolle IN (${platz}) + OR id IN (SELECT verantwortlich_id FROM aufgaben + WHERE verantwortlich_id IS NOT NULL) + OR id IN (SELECT person_id FROM aufgaben_zuteilung)) + AND rolle NOT IN ('creator','scout','manager','spicy','gast') + ORDER BY CASE rolle WHEN 'admin' THEN 0 WHEN 'hand' THEN 1 + WHEN 'linke' THEN 2 ELSE 3 END, + name COLLATE NOCASE`).all(...rollen); res.json({ leute: leute.map((p) => ({ ...p, ...resuemeeFuer(p.id) })), darf_bewerten: true,