diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs index 8602c20a..2d867fef 100644 --- a/server/pruef-bewerbung-aufgaben.mjs +++ b/server/pruef-bewerbung-aufgaben.mjs @@ -580,6 +580,124 @@ melde("=== 6. Am echten Bildschirm ==="); `und keinen Block zum Entscheiden (${b?.bewerbungen})`); } finally { await m2.ctx.close(); } + /* ================================================================= + UND JETZT WIRD ANGENOMMEN -- VanVans Meldung (30.09.2026) + + „Wenn ein Modi sich auf eine Aufgabe beworben hat und die von uns + angenommen wurde, dann steht beim Modi unter der Aufgabe immer + noch ich bewerbe mich." + + DIESER SCHRITT HAT HIER GEFEHLT. Die Pruefung ging den ganzen + Weg bis zur Bewerbung und hoerte dann auf. Serverseitig ist das + Annehmen weiter oben geprueft („Fridas Stand ist angenommen"); + was der Mensch danach auf seinem Brett LIEST, hat nie jemand + nachgesehen. + ================================================================= */ + const annahme = await roh(`/workspace/api/aufgaben/${frischId}/bewerbung/annehmen`, { + methode: "POST", keks: hand.keks, + body: { person_id: idModi, kommentar: "Gern, mach das." } }); + ok(annahme.code === 200 && annahme.json?.zustand === "angenommen", + `die rechte Hand nimmt Fridas Bewerbung an (${annahme.code}/${annahme.json?.zustand})`); + + const m3 = await seiteAls("modi", "CODE-MODI-0001", 390); + try { + const b = await knoepfeAn(m3.s, "Fuer den Bildschirm"); + ok(!!b, "Frida sieht die Aufgabe weiterhin"); + /* DER BEFUND. Ein Knopf, der nach der Zusage weiter „Ich + bewerbe mich" sagt, behauptet, es sei nichts entschieden -- + und wer ihn drueckt, bekommt eine Fehlermeldung auf etwas, + das laengst geklappt hat. */ + ok(!!b && !b.knoepfe.some((t) => /bewerbe mich/i.test(t)), + `und NICHT mehr „Ich bewerbe mich" (${b?.knoepfe.join(" | ")})`); + ok(!!b && !b.knoepfe.some((t) => /zurückziehen/i.test(t)), + "auch nichts mehr zum Zurueckziehen -- es gibt keine offene Bewerbung mehr"); + /* UND DAS EIGENTLICHE: Steht da, dass sie die Aufgabe HAT? + „Kein falscher Knopf" allein waere eine leere Karte. */ + ok(!!b && /(du machst|gehört dir|angenommen|übernommen|deine aufgabe)/i.test(b.text), + `sondern dass sie die Aufgabe hat ("${(b?.text || "").slice(0, 90)}…")`); + } finally { await m3.ctx.close(); } + + /* ================================================================= + DERSELBE WEG, ABER UEBER DAS VORLAGENBRETT -- VanVans Fall + + Der Abschnitt darueber geht ueber das Aufgabenbrett + (`/api/aufgaben/:id/bewerben`). Dort war alles in Ordnung. + + VanVans Weg ist der andere: Sie bewirbt sich auf eine VORLAGE. + Wird die angenommen, entsteht eine NEUE Aufgabe -- und die + bekam bis zum 30.09.2026 keine Zeile in `aufgaben_zuteilung`. + + WAS DAS BEDEUTET HAT. Das Brett liest die Zuteilung, nicht + `verantwortlich_id`. Ohne Zeile sagt es „niemand zugeteilt" und + bietet an, sich auf die eigene Aufgabe zu bewerben. Und weil + „Ich fange an" und „Fertig" an derselben Zeile haengen, fehlten + sie: eine Karte, auf der man das Falsche tun kann und das + Richtige nicht. + + Gemessen an den echten Daten hatten 8 von 11 Aufgaben mit einem + Verantwortlichen keine Zuteilungszeile. + ================================================================= */ + const vorl = await roh("/workspace/api/vorlagen", { keks: modi.keks }); + /* DIE VORLAGEN STEHEN UNTER `katalog.aufgaben`. Beim ersten + Anlauf habe ich auf `gruppen` geraten und einen leeren Treffer + bekommen -- die Pruefung meldete daraufhin „konnte nicht + nachsehen", und das war die richtige Antwort auf eine falsche + Frage. */ + /* DIE LETZTE UND NICHT DIE ERSTE. Ein spaeterer Abschnitt (Zeile + ~999) bewirbt sich ebenfalls, und zwar auf die ERSTE Vorlage. + Nahm ich sie auch, bekam er 409 („liegt schon bei dir") -- und + zehn Pruefungen wurden rot, ohne dass am Programm etwas falsch + war. Ein Messfehler, der wie ein Befund aussieht, und einer, + den ich selbst eingebaut hatte. */ + const katalogAufgaben = vorl.json?.katalog?.aufgaben || []; + const v = katalogAufgaben.at(-1); + if (!v) { + ok(false, "KONNTE NICHT NACHSEHEN: keine Vorlage gefunden " + + `(${Object.keys(vorl.json || {}).join(", ")})`); + } else { + const vb = await roh("/workspace/api/vorlagen/bewerben", { + methode: "POST", keks: modi.keks, + body: { vorlage: v.schluessel, text: "Das mache ich gern." } }); + ok(vb.code === 201 || vb.code === 200, + `Frida bewirbt sich auf die Vorlage „${v.titel}" (${vb.code})`); + + const offen2 = await roh("/workspace/api/vorlagen/bewerbungen", { keks: hand.keks }); + const bw = (offen2.json?.bewerbungen || []).find((x) => x.vorlage === v.schluessel); + ok(!!bw, `die rechte Hand sieht die Bewerbung (${offen2.json?.bewerbungen?.length})`); + + const vann = await roh("/workspace/api/vorlagen/bewerbung/annehmen", { + methode: "POST", keks: hand.keks, + body: { id: bw?.id, kommentar: "Gern, mach das." } }); + ok(vann.code === 200, `und nimmt sie an (${vann.code})`); + + /* ---- DER KERN: was steht jetzt auf Fridas Brett? ---------- */ + const nach = await roh("/workspace/api/aufgaben", { keks: modi.keks }); + const liste = nach.json?.aufgaben || nach.json?.liste || []; + const neue = liste.find((x) => x.vorlage === v.schluessel); + ok(!!neue, `die Aufgabe steht auf ihrem Brett (${liste.length} insgesamt)`); + ok(!!neue && !!neue.meine_zuteilung, + `und sie hat eine Zuteilungszeile (${JSON.stringify(neue?.meine_zuteilung?.zustand)})`); + ok(neue?.meine_zuteilung?.zustand === "angenommen", + `im Zustand „angenommen" -- sie hat darum gebeten und sie bekommen ` + + `(${neue?.meine_zuteilung?.zustand})`); + ok(neue?.ich_beworben === false, + `und es liegt keine offene Bewerbung mehr vor (${neue?.ich_beworben})`); + + const m4 = await seiteAls("modi", "CODE-MODI-0001", 390); + try { + const b = await knoepfeAn(m4.s, v.titel); + ok(!!b, "Frida sieht die neue Aufgabe mit ihrem Zuteilungsblock"); + /* VANVANS BEFUND. Ein Knopf, der nach der Zusage weiter „Ich + bewerbe mich" sagt, behauptet, es sei nichts entschieden. */ + ok(!!b && !b.knoepfe.some((t) => /bewerbe mich/i.test(t)), + `und NICHT „Ich bewerbe mich" (${b?.knoepfe.join(" | ")})`); + /* UND DIE ANDERE HAELFTE: Sie kann auch wirklich etwas tun. + „Kein falscher Knopf" allein waere eine Karte ohne Griff. */ + ok(!!b && b.knoepfe.some((t) => /fange an/i.test(t)), + `sondern „Ich fange an" (${b?.knoepfe.join(" | ")})`); + } finally { await m4.ctx.close(); } + } + /* ================================================================= 7. AUFGABEN DORT ANLEGEN, WO DIE PERSON STEHT (A5 und B6) ================================================================= diff --git a/server/workspace-vorlagen.js b/server/workspace-vorlagen.js index 41cf40ae..064c70bf 100644 --- a/server/workspace-vorlagen.js +++ b/server/workspace-vorlagen.js @@ -1509,7 +1509,34 @@ function gleicheHerkunft(req, res, next) { DIE KENNUNG DER VORLAGE WANDERT MIT. Nur so kann das Brett spaeter zeigen, was schon uebernommen ist -- der Titel taugt dafuer nicht, er darf geaendert werden. */ -function katalogAufgabeAnlegen(v, zielId, vonId) { +/** Legt eine Aufgabe aus einer Vorlage an -- MIT Zuteilungszeile. + * + * ==== DIE ZUTEILUNG HAT GEFEHLT (berichtigt 30.09.2026) ========== + * + * VanVan im Support: „Wenn ein Modi sich auf eine Aufgabe beworben + * hat und die von uns angenommen wurde, dann steht beim Modi unter + * der Aufgabe immer noch ich bewerbe mich." + * + * Hier stand nur das `INSERT INTO aufgaben` mit `verantwortlich_id`. + * Fuer das Aufgabenbrett war damit NIEMAND zugeteilt: Es liest + * `aufgaben_zuteilung`, findet nichts, und liefert + * `meine_zuteilung: null`. Die Bedingung im Browser beginnt mit + * `!mein` -- also bot sie dem Menschen an, sich auf eine Aufgabe zu + * bewerben, die ihm laengst gehoert. + * + * ES WAR NICHT NUR EIN FALSCHER KNOPF. „Ich fange an" und „Fertig" + * haengen ebenfalls an `mein.zustand`. Ohne Zuteilungszeile konnte + * er auf der Karte das Falsche tun und das Richtige nicht. + * + * GEMESSEN an den echten Daten: 8 von 11 Aufgaben mit einem + * Verantwortlichen hatten keine Zuteilungszeile. + * + * @param {string} zustand `angenommen`, wenn die Person darum + * gebeten hat und die Bitte angenommen wurde. `offen`, wenn die + * Leitung sie zutraegt -- dann steht bei ihr Annehmen/Ablehnen, + * und das ist richtig so: Sie hat noch nicht ja gesagt. + */ +function katalogAufgabeAnlegen(v, zielId, vonId, zustand = "offen") { const d = new Date(Date.now() + (v.tage || 7) * 86400_000); const frist = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}` + `-${String(d.getDate()).padStart(2, "0")}`; @@ -1519,7 +1546,16 @@ function katalogAufgabeAnlegen(v, zielId, vonId) { VALUES (?,?,'offen',?,?,?,?,?,?,?)`) .run(v.titel, v.text, v.tage <= 1 ? "hoch" : "mittel", zielId, frist, v.kategorie, v.schluessel, jetzt(), vonId); - return db().prepare("SELECT last_insert_rowid() AS id").get().id; + const id = db().prepare("SELECT last_insert_rowid() AS id").get().id; + const nun = jetzt(); + db().prepare(`INSERT INTO aufgaben_zuteilung + (aufgabe_id, person_id, zustand, zugeteilt_am, zugeteilt_von, geantwortet_am) + VALUES (?,?,?,?,?,?)`) + .run(id, zielId, zustand, nun, vonId ?? null, + /* „Wann geantwortet" nur, wenn schon geantwortet wurde. Bei + `offen` steht die Frage noch aus. */ + zustand === "offen" ? null : nun); + return id; } /* Wie lang darf ein Wort zu einer Bewerbung sein? Dieselbe Groesse wie @@ -1776,7 +1812,10 @@ for (const [weg, ja] of [["annehmen", true], ["ablehnen", false]]) { let aufgabeId = null; if (ja) { - aufgabeId = katalogAufgabeAnlegen(v, wer.id, req.person.id); + /* `angenommen`: Er hat darum gebeten, und die Bitte wurde + angenommen. Ihn danach noch einmal „Annehmen" druecken zu + lassen waere dieselbe Frage zweimal. */ + aufgabeId = katalogAufgabeAnlegen(v, wer.id, req.person.id, "angenommen"); } d.prepare(`UPDATE vorlagen_bewerbungen SET zustand = ?, entscheid_text = ?, entschieden_von = ?, diff --git a/server/workspace.js b/server/workspace.js index 349758a0..5e0e7827 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -4361,6 +4361,68 @@ function umstellungen(d) { } } + /* ===================================================================== + WER VERANTWORTLICH IST, BRAUCHT EINE ZUTEILUNGSZEILE (30.09.2026) + ===================================================================== + + VanVan im Support: „Wenn ein Modi sich auf eine Aufgabe beworben + hat und die von uns angenommen wurde, dann steht beim Modi unter + der Aufgabe immer noch ich bewerbe mich." + + GEMESSEN an den echten Daten: 11 Aufgaben hatten einen + Verantwortlichen, 8 davon KEINE Zeile in `aufgaben_zuteilung`. + Darunter beide Vorlagen-Aufgaben, die in ihrem Bildschirmfoto zu + sehen sind. + + Das Aufgabenbrett liest die Zuteilung, nicht `verantwortlich_id`. + Ohne Zeile sagt es „niemand zugeteilt" -- und bietet dem Menschen + an, sich auf seine eigene Aufgabe zu bewerben. „Ich fange an" und + „Fertig" fehlen ihm dabei, denn die haengen an derselben Zeile. + + Die Ursache ist behoben (`katalogAufgabeAnlegen` legt sie jetzt + mit an). Dieser Block holt nach, was schon dasteht. + + WELCHER ZUSTAND: Die Aufgabe GEHOERT der Person schon -- sie + jetzt „Annehmen/Ablehnen" druecken zu lassen waere eine Frage zu + etwas, das laengst entschieden ist. Also `angenommen`, und bei + abgeschlossenen Aufgaben der passende Endzustand. + + WIEDERHOLBAR: `NOT EXISTS` plus `INSERT OR IGNORE`. Beim zweiten + Start ist alles da und nichts passiert -- derselbe Gedanke wie + bei der Teilnehmer-Uebernahme darueber. */ + try { + const spalten = d.prepare("PRAGMA table_info(aufgaben)").all().map((x) => x.name); + if (spalten.includes("verantwortlich_id")) { + const vorher = d.prepare("SELECT COUNT(*) AS n FROM aufgaben_zuteilung").get().n; + d.exec(`INSERT OR IGNORE INTO aufgaben_zuteilung + (aufgabe_id, person_id, zustand, zugeteilt_am, zugeteilt_von, geantwortet_am) + SELECT a.id, a.verantwortlich_id, + CASE a.status + WHEN 'erledigt' THEN 'erledigt' + WHEN 'review' THEN 'erledigt' + WHEN 'abgebrochen' THEN 'abgelehnt' + WHEN 'arbeit' THEN 'arbeit' + ELSE 'angenommen' + END, + COALESCE(a.erstellt, datetime('now')), + a.erstellt_von, + COALESCE(a.erstellt, datetime('now')) + FROM aufgaben a + WHERE a.verantwortlich_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM aufgaben_zuteilung z + WHERE z.aufgabe_id = a.id + AND z.person_id = a.verantwortlich_id)`); + const nachher = d.prepare("SELECT COUNT(*) AS n FROM aufgaben_zuteilung").get().n; + if (nachher > vorher) { + console.log(`[workspace] ${nachher - vorher} Aufgaben mit Verantwortlichem ` + + "haben ihre fehlende Zuteilungszeile bekommen."); + } + } + } catch (fehler) { + console.error("[workspace] Zuteilung nachtragen:", fehler?.message); + } + loeschspurenAufraeumen(d); /* ===================================================================== diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 906e2fc3..60402299 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +