Files
dogfather-universe/server/workspace-uebernahme.js
DogFatherGitandClaude Opus 5 d76d9aa5da Aus einer Idee wird Arbeit -- mit einem Knopf statt mit Abtippen
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 wollte, tippte sie ein
zweites Mal ab. Und was man abtippt, tippt man irgendwann nicht mehr ab.

DREI ENTSCHEIDUNGEN

  GENAU EINMAL. Ein zweiter Versuch fuehrt nicht zu einer zweiten
  Aufgabe, sondern zum Verweis auf die vorhandene (409). Gefragt wird
  an der AUFGABE, nicht an der Idee: Dort steht die Tatsache, und sie
  kann nicht auseinanderlaufen.

  DIE IDEE BLEIBT STEHEN. Sie wird nicht verschoben und nicht geloescht
  -- wer nachsieht, was aus einem Gedanken geworden ist, soll den
  Gedanken noch finden, und daneben die Antwort.

  DIE AUFGABE WEISS, WOHER SIE KAM. `aus_eintrag_id` mit echtem
  Fremdschluessel, nicht als Text im Beschreibungsfeld. Ein Verweis, den
  nur ein Mensch lesen kann, ist kein Verweis. ON DELETE SET NULL, nicht
  CASCADE: Wird die Idee geloescht, bleibt die Arbeit.

NICHT AUS JEDEM BRETT

Aus einem Beitrag im Treff wird keine Aufgabe. Was jemand aus der
Community schreibt, gehoert ihm; daraus stillschweigend Arbeit zu
machen waere eine Verwendung, mit der er nicht rechnet. Welche Bretter
es sind, entscheidet der Server -- die Oberflaeche fragt nach, statt
eine zweite Liste zu fuehren.

WAS ABSICHTLICH NICHT MITWANDERT: die Frist. Eine Idee hat keinen
Termin; wer ihr beim Uebernehmen automatisch einen gaebe, erfaende ihn.
Die Dringlichkeit wandert dagegen mit -- dieselben drei Stufen.

ZUSTAENDIG IST, WER UEBERNIMMT, nicht wer die Idee aufgeschrieben hat.
Eine Aufgabe, die jemandem ohne sein Zutun zugeteilt wird, wird nicht
angefangen.

ZWEIMAL AM FALSCHEN ORT GELANDET

Der Knopf stand erst in `if (lage.team)`, dann immer noch innerhalb von
`if (treffBrett && lage)` -- also beide Male ausgerechnet am Treff und
an keinem Arbeitsbrett. Gefunden hat das beide Male die Pruefung im
Browser ("0 Knoepfe auf dem Ideenbrett"), nicht das Lesen. Jetzt steht
er in der allgemeinen Knopfreihe, hinter `darfEintragen()` -- eine
Uebernahme ist ein Schreibvorgang.

Und die Gegenprobe zur Brettliste lief anfangs ins Leere: Sie fragte
nach `BEREICHE[b].treff`, ein Merkmal, das es nicht gibt, und fand null
Treff-Bretter -- eine Gegenprobe, die nichts gegenprueft. TREFF_BRETTER
steht in workspace.js.

PRUEFUNGEN: pruef-uebernahme neu mit 39, davon 4 im Browser (der Klick
muss eine Aufgabe in der DATENBANK erzeugen, nicht nur eine Meldung).

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-15 12:25:36 +02:00

170 lines
7.1 KiB
JavaScript

/* =====================================================================
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" });
}
});