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`. -->
-
-
+
+
-
+