diff --git a/server/pruef-bewerbung-aufgaben.mjs b/server/pruef-bewerbung-aufgaben.mjs index 82cfd4bc..50b009ad 100644 --- a/server/pruef-bewerbung-aufgaben.mjs +++ b/server/pruef-bewerbung-aufgaben.mjs @@ -285,6 +285,47 @@ melde("=== 4. Bewerben ==="); /* ===================================================================== 5. ENTSCHEIDEN — nur die rechte Hand und DogFather ===================================================================== */ +melde(""); +melde("=== 4b. Wer zuerst Zeit hat, steht zuerst ==="); +{ + /* Filipe, nachdem er meinen Satz „wer zuerst Zeit hat — genau das + geht ja nicht mehr" gelesen hatte: „falls das nicht mehr geht mach + das es geht wenn es wieder geht ist alles gut." + + „Wer zuerst Zeit hat" heisst nicht zwingend „man bedient sich + selbst" -- es kann genauso heissen, dass die ERSTE BEWERBUNG zuerst + drankommt. Damit gilt beides gleichzeitig. + + GEPRUEFT WIRD DIE REIHENFOLGE, NICHT DER SATZ. Die Abfrage + sortiert sonst nach NAMEN; nach Alphabet haette nicht der + Schnelle den Vorteil, sondern Frida. Genau deshalb steht hier + eine Messung und kein Blick in den Quelltext. */ + const r = await aufgabeAnlegen(chef.keks, "Wer zuerst kommt", [idModi, idModi2], "pool"); + const id = r.json?.id; + ok(r.code === 201, `eine neue Pool-Aufgabe (${r.code})`); + + /* NELE ZUERST -- und sie steht im Alphabet HINTER Frida. Waere die + Liste nach Namen sortiert, faellt genau das auf. */ + const n1 = await roh(`/workspace/api/aufgaben/${id}/bewerben`, { + methode: "POST", keks: modi2.keks, body: { text: "Ich kann sofort." } }); + ok(n1.code === 200, `Nele bewirbt sich zuerst (${n1.code})`); + await new Promise((w) => setTimeout(w, 1100)); + const n2 = await roh(`/workspace/api/aufgaben/${id}/bewerben`, { + methode: "POST", keks: modi.keks, body: { text: "Ich auch." } }); + ok(n2.code === 200, `Frida danach (${n2.code})`); + + const a = await finde(chef.keks, "Wer zuerst kommt"); + const reihe = (a?.bewerbungen || []).map((z) => z.person_name); + ok(reihe.length === 2, `zwei Bewerbungen liegen vor (${reihe.length})`); + ok(reihe[0] === "Nele", + `Nele steht vorn, weil sie zuerst da war (${reihe.join(" vor ")})`); + /* GEGENPROBE: Nach Namen sortiert stuende Frida vorn. Die Messung + kann den Unterschied also sehen -- ohne diese Zeile waere + „Nele steht vorn" auch bei einer zufaelligen Ordnung mal richtig. */ + ok([...reihe].sort()[0] === "Frida", + "nach Alphabet staende Frida vorn — die Reihenfolge ist also wirklich die des Eingangs"); +} + melde(""); melde("=== 5. Entscheiden ==="); { @@ -541,6 +582,93 @@ melde("=== 6. Am echten Bildschirm ==="); `und keinen Block zum Entscheiden (${b?.bewerbungen})`); } finally { await m2.ctx.close(); } + /* ================================================================= + 7. AUFGABEN DORT ANLEGEN, WO DIE PERSON STEHT (A5 und B6) + ================================================================= + Filipe: „dieser buttion da soll nicht einen zu der seite + aufgaben fuehren sondern da in dieser seite die aufgaben + erstellen und vergeben koennen. plus man soll die aufgaben hier + in dieser seite auch sehen." Und zum Aufgabenbrett: „dieser + button kann da jetzt doch endlich verschwinden." + + DER ORT WIRD GEMESSEN, NICHT ANGENOMMEN: Auf der Agenturadresse + gibt es die Entwicklungsseite nicht, und vier Rollen dort + duerfen sehr wohl Aufgaben anlegen. Der Knopf verschwindet also + genau dort, wo es einen anderen Ort gibt -- und nur dort. + ================================================================= */ + const e = await seiteAls("admin", "CODE-DOGI-0001", 1180); + try { + /* B6: auf dem Brett ist der Knopf weg. */ + const b6 = await e.s.evaluate(() => { + const k2 = document.getElementById("neu-oeffnen"); + return { da: !!k2, versteckt: k2 ? k2.hidden : null, + ort: window.__ich?.aufgaben_anlegen_auf }; + }); + ok(b6.ort === "entwicklung", + `der Server nennt die Entwicklungsseite als Ort (${b6.ort})`); + ok(b6.da && b6.versteckt === true, + `und der Knopf auf dem Aufgabenbrett ist weg (${b6.versteckt})`); + + /* A5: auf der Entwicklungsseite geht es jetzt. */ + await e.s.goto(`${SEITE}/workspace/entwicklung.html`, { waitUntil: "networkidle" }); + await e.s.waitForTimeout(1800); + + const vorher = await e.s.evaluate(() => { + const k2 = document.getElementById("verteilen-knopf"); + return { istLink: k2?.tagName === "A", aus: k2?.disabled, + aufgabenBlock: !document.getElementById("e-aufgaben-block")?.hidden }; + }); + ok(vorher.istLink === false, + "der Knopf fuehrt nicht mehr auf eine andere Seite (kein Link mehr)"); + ok(vorher.aus === true, + "und ist aus, solange niemand gewaehlt ist — er haette nichts zu verteilen"); + ok(vorher.aufgabenBlock === false, + "ohne Person steht auch keine Aufgabenliste da"); + + await e.s.click("#personen .e-person"); + await e.s.waitForTimeout(1400); + const nachher = await e.s.evaluate(() => ({ + knopf: document.getElementById("verteilen-knopf")?.textContent.trim(), + aus: document.getElementById("verteilen-knopf")?.disabled, + block: !document.getElementById("e-aufgaben-block")?.hidden, + titel: document.getElementById("e-aufgaben-titel")?.textContent.trim(), + })); + ok(nachher.aus === false && /Aufgabe für/.test(nachher.knopf || ""), + `nach der Auswahl heisst er „${nachher.knopf}"`); + ok(nachher.block === true && /Aufgaben von/.test(nachher.titel || ""), + `und die Aufgabenliste steht da („${nachher.titel}")`); + + await e.s.click("#verteilen-knopf"); + await e.s.waitForTimeout(500); + const form = await e.s.evaluate(() => ({ + offen: !document.getElementById("e-neu")?.hidden, + felder: document.querySelectorAll("#e-neu input, #e-neu textarea").length, + })); + ok(form.offen === true && form.felder >= 3, + `das Formular oeffnet sich auf derselben Seite (${form.felder} Felder)`); + + const titel = "Auf der Entwicklungsseite angelegt"; + await e.s.fill("#e-titel", titel); + await e.s.click("#e-neu-speichern"); + await e.s.waitForTimeout(1800); + const fertig = await e.s.evaluate((t) => ({ + zu: document.getElementById("e-neu")?.hidden, + steht: [...document.querySelectorAll(".e-aufgabe__titel")].some((x) => x.textContent === t), + gut: document.getElementById("fehler")?.dataset.gut, + }), titel); + ok(fertig.zu === true, "nach dem Anlegen ist das Formular zu"); + ok(fertig.steht === true, "und die Aufgabe steht sofort in der Liste darunter"); + /* EINE BESTAETIGUNG DARF NICHT AUSSEHEN WIE EIN FEHLER. Auf dem + ersten Bildschirmfoto stand sie in Warnrot -- `melde` schreibt + immer in denselben Absatz, und der heisst `.fehler`. */ + ok(fertig.gut === "ja", + `und die Bestaetigung ist als gute Nachricht gekennzeichnet (${fertig.gut})`); + + /* GEGENPROBE: Die Aufgabe ist wirklich da, nicht nur im Bild. */ + const ausDerListe = await finde(chef.keks, titel); + ok(!!ausDerListe, "sie steht auch wirklich in der Aufgabenliste des Servers"); + } finally { await e.ctx.close(); } + ok(meckern.length === 0, meckern.length ? `der Browser meldet: ${meckern.slice(0, 3).join(" | ")}` : "kein Absturz und keine Fehlermeldung im Browser"); @@ -554,8 +682,8 @@ melde("=== 6. Am echten Bildschirm ==="); ERGEBNIS ===================================================================== */ melde(""); -if (geprueft < 55) { - console.log(` FEHL nur ${geprueft} Pruefungen gelaufen -- erwartet waren 55+`); +if (geprueft < 70) { + console.log(` FEHL nur ${geprueft} Pruefungen gelaufen -- erwartet waren 70+`); fehler++; } melde(fehler === 0 diff --git a/server/workspace-zuteilung.js b/server/workspace-zuteilung.js index af25c714..b741e41d 100644 --- a/server/workspace-zuteilung.js +++ b/server/workspace-zuteilung.js @@ -183,7 +183,30 @@ export function mitZuteilung(reihen, ich) { /* DIE OFFENEN BEWERBUNGEN, abgeleitet statt danebengefuehrt. Die Oberflaeche soll nicht dieselbe Rechnung noch einmal machen -- zwei Rechnungen fuer dieselbe Frage laufen auseinander. */ - r.bewerbungen = zu.filter((z) => z.zustand === "beworben"); + /* NACH EINGANG SORTIERT, NICHT NACH NAMEN (22.09.2026). + + Filipe zu meinem eigenen Satz „wer zuerst Zeit hat — genau das + geht ja nicht mehr": „falls das nicht mehr geht mach das es + geht wenn es wieder geht ist alles gut." + + Er hat recht, und ich hatte es zu schnell aufgegeben. „Wer + zuerst Zeit hat" muss nicht heissen, dass man sich selbst + bedient -- es kann genauso heissen, dass die ERSTE BEWERBUNG + zuerst drankommt. Damit gilt beides: Die Leitung entscheidet + (sein Wunsch von vorhin), und wer schnell ist, hat den Vorteil + (sein Wunsch von eben). + + Die Abfrage weiter oben sortiert nach NAMEN -- gut fuer die + Liste der Zugeteilten, falsch hier: Nach Alphabet gaebe es + keinen Vorteil fuer den Schnellen, sondern einen fuer Anna. + + `geantwortet_am` ist der Zeitpunkt der Bewerbung (beim + Bewerben gesetzt). Gleichstand gibt es praktisch nicht; falls + doch, entscheidet die laufende Nummer -- irgendeine Ordnung + ist besser als eine zufaellige. */ + r.bewerbungen = zu.filter((z) => z.zustand === "beworben") + .sort((x, y) => String(x.geantwortet_am || "").localeCompare(String(y.geantwortet_am || "")) + || (x.id - y.id)); /* UND OB ICH MICH UEBERHAUPT NOCH BEWERBEN KANN. Ein Knopf, der beim Druecken eine Absage bringt, ist schlimmer als keiner. */ r.ich_beworben = !!(r.meine_zuteilung diff --git a/server/workspace.js b/server/workspace.js index ada98600..cfd85c9c 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -5941,6 +5941,47 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => { /* Damit die Oberflaeche den Knopf gar nicht erst anbietet -- ein Knopf, der mit 403 antwortet, ist schlimmer als keiner. */ darf_aufgaben_anlegen: darfAufgabenAnlegen(person), + /* WO AUFGABEN ANGELEGT WERDEN (22.09.2026). + + Filipe, zum Knopf auf dem Aufgabenbrett: „dieser button kann da + jetzt doch endlich verschwinden, auf dieser seite sollen ja + keine aufgaben mehr verteilt werden." Und zur + Entwicklungsseite: „dieser buttion da soll nicht einen zu der + seite aufgaben fuehren sondern da in dieser seite die aufgaben + erstellen und vergeben koennen." + + ABER NICHT UEBERALL, UND DAS IST GEMESSEN. Die Entwicklungs- + seite steht vier Rollen gar nicht offen, die sehr wohl Aufgaben + anlegen duerfen: + + Rolle/Haus aufgaben.html entwicklung.html darfAnlegen + manager/agentur ja NEIN ja + creator/agentur ja NEIN ja + scout/agentur ja NEIN ja + spicy/agentur ja NEIN ja + + Haette ich den Knopf einfach entfernt, koennten vier Rollen gar + keine Aufgabe mehr anlegen -- und Filipe hat beim Schreiben auf + den Team-Dogi-Bildschirm gesehen, nicht auf ihren. + + DER ORT WIRD AUS DEN KACHELN ABGELEITET, nicht aus dem + Seitenrecht und schon gar nicht aus dem Haus. + + Der erste Entwurf fragte `darfSeite(..., entwicklung.html)`. + Gemessen war das falsch: DogFather DARF die Seite auf der + Agenturadresse oeffnen (er darf alles), hat dort aber keine + Kachel dorthin -- der Knopf waere ihm weggenommen worden und + der Ersatz unauffindbar gewesen. + + Massgeblich ist deshalb, ob die Entwicklungsseite ueberhaupt zu + seinen Bereichen gehoert. Das ist dieselbe Quelle, aus der die + Startseite ihre Kacheln baut -- eine zweite Liste waere die, + die auseinanderlaeuft. */ + aufgaben_anlegen_auf: [ + ...(bereicheFuer(person) || []), + ...(zusatzBereicheFuer(person) || []), + ].some((k) => String(k.ziel || "").startsWith("entwicklung.html")) + ? "entwicklung" : "aufgaben", /* FUEHRT JEMAND TEAM DOGI? (22.09.2026) Damit die Entwicklungsseite die Kartenliste GAR NICHT ERST abfragt, wenn es sie fuer diese Person nicht gibt. Der Code diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 525ca177..aa903e2f 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +