diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs
index 8602c20a..2d867fef 100644
--- a/server/pruef-bewerbung-aufgaben.mjs
+++ b/server/pruef-bewerbung-aufgaben.mjs
@@ -580,6 +580,124 @@ melde("=== 6. Am echten Bildschirm ===");
`und keinen Block zum Entscheiden (${b?.bewerbungen})`);
} finally { await m2.ctx.close(); }
+ /* =================================================================
+ UND JETZT WIRD ANGENOMMEN -- VanVans Meldung (30.09.2026)
+
+ „Wenn ein Modi sich auf eine Aufgabe beworben hat und die von uns
+ angenommen wurde, dann steht beim Modi unter der Aufgabe immer
+ noch ich bewerbe mich."
+
+ DIESER SCHRITT HAT HIER GEFEHLT. Die Pruefung ging den ganzen
+ Weg bis zur Bewerbung und hoerte dann auf. Serverseitig ist das
+ Annehmen weiter oben geprueft („Fridas Stand ist angenommen");
+ was der Mensch danach auf seinem Brett LIEST, hat nie jemand
+ nachgesehen.
+ ================================================================= */
+ const annahme = await roh(`/workspace/api/aufgaben/${frischId}/bewerbung/annehmen`, {
+ methode: "POST", keks: hand.keks,
+ body: { person_id: idModi, kommentar: "Gern, mach das." } });
+ ok(annahme.code === 200 && annahme.json?.zustand === "angenommen",
+ `die rechte Hand nimmt Fridas Bewerbung an (${annahme.code}/${annahme.json?.zustand})`);
+
+ const m3 = await seiteAls("modi", "CODE-MODI-0001", 390);
+ try {
+ const b = await knoepfeAn(m3.s, "Fuer den Bildschirm");
+ ok(!!b, "Frida sieht die Aufgabe weiterhin");
+ /* DER BEFUND. Ein Knopf, der nach der Zusage weiter „Ich
+ bewerbe mich" sagt, behauptet, es sei nichts entschieden --
+ und wer ihn drueckt, bekommt eine Fehlermeldung auf etwas,
+ das laengst geklappt hat. */
+ ok(!!b && !b.knoepfe.some((t) => /bewerbe mich/i.test(t)),
+ `und NICHT mehr „Ich bewerbe mich" (${b?.knoepfe.join(" | ")})`);
+ ok(!!b && !b.knoepfe.some((t) => /zurückziehen/i.test(t)),
+ "auch nichts mehr zum Zurueckziehen -- es gibt keine offene Bewerbung mehr");
+ /* UND DAS EIGENTLICHE: Steht da, dass sie die Aufgabe HAT?
+ „Kein falscher Knopf" allein waere eine leere Karte. */
+ ok(!!b && /(du machst|gehört dir|angenommen|übernommen|deine aufgabe)/i.test(b.text),
+ `sondern dass sie die Aufgabe hat ("${(b?.text || "").slice(0, 90)}…")`);
+ } finally { await m3.ctx.close(); }
+
+ /* =================================================================
+ DERSELBE WEG, ABER UEBER DAS VORLAGENBRETT -- VanVans Fall
+
+ Der Abschnitt darueber geht ueber das Aufgabenbrett
+ (`/api/aufgaben/:id/bewerben`). Dort war alles in Ordnung.
+
+ VanVans Weg ist der andere: Sie bewirbt sich auf eine VORLAGE.
+ Wird die angenommen, entsteht eine NEUE Aufgabe -- und die
+ bekam bis zum 30.09.2026 keine Zeile in `aufgaben_zuteilung`.
+
+ WAS DAS BEDEUTET HAT. Das Brett liest die Zuteilung, nicht
+ `verantwortlich_id`. Ohne Zeile sagt es „niemand zugeteilt" und
+ bietet an, sich auf die eigene Aufgabe zu bewerben. Und weil
+ „Ich fange an" und „Fertig" an derselben Zeile haengen, fehlten
+ sie: eine Karte, auf der man das Falsche tun kann und das
+ Richtige nicht.
+
+ Gemessen an den echten Daten hatten 8 von 11 Aufgaben mit einem
+ Verantwortlichen keine Zuteilungszeile.
+ ================================================================= */
+ const vorl = await roh("/workspace/api/vorlagen", { keks: modi.keks });
+ /* DIE VORLAGEN STEHEN UNTER `katalog.aufgaben`. Beim ersten
+ Anlauf habe ich auf `gruppen` geraten und einen leeren Treffer
+ bekommen -- die Pruefung meldete daraufhin „konnte nicht
+ nachsehen", und das war die richtige Antwort auf eine falsche
+ Frage. */
+ /* DIE LETZTE UND NICHT DIE ERSTE. Ein spaeterer Abschnitt (Zeile
+ ~999) bewirbt sich ebenfalls, und zwar auf die ERSTE Vorlage.
+ Nahm ich sie auch, bekam er 409 („liegt schon bei dir") -- und
+ zehn Pruefungen wurden rot, ohne dass am Programm etwas falsch
+ war. Ein Messfehler, der wie ein Befund aussieht, und einer,
+ den ich selbst eingebaut hatte. */
+ const katalogAufgaben = vorl.json?.katalog?.aufgaben || [];
+ const v = katalogAufgaben.at(-1);
+ if (!v) {
+ ok(false, "KONNTE NICHT NACHSEHEN: keine Vorlage gefunden "
+ + `(${Object.keys(vorl.json || {}).join(", ")})`);
+ } else {
+ const vb = await roh("/workspace/api/vorlagen/bewerben", {
+ methode: "POST", keks: modi.keks,
+ body: { vorlage: v.schluessel, text: "Das mache ich gern." } });
+ ok(vb.code === 201 || vb.code === 200,
+ `Frida bewirbt sich auf die Vorlage „${v.titel}" (${vb.code})`);
+
+ const offen2 = await roh("/workspace/api/vorlagen/bewerbungen", { keks: hand.keks });
+ const bw = (offen2.json?.bewerbungen || []).find((x) => x.vorlage === v.schluessel);
+ ok(!!bw, `die rechte Hand sieht die Bewerbung (${offen2.json?.bewerbungen?.length})`);
+
+ const vann = await roh("/workspace/api/vorlagen/bewerbung/annehmen", {
+ methode: "POST", keks: hand.keks,
+ body: { id: bw?.id, kommentar: "Gern, mach das." } });
+ ok(vann.code === 200, `und nimmt sie an (${vann.code})`);
+
+ /* ---- DER KERN: was steht jetzt auf Fridas Brett? ---------- */
+ const nach = await roh("/workspace/api/aufgaben", { keks: modi.keks });
+ const liste = nach.json?.aufgaben || nach.json?.liste || [];
+ const neue = liste.find((x) => x.vorlage === v.schluessel);
+ ok(!!neue, `die Aufgabe steht auf ihrem Brett (${liste.length} insgesamt)`);
+ ok(!!neue && !!neue.meine_zuteilung,
+ `und sie hat eine Zuteilungszeile (${JSON.stringify(neue?.meine_zuteilung?.zustand)})`);
+ ok(neue?.meine_zuteilung?.zustand === "angenommen",
+ `im Zustand „angenommen" -- sie hat darum gebeten und sie bekommen `
+ + `(${neue?.meine_zuteilung?.zustand})`);
+ ok(neue?.ich_beworben === false,
+ `und es liegt keine offene Bewerbung mehr vor (${neue?.ich_beworben})`);
+
+ const m4 = await seiteAls("modi", "CODE-MODI-0001", 390);
+ try {
+ const b = await knoepfeAn(m4.s, v.titel);
+ ok(!!b, "Frida sieht die neue Aufgabe mit ihrem Zuteilungsblock");
+ /* VANVANS BEFUND. Ein Knopf, der nach der Zusage weiter „Ich
+ bewerbe mich" sagt, behauptet, es sei nichts entschieden. */
+ ok(!!b && !b.knoepfe.some((t) => /bewerbe mich/i.test(t)),
+ `und NICHT „Ich bewerbe mich" (${b?.knoepfe.join(" | ")})`);
+ /* UND DIE ANDERE HAELFTE: Sie kann auch wirklich etwas tun.
+ „Kein falscher Knopf" allein waere eine Karte ohne Griff. */
+ ok(!!b && b.knoepfe.some((t) => /fange an/i.test(t)),
+ `sondern „Ich fange an" (${b?.knoepfe.join(" | ")})`);
+ } finally { await m4.ctx.close(); }
+ }
+
/* =================================================================
7. AUFGABEN DORT ANLEGEN, WO DIE PERSON STEHT (A5 und B6)
=================================================================
diff --git a/server/workspace-vorlagen.js b/server/workspace-vorlagen.js
index 41cf40ae..064c70bf 100644
--- a/server/workspace-vorlagen.js
+++ b/server/workspace-vorlagen.js
@@ -1509,7 +1509,34 @@ function gleicheHerkunft(req, res, next) {
DIE KENNUNG DER VORLAGE WANDERT MIT. Nur so kann das Brett spaeter
zeigen, was schon uebernommen ist -- der Titel taugt dafuer nicht,
er darf geaendert werden. */
-function katalogAufgabeAnlegen(v, zielId, vonId) {
+/** Legt eine Aufgabe aus einer Vorlage an -- MIT Zuteilungszeile.
+ *
+ * ==== DIE ZUTEILUNG HAT GEFEHLT (berichtigt 30.09.2026) ==========
+ *
+ * VanVan im Support: „Wenn ein Modi sich auf eine Aufgabe beworben
+ * hat und die von uns angenommen wurde, dann steht beim Modi unter
+ * der Aufgabe immer noch ich bewerbe mich."
+ *
+ * Hier stand nur das `INSERT INTO aufgaben` mit `verantwortlich_id`.
+ * Fuer das Aufgabenbrett war damit NIEMAND zugeteilt: Es liest
+ * `aufgaben_zuteilung`, findet nichts, und liefert
+ * `meine_zuteilung: null`. Die Bedingung im Browser beginnt mit
+ * `!mein` -- also bot sie dem Menschen an, sich auf eine Aufgabe zu
+ * bewerben, die ihm laengst gehoert.
+ *
+ * ES WAR NICHT NUR EIN FALSCHER KNOPF. „Ich fange an" und „Fertig"
+ * haengen ebenfalls an `mein.zustand`. Ohne Zuteilungszeile konnte
+ * er auf der Karte das Falsche tun und das Richtige nicht.
+ *
+ * GEMESSEN an den echten Daten: 8 von 11 Aufgaben mit einem
+ * Verantwortlichen hatten keine Zuteilungszeile.
+ *
+ * @param {string} zustand `angenommen`, wenn die Person darum
+ * gebeten hat und die Bitte angenommen wurde. `offen`, wenn die
+ * 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") {
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")}`;
@@ -1519,7 +1546,16 @@ function katalogAufgabeAnlegen(v, zielId, vonId) {
VALUES (?,?,'offen',?,?,?,?,?,?,?)`)
.run(v.titel, v.text, v.tage <= 1 ? "hoch" : "mittel",
zielId, frist, v.kategorie, v.schluessel, jetzt(), vonId);
- return db().prepare("SELECT last_insert_rowid() AS id").get().id;
+ const id = db().prepare("SELECT last_insert_rowid() AS id").get().id;
+ const nun = jetzt();
+ db().prepare(`INSERT INTO aufgaben_zuteilung
+ (aufgabe_id, person_id, zustand, zugeteilt_am, zugeteilt_von, geantwortet_am)
+ VALUES (?,?,?,?,?,?)`)
+ .run(id, zielId, zustand, nun, vonId ?? null,
+ /* „Wann geantwortet" nur, wenn schon geantwortet wurde. Bei
+ `offen` steht die Frage noch aus. */
+ zustand === "offen" ? null : nun);
+ return id;
}
/* Wie lang darf ein Wort zu einer Bewerbung sein? Dieselbe Groesse wie
@@ -1776,7 +1812,10 @@ for (const [weg, ja] of [["annehmen", true], ["ablehnen", false]]) {
let aufgabeId = null;
if (ja) {
- aufgabeId = katalogAufgabeAnlegen(v, wer.id, req.person.id);
+ /* `angenommen`: Er hat darum gebeten, und die Bitte wurde
+ angenommen. Ihn danach noch einmal „Annehmen" druecken zu
+ lassen waere dieselbe Frage zweimal. */
+ aufgabeId = katalogAufgabeAnlegen(v, wer.id, req.person.id, "angenommen");
}
d.prepare(`UPDATE vorlagen_bewerbungen
SET zustand = ?, entscheid_text = ?, entschieden_von = ?,
diff --git a/server/workspace.js b/server/workspace.js
index 349758a0..5e0e7827 100644
--- a/server/workspace.js
+++ b/server/workspace.js
@@ -4361,6 +4361,68 @@ function umstellungen(d) {
}
}
+ /* =====================================================================
+ WER VERANTWORTLICH IST, BRAUCHT EINE ZUTEILUNGSZEILE (30.09.2026)
+ =====================================================================
+
+ VanVan im Support: „Wenn ein Modi sich auf eine Aufgabe beworben
+ hat und die von uns angenommen wurde, dann steht beim Modi unter
+ der Aufgabe immer noch ich bewerbe mich."
+
+ GEMESSEN an den echten Daten: 11 Aufgaben hatten einen
+ Verantwortlichen, 8 davon KEINE Zeile in `aufgaben_zuteilung`.
+ Darunter beide Vorlagen-Aufgaben, die in ihrem Bildschirmfoto zu
+ sehen sind.
+
+ Das Aufgabenbrett liest die Zuteilung, nicht `verantwortlich_id`.
+ Ohne Zeile sagt es „niemand zugeteilt" -- und bietet dem Menschen
+ an, sich auf seine eigene Aufgabe zu bewerben. „Ich fange an" und
+ „Fertig" fehlen ihm dabei, denn die haengen an derselben Zeile.
+
+ Die Ursache ist behoben (`katalogAufgabeAnlegen` legt sie jetzt
+ mit an). Dieser Block holt nach, was schon dasteht.
+
+ WELCHER ZUSTAND: Die Aufgabe GEHOERT der Person schon -- sie
+ jetzt „Annehmen/Ablehnen" druecken zu lassen waere eine Frage zu
+ etwas, das laengst entschieden ist. Also `angenommen`, und bei
+ abgeschlossenen Aufgaben der passende Endzustand.
+
+ WIEDERHOLBAR: `NOT EXISTS` plus `INSERT OR IGNORE`. Beim zweiten
+ Start ist alles da und nichts passiert -- derselbe Gedanke wie
+ bei der Teilnehmer-Uebernahme darueber. */
+ try {
+ const spalten = d.prepare("PRAGMA table_info(aufgaben)").all().map((x) => x.name);
+ if (spalten.includes("verantwortlich_id")) {
+ const vorher = d.prepare("SELECT COUNT(*) AS n FROM aufgaben_zuteilung").get().n;
+ d.exec(`INSERT OR IGNORE INTO aufgaben_zuteilung
+ (aufgabe_id, person_id, zustand, zugeteilt_am, zugeteilt_von, geantwortet_am)
+ SELECT a.id, a.verantwortlich_id,
+ CASE a.status
+ WHEN 'erledigt' THEN 'erledigt'
+ WHEN 'review' THEN 'erledigt'
+ WHEN 'abgebrochen' THEN 'abgelehnt'
+ WHEN 'arbeit' THEN 'arbeit'
+ ELSE 'angenommen'
+ END,
+ COALESCE(a.erstellt, datetime('now')),
+ a.erstellt_von,
+ COALESCE(a.erstellt, datetime('now'))
+ FROM aufgaben a
+ WHERE a.verantwortlich_id IS NOT NULL
+ AND NOT EXISTS (
+ SELECT 1 FROM aufgaben_zuteilung z
+ WHERE z.aufgabe_id = a.id
+ AND z.person_id = a.verantwortlich_id)`);
+ const nachher = d.prepare("SELECT COUNT(*) AS n FROM aufgaben_zuteilung").get().n;
+ if (nachher > vorher) {
+ console.log(`[workspace] ${nachher - vorher} Aufgaben mit Verantwortlichem `
+ + "haben ihre fehlende Zuteilungszeile bekommen.");
+ }
+ }
+ } catch (fehler) {
+ console.error("[workspace] Zuteilung nachtragen:", fehler?.message);
+ }
+
loeschspurenAufraeumen(d);
/* =====================================================================
diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html
index 906e2fc3..60402299 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`. -->
-
-
+
+
-
+