diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs index df930559..33d16058 100644 --- a/server/pruef-bewerbung-aufgaben.mjs +++ b/server/pruef-bewerbung-aufgaben.mjs @@ -867,6 +867,89 @@ melde("=== 6. Am echten Bildschirm ==="); } finally { await h2.ctx.close(); } } + /* ================================================================= + EINE BEWERBUNG AUF EINE ERLEDIGTE AUFGABE IST KEINE (01.10.2026) + + Beim Durchsehen der echten Daten gefunden: Zwei Bewerbungen + standen auf „beworben" an Aufgaben, die laengst durch waren. + Bei der Person stand weiter „wartet auf Antwort", bei der + Leitung eine Entscheidung, die es nicht mehr gibt. + + DAS MUSTER GIBT ES IM HAUS SCHON -- `uebernahmeAbschliessen` + raeumt genauso auf, wenn jemand anderes eine Pool-Aufgabe + bekommt. Derselbe Fall, ein anderer Ausloeser. + ================================================================= */ + console.log(""); + console.log("=== Bewerbungen auf durchgelaufene Aufgaben ==="); + { + /* UEBER DIE LISTE, nicht ueber die Einzelaufgabe. Der erste + Anlauf las `/api/aufgaben/:id` und bekam `undefined` -- die + Antwort dort hat eine andere Form. Die Liste ist in dieser + Datei erprobt, und „erprobt" schlaegt „naheliegend". */ + const stand = async (id, wer) => { + const a = await roh("/workspace/api/aufgaben", { keks: hand.keks }); + const auf = (a.json?.aufgaben || a.json?.liste || []).find((x) => x.id === id); + return (auf?.zuteilung || []).find((x) => x.person_id === wer) || null; + }; + + /* ---- Weg 1: die Aufgabe wird ERLEDIGT ---------------------- */ + const e1 = await roh("/workspace/api/aufgaben", { + methode: "POST", keks: hand.keks, + body: { titel: "Kurz die Liste sortieren", prioritaet: "niedrig" } }); + const eId = e1.json?.aufgabe?.id ?? e1.json?.id; + const b1 = await roh(`/workspace/api/aufgaben/${eId}/bewerben`, { + methode: "POST", keks: modi.keks, body: { text: "Kann ich machen." } }); + ok(b1.code === 200, `Frida bewirbt sich (${b1.code})`); + ok((await stand(eId, idModi))?.zustand === "beworben", + "und steht auf „beworben\""); + + const fertig = await roh(`/workspace/api/aufgaben/${eId}`, { + methode: "PATCH", keks: hand.keks, body: { status: "erledigt" } }); + ok(fertig.code === 200, `die rechte Hand erledigt sie (${fertig.code})`); + + const nach1 = await stand(eId, idModi); + ok(nach1?.zustand !== "beworben", + `danach wartet Frida nicht mehr (${nach1?.zustand})`); + ok(/inzwischen erledigt/.test(nach1?.entscheid_text || ""), + `und liest, warum (${JSON.stringify(nach1?.entscheid_text)})`); + /* NIEMAND HAT ENTSCHIEDEN. Dadurch faellt die Zeile aus der + Absagen-Uebersicht heraus -- es ist keine Absage an sie. */ + ok(!nach1?.entschieden_von, + `niemand hat ihr abgesagt (entschieden_von=${JSON.stringify(nach1?.entschieden_von)})`); + + /* ---- Weg 2: die Aufgabe wird ABGEBROCHEN ------------------- */ + const e2 = await roh("/workspace/api/aufgaben", { + methode: "POST", keks: hand.keks, + body: { titel: "Etwas, das sich erledigt hat", prioritaet: "niedrig" } }); + const aId = e2.json?.aufgabe?.id ?? e2.json?.id; + await roh(`/workspace/api/aufgaben/${aId}/bewerben`, { + methode: "POST", keks: modi.keks, body: { text: "Mach ich." } }); + const ab = await roh(`/workspace/api/aufgaben/${aId}/abbrechen`, { + methode: "POST", keks: chef.keks, + body: { grund: "Brauchen wir doch nicht." } }); + const nach2 = await stand(aId, idModi); + ok(ab.code === 200 && nach2?.zustand !== "beworben", + `beim Abbrechen ebenso (${ab.code}, ${nach2?.zustand})`); + /* DER SATZ IST EIN ANDERER -- „abgebrochen" ist nicht + „erledigt", und wer gewartet hat, soll den Unterschied + lesen koennen. */ + ok(/abgebrochen/.test(nach2?.entscheid_text || ""), + `mit dem passenden Satz (${JSON.stringify(nach2?.entscheid_text)})`); + + /* ---- GEGENPROBE: eine OFFENE Aufgabe behaelt ihre Bewerbung. + Ohne sie hiesse „faellt weg" womoeglich nur, dass jede + Bewerbung wegfaellt. */ + const e3 = await roh("/workspace/api/aufgaben", { + methode: "POST", keks: hand.keks, + body: { titel: "Laeuft noch", prioritaet: "niedrig" } }); + const oId = e3.json?.aufgabe?.id ?? e3.json?.id; + await roh(`/workspace/api/aufgaben/${oId}/bewerben`, { + methode: "POST", keks: modi.keks, body: { text: "Gern." } }); + const nach3 = await stand(oId, idModi); + ok(nach3?.zustand === "beworben", + `eine offene Aufgabe behaelt ihre Bewerbung (${nach3?.zustand})`); + } + /* ================================================================= DAUERHAFTE AUFGABEN (30.09.2026) diff --git a/server/workspace-aufgaben.js b/server/workspace-aufgaben.js index 14024845..e7801120 100644 --- a/server/workspace-aufgaben.js +++ b/server/workspace-aufgaben.js @@ -28,7 +28,7 @@ const TEXT_MAX = 4000; const jetzt = () => new Date().toISOString(); -import { zuteilen, mitZuteilung } from "./workspace-zuteilung.js"; +import { zuteilen, mitZuteilung, bewerbungenSchliessen } from "./workspace-zuteilung.js"; /* ---------- Schranke ---------------------------------------------------- */ @@ -1064,6 +1064,17 @@ aufgabenRouter.patch("/workspace/api/aufgaben/:id", gleicheHerkunft, (req, res) if (aus.status === "erledigt" && aufgabe.status !== "erledigt") { setz.push("erledigt_am = ?"); daten.push(jetzt()); + /* WER SICH BEWORBEN HAT, WARTET NICHT WEITER (01.10.2026). + Eine erledigte Aufgabe hat keine offenen Bewerbungen mehr -- + sonst steht bei dem Menschen „wartet auf Antwort" auf etwas, + das es nicht mehr gibt, und bei der Leitung eine + Entscheidung, die sich erledigt hat. */ + const weg = bewerbungenSchliessen(aufgabe.id, + "Die Aufgabe wurde inzwischen erledigt."); + if (weg) { + console.log(`[aufgaben] ${weg} offene Bewerbung(en) zu #${aufgabe.id} ` + + "sind mit der Erledigung weggefallen."); + } } else if (aus.status && aus.status !== "erledigt") { setz.push("erledigt_am = NULL"); } @@ -1161,6 +1172,19 @@ aufgabenRouter.post("/workspace/api/aufgaben/:id/abbrechen", gleicheHerkunft, (r WHERE id = ?`) .run(aufgabe.status, grund, jetzt(), req.person.id, jetzt(), id); + /* AUCH HIER FALLEN DIE OFFENEN BEWERBUNGEN WEG (01.10.2026). + Zwei Wege fuehren in den Endzustand -- erledigen und abbrechen + --, und beide brauchen dieselbe Aufraeumung. Nur einen zu + bedienen waere die Haelfte, die man spaeter sucht. + + DER SATZ IST EIN ANDERER: „abgebrochen" ist nicht „erledigt", + und wer gewartet hat, soll den Unterschied lesen koennen. */ + const wegAb = bewerbungenSchliessen(id, "Die Aufgabe wurde abgebrochen."); + if (wegAb) { + console.log(`[aufgaben] ${wegAb} offene Bewerbung(en) zu #${id} ` + + "sind mit dem Abbruch weggefallen."); + } + protokolliere("aufgabe_abgebrochen", { personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req), detail: `#${id} (${aufgabe.status}) ${grund}`.slice(0, 120), diff --git a/server/workspace-zuteilung.js b/server/workspace-zuteilung.js index ee9a03e9..870d7671 100644 --- a/server/workspace-zuteilung.js +++ b/server/workspace-zuteilung.js @@ -334,6 +334,42 @@ function holen(req, res) { return a; } +/** Eine erledigte oder abgebrochene Aufgabe hat keine offenen + * Bewerbungen mehr (01.10.2026). + * + * GEFUNDEN AN DEN ECHTEN DATEN: Zwei Bewerbungen standen auf + * `beworben` an Aufgaben, die laengst durch waren. Bei den + * Bewerbern stand weiter „wartet auf Antwort", und in der Liste der + * Leitung stand eine Entscheidung an, die es nicht mehr gibt. + * + * DAS MUSTER GIBT ES HIER SCHON, eine Funktion weiter oben: + * `uebernahmeAbschliessen` raeumt genau so auf, wenn jemand anderes + * eine Pool-Aufgabe bekommt. Derselbe Fall, ein anderer Ausloeser -- + * deshalb dieselbe Behandlung und dasselbe Feld. + * + * KEIN `entschieden_von`: Niemand hat entschieden, die Frage hat + * sich erledigt. Dadurch faellt die Zeile auch aus der + * Absagen-Uebersicht heraus (die fragt `entschieden_von IS NOT + * NULL`) -- richtig, denn es ist keine Absage an diesen Menschen. + * + * @returns {number} wie viele weggefallen sind -- die Zahl braucht + * die Pruefung, und der Aufrufer soll nicht raten muessen. + */ +export function bewerbungenSchliessen(aufgabeId, grund) { + try { + const nun = jetzt(); + const e = db().prepare(`UPDATE aufgaben_zuteilung + SET zustand = 'abgelehnt', entscheid_text = ?, + entschieden_am = ?, geantwortet_am = ? + WHERE aufgabe_id = ? AND zustand = 'beworben'`) + .run(grund, nun, nun, Number(aufgabeId)); + return Number(e?.changes) || 0; + } catch (fehler) { + console.error("[zuteilung] Bewerbungen schliessen:", fehler?.message); + return 0; + } +} + /** Meine eigene Zuteilung zu dieser Aufgabe — oder nichts. */ function meine(aufgabeId, personId) { return db().prepare( diff --git a/server/workspace.js b/server/workspace.js index ded06aa7..15201b37 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -4445,6 +4445,53 @@ function umstellungen(d) { console.error("[workspace] Zuteilung nachtragen:", fehler?.message); } + /* ===================================================================== + BEWERBUNGEN AUF DURCHGELAUFENE AUFGABEN (01.10.2026) + ===================================================================== + + Beim Durchsehen der echten Daten am 30.09. gefunden: Zwei + Bewerbungen standen auf `beworben` an Aufgaben, die laengst + `erledigt` bzw. `review` waren. Bei der Person stand weiter + „wartet auf Antwort", und in der Liste der Leitung stand eine + Entscheidung an, die es nicht mehr gibt. + + Die Ursache ist behoben (`bewerbungenSchliessen` laeuft jetzt an + beiden Wegen in den Endzustand). Dieser Block holt nach, was + schon dasteht. + + OHNE `entschieden_von`: Niemand hat entschieden, die Frage hat + sich erledigt. Dadurch faellt die Zeile auch aus der + Absagen-Uebersicht heraus -- richtig, es ist keine Absage an + diesen Menschen. + + WIEDERHOLBAR: Beim zweiten Start gibt es nichts mehr mit + `zustand = 'beworben'` an einer durchgelaufenen Aufgabe. */ + try { + const vorher = d.prepare(`SELECT COUNT(*) AS n FROM aufgaben_zuteilung z + JOIN aufgaben a ON a.id = z.aufgabe_id + WHERE z.zustand = 'beworben' + AND a.status IN ('erledigt', 'abgebrochen', 'review')`).get().n; + if (vorher > 0) { + const nun = new Date().toISOString(); + d.prepare(`UPDATE aufgaben_zuteilung + SET zustand = 'abgelehnt', + entscheid_text = ( + SELECT CASE a.status WHEN 'abgebrochen' + THEN 'Die Aufgabe wurde abgebrochen.' + ELSE 'Die Aufgabe wurde inzwischen erledigt.' END + FROM aufgaben a WHERE a.id = aufgaben_zuteilung.aufgabe_id), + entschieden_am = ?, geantwortet_am = ? + WHERE zustand = 'beworben' + AND aufgabe_id IN (SELECT id FROM aufgaben + WHERE status IN ('erledigt', 'abgebrochen', 'review'))`) + .run(nun, nun); + console.log(`[workspace] ${vorher} Bewerbung(en) auf durchgelaufene ` + + "Aufgaben sind weggefallen."); + } + } catch (fehler) { + console.error("[workspace] Bewerbungen aufraeumen:", fehler?.message); + } + loeschspurenAufraeumen(d); /* ===================================================================== diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 982bbeb5..1debf2ab 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`. --> - - + + - +