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 <[email protected]>
This commit is contained in:
2026-09-22 09:12:20 +02:00
co-authored by Claude Opus 5
parent 06f3fecaa7
commit 5bde49e0cd
2 changed files with 130 additions and 3 deletions
+62
View File
@@ -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
===================================================================== */
+68 -3
View File
@@ -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,