Aufgaben: dauerhafte Aufgaben, die nicht abgehakt werden

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."

Nachgesehen: Das Wort kam im Aufgabenmodul kein einziges Mal vor. Es
war keine vergessene Zeile, es fehlte ganz.

WAS EINE DAUERHAFTE AUFGABE IST: keine, die man abarbeitet, sondern
eine, die man TUT. „Neue begruessen" ist nicht fertig, wenn man es
einmal gemacht hat.

ZWEI FOLGEN, und die zweite faellt leicht durchs Raster

1. Der Zugeteilte kann sie nicht auf „erledigt" setzen. Die Sperre
   steht im SERVER -- ein fehlender Knopf ist eine Bitte, abgelehnt
   wird an der Route. „Ich fange an" bleibt erlaubt: Auch eine
   stehende Aufgabe hat einen Anfang.

2. SIE HAT KEINE FRIST. Eine dauerhafte Aufgabe mit Frist waere ab
   dem naechsten Tag fuer immer ueberfaellig -- und eine Warnung, die
   immer kommt, ist keine mehr. Die Frist wird GELOESCHT, nicht
   ignoriert: Ein Datum, das dasteht und nicht gilt, ist schlimmer
   als keins.

WER DARF DAS SETZEN: nur, wer verteilt. Koennte der Zugeteilte seine
eigene Aufgabe dauerhaft machen, waere das eine Ausrede; koennte er
es zuruecknehmen, waere die Sperre ein Knopf weiter offen. Beides
nachgemessen.

UND SIE LAESST SICH BEENDEN. Eine Pflicht, die niemand mehr beenden
kann, waere eine Falle statt einer Regel.

DREI STELLEN, KEINE VIERTE: das Anlegeformular auf „Aufgaben", das
auf „Eure Aufgaben" (dort wird verteilt) und das Bearbeiten-Feld.
Ueber das letzte laeuft VanVans „oder ueber die Vorlage" -- eine
Vorlagen-Aufgabe entsteht ohne Formular, ein Schalter im
Vorlagenbrett waere eine vierte Stelle fuer dieselbe Frage.

An der Karte steht die Marke fuer ALLE, nicht nur fuer den
Zugeteilten: Wer sie ansieht, soll wissen, warum dort kein „Fertig"
steht. Ein fehlender Knopf ohne Erklaerung liest sich wie ein Fehler.

GEPRUEFT
pruef-bewerbung-aufgaben 154/0 (10 neue) mit vier Gegenproben: eine
GEWOEHNLICHE Aufgabe laesst sich sehr wohl abhaken (sonst hiesse 409
nur, dass niemand je etwas abhaken kann), „Ich fange an" geht
weiterhin, der Zugeteilte setzt und nimmt „dauerhaft" nicht, und nach
dem Beenden durch die Leitung geht das Abhaken wieder.

ZWEI EIGENE FEHLER, beide von Pruefungen gefunden

* Ein BACKTICK in einem Kommentar -- mitten in einem Template-String
  (`SPALTEN`). Er hat ihn beendet, die Datei war syntaktisch kaputt.
  Dieselbe Familie wie die deutsche Anfuehrung in einem
  Anfuehrungsstring: ein Zeichen, das in der Umgebung etwas bedeutet.
* `toISOString().slice(0,10)` fuer „morgen" -- pruef-struktur hat es
  noch am selben Abend gefunden. Zwischen 00:00 und 02:00 liegt der
  UTC-Tag noch auf gestern; die Pruefung haette nachts falsch
  angeschlagen. Jetzt ueber `tagLokal()`.

Und einer, den nur die Messung zeigen konnte: `holen()` in
workspace-zuteilung liest die Aufgabe mit einer eigenen, kurzen
Spaltenliste. Ohne `dauerhaft` darin fragte die Sperre `a.dauerhaft`
und bekam `undefined` -- sie war still wirkungslos, und im Quelltext
daneben sah alles richtig aus.

pruef-aufgabenbrett, pruef-zuteilung, pruef-aufgaben-vorlagen,
pruef-entwicklung 79/0, pruef-css-klassen, pruef-deutsche-texte,
pruef-struktur, pruef-zwischenspeicher 34/0.

Schemaaenderung: ADD COLUMN dauerhaft. Datenbank vorher gesichert und
geprueft (integrity_check ok, 12 Aufgaben).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-30 18:52:37 +02:00
co-authored by Claude Opus 5
parent 5e26df788b
commit d4a3e54a72
54 changed files with 1026 additions and 674 deletions
+107
View File
@@ -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)
=================================================================
+38 -2
View File
@@ -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).
+35 -1
View File
@@ -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);
+22
View File
@@ -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)
=====================================================================