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