diff --git a/server/pruef-aufgaben-vorlagen.mjs b/server/pruef-aufgaben-vorlagen.mjs
index b8dfa060..aab4560a 100644
--- a/server/pruef-aufgaben-vorlagen.mjs
+++ b/server/pruef-aufgaben-vorlagen.mjs
@@ -75,6 +75,9 @@ const keks = async (rolle, code) => {
};
const kScout = await keks("scout", "CODE-SCOU-0001");
const kTili = await keks("creator", "CODE-ANNA-0001");
+/* DogFather darf als Einziger „dauerhaft" setzen -- deshalb braucht
+ diese Pruefung seit dem 02.10.2026 auch seinen Zugang. */
+const kDogi = await keks("admin", "CODE-DOGI-0001");
const ruf = (pfad, k, o = {}) => fetch(BASIS + pfad, { ...o, headers: { Cookie: k, ...(o.headers || {}) } });
const senden = (pfad, k, art, koerper) => ruf(pfad, k, {
method: art, headers: { "Content-Type": "application/json" }, body: JSON.stringify(koerper || {}) });
@@ -234,6 +237,119 @@ console.log("\n=== Von der Vorlage zur Aufgabe");
`alle ${sollen} einer Stufe auf einmal (${vorher} → ${nachher})`);
}
+/* ---------- 2b. Frist, „dauerhaft" und eine Anmerkung -------------------- */
+/* =========================================================================
+ VanVan im Support, Meldung #6, zweite Haelfte: „Bei den Vorlagen laesst
+ sich weder die Frist anpassen noch ‚dauerhaft' einstellen, und eine
+ Anmerkung fehlt auch."
+
+ GEPRUEFT WIRD AM SERVER, nicht am Fenster. Das Fenster bietet die drei
+ Felder an; was ankommt, ist trotzdem eine Zeichenkette aus dem Netz.
+ ========================================================================= */
+console.log("\n=== Frist, dauerhaft und Anmerkung");
+{
+ const in20 = (() => {
+ const d = new Date(Date.now() + 20 * 86400000);
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`
+ + `-${String(d.getDate()).padStart(2, "0")}`;
+ })();
+
+ /* ---- Eine eigene Frist --------------------------------------------- */
+ const mitFrist = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
+ { art: "aufgabe", bereich: "technik", stufe: "fort", nr: 0,
+ creator_id: idTili, frist: in20 });
+ const nachFrist = ((await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || [])
+ .find((x) => x.titel === V.technik.fort[0].titel);
+ ok(mitFrist.ok && nachFrist?.frist === in20,
+ `eine eigene Frist kommt durch (${nachFrist?.frist}, gewollt ${in20})`);
+ /* GEGENPROBE: Die Vorlage selbst sieht eine ANDERE Frist vor. Ohne
+ diesen Vergleich waere die Zeile oben auch dann gruen, wenn der
+ Server die Angabe verwirft und zufaellig dasselbe herauskommt. */
+ ok(V.technik.fort[0].tage !== 20,
+ ` und die Vorlage selbst saehe ${V.technik.fort[0].tage} Tage vor — `
+ + "die Angabe hat also wirklich gewirkt");
+
+ /* ---- Eine Anmerkung wird zur ersten Notiz --------------------------- */
+ const satz = "Bitte zuerst mit Rieke absprechen.";
+ const mitNotiz = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
+ { art: "aufgabe", bereich: "technik", stufe: "fort", nr: 1,
+ creator_id: idTili, anmerkung: satz });
+ const mitNotizA = ((await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || [])
+ .find((x) => x.titel === V.technik.fort[1].titel);
+ ok(mitNotiz.ok && mitNotizA?.notizen === 1,
+ `die Anmerkung wird eine Notiz an der Aufgabe (${mitNotizA?.notizen})`);
+ const nz = await (await ruf(`/workspace/api/aufgaben/${mitNotizA?.id}/notizen`, kTili)).json();
+ ok((nz.notizen || []).length === 1 && nz.notizen[0].text === satz,
+ ` und zwar woertlich („${(nz.notizen || [])[0]?.text}")`);
+ /* SIE TRAEGT, VON WEM SIE STAMMT -- in die Beschreibung geschrieben
+ waere das verloren, und beim naechsten Mal haette dieselbe Vorlage
+ einen anderen Text. */
+ ok(!!(nz.notizen || [])[0]?.von,
+ ` mit dem Namen dessen, der sie geschrieben hat (${(nz.notizen || [])[0]?.von})`);
+
+ /* ---- OHNE ANGABE AENDERT SICH NICHTS -------------------------------- */
+ const ohne = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
+ { art: "aufgabe", bereich: "technik", stufe: "fort", nr: 2,
+ creator_id: idTili });
+ const ohneA = ((await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || [])
+ .find((x) => x.titel === V.technik.fort[2].titel);
+ ok(ohne.ok && ohneA?.notizen === 0 && !ohneA?.dauerhaft && !!ohneA?.frist,
+ `ohne Angabe bleibt alles wie bisher (${ohneA?.notizen} Notizen, `
+ + `dauerhaft ${ohneA?.dauerhaft || 0}, Frist ${ohneA?.frist})`);
+
+ /* ---- „Dauerhaft" darf nur, wer verteilen darf ------------------------ */
+ const alsScout = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
+ { art: "aufgabe", bereich: "live", stufe: "fort", nr: 0,
+ creator_id: idTili, dauerhaft: true });
+ ok(alsScout.status === 403,
+ `ein Scout kann keine dauerhafte Aufgabe anlegen (${alsScout.status})`);
+ /* UND ES ENTSTEHT AUCH KEINE OHNE DEN HAKEN. Eine Absage, die
+ trotzdem etwas anlegt, waere schlimmer als gar keine. */
+ const nixDa = ((await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || [])
+ .filter((x) => x.titel === V.live.fort[0].titel).length;
+ ok(nixDa === 0, ` und die Absage legt auch nichts an (${nixDa})`);
+
+ const alsChef = await senden("/workspace/api/vorlagen/uebernehmen", kDogi, "POST",
+ { art: "aufgabe", bereich: "live", stufe: "fort", nr: 0,
+ creator_id: idTili, dauerhaft: true, frist: in20 });
+ const dauerA = ((await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || [])
+ .find((x) => x.titel === V.live.fort[0].titel);
+ ok(alsChef.ok && !!dauerA?.dauerhaft,
+ `DogFather kann es (${alsChef.status}, dauerhaft ${dauerA?.dauerhaft})`);
+ /* UND DANN GIBT ES KEINE FRIST -- obwohl eine mitgeschickt wurde.
+ Eine dauerhafte Aufgabe mit Frist waere ab morgen fuer immer
+ ueberfaellig, und eine Warnung, die immer kommt, ist keine mehr.
+ Dieselbe Regel steht im Aenderungsweg; haette sie hier gefehlt,
+ gaebe es zwei Antworten auf dieselbe Frage. */
+ ok(alsChef.ok && !dauerA?.frist,
+ ` und sie hat KEINE Frist, auch wenn eine mitkam (${dauerA?.frist ?? "keine"})`);
+
+ /* ---- Was der Server nicht annimmt ----------------------------------- */
+ const krumm = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
+ { art: "aufgabe", bereich: "live", stufe: "meister", nr: 0,
+ creator_id: idTili, frist: "morgen" });
+ ok(krumm.status === 400, `„morgen" ist kein Datum (${krumm.status})`);
+
+ const unmoeglich = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
+ { art: "aufgabe", bereich: "live", stufe: "meister", nr: 0,
+ creator_id: idTili, frist: "2026-13-45" });
+ ok(unmoeglich.status === 400, `den 45. des 13. Monats gibt es nicht (${unmoeglich.status})`);
+
+ const vorbei = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
+ { art: "aufgabe", bereich: "live", stufe: "meister", nr: 0,
+ creator_id: idTili, frist: "2020-01-01" });
+ ok(vorbei.status === 400,
+ `eine Frist in der Vergangenheit wird abgelehnt (${vorbei.status}) — sonst waere `
+ + "die Aufgabe in dem Moment rot, in dem sie entsteht");
+
+ /* GEGENPROBE ZU DEN DREI ABSAGEN: Dieselbe Vorlage OHNE Frist geht
+ durch. Ohne sie koennte die Route alles ablehnen und waere dreimal
+ gruen. */
+ const dochGut = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
+ { art: "aufgabe", bereich: "live", stufe: "meister", nr: 0, creator_id: idTili });
+ ok(dochGut.ok, `Gegenprobe: dieselbe Vorlage ohne Frist geht durch (${dochGut.status})`);
+}
+
/* ---------- 3. Gegenproben ------------------------------------------------ */
console.log("\n=== Gegenproben");
{
@@ -416,6 +532,92 @@ console.log("\n=== Auf der Aufgabenseite");
`die übrigen ${m.gesamt - m.markiert} sind nicht markiert`);
}
+ /* ---- ANPASSEN: FRIST UND ANMERKUNG AUS DEM FENSTER (02.10.2026) ---
+
+ VanVan im Support, Meldung #6: „Bei den Vorlagen laesst sich weder
+ die Frist anpassen noch ‚dauerhaft' einstellen, und eine Anmerkung
+ fehlt auch."
+
+ DIE SERVERSEITE STEHT WEITER OBEN -- hier geht es um den Weg, den
+ SIE nimmt: Knopf, Fenster, Felder, und ob am Ende wirklich die
+ Frist dransteht, die sie eingetragen hat. Ein Server, der die
+ Angabe annimmt, hilft niemandem, dessen Fenster sie nicht
+ mitschickt. */
+ {
+ const anpassen = seite.locator("#vorlagenbrett .vorlagenbrett__anpassen").first();
+ ok(await anpassen.count() > 0, "jede Karte hat einen „Anpassen …\"-Knopf");
+ await anpassen.click();
+ await seite.waitForSelector("dialog.nachfrage[open]", { timeout: 8000 });
+
+ const felder = await seite.evaluate(() => {
+ const dlg = document.querySelector("dialog.nachfrage");
+ const dz = dlg.querySelector('[data-teil="datumzeile"]');
+ const hz = dlg.querySelector('[data-teil="hakenzeile"]');
+ const gz = dlg.querySelector('[data-teil="grundzeile"]');
+ return {
+ datum: !!dz && !dz.hidden,
+ datumWert: dlg.querySelector("#nachfrage-datum")?.value || "",
+ datumMin: dlg.querySelector("#nachfrage-datum")?.min || "",
+ haken: !!hz && !hz.hidden,
+ grund: !!gz && !gz.hidden,
+ };
+ });
+ ok(felder.datum && felder.grund,
+ `das Fenster hat ein Datum und ein Anmerkungsfeld (${felder.datum}, ${felder.grund})`);
+ /* DIE VORGABE IST DIE FRIST DER VORLAGE -- ein leeres Feld hiesse
+ „rechne selbst, in wie vielen Tagen", und genau das Rechnen
+ nimmt die Vorlage einem ab. */
+ ok(/^\d{4}-\d{2}-\d{2}$/.test(felder.datumWert) && felder.datumWert > felder.datumMin,
+ `mit der Frist der Vorlage als Vorgabe (${felder.datumWert}, frühestens `
+ + `${felder.datumMin})`);
+ /* EIN CREATOR SIEHT DEN HAKEN NICHT. „Dauerhaft" darf nur, wer
+ verteilen darf -- und ein Knopf, der eine Absage holt, ist
+ schlimmer als keiner. Der Server lehnt es ohnehin ab (oben
+ gemessen); hier wird geprueft, dass er gar nicht erst angeboten
+ wird. */
+ ok(felder.haken === false,
+ `und ein Creator bekommt den „dauerhaft\"-Haken NICHT (${felder.haken})`);
+
+ const in9 = await seite.evaluate(() => {
+ const d = new Date(Date.now() + 9 * 86400000);
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`
+ + `-${String(d.getDate()).padStart(2, "0")}`;
+ });
+ await seite.fill("dialog.nachfrage #nachfrage-datum", in9);
+ await seite.fill("dialog.nachfrage #nachfrage-grund", "Erst nach dem Umzug anfangen.");
+ const vorAn = await zaehle();
+ await seite.click('dialog.nachfrage [data-teil="ja"]');
+ await seite.waitForTimeout(2500);
+ const nachAn = await zaehle();
+ ok(nachAn === vorAn + 1,
+ `„Anpassen" legt genauso eine Aufgabe an (${vorAn} → ${nachAn})`);
+
+ /* UND JETZT DIE EIGENTLICHE FRAGE: Steht die Frist dran, die sie
+ eingetragen hat -- und die Anmerkung? Gefragt wird die
+ Schnittstelle, nicht die Karte: Was auf dem Brett steht, ist
+ eine Anzeige; was in der Aufgabe steht, ist die Tatsache. */
+ const alleA = (await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || [];
+ const frisch = alleA.filter((x) => x.frist === in9);
+ ok(frisch.length === 1,
+ `und sie trägt die eingetragene Frist (${frisch.length} Aufgabe mit ${in9})`);
+ ok(frisch[0]?.notizen === 1,
+ ` samt der Anmerkung als Notiz (${frisch[0]?.notizen})`);
+ const nz2 = await (await ruf(`/workspace/api/aufgaben/${frisch[0]?.id}/notizen`, kTili)).json();
+ ok(/nach dem Umzug/.test((nz2.notizen || [])[0]?.text || ""),
+ ` mit ihrem Wortlaut („${(nz2.notizen || [])[0]?.text}")`);
+
+ /* GEGENPROBE: Abbrechen legt NICHTS an. Ohne sie koennte das
+ Fenster beim Wegklicken trotzdem verteilen -- und davon merkt
+ man erst etwas, wenn die Aufgabe irgendwo auftaucht. */
+ await anpassen.click();
+ await seite.waitForSelector("dialog.nachfrage[open]", { timeout: 8000 });
+ const vorAb = await zaehle();
+ await seite.click('dialog.nachfrage [data-teil="nein"]');
+ await seite.waitForTimeout(1200);
+ ok(await zaehle() === vorAb,
+ `Gegenprobe: Abbrechen legt nichts an (${vorAb} → ${await zaehle()})`);
+ }
+
/* Und der Zustand des Blocks überlebt ein Neuladen -- wer Vorlagen
holt, holt meistens mehrere. */
await seite.reload({ waitUntil: "domcontentloaded" });
diff --git a/server/pruef-nachfrage.mjs b/server/pruef-nachfrage.mjs
index 562a5f4b..2f89b6e0 100644
--- a/server/pruef-nachfrage.mjs
+++ b/server/pruef-nachfrage.mjs
@@ -945,9 +945,150 @@ if (process.env.OHNE_BROWSER === "ja") {
pruef(` und der Dialog gibt beide zurueck (${(viele || []).join(", ")})`,
Array.isArray(viele) && viele.length === 2
&& viele[0] === "a.png" && viele[1] === "c.png");
+
+ /* ==== EIN DATUM UND EIN HAKEN (02.10.2026) ==================
+
+ VanVan im Support, Meldung #6: „Bei den Vorlagen laesst sich
+ weder die Frist anpassen noch ‚dauerhaft' einstellen, und eine
+ Anmerkung fehlt auch." Die Anmerkung gab es hier schon
+ (`grund`), die zwei anderen Felder nicht.
+
+ SIE STEHEN IM GEMEINSAMEN FENSTER und nicht in
+ vorlagenbrett.js -- sonst waere das der zweite Dialog im Haus,
+ in dem Esc, Fokus und Abbruch von Hand nachgebaut werden
+ muessten. */
+ await seite.evaluate(() => {
+ window.__probe5 = window.frageNach({
+ titel: "Anpassen", ja: "Übernehmen",
+ datum: "Frist", datumWert: "2030-05-17", datumMin: "2026-10-01",
+ haken: "Dauerhafte Aufgabe", hakenHinweis: "Sie wird nicht abgehakt.",
+ hakenSperrtDatum: true,
+ grund: "Anmerkung (freiwillig)",
+ });
+ });
+ await seite.waitForSelector("dialog.nachfrage[open]", { timeout: 6000 });
+ const dh = await seite.evaluate(() => {
+ const dlg = document.querySelector("dialog.nachfrage");
+ const dfeld = dlg.querySelector("#nachfrage-datum");
+ const hfeld = dlg.querySelector("#nachfrage-haken");
+ const etikett = dlg.querySelector(".nachfrage__haken");
+ return {
+ datumDa: !dlg.querySelector('[data-teil="datumzeile"]').hidden,
+ wert: dfeld.value, min: dfeld.min,
+ hakenDa: !dlg.querySelector('[data-teil="hakenzeile"]').hidden,
+ hakenAn: hfeld.checked,
+ hinweis: dlg.querySelector('[data-teil="hakenhinweis"]').textContent,
+ /* 44 Pixel, wie jeder Knopf im Haus -- und die Flaeche kommt
+ vom Etikett, nicht vom Kaestchen. */
+ etikettHoch: Math.round(etikett.getBoundingClientRect().height),
+ kaestchen: Math.round(hfeld.getBoundingClientRect().width),
+ };
+ });
+ pruef(`das Datumsfeld steht da, mit Vorgabe („${dh.wert}")`,
+ dh.datumDa && dh.wert === "2030-05-17");
+ pruef(` und mit einer Untergrenze (${dh.min})`, dh.min === "2026-10-01");
+ pruef(`der Haken steht da und ist aus (${dh.hakenAn})`,
+ dh.hakenDa && dh.hakenAn === false);
+ pruef(` sein Etikett ist ${dh.etikettHoch} px hoch (Fingermass 44)`,
+ dh.etikettHoch >= 44);
+ /* EIN ANKREUZFELD UEBER DIE GANZE ZEILE sieht aus wie ein leeres
+ Textfeld -- die Regel `.nachfrage__form input { width: 100% }`
+ haette genau das gemacht. */
+ pruef(` und das Kaestchen ist ${dh.kaestchen} px breit, nicht die ganze Zeile`,
+ dh.kaestchen > 10 && dh.kaestchen < 60);
+ pruef(` der Hinweis steht dabei („${dh.hinweis}")`, /nicht abgehakt/.test(dh.hinweis));
+
+ /* DER HAKEN SPERRT DAS DATUM. Eine dauerhafte Aufgabe hat keine
+ Frist -- ein Datum, das dasteht und nicht gilt, ist schlimmer
+ als keins. */
+ await seite.check("dialog.nachfrage #nachfrage-haken");
+ await seite.waitForTimeout(150);
+ const gesperrt = await seite.evaluate(() => {
+ const dlg = document.querySelector("dialog.nachfrage");
+ return {
+ aus: dlg.querySelector("#nachfrage-datum").disabled,
+ blass: parseFloat(getComputedStyle(
+ dlg.querySelector('[data-teil="datumzeile"]')).opacity) < 1,
+ };
+ });
+ pruef(`mit Haken wird das Datum gesperrt (${gesperrt.aus})`, gesperrt.aus === true);
+ pruef(" und sieht auch gesperrt aus", gesperrt.blass === true);
+
+ /* GEGENPROBE: Haken wieder weg -> das Datum geht wieder. Ohne sie
+ koennte die Sperre dauerhaft sein und die Zeile oben waere
+ trotzdem gruen. */
+ await seite.uncheck("dialog.nachfrage #nachfrage-haken");
+ await seite.waitForTimeout(150);
+ pruef("Gegenprobe: ohne Haken geht das Datum wieder",
+ (await seite.evaluate(() =>
+ document.querySelector("dialog.nachfrage #nachfrage-datum").disabled)) === false);
+
+ /* ==== EIN DATUM VOR DER UNTERGRENZE -- ZWEI SCHRANKEN =======
+
+ Die erste ist das `min` am Feld: Der Datumswaehler bietet
+ fruehere Tage gar nicht erst an, und der Browser laesst das
+ Formular nicht abschicken. Das ist die freundlichere -- sie
+ greift, bevor jemand etwas tippt.
+
+ GEMESSEN WIRD, DASS SIE WIRKLICH GREIFT, nicht dass sie
+ dasteht: `rangeUnderflow` sagt es, das Attribut allein nicht.
+ Mein erster Anlauf erwartete hier den HAUSEIGENEN Fehlersatz
+ und wurde rot -- zu Recht: Der Browser haelt das Formular
+ vorher an, mein Handler laeuft gar nicht. Die Pruefung hatte
+ recht, die Erwartung war falsch.
+
+ DIE ZWEITE SCHRANKE steht im Skript und wird darunter einzeln
+ geprueft -- mit abgenommenem `min`, sonst kaeme sie nie zum
+ Zug. Zwei Schranken sind hier keine Dopplung: Die eine ist
+ Bedienkomfort, die andere die Regel. Wer `min` morgen
+ entfernt, hat immer noch die Regel. */
+ await seite.fill("dialog.nachfrage #nachfrage-datum", "2020-01-01");
+ await seite.click('dialog.nachfrage [data-teil="ja"]');
+ await seite.waitForTimeout(250);
+ const nochOffen = await seite.evaluate(() => {
+ const f = document.querySelector("dialog.nachfrage #nachfrage-datum");
+ return {
+ offen: !!document.querySelector("dialog.nachfrage[open]"),
+ zuFrueh: f.validity.rangeUnderflow,
+ };
+ });
+ pruef(`ein Datum vor der Untergrenze haelt den Dialog auf (offen ${nochOffen.offen})`,
+ nochOffen.offen === true);
+ pruef(` der Browser nennt es zu frueh (rangeUnderflow ${nochOffen.zuFrueh})`,
+ nochOffen.zuFrueh === true);
+
+ /* UND JETZT OHNE `min`: Nur so kommt die Regel im Skript dran. */
+ await seite.evaluate(() =>
+ document.querySelector("dialog.nachfrage #nachfrage-datum").removeAttribute("min"));
+ await seite.click('dialog.nachfrage [data-teil="ja"]');
+ await seite.waitForTimeout(250);
+ const eigene = await seite.evaluate(() => ({
+ offen: !!document.querySelector("dialog.nachfrage[open]"),
+ fehler: document.querySelector('dialog.nachfrage [data-teil="fehler"]')?.textContent || "",
+ }));
+ pruef(`ohne Untergrenze faengt es das Skript ab („${eigene.fehler}")`,
+ eigene.offen && /Vergangenheit/.test(eigene.fehler));
+
+ await seite.fill("dialog.nachfrage #nachfrage-datum", "2030-06-01");
+ await seite.check("dialog.nachfrage #nachfrage-haken");
+ await seite.fill("dialog.nachfrage #nachfrage-grund", "Ein Wort dazu.");
+ await seite.click('dialog.nachfrage [data-teil="ja"]');
+ await seite.waitForTimeout(400);
+ const erg5 = await seite.evaluate(async () => {
+ const a = await window.__probe5;
+ return a === false ? null : { datum: a.datum, haken: a.haken, grund: a.grund };
+ });
+ pruef(`der Dialog gibt den Haken zurueck (${erg5?.haken})`, erg5?.haken === true);
+ /* UND DAS DATUM IST LEER, weil der Haken es gesperrt hat. Haette
+ es den Wert behalten, schickte der Browser etwas mit, das der
+ Server wegwirft -- und beim naechsten Lesen stuende in der
+ Antwort etwas anderes als in der Datenbank. */
+ pruef(` und das gesperrte Datum kommt LEER zurueck („${erg5?.datum}")`,
+ erg5?.datum === "");
+ pruef(` die Anmerkung kommt mit („${erg5?.grund}")`, erg5?.grund === "Ein Wort dazu.");
}
- pruef("keine Abstürze im Browser", abstuerze.length === 0, abstuerze[0] || "");
+ pruef("keine Abst\u00fcrze im Browser", abstuerze.length === 0, abstuerze[0] || "");
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
diff --git a/server/workspace-vorlagen.js b/server/workspace-vorlagen.js
index 4305d757..9b6944f8 100644
--- a/server/workspace-vorlagen.js
+++ b/server/workspace-vorlagen.js
@@ -1554,17 +1554,65 @@ function gleicheHerkunft(req, res, next) {
* Leitung sie zutraegt -- dann steht bei ihr Annehmen/Ablehnen,
* und das ist richtig so: Sie hat noch nicht ja gesagt.
*/
-function katalogAufgabeAnlegen(v, zielId, vonId, zustand = "offen") {
+/* ==== WAS BEIM UEBERNEHMEN ANDERS SEIN DARF (02.10.2026) ============
+
+ VanVan im Support, Meldung #6, zweite Haelfte: „Bei den Vorlagen
+ laesst sich weder die Frist anpassen noch ‚dauerhaft' einstellen,
+ und eine Anmerkung fehlt auch."
+
+ DREI WUENSCHE, EINE STELLE. Sie reisen als `wunsch`-Objekt durch
+ beide Zweige, die aus einer Vorlage eine Aufgabe machen -- der
+ Katalog hier und die Creator-Vorlagen weiter unten. Drei einzelne
+ Parameter waeren an der naechsten Aufrufstelle drei Gelegenheiten,
+ einen zu vergessen.
+
+ NICHTS IST PFLICHT. Ohne Angabe bleibt alles, wie es war: Frist
+ aus `tage`, nicht dauerhaft, keine Notiz. Der schnelle Weg
+ („An alle", zwoelf Aufgaben mit einem Druck) darf durch diesen
+ Umbau nicht laenger werden.
+
+ DIE ANMERKUNG WIRD EINE NOTIZ, kein Anhaengsel an der
+ Beschreibung. `aufgaben_notizen` gibt es seit Langem, die Aufgabe
+ zeigt sie ohnehin an, und sie traegt, von wem sie stammt. In die
+ Beschreibung geschrieben waere sie nicht mehr von der Vorlage zu
+ unterscheiden -- und beim naechsten Mal haette dieselbe Vorlage
+ einen anderen Text. */
+function fristAusWunsch(v, wunsch) {
+ /* EINE DAUERHAFTE AUFGABE HAT KEINE FRIST. Sie waere ab dem
+ naechsten Tag fuer immer ueberfaellig -- und eine Warnung, die
+ immer kommt, ist keine mehr. Dieselbe Regel steht seit Langem im
+ Aenderungsweg (workspace-aufgaben.js, `if (aus.dauerhaft)
+ aus.frist = null`); haette sie hier gefehlt, gaebe es zwei
+ Antworten auf dieselbe Frage, und die hiesige waere die falsche. */
+ if (wunsch?.dauerhaft) return null;
+ if (wunsch?.frist) return wunsch.frist;
+ /* Die Frist wird aus `tage` gerechnet, in ORTSZEIT. Ueber
+ toISOString kaeme zwischen Mitternacht und zwei Uhr der Vortag
+ heraus -- derselbe Fehler, der im Haus schon einmal eine
+ Pruefung falsch rot gemacht hat. */
const d = new Date(Date.now() + (v.tage || 7) * 86400_000);
- const frist = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`
+ `-${String(d.getDate()).padStart(2, "0")}`;
+}
+
+/** Die Anmerkung als erste Notiz an der frischen Aufgabe. */
+function anmerkungAnlegen(aufgabeId, wunsch, vonId) {
+ if (!wunsch?.anmerkung) return;
+ db().prepare(`INSERT INTO aufgaben_notizen (aufgabe_id, person_id, text, erstellt)
+ VALUES (?,?,?,?)`).run(aufgabeId, vonId ?? null, wunsch.anmerkung, jetzt());
+}
+
+function katalogAufgabeAnlegen(v, zielId, vonId, zustand = "offen", wunsch = null) {
+ const frist = fristAusWunsch(v, wunsch);
db().prepare(`INSERT INTO aufgaben
(titel, beschreibung, status, prioritaet, verantwortlich_id, frist,
- kategorie, vorlage, erstellt, erstellt_von)
- VALUES (?,?,'offen',?,?,?,?,?,?,?)`)
+ kategorie, vorlage, dauerhaft, erstellt, erstellt_von)
+ VALUES (?,?,'offen',?,?,?,?,?,?,?,?)`)
.run(v.titel, v.text, v.tage <= 1 ? "hoch" : "mittel",
- zielId, frist, v.kategorie, v.schluessel, jetzt(), vonId);
+ zielId, frist, v.kategorie, v.schluessel, wunsch?.dauerhaft ? 1 : 0,
+ jetzt(), vonId);
const id = db().prepare("SELECT last_insert_rowid() AS id").get().id;
+ anmerkungAnlegen(id, wunsch, vonId);
const nun = jetzt();
db().prepare(`INSERT INTO aufgaben_zuteilung
(aufgabe_id, person_id, zustand, zugeteilt_am, zugeteilt_von, geantwortet_am)
@@ -1580,6 +1628,12 @@ function katalogAufgabeAnlegen(v, zielId, vonId, zustand = "offen") {
drueben bei den Aufgaben -- ein Satz, kein Aufsatz. */
const BEWERBUNG_TEXT_MAX = 600;
+/* Wie lang darf die Anmerkung beim Uebernehmen sein? Dieselbe Zahl wie
+ `NOTIZ_MAX` in workspace-aufgaben.js -- sie WIRD eine Notiz, und
+ zwei verschiedene Grenzen fuer dieselbe Zeile waeren die Sorte
+ Unterschied, die man erst bemerkt, wenn etwas abgeschnitten ist. */
+const ANMERKUNG_MAX = 2000;
+
/* =====================================================================
WAS NACH EINER ANTWORT UEBRIG BLEIBT (30.09.2026)
@@ -2017,6 +2071,66 @@ vorlagenRouter.post("/workspace/api/vorlagen/uebernehmen", gleicheHerkunft,
try {
const art = String(req.body.art || "");
const nr = Number(req.body.nr);
+
+ /* ==== DIE DREI WUENSCHE AUS DEM FENSTER (02.10.2026) ========
+
+ VanVan, Meldung #6: „Bei den Vorlagen laesst sich weder die
+ Frist anpassen noch ‚dauerhaft' einstellen, und eine
+ Anmerkung fehlt auch."
+
+ GEPRUEFT WIRD HIER, nicht im Browser. Das Fenster bietet ein
+ Datumsfeld an; was ankommt, ist trotzdem eine Zeichenkette
+ aus dem Netz. Ein `frist` wie „morgen" oder „2026-13-45"
+ stuende sonst in der Datenbank und waere beim Sortieren
+ stiller Unsinn.
+
+ NICHT IN DIE VERGANGENHEIT. Eine Frist, die schon vorbei
+ ist, macht die Aufgabe in dem Moment rot, in dem sie
+ entsteht -- und „ueberfaellig" verliert seine Bedeutung,
+ wenn es auch frisch Verteiltes trifft.
+
+ „DAUERHAFT" DARF NUR, WER VERTEILEN DARF. Eine dauerhafte
+ Aufgabe laesst sich nicht abhaken (siehe den Commit vom
+ 02.10. frueh) -- wer sie sich selbst anlegen koennte, haette
+ etwas, das er nie wieder loswird. Die Entscheidung gehoert
+ zur Leitung, genau wie das Beenden.
+
+ DIE PRUEFUNG STEHT VOR ALLEN ZWEIGEN, weil beide, die
+ Aufgaben anlegen, sie brauchen -- zweimal dasselbe waere
+ zweimal zu pflegen. */
+ const wunsch = (() => {
+ const roh = req.body || {};
+ const w = { frist: null, dauerhaft: false, anmerkung: null };
+ if (typeof roh.frist === "string" && roh.frist) {
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(roh.frist)) return { fehler: "frist_ungueltig" };
+ const d = new Date(roh.frist + "T12:00:00");
+ if (Number.isNaN(d.getTime())
+ || d.toISOString().slice(0, 10) !== roh.frist) {
+ return { fehler: "frist_ungueltig" };
+ }
+ if (roh.frist < heuteLokal()) return { fehler: "frist_vergangen" };
+ w.frist = roh.frist;
+ }
+ if (roh.dauerhaft === true) {
+ if (!darfAufgabenVerteilen(req.person)) return { fehler: "dauerhaft_nicht_erlaubt" };
+ w.dauerhaft = true;
+ }
+ if (typeof roh.anmerkung === "string" && roh.anmerkung.trim()) {
+ w.anmerkung = roh.anmerkung.replace(/\r\n/g, "\n").trim().slice(0, ANMERKUNG_MAX);
+ }
+ return w;
+ })();
+ if (wunsch.fehler) {
+ const saetze = {
+ frist_ungueltig: "Diese Frist versteht der Server nicht – bitte ein Datum wählen.",
+ frist_vergangen: "Diese Frist liegt in der Vergangenheit.",
+ dauerhaft_nicht_erlaubt:
+ "„Dauerhaft“ dürfen nur DogFather und die rechte Hand setzen – "
+ + "eine dauerhafte Aufgabe lässt sich nicht abhaken.",
+ };
+ return res.status(wunsch.fehler === "dauerhaft_nicht_erlaubt" ? 403 : 400)
+ .json({ fehler: saetze[wunsch.fehler] });
+ }
/* Für wen? Ein Creator immer für sich; Betreuer für den Creator,
den sie gerade ansehen. */
let creatorId = req.person.rolle === "creator"
@@ -2095,22 +2209,24 @@ vorlagenRouter.post("/workspace/api/vorlagen/uebernehmen", gleicheHerkunft,
const auswahl = art === "aufgaben_alle" ? feld : [feld[nr]];
if (auswahl.some((v) => !v)) return res.status(400).json({ fehler: "Unbekannte Vorlage." });
for (const v of auswahl) {
- /* Die Frist wird aus `tage` gerechnet, in ORTSZEIT. Ueber
- toISOString kaeme zwischen Mitternacht und zwei Uhr der
- Vortag heraus -- derselbe Fehler, der im Haus schon einmal
- eine Pruefung falsch rot gemacht hat. */
- const d = new Date(Date.now() + (v.tage || 7) * 86400_000);
- const frist = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`
- + `-${String(d.getDate()).padStart(2, "0")}`;
+ /* Frist, „dauerhaft" und die Anmerkung kommen aus demselben
+ `wunsch` wie im Katalogzweig -- und aus derselben
+ Funktion. Zwei Rechnungen fuer dieselbe Frist waeren die
+ zweite, die beim naechsten Mal nicht mitgeaendert wird. */
+ const frist = fristAusWunsch(v, wunsch);
/* Die Kennung der Vorlage wandert mit (09.09.2026). Nur so
kann das Brett spaeter zeigen, was schon uebernommen ist --
und der Titel taugt dafuer nicht: Er darf geaendert werden. */
db().prepare(`INSERT INTO aufgaben
(titel, beschreibung, status, prioritaet, creator_id, frist,
- vorlage, erstellt, erstellt_von)
- VALUES (?,?,'offen',?,?,?,?,?,?)`)
+ vorlage, dauerhaft, erstellt, erstellt_von)
+ VALUES (?,?,'offen',?,?,?,?,?,?,?)`)
.run(v.titel, v.text, stufe === "anfaenger" ? "hoch" : "mittel",
- creatorId, frist, v.schluessel, jetzt(), req.person.id);
+ creatorId, frist, v.schluessel, wunsch.dauerhaft ? 1 : 0,
+ jetzt(), req.person.id);
+ anmerkungAnlegen(
+ db().prepare("SELECT last_insert_rowid() AS id").get().id,
+ wunsch, req.person.id);
angelegt.push(v.titel);
}
@@ -2298,7 +2414,7 @@ vorlagenRouter.post("/workspace/api/vorlagen/uebernehmen", gleicheHerkunft,
: new Set();
for (const v of auswahl) {
if (liegtSchon.has(v.schluessel)) { lagSchonDa.push(v.titel); continue; }
- katalogAufgabeAnlegen(v, ziel.id, req.person.id);
+ katalogAufgabeAnlegen(v, ziel.id, req.person.id, "offen", wunsch);
angelegt.push(v.titel);
}
}
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index 2398b926..1908039b 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`. -->
-
-
+
+
-
+