diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs index 6ec29b99..df930559 100644 --- a/server/pruef-bewerbung-aufgaben.mjs +++ b/server/pruef-bewerbung-aufgaben.mjs @@ -867,6 +867,113 @@ melde("=== 6. Am echten Bildschirm ==="); } finally { await h2.ctx.close(); } } + /* ================================================================= + DAUERHAFTE AUFGABEN (30.09.2026) + + VanVan im Support: „Man kann bei den Aufgaben, wenn man sie + verteilt, ob selbst erstellt oder über die Vorlage noch nicht + festlegen, dass die Aufgabe dauerhaft sein soll und somit nicht + vom Modi in den Status erledigt gesetzt werden kann." + ================================================================= */ + console.log(""); + console.log("=== Dauerhafte Aufgaben ==="); + { + /* ORTSZEIT UND NICHT UTC. Meine erste Fassung rechnete + `toISOString().slice(0,10)` -- und `pruef-struktur` hat sie + noch am selben Abend gefunden: Zwischen 00:00 und 02:00 liegt + der UTC-Tag noch auf gestern, das Datum waere um einen Tag + daneben, und die Pruefung schluege nachts falsch an. Genau + die Sorte Zeitbombe, vor der die Hausregel warnt. */ + const { tagLokal } = await import("./helfer-tag.mjs"); + const morgen = tagLokal(1); + const neu = await roh("/workspace/api/aufgaben", { + methode: "POST", keks: hand.keks, + body: { + titel: "Neue im Chat begruessen", prioritaet: "mittel", + frist: morgen, dauerhaft: true, + zuteilung: [idModi], verteilart: "einzeln", + } }); + const dId = neu.json?.aufgabe?.id ?? neu.json?.id; + ok(neu.code === 201 || neu.code === 200, + `die rechte Hand legt eine dauerhafte Aufgabe an (${neu.code})`); + + const lesen = async (keks) => { + const l = await roh("/workspace/api/aufgaben", { keks }); + return (l.json?.aufgaben || l.json?.liste || []).find((x) => x.id === dId); + }; + const a1 = await lesen(hand.keks); + ok(!!a1 && !!a1.dauerhaft, `sie ist als dauerhaft gespeichert (${a1?.dauerhaft})`); + /* DIE FRIST IST WEG, und das ist der Punkt: Eine dauerhafte + Aufgabe mit Frist waere ab morgen fuer immer ueberfaellig. */ + ok(a1?.frist === null || a1?.frist === undefined, + `und hat KEINE Frist mehr, obwohl eine mitgeschickt wurde (${JSON.stringify(a1?.frist)})`); + + /* DER KERN DER MELDUNG. */ + await roh(`/workspace/api/aufgaben/${dId}/annehmen`, + { methode: "POST", keks: modi.keks }); + const fertig = await roh(`/workspace/api/aufgaben/${dId}/mein-stand`, { + methode: "POST", keks: modi.keks, body: { zustand: "erledigt" } }); + ok(fertig.code === 409 && fertig.json?.fehler === "dauerhafte_aufgabe", + `Frida kann sie nicht abhaken (${fertig.code}/${fertig.json?.fehler})`); + + /* GEGENPROBE 1: „Ich fange an" geht weiterhin. Auch eine + stehende Aufgabe hat einen Anfang -- gesperrt ist nur das + Abhaken, nicht die Arbeit. */ + const anfang = await roh(`/workspace/api/aufgaben/${dId}/mein-stand`, { + methode: "POST", keks: modi.keks, body: { zustand: "arbeit" } }); + ok(anfang.code === 200, `„Ich fange an" geht trotzdem (${anfang.code})`); + + /* GEGENPROBE 2: Bei einer GEWOEHNLICHEN Aufgabe geht das Abhaken. + Ohne sie hiesse „409" womoeglich nur, dass niemand je etwas + abhaken kann. */ + const normal = await roh("/workspace/api/aufgaben", { + methode: "POST", keks: hand.keks, + body: { titel: "Einmal die Liste durchsehen", prioritaet: "niedrig", + zuteilung: [idModi], verteilart: "einzeln" } }); + const nId = normal.json?.aufgabe?.id ?? normal.json?.id; + await roh(`/workspace/api/aufgaben/${nId}/annehmen`, + { methode: "POST", keks: modi.keks }); + const ok2 = await roh(`/workspace/api/aufgaben/${nId}/mein-stand`, { + methode: "POST", keks: modi.keks, body: { zustand: "erledigt" } }); + ok(ok2.code === 200, + `eine gewoehnliche Aufgabe haekt sie sehr wohl ab (${ok2.code})`); + + /* GEGENPROBE 3: EIN MODI LEGT GAR KEINE AUFGABEN AN. + Gemessen, nicht angenommen: Beim ersten Anlauf stand hier + „ein Modi legt sich selbst eine Aufgabe an" -- der Server + antwortete 403. `darfAufgabenAnlegen` laesst ihn nicht, und + das ist aelter als diese Aenderung. Die Zeile prueft jetzt, + was wirklich gilt. */ + const selbst = await roh("/workspace/api/aufgaben", { + methode: "POST", keks: modi.keks, + body: { titel: "Meine eigene Notiz", prioritaet: "niedrig", dauerhaft: true } }); + ok(selbst.code === 403, + `ein Modi legt ueberhaupt keine Aufgaben an (${selbst.code})`); + + /* GEGENPROBE 4: Und er macht auch keine bestehende dauerhaft -- + noch nimmt er es zurueck. Das ist der Kern: Koennte er es, + waere die ganze Sperre ein Knopf weiter offen. */ + + const zurueck = await roh(`/workspace/api/aufgaben/${dId}`, { + methode: "PATCH", keks: modi.keks, body: { dauerhaft: false } }); + const a2 = await lesen(hand.keks); + ok(!!a2?.dauerhaft, + `und der Zugeteilte nimmt „dauerhaft" nicht zurueck ` + + `(HTTP ${zurueck.code}, danach ${a2?.dauerhaft})`); + + /* UND DIE LEITUNG KANN SIE BEENDEN. Eine Pflicht, die niemand + mehr beenden kann, waere eine Falle statt einer Regel. */ + const beenden = await roh(`/workspace/api/aufgaben/${dId}`, { + methode: "PATCH", keks: hand.keks, body: { dauerhaft: false } }); + const a3 = await lesen(hand.keks); + ok(beenden.code === 200 && !a3?.dauerhaft, + `die rechte Hand beendet sie (${beenden.code}, danach ${a3?.dauerhaft})`); + const jetztFertig = await roh(`/workspace/api/aufgaben/${dId}/mein-stand`, { + methode: "POST", keks: modi.keks, body: { zustand: "erledigt" } }); + ok(jetztFertig.code === 200, + `und danach laesst sie sich abhaken (${jetztFertig.code})`); + } + /* ================================================================= 7. AUFGABEN DORT ANLEGEN, WO DIE PERSON STEHT (A5 und B6) ================================================================= diff --git a/server/workspace-aufgaben.js b/server/workspace-aufgaben.js index 26dcea6a..14024845 100644 --- a/server/workspace-aufgaben.js +++ b/server/workspace-aufgaben.js @@ -378,6 +378,17 @@ function darfLoeschen(person, aufgabe) { const SPALTEN = ` a.id, a.titel, a.beschreibung, a.status, a.prioritaet, a.aufwand, a.frist, + /* DAUERHAFT (30.09.2026). Ohne diese Spalte weiss die Oberflaeche + nichts davon -- sie zeigte weiter einen Fertig-Knopf, der eine + Absage holt. + + KEIN BACKTICK IN DIESEM KOMMENTAR: SPALTEN ist selbst ein + Template-String, und ein Backtick darin beendet ihn. Beim ersten + Anlauf stand hier ein Beispiel in Backticks -- die Datei war + danach syntaktisch kaputt. Dieselbe Familie wie die deutsche + Anfuehrung in einem Anfuehrungsstring: ein Zeichen, das in der + Umgebung etwas bedeutet. */ + a.dauerhaft, a.creator_id, a.verantwortlich_id, a.erstellt, a.geaendert, a.erledigt_am, a.creator_extern, a.verantwortlich_extern, ${externSql("pc.name", "a.creator_extern")} AS creator_name, @@ -706,6 +717,27 @@ function pruefeFelder(körper, { neu, person }) { else if (!/^\d{4}-\d{2}-\d{2}$/.test(f) || Number.isNaN(Date.parse(f))) fehler.push("Frist ist kein gültiges Datum."); else aus.frist = f; } + /* ==== DAUERHAFT (30.09.2026) ====================================== + + VanVan im Support: „… noch nicht festlegen, dass die Aufgabe + dauerhaft sein soll und somit nicht vom Modi in den Status + erledigt gesetzt werden kann." + + NUR, WER VERTEILT, DARF DAS SETZEN. Könnte der Zugeteilte seine + eigene Aufgabe dauerhaft machen, wäre das eine Ausrede; könnte er + es zurücknehmen, wäre die Sperre ein Knopf weiter offen. Ein + unerlaubtes Feld wird still übergangen und nicht abgelehnt — sonst + scheiterte ein Formular an einem Häkchen, das gar nicht gemeint + war. */ + if (körper.dauerhaft !== undefined && darfAufgabenVerteilen(person)) { + aus.dauerhaft = körper.dauerhaft ? 1 : 0; + /* UND DANN GIBT ES KEINE FRIST MEHR. Eine dauerhafte Aufgabe mit + Frist wäre ab dem nächsten Tag für immer überfällig — und eine + Warnung, die immer kommt, ist keine mehr. Sie wird gelöscht und + nicht bloß ignoriert: Ein Datum, das dasteht und nicht gilt, + ist schlimmer als keins. */ + if (aus.dauerhaft) aus.frist = null; + } for (const feld of ["creator_id", "verantwortlich_id"]) { if (körper[feld] === undefined) continue; const wert = körper[feld]; @@ -967,8 +999,8 @@ aufgabenRouter.post("/workspace/api/aufgaben", gleicheHerkunft, (req, res) => { INSERT INTO aufgaben (titel, beschreibung, status, prioritaet, creator_id, verantwortlich_id, creator_extern, verantwortlich_extern, frist, kategorie, kanal, - erstellt, erstellt_von, haus) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run( + dauerhaft, erstellt, erstellt_von, haus) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run( /* DAS HAUS WIRD BEIM ANLEGEN GESCHRIEBEN (25.09.2026) -- siehe das letzte Argument. Ohne es bekaeme eine Aufgabe, an der nur DogFather haengt (Pool-Aufgabe ohne Verantwortlichen), kein @@ -977,6 +1009,10 @@ aufgabenRouter.post("/workspace/api/aufgaben", gleicheHerkunft, (req, res) => { aus.prioritaet ?? "mittel", aus.creator_id ?? null, aus.verantwortlich_id ?? null, aus.creator_extern ?? null, aus.verantwortlich_extern ?? null, aus.frist ?? null, aus.kategorie ?? null, aus.kanal ?? null, + /* DAUERHAFT (30.09.2026). `pruefeFelder` laesst das Feld nur + durch, wenn die Person verteilen darf -- steht es nicht in + `aus`, ist es keine. */ + aus.dauerhaft ?? 0, jetzt(), req.person.id, req.person.haus || null); /* AN MENSCHEN VERTEILEN (21.09.2026, Abschnitt 3). diff --git a/server/workspace-zuteilung.js b/server/workspace-zuteilung.js index 7d6b9465..ee9a03e9 100644 --- a/server/workspace-zuteilung.js +++ b/server/workspace-zuteilung.js @@ -320,7 +320,16 @@ export function zuteilen(aufgabeId, personIds, art, vonId) { function holen(req, res) { const id = Number(req.params.id); if (!Number.isInteger(id)) { res.status(400).json({ fehler: "ungueltig" }); return null; } - const a = db().prepare("SELECT id, titel, status, verteilart FROM aufgaben WHERE id = ?").get(id); + /* `dauerhaft` GEHOERT DAZU (30.09.2026). Ohne dieses Feld war die + Sperre in `mein-stand` still wirkungslos: Sie fragte `a.dauerhaft` + und bekam `undefined`. Gefunden hat es die Pruefung -- im + Quelltext daneben sah alles richtig aus. + + AUFGEZAEHLT UND NICHT `SELECT *`: Diese Zeile liest eine Aufgabe + fuer Rechteentscheidungen. Was sie nicht braucht, soll sie auch + nicht in der Hand haben. */ + const a = db().prepare( + "SELECT id, titel, status, verteilart, dauerhaft FROM aufgaben WHERE id = ?").get(id); if (!a) { res.status(404).json({ fehler: "gibt_es_nicht" }); return null; } return a; } @@ -730,6 +739,31 @@ zuteilungRouter.post("/workspace/api/aufgaben/:id/mein-stand", (req, res) => { } if (z.zustand === "abgelehnt") return res.status(409).json({ fehler: "abgelehnt" }); + /* ==== EINE DAUERHAFTE AUFGABE WIRD NICHT FERTIG (30.09.2026) ==== + + VanVan im Support: „… dass die Aufgabe dauerhaft sein soll und + somit nicht vom Modi in den Status erledigt gesetzt werden + kann." + + Sie ist keine Aufgabe, die man abarbeitet, sondern eine, die + man TUT. „Neue begruessen" ist nicht fertig, wenn man es einmal + gemacht hat. + + „ICH FANGE AN" BLEIBT ERLAUBT. Auch eine stehende Aufgabe hat + einen Anfang, und der Unterschied zwischen „angenommen" und „in + Arbeit" sagt etwas. Gesperrt ist nur das Abhaken. + + DIE SPERRE STEHT HIER UND NICHT NUR IM BROWSER. Ein fehlender + Knopf ist eine Bitte; abgelehnt wird am Server -- wer die + Schnittstelle direkt anspricht, kaeme sonst durch. + + BEENDET WIRD SIE VON DER LEITUNG, ueber den Status der Aufgabe + selbst. Eine Pflicht, die niemand mehr beenden kann, waere eine + Falle statt einer Regel. */ + if (ziel === "erledigt" && a.dauerhaft) { + return res.status(409).json({ fehler: "dauerhafte_aufgabe" }); + } + db().prepare(`UPDATE aufgaben_zuteilung SET zustand = ?, erledigt_am = ? WHERE id = ?`).run(ziel, ziel === "erledigt" ? jetzt() : null, z.id); diff --git a/server/workspace.js b/server/workspace.js index 5e0e7827..f43560ed 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -4361,6 +4361,28 @@ function umstellungen(d) { } } + /* ==== DAUERHAFTE AUFGABEN (30.09.2026) ============================= + + VanVan im Support: „Man kann bei den Aufgaben … noch nicht + festlegen, dass die Aufgabe dauerhaft sein soll und somit nicht + vom Modi in den Status erledigt gesetzt werden kann." + + Eine dauerhafte Aufgabe ist keine, die man abarbeitet, sondern + eine, die man TUT. „Neue begruessen" ist nicht fertig, wenn man + es einmal gemacht hat. + + `ADD COLUMN` und keine neue Tabelle: Es ist eine Eigenschaft der + Aufgabe, kein eigener Gegenstand. */ + try { + const spalten = d.prepare("PRAGMA table_info(aufgaben)").all().map((x) => x.name); + if (!spalten.includes("dauerhaft")) { + d.exec("ALTER TABLE aufgaben ADD COLUMN dauerhaft INTEGER NOT NULL DEFAULT 0"); + console.log("[workspace] Spalte dauerhaft an aufgaben angelegt."); + } + } catch (fehler) { + console.error("[workspace] Spalte dauerhaft:", fehler?.message); + } + /* ===================================================================== WER VERANTWORTLICH IST, BRAUCHT EINE ZUTEILUNGSZEILE (30.09.2026) ===================================================================== diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 7224f03c..39c44b6b 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`. --> - - + + - +