/* ===================================================================== Aus einer Idee wird Arbeit (15.09.2026) Stufe 9 aus dem Plan zur Perfektion: „Idee → Aufgabe/Termin". --------------------------------------------------------------------- WARUM DAS MEHR IST ALS EINE BEQUEMLICHKEIT Das ganze Haus steht auf einem Satz: *„Was hier nicht steht, passiert nicht."* Ein Ideenbrett, aus dem keine Aufgabe werden kann, ist der eine Ort, an dem dieser Satz nicht gilt — dort steht etwas, und passieren tut es trotzdem nicht. Wer eine Idee umsetzen will, tippt sie heute ein zweites Mal ab. Und was man abtippt, tippt man irgendwann nicht mehr ab. --------------------------------------------------------------------- DREI ENTSCHEIDUNGEN 1. GENAU EINMAL. An der Idee steht danach, welche Aufgabe daraus wurde. Ein zweiter Versuch führt nicht zu einer zweiten Aufgabe, sondern zu dem Hinweis, dass es sie schon gibt. Dasselbe Muster wie bei „zu den Talenten übernehmen" und bei `gesendet_am`. 2. DIE IDEE BLEIBT STEHEN. Sie wird nicht verschoben und nicht gelöscht. Wer im Brett nachsieht, was aus einem Gedanken geworden ist, soll den Gedanken noch finden — und daneben die Antwort. 3. DIE AUFGABE WEISS, WOHER SIE KAM. `aus_eintrag_id` mit echtem Fremdschlüssel. Nicht als Text im Beschreibungsfeld: Ein Verweis, den nur ein Mensch lesen kann, ist kein Verweis. --------------------------------------------------------------------- WAS ABSICHTLICH NICHT MITWANDERT Die Frist. Eine Idee hat keinen Termin — wer ihr beim Übernehmen automatisch einen gäbe, erfände ihn. Die Aufgabe entsteht ohne Frist, und wer eine braucht, setzt sie bewusst. ===================================================================== */ import express from "express"; import { db, sitzungLesen, protokolliere, echteIp, } from "./workspace.js"; export const uebernahmeRouter = express.Router(); /* workspace.js fuehrt `jetzt` nicht aus -- wie in den uebrigen Modulen steht es deshalb hier. */ const jetzt = () => new Date().toISOString(); /** Aus welchen Brettern eine Aufgabe werden darf. * * NICHT AUS ALLEN. Ein Beitrag aus dem Treff gehört einem Menschen * aus der Community; daraus stillschweigend eine Arbeitsaufgabe zu * machen, wäre eine Verwendung, mit der er nicht rechnet. Hier stehen * die Bretter des Hauses, auf denen ohnehin Arbeit geplant wird. * * Die Liste ist kurz und steht namentlich da — `pruef-uebernahme` * vergleicht sie gegen die Bretter, die es wirklich gibt, damit hier * kein Name stehen bleibt, den niemand mehr kennt. */ export const AUS_BRETTERN = ["ideen", "live", "content", "technik", "community", "schutz", "agentur"]; function angemeldet(req, res, next) { const person = sitzungLesen(req); if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" }); req.person = person; next(); } /** Gleiche Herkunft — wie überall im Haus bei schreibenden Wegen. */ function gleicheHerkunft(req, res, next) { const herkunft = req.get("origin"); if (!herkunft) return next(); let erlaubt; try { erlaubt = new URL(herkunft).host === req.get("host"); } catch { erlaubt = false; } if (!erlaubt) return res.status(403).json({ fehler: "fremde_herkunft" }); next(); } uebernahmeRouter.post("/workspace/api/eintrag/:id(\\d+)/zur-aufgabe", express.json({ limit: "8kb" }), angemeldet, gleicheHerkunft, (req, res) => { try { const id = Number(req.params.id); const e = db().prepare( `SELECT id, bereich, titel, text, creator_id, dringlichkeit, erstellt_von FROM eintraege WHERE id = ?`).get(id); if (!e) return res.status(404).json({ fehler: "nicht_gefunden" }); if (!AUS_BRETTERN.includes(e.bereich)) { return res.status(400).json({ fehler: "Aus diesem Brett wird keine Aufgabe." }); } /* SCHON EINMAL GESCHEHEN? Die Frage wird an der AUFGABE gestellt, nicht an der Idee: Dort steht die Tatsache, und sie kann nicht auseinanderlaufen. Eine Notiz an der Idee waere eine zweite Wahrheit. */ const schon = db().prepare( "SELECT id, titel FROM aufgaben WHERE aus_eintrag_id = ?").get(id); if (schon) { return res.status(409).json({ fehler: "schon_uebernommen", aufgabe_id: schon.id }); } const nun = jetzt(); /* Die Dringlichkeit der Idee wird zur Prioritaet der Aufgabe -- dieselben drei Stufen, also keine Umrechnung noetig. Fehlt sie, gilt die Vorgabe des Bretts: mittel. */ const prio = ["hoch", "mittel", "niedrig"].includes(e.dringlichkeit) ? e.dringlichkeit : "mittel"; const r = db().prepare(`INSERT INTO aufgaben (titel, beschreibung, status, prioritaet, creator_id, verantwortlich_id, erstellt, erstellt_von, aus_eintrag_id) VALUES (?,?,'offen',?,?,?,?,?,?)`) .run( String(e.titel || "Ohne Titel").slice(0, 200), String(e.text || "").slice(0, 4000) || null, prio, e.creator_id ?? null, /* WER UEBERNIMMT, IST ZUSTAENDIG. Nicht der, der die Idee aufgeschrieben hat: Eine Aufgabe, die jemandem ohne sein Zutun zugeteilt wird, wird nicht angefangen. */ req.person.id, nun, req.person.id, id); protokolliere("idee_zur_aufgabe", { personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req), detail: `Eintrag ${id} (${e.bereich}) → Aufgabe ${r.lastInsertRowid}`, }); res.status(201).json({ aufgabe_id: Number(r.lastInsertRowid), titel: String(e.titel || "Ohne Titel").slice(0, 200), prioritaet: prio, }); } catch (fehler) { console.error("[uebernahme]", fehler?.message); res.status(503).json({ fehler: "nicht_verfuegbar" }); } }); /** Was aus den Einträgen eines Bretts geworden ist. * * Eine Abfrage für das ganze Brett statt einer je Eintrag: Bei * zwanzig Ideen wären das zwanzig Anfragen, und die Liste stünde * zwanzigmal kurz falsch da, bevor sie stimmt. */ uebernahmeRouter.get("/workspace/api/bereich/:bereich/uebernahmen", angemeldet, (req, res) => { try { const bereich = String(req.params.bereich || ""); /* „moeglich" sagt der Oberflaeche, ob der Knopf ueberhaupt hingehoert. Ohne dieses Feld muesste der Browser eine zweite Liste der Bretter fuehren -- und die waere die, die veraltet. */ if (!AUS_BRETTERN.includes(bereich)) { return res.json({ moeglich: false, uebernahmen: {} }); } const zeilen = db().prepare( `SELECT a.aus_eintrag_id AS eintrag_id, a.id AS aufgabe_id, a.status FROM aufgaben a JOIN eintraege e ON e.id = a.aus_eintrag_id WHERE a.aus_eintrag_id IS NOT NULL AND e.bereich = ?`).all(bereich); const raus = {}; for (const z of zeilen) raus[z.eintrag_id] = { aufgabe_id: z.aufgabe_id, status: z.status }; res.json({ moeglich: true, uebernahmen: raus }); } catch (fehler) { console.error("[uebernahme] Liste:", fehler?.message); res.status(503).json({ fehler: "nicht_verfuegbar" }); } });