diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs index 828cd8ea..c2b360d0 100644 --- a/server/pruef-bewerbung-aufgaben.mjs +++ b/server/pruef-bewerbung-aufgaben.mjs @@ -518,6 +518,13 @@ melde("=== 6. Am echten Bildschirm ==="); if (!k) return null; return { knoepfe: [...k.querySelectorAll(".z-karte button")].map((b) => b.textContent.trim()), + /* DIE SCHRITTKNOEPFE DER KARTE (02.10.2026). Sie liegen NEBEN + dem Zuteilungsblock, nicht darin -- „kann sie etwas tun?" + laesst sich am Block allein nicht beantworten. Genau diese + Luecke hat die Pruefung hier am 02.10. rot werden lassen, + und zwar zu Recht: Die zwei Knoepfe im Block waren weg, der + richtige stand daneben und wurde nicht angesehen. */ + schritte: [...k.querySelectorAll(".schritt")].map((b) => b.textContent.trim()), bewerbungen: k.querySelectorAll(".z-bewerbung").length, text: (k.querySelector(".z-karte")?.textContent || "").replace(/\s+/g, " ").trim().slice(0, 200), }; @@ -692,9 +699,25 @@ melde("=== 6. Am echten Bildschirm ==="); 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(" | ")})`); + „Kein falscher Knopf" allein waere eine Karte ohne Griff. + + ==== DER GRIFF IST SEIT DEM 02.10.2026 EIN ANDERER ======== + + Hier stand `„Ich fange an"`. Den Knopf gibt es nicht mehr -- + VanVan hat gemeldet, dass er nur den halben Zustand setzte: + „Wenn man auf ich fange an drückt steht dort in Bearbeitung + … Die Aufgabe bleibt aber im status offen stehen." + + Die Absicht dieser Zeile bleibt woertlich dieselbe: Kann + sie etwas tun? Nur ist es jetzt der STATUSKNOPF, der auch + bewegt, was alle anderen lesen. Dass eine Modi ihn + ueberhaupt bekommt, ist ebenfalls neu -- vorher sah sie + keinen einzigen (gemessen in pruef-zuteilung). */ + ok(!!b && b.schritte.some((t) => /starten|Freigabe|erledigt/i.test(t)), + `sondern den Statusknopf (${b?.schritte.join(" | ") || "KEINER"})`); + ok(!!b && !b.knoepfe.some((t) => /fange an/i.test(t)) + && !b.knoepfe.some((t) => /^Fertig$/i.test(t)), + `und die zwei halben Knoepfe sind weg (${b?.knoepfe.join(" | ") || "keine"})`); } finally { await m4.ctx.close(); } /* =============================================================== diff --git a/server/pruef-zuteilung.mjs b/server/pruef-zuteilung.mjs index 9fdfa9ff..9bc1baa5 100644 --- a/server/pruef-zuteilung.mjs +++ b/server/pruef-zuteilung.mjs @@ -300,8 +300,163 @@ melde("\n=== Fertig machen und bewerten ==="); ok(zuFrueh.code === 409 && zuFrueh.json?.fehler === "noch_nicht_erledigt", `vor dem Fertigwerden wird nicht bewertet (HTTP ${zuFrueh.code})`); + /* =================================================================== + DER STATUS DER AUFGABE ZIEHT DIE ZUTEILUNG MIT (02.10.2026) + + VanVan im Support: „Wenn man auf ich fange an drückt steht dort in + Bearbeitung und wenn man auf fertig drückt dann wird es zu + erledigt. Die Aufgabe bleibt aber im status offen stehen." + + Sie hat ZWEI Zustaende gefunden, die nebeneinander liefen: + + aufgaben.status offen · arbeit · review · erledigt + aufgaben_zuteilung.zustand angenommen · arbeit · erledigt + + Auf der Karte stand „in Bearbeitung", in der Liste „offen" -- und + beides stimmte. Das ist schlimmer als ein Fehler: Es gibt nichts, + dem man glauben kann. + + Die zwei Knoepfe sind weg (ihr Wunsch). Damit das keine stille + Verschlechterung ist -- die ZAEHLER einer Person lesen die + Zuteilung, nicht die Aufgabe --, folgt die Zuteilung jetzt dem + Status. Hier wird beides zusammen gemessen: der Zustand UND die + Zahl, die daraus entsteht. + =================================================================== */ + /* UEBER DIE LISTE, nicht ueber einen Einzelabruf: Den gibt es nicht, + und `meine_zuteilung` haengt genau dort dran. Mein erster Anlauf + fragte `/aufgaben/` und bekam ueberall `null` -- die Pruefung + war rot, und der Fehler war meiner. */ + const standVon = async (keks, id) => { + const r = await rufe("/workspace/api/aufgaben", { keks }); + const a = (r.json?.aufgaben || []).find((x) => x.id === id); + return a?.meine_zuteilung?.zustand ?? null; + }; + const zaehlerVon = async (keks) => + (await rufe("/workspace/api/aufgaben/resuemee", { keks })).json || {}; + + const vorher = await standVon(bea, idPool); + const zVorher = await zaehlerVon(bea); + ok(vorher === "angenommen", + `Bea hat die Aufgabe angenommen, mehr nicht (${vorher})`); + + const aufArbeit = await rufe(`/workspace/api/aufgaben/${idPool}`, { method: "PATCH", + keks: chef, body: { status: "arbeit" } }); + ok(aufArbeit.code === 200, `die Leitung setzt die AUFGABE auf "arbeit" (${aufArbeit.code})`); + const nachArbeit = await standVon(bea, idPool); + ok(nachArbeit === "arbeit", + ` und Beas Zuteilung geht mit (${vorher} -> ${nachArbeit})`); + const zArbeit = await zaehlerVon(bea); + ok(zArbeit.arbeit > (zVorher.arbeit || 0), + ` ihr Zaehler „in Arbeit" steigt (${zVorher.arbeit || 0} -> ${zArbeit.arbeit})`); + + /* ZURUECK AUF OFFEN HEISST NICHT „NICHT ZUGESAGT". Das ist die eine + Abweichung von der Zuordnung, und sie wird hier gemessen: Wer + zugesagt hat, faellt nicht auf „offen" zurueck. */ + const zurueck = await rufe(`/workspace/api/aufgaben/${idPool}`, { method: "PATCH", + keks: chef, body: { status: "offen" } }); + ok(zurueck.code === 200, `zurueck auf "offen" (${zurueck.code})`); + const nachZurueck = await standVon(bea, idPool); + ok(nachZurueck === "angenommen", + ` Beas Zusage bleibt bestehen — „angenommen", nicht „offen" (${nachZurueck})`); + + /* UND EINE BEWERBUNG WIRD NICHT UEBERSCHRIEBEN. Wer sich nur + beworben hat, hat nicht zugesagt -- ein Statuswechsel darf daraus + keine Zusage machen. */ + const vorBew = await rufe(`/workspace/api/aufgaben/${idPool}/bewerben`, + { method: "POST", keks: cem, body: {} }); + const bewStand = await standVon(cem, idPool); + await rufe(`/workspace/api/aufgaben/${idPool}`, { method: "PATCH", + keks: chef, body: { status: "arbeit" } }); + const bewNachher = await standVon(cem, idPool); + ok(bewStand === bewNachher, + `eine Bewerbung bleibt eine Bewerbung (${bewStand} -> ${bewNachher},` + + ` HTTP ${vorBew.code})`); + + /* =================================================================== + DIE SCHMALE TUER -- UND IHRE GRENZEN (02.10.2026) + + Damit VanVans Satz stimmt („es gibt darüber ja den button + starten"), darf jetzt auch jemand den STATUS setzen, der die + Aufgabe nur HAT. Vorher konnte das nur, wer sie umschreiben + durfte -- eine Modi sah deshalb gar keinen Knopf (gemessen: + „Schritt-Knoepfe: []"). + + EIN ERWEITERTES RECHT BRAUCHT GEGENPROBEN, sonst ist es ein Loch + mit Begruendung. Drei Stueck: + · Der Status ja. + · Alles andere nein -- auch nicht in derselben Anfrage. + · Und wer sich nur beworben hat, gar nichts. + =================================================================== */ + /* DER REINE FALL WIRD GESUCHT, NICHT GERATEN. + + Mein erster Anlauf nahm Bea und die Pool-Aufgabe -- und die + Gegenprobe wurde rot: Bea darf sie ohnehin umschreiben, weil das + Uebernehmen aus dem Pool sie verantwortlich macht. Sie ist also + der falsche Zeuge; an ihr laesst sich ueber die neue, schmale + Tuer gar nichts zeigen. + + Gebraucht wird jemand, der eine Aufgabe NUR ueber die Zuteilung + hat: `meine_zuteilung` gesetzt, `darf_aendern` falsch. Genau das + wird hier gesucht -- eine fest hingeschriebene Nummer waere die + naechste, die beim naechsten Umbau nicht mehr stimmt. */ + const annaListe = await rufe("/workspace/api/aufgaben", { keks: anna }); + const rein = (annaListe.json?.aufgaben || []).find((x) => + x.darf_aendern === false + && ["angenommen", "arbeit"].includes(x.meine_zuteilung?.zustand)); + ok(!!rein, + `Anna hat eine Aufgabe, die ihr NUR zugeteilt ist (#${rein?.id},` + + ` zustand ${rein?.meine_zuteilung?.zustand}, darf_aendern ${rein?.darf_aendern})`); + ok(rein?.darf_status === true, + ` und der Server gibt ihr trotzdem das Statusrecht (darf_status ${rein?.darf_status})`); + + const selbstStatus = await rufe(`/workspace/api/aufgaben/${rein?.id}`, + { method: "PATCH", keks: anna, body: { status: "arbeit" } }); + ok(selbstStatus.code === 200, + `sie setzt den Status ihrer Aufgabe (HTTP ${selbstStatus.code})`); + + const auchTitel = await rufe(`/workspace/api/aufgaben/${rein?.id}`, + { method: "PATCH", keks: anna, body: { status: "offen", titel: "Umbenannt" } }); + ok(auchTitel.code === 403, + `Gegenprobe: mit einem zweiten Feld kommt sie nicht durch (${auchTitel.code})` + + " — sonst waere die schmale Tuer die breite mit Zusatzfeld"); + + const nurTitel = await rufe(`/workspace/api/aufgaben/${rein?.id}`, + { method: "PATCH", keks: anna, body: { titel: "Umbenannt" } }); + ok(nurTitel.code === 403, + ` und umschreiben darf sie gar nicht (${nurTitel.code})`); + + const heisstNoch = await rufe("/workspace/api/aufgaben", { keks: chef }); + const titelJetzt = (heisstNoch.json?.aufgaben || []) + .find((x) => x.id === rein?.id)?.titel || ""; + ok(titelJetzt !== "Umbenannt", + ` der Titel steht unveraendert da („${titelJetzt}")`); + + const fremderStatus = await rufe(`/workspace/api/aufgaben/${rein?.id}`, + { method: "PATCH", keks: cem, body: { status: "erledigt" } }); + ok(fremderStatus.code === 403 || fremderStatus.code === 404, + ` und wer sie nicht hat, setzt auch keinen Status (${fremderStatus.code})`); + + /* ZURUECKRAEUMEN -- und die Rueckfahrt ist selbst eine Messung. + + Dieser Abschnitt hat Annas Aufgabe auf „arbeit" gestellt. Ein + spaeterer Abschnitt zaehlt ihre „angenommen" und wurde dadurch + rot: ein Befund, den ich selbst erzeugt habe. Eine Pruefung, die + den Bestand fuer die naechste veraendert, misst ab da etwas + anderes als sie glaubt. + + Das Zuruecksetzen beweist dabei die zweite Haelfte der Regel: + Wer zugesagt hat, faellt beim Zurueckdrehen auf „angenommen" -- + nicht auf „offen". */ + await rufe(`/workspace/api/aufgaben/${rein?.id}`, + { method: "PATCH", keks: chef, body: { status: "offen" } }); + const wiederDa = await standVon(anna, rein?.id); + ok(wiederDa === "angenommen", + ` zurueckgedreht steht Anna wieder auf „angenommen" (${wiederDa})`); + const arbeit = await rufe(`/workspace/api/aufgaben/${idPool}/mein-stand`, { method: "POST", keks: bea, body: { zustand: "arbeit" } }); + /* DER WEG BLEIBT BESTEHEN, auch ohne Knopf: Er ist die Schranke + fuer den, der die Schnittstelle direkt anspricht. */ ok(arbeit.code === 200, `Bea setzt "in Bearbeitung" (HTTP ${arbeit.code})`); const fertig = await rufe(`/workspace/api/aufgaben/${idPool}/mein-stand`, { method: "POST", keks: bea, body: { zustand: "erledigt" } }); @@ -573,15 +728,35 @@ melde("\n=== Am echten Bildschirm ==="); ok(band && band.zahlen.length >= 3, `mit Zahlen (${(band?.zahlen || []).join(" · ")})`); - /* DER KERN DER OBERFLAECHE: Der Knopf, mit dem sie annimmt. */ - const knopf = await pAnna.seite.evaluate(() => { - const k = [...document.querySelectorAll(".z-karte .knopf")] - .map((b) => b.textContent.trim()); - return k; - }); - ok(knopf.length > 0, `an ihren Karten stehen Knoepfe (${knopf.join(", ")})`); - ok(knopf.includes("Fertig") || knopf.includes("Ich fange an") || knopf.includes("Annehmen"), - "und darunter einer, der sie weiterbringt"); + /* ==== KOMMT SIE WEITER? (neu gemessen 02.10.2026) ============= + + Hier stand: „an ihren Karten stehen Knoepfe" und darunter eine + ODER-Bedingung auf „Fertig", „Ich fange an" oder „Annehmen". + + Seit die zwei Knoepfe weg sind (VanVans Meldung), trifft keiner + der ersten beiden mehr zu -- und die Pruefung wurde rot. Das war + richtig von ihr: Sie hat gefragt, ob ein Modi noch + weiterkommt, und genau das ist die Frage. + + GEMESSEN WIRD JETZT DIE GANZE SEITE und nicht nur die + Zuteilungskarte. Die Zuteilungskarte ist fuer das ANNEHMEN + zustaendig; weitergebracht wird man mit dem Statusknopf + („starten ▶"), und der steht im Aufgabenbrett darueber. Eine + Pruefung, die nur auf die Karte sieht, haette gemeldet „keine + Knoepfe", waehrend direkt daneben einer steht. */ + const knopf = await pAnna.seite.evaluate(() => + [...document.querySelectorAll(".z-karte .knopf")].map((b) => b.textContent.trim())); + const weiter = await pAnna.seite.evaluate(() => + [...document.querySelectorAll(".schritt")].map((b) => b.textContent.trim())); + console.log(` Karten-Knoepfe: [${knopf.join(", ")}]`); + console.log(` Schritt-Knoepfe: [${weiter.join(", ")}]`); + /* „Ich fange an" und „Fertig" duerfen NICHT mehr dastehen -- das + ist VanVans Meldung, als Behauptung festgehalten. */ + ok(!knopf.includes("Ich fange an") && !knopf.includes("Fertig") + && !weiter.includes("Ich fange an") && !weiter.includes("Fertig"), + "die zwei Knoepfe, die nur den halben Zustand setzten, sind weg"); + ok(weiter.some((t) => /starten|Freigabe|erledigt/.test(t)), + `aber sie kommt weiter — ueber den Statusknopf (${weiter.join(", ") || "KEINER"})`); /* GEGENPROBE: Es darf KEIN Bewerten-Knopf dastehen. */ const bewerten = await pAnna.seite.evaluate(() => diff --git a/server/workspace-aufgaben.js b/server/workspace-aufgaben.js index 93975e4f..47926626 100644 --- a/server/workspace-aufgaben.js +++ b/server/workspace-aufgaben.js @@ -28,7 +28,8 @@ const TEXT_MAX = 4000; const jetzt = () => new Date().toISOString(); -import { zuteilen, mitZuteilung, bewerbungenSchliessen } from "./workspace-zuteilung.js"; +import { zuteilen, mitZuteilung, bewerbungenSchliessen, + zuteilungenNachStatus } from "./workspace-zuteilung.js"; /* ---------- Schranke ---------------------------------------------------- */ @@ -376,6 +377,50 @@ function darfLoeschen(person, aufgabe) { return darfAufgabenVerteilen(person) && darfAendern(person, aufgabe); } +/* ===== UND DEN STATUS? (02.10.2026) ================================= + + VanVan im Support: „Die beiden Buttons können entfernt werden, weil + es darüber ja den button starten gibt, der auch korrekt funktioniert + wenn man ihn benutzt und die Aufgabe dann aus dem Status offen zum + Status in Arbeit schiebt." + + SIE HAT RECHT -- ABER NICHT FUER DEN MODI. Nachgemessen am + Bildschirm einer Modi mit einer angenommenen Aufgabe: + + Karten-Knoepfe: [] + Schritt-Knoepfe: [] + + Keiner. Sie sieht den Starten-Knopf nicht, weil `darfAendern` fuer + sie falsch ist -- sie ist weder Leitung noch `creator_id`, + `verantwortlich_id` oder `erstellt_von`; die Zuteilung laeuft ueber + eine eigene Tabelle. VanVan ist Leitung und sieht ihn, deshalb + klang „den gibt es doch" selbstverstaendlich. + + Haette ich die zwei Knoepfe einfach geloescht, haette ich der Modi + die einzige Handlung weggenommen, die sie hatte. Das waere eine + Meldung „behoben" gewesen, nach der weniger geht als vorher. + + ALSO WIRD IHR SATZ WAHR GEMACHT: Wer eine Aufgabe wirklich hat, + darf ihren STATUS setzen. Damit gibt es EINEN Weg fuer alle, und + der bewegt das, was alle lesen. + + ENG GEFASST, UND ZWAR ABSICHTLICH: nur der Status. Nicht Titel, + nicht Frist, nicht die Zuteilung. `darfAendern` bleibt unberuehrt + -- wer eine Aufgabe bekommt, darf sie tun, nicht umschreiben. + + NUR WER ZUGESAGT HAT. Eine Bewerbung ist keine Zusage, und ein + „abgelehnt" ist eine Entscheidung; beide geben kein Recht. */ +function darfStatusSetzen(person, aufgabe) { + if (darfAendern(person, aufgabe)) return true; + try { + const z = db().prepare(`SELECT 1 FROM aufgaben_zuteilung + WHERE aufgabe_id = ? AND person_id = ? + AND zustand IN ('angenommen','arbeit','erledigt')`) + .get(aufgabe.id, person.id); + return !!z; + } catch { return false; } +} + const SPALTEN = ` a.id, a.titel, a.beschreibung, a.status, a.prioritaet, a.aufwand, a.frist, /* DAUERHAFT (30.09.2026). Ohne diese Spalte weiss die Oberflaeche @@ -492,6 +537,13 @@ aufgabenRouter.get("/workspace/api/aufgaben", (req, res) => { sichtbare. */ for (const r of reihen) { r.darf_aendern = darfAendern(req.person, r); + /* DEN STATUS DARF AUCH, WER SIE NUR HAT (02.10.2026). + Zwei Rechte statt einem, weil es zwei Fragen sind: + „darf ich das umschreiben?" und „darf ich das tun?". + Die Oberflaeche zeigt die Schrittknoepfe jetzt an diesem + Recht -- vorher hing sie am falschen, und eine Modi sah + gar keinen. */ + r.darf_status = darfStatusSetzen(req.person, r); /* DIE OBERFLAECHE SOLL DEN KNOPF NUR ZEIGEN, WENN ER GEHT. Bisher entschied sie es an `darf_verteilen` -- einer Auskunft ueber die PERSON. Loeschen haengt aber an der AUFGABE: Die @@ -1050,7 +1102,21 @@ aufgabenRouter.patch("/workspace/api/aufgaben/:id", gleicheHerkunft, (req, res) const aufgabe = db().prepare( `SELECT a.* ${VERBUND} WHERE ${wo} AND a.id = ?`).get(...werte, id); if (!aufgabe) return res.status(404).json({ fehler: "nicht_gefunden" }); - if (!darfAendern(req.person, aufgabe)) return res.status(403).json({ fehler: "nicht_erlaubt" }); + /* ==== ZWEI TUEREN, UND DIE SCHMALE IST NEU (02.10.2026) ======= + + Wer die Aufgabe aendern darf, darf alles. Wer sie nur HAT, darf + genau eine Sache: ihren Status setzen -- und nichts sonst in + derselben Anfrage. Die Begruendung steht bei `darfStatusSetzen`. + + DIE ENGE PRUEFUNG IST `Object.keys`, nicht „enthaelt status": + Wer `{status, titel}` schickt, kommt nicht durch. Sonst waere + die schmale Tuer die breite mit einem Zusatzfeld. */ + const nurStatus = Object.keys(req.body || {}).length === 1 + && typeof req.body?.status === "string"; + if (!darfAendern(req.person, aufgabe) + && !(nurStatus && darfStatusSetzen(req.person, aufgabe))) { + return res.status(403).json({ fehler: "nicht_erlaubt" }); + } const { aus, fehler } = pruefeFelder(req.body || {}, { neu: false, person: req.person }); if (fehler.length) return res.status(400).json({ fehler: fehler.join(" ") }); @@ -1084,6 +1150,30 @@ aufgabenRouter.patch("/workspace/api/aufgaben/:id", gleicheHerkunft, (req, res) db().prepare(`UPDATE aufgaben SET ${setz.join(", ")} WHERE id = ?`).run(...daten, id); + /* ==== DER STATUS ZIEHT DIE ZUTEILUNGEN MIT (02.10.2026) ======== + + VanVan im Support: „Wenn man auf ich fange an drückt steht dort + in Bearbeitung … Die Aufgabe bleibt aber im status offen + stehen." + + Bis heute liefen zwei Zustaende nebeneinander: der der AUFGABE + (offen/arbeit/review/erledigt) und der der ZUTEILUNG + (angenommen/arbeit/erledigt). Zwei Knoepfe setzten den einen, + der Starten-Knopf den anderen -- und keiner wusste vom anderen. + Auf der Karte stand „in Bearbeitung", in der Liste „offen". + + Die Knoepfe sind weg. Damit die Zaehler der Person trotzdem + stimmen (sie lesen die ZUTEILUNG), folgt sie jetzt dem Status. + Die Begruendung im Einzelnen steht bei `zuteilungenNachStatus`; + sie gehoert dorthin, wo die Zuordnung steht. */ + if (aus.status && aus.status !== aufgabe.status) { + const mit = zuteilungenNachStatus(id, aus.status); + if (mit) { + console.log(`[aufgaben] #${id} -> ${aus.status}: ${mit} Zuteilung(en) ` + + "sind mitgegangen."); + } + } + /* DIE ZUTEILUNG MITAENDERN (21.09.2026). Wer eine Aufgabe bearbeitet und dabei die Leute wechselt, meint genau das -- eine Aenderung, die nur den Titel mitnimmt und die Zuteilung stehen diff --git a/server/workspace-zuteilung.js b/server/workspace-zuteilung.js index 03559037..4616afc3 100644 --- a/server/workspace-zuteilung.js +++ b/server/workspace-zuteilung.js @@ -883,6 +883,63 @@ const STATUS_ALS_ZUSTAND = { offen: "offen", arbeit: "arbeit", review: "arbeit", erledigt: "erledigt", }; +/** Der Status der Aufgabe zieht die Zuteilungen mit. (02.10.2026) + * + * ==== WARUM ES DAS GIBT ============================================= + * + * VanVan im Support: „Wenn man auf ich fange an drückt steht dort in + * Bearbeitung und wenn man auf fertig drückt dann wird es zu erledigt. + * Die Aufgabe bleibt aber im status offen stehen. Die beiden Buttons + * können entfernt werden, weil es darüber ja den button starten gibt, + * der auch korrekt funktioniert." + * + * Sie hat zwei Zustandsmaschinen gefunden, die nebeneinander liefen + * und sich nicht kannten: + * + * aufgaben.status offen · arbeit · review · erledigt + * aufgaben_zuteilung.zustand angenommen · arbeit · erledigt + * + * Die zwei Knoepfe setzten den zweiten, der „starten"-Knopf den + * ersten. Auf der Karte stand „in Bearbeitung", in der Liste „offen", + * und beides war richtig -- was es noch schlimmer macht. + * + * DIE KNOEPFE SIND WEG, WIE SIE ES WOLLTE. Aber sie einfach zu + * loeschen waere eine stille Verschlechterung gewesen: Die ZAEHLER + * einer Person („offen / in Arbeit / erledigt" im Resuemee) lesen den + * Zustand der ZUTEILUNG, nicht den der Aufgabe. Ohne die Knoepfe + * waere jede Zuteilung fuer immer auf „angenommen" stehen geblieben, + * und die Zahlen haetten aufgehoert, die Wirklichkeit zu zeigen -- + * ohne dass irgendwo etwas rot wird. + * + * Jetzt gibt es EINE Wahrheit: den Status der Aufgabe. Die Zuteilung + * folgt ihm. + * + * ABGELEITET, NICHT ZWEIMAL GESCHRIEBEN: Die Zuordnung oben wird + * benutzt, mit genau EINER Abweichung, und die steht hier -- + * `offen` bedeutet in der Zuteilung „noch nicht zugesagt". Wer schon + * zugesagt hat, faellt nicht dorthin zurueck, nur weil die Aufgabe + * wieder auf offen gesetzt wird; er ist dann „angenommen". + * + * WESSEN ZUSTAND SICH AENDERT: nur der von Leuten, die die Aufgabe + * WIRKLICH haben (angenommen/arbeit/erledigt). Eine Bewerbung + * („beworben") ist keine Zusage, und ein „abgelehnt" ist eine + * Entscheidung -- beide darf ein Statuswechsel nicht ueberschreiben. + */ +export function zuteilungenNachStatus(aufgabeId, status) { + const abgeleitet = STATUS_ALS_ZUSTAND[status]; + /* „abgebrochen" steht nicht in der Zuordnung -- dort bleibt alles, + wie es war. Wer wieder aufnimmt, findet seinen Stand vor. */ + if (!abgeleitet) return 0; + const ziel = abgeleitet === "offen" ? "angenommen" : abgeleitet; + const e = db().prepare(`UPDATE aufgaben_zuteilung + SET zustand = ?, erledigt_am = ? + WHERE aufgabe_id = ? + AND zustand IN ('angenommen','arbeit','erledigt') + AND zustand <> ?`) + .run(ziel, ziel === "erledigt" ? jetzt() : null, Number(aufgabeId), ziel); + return e.changes || 0; +} + export function resuemeeFuer(personId) { /* ZWEI QUELLEN, EIN ERGEBNIS (22.09.2026). ========================================================== diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 0bd54874..a1dfba7e 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`. --> - - + + - +