From 8e18c7bcf0a111b1d8a2421bc4617a7fe14260dc Mon Sep 17 00:00:00 2001 From: Dogfather Date: Thu, 1 Oct 2026 00:59:27 +0200 Subject: [PATCH] Aufgaben: eine Bewerbung auf eine erledigte Aufgabe ist keine mehr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim Durchsehen der echten Daten am 30.09. gefunden, Filipe am 01.10.: „mach alles los." GEMESSEN: Zwei Bewerbungen von Miss standen auf „beworben" -- an Aufgaben, die laengst `erledigt` bzw. `review` waren. Bei ihr stand weiter „wartet auf Antwort", und in der Liste der Leitung stand eine Entscheidung an, die es nicht mehr gibt. DAS MUSTER GIBT ES IM HAUS SCHON. `uebernahmeAbschliessen` raeumt genau so die offenen Bewerbungen weg, wenn jemand anderes eine Pool-Aufgabe bekommt -- samt dem Kommentar daneben: „Ohne diese Zeile blieb eine Bewerbung auf ,beworben' stehen, nachdem jemand anders die Aufgabe bekommen hat." Derselbe Fall, ein anderer Ausloeser, dieselbe Behandlung. ZWEI WEGE FUEHREN IN DEN ENDZUSTAND -- erledigen und abbrechen. Beide rufen jetzt dieselbe Funktion; nur einen zu bedienen waere die Haelfte, die man spaeter sucht. Der SATZ ist verschieden: „abgebrochen" ist nicht „erledigt", und wer gewartet hat, soll den Unterschied lesen koennen. KEIN `entschieden_von`. Niemand hat entschieden, die Frage hat sich erledigt. Dadurch faellt die Zeile auch aus der Absagen-Uebersicht von gestern heraus (die fragt `entschieden_von IS NOT NULL`) -- richtig, es ist keine Absage an diesen Menschen. KEINE BENACHRICHTIGUNG. „Deine Bewerbung: diesmal nicht" waere falsch -- es hat niemand nein gesagt. Der Satz steht an der Zeile. Wenn Filipe hier doch eine Meldung will, ist es eine eigene Art mit eigenem Wortlaut, kein Anhaengsel an die bestehende. GEPRUEFT -- pruef-bewerbung-aufgaben 163/0 (9 neue): bewirbt sich -> „beworben" Aufgabe erledigt -> faellt weg, mit Satz, ohne Entscheider Aufgabe abgebrochen -> ebenso, mit anderem Satz Aufgabe noch offen -> Bewerbung bleibt <- die Gegenprobe Ohne die letzte Zeile hiesse „faellt weg" womoeglich nur, dass jede Bewerbung wegfaellt. Ein eigener Messfehler unterwegs: Mein Lesehelfer fragte `/api/aufgaben/:id` und bekam `undefined` -- die Antwort dort hat eine andere Form. Vier Pruefungen waren rot, waehrend der Mechanismus im Protokoll nachweislich lief. Jetzt ueber die Liste, die in dieser Datei erprobt ist. Die eine vorhandene Zeile wird nachgetragen; auf einer Kopie der echten Datenbank durchgespielt (danach 0 offene Bewerbungen auf durchgelaufenen Aufgaben, integrity_check ok). pruef-zuteilung, pruef-aufgabenbrett, pruef-zwischenspeicher. Co-Authored-By: Claude Opus 5 --- server/pruef-bewerbung-aufgaben.mjs | 83 +++++++++++++++++++++++++++++ server/workspace-aufgaben.js | 26 ++++++++- server/workspace-zuteilung.js | 36 +++++++++++++ server/workspace.js | 47 ++++++++++++++++ workspace/anruf-probe.html | 10 ++-- workspace/app.webmanifest | 8 +-- workspace/aufgaben.html | 38 ++++++------- workspace/automation.html | 32 +++++------ workspace/befinden.html | 34 ++++++------ workspace/bereich.html | 44 +++++++-------- workspace/bewerben.html | 38 ++++++------- workspace/bewerbungen.html | 36 ++++++------- workspace/buehne.html | 6 +-- workspace/calls.html | 34 ++++++------ workspace/chat.html | 40 +++++++------- workspace/content.html | 34 ++++++------ workspace/crew-index.html | 12 ++--- workspace/crew.webmanifest | 8 +-- workspace/dateien.html | 34 ++++++------ workspace/entwicklung.html | 36 ++++++------- workspace/hilfe.html | 30 +++++------ workspace/index.html | 10 ++-- workspace/kalender.html | 34 ++++++------ workspace/leistung.html | 32 +++++------ workspace/material.html | 32 +++++------ workspace/notizen.html | 28 +++++----- workspace/personen.html | 32 +++++------ workspace/profil.html | 36 ++++++------- workspace/reaktion.html | 38 ++++++------- workspace/rechte.html | 34 ++++++------ workspace/report.html | 36 ++++++------- workspace/scouting.html | 32 +++++------ workspace/start.html | 32 +++++------ workspace/startcheck.html | 34 ++++++------ workspace/steckbrief.html | 36 ++++++------- workspace/support.html | 28 +++++----- workspace/tafel.html | 6 +-- workspace/talente.html | 36 ++++++------- workspace/team.html | 32 +++++------ workspace/teamlage.html | 34 ++++++------ workspace/teilen.html | 30 +++++------ workspace/treff-moderation.html | 32 +++++------ workspace/treff-regeln.html | 32 +++++------ workspace/uebersicht.html | 32 +++++------ workspace/unsere-seiten.html | 30 +++++------ workspace/unterstuetzen.html | 32 +++++------ workspace/werdegang.html | 32 +++++------ workspace/willkommen.html | 30 +++++------ workspace/wissen.html | 34 ++++++------ 49 files changed, 861 insertions(+), 671 deletions(-) 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`. --> - - + + - +