diff --git a/server/mess-manager-ziele.mjs b/server/mess-manager-ziele.mjs index 8a99f418..4b322a4a 100644 --- a/server/mess-manager-ziele.mjs +++ b/server/mess-manager-ziele.mjs @@ -94,6 +94,22 @@ setz.run(idScout, "meeting", MONAT, HEUTE, null, null, null, null, null, "Wochen setz.run(idScout, "werbung", MONAT, HEUTE, null, null, "https://www.tiktok.com/@dogfather/video/7312", "tiktok.com/@dogfather/video/7312", "agentur", null, jetzt); +/* ZWEI WERBE-VIDEOS UEBER DEM ZIEL (07.10.2026). + + „Werbung" fordert eines im Monat -- mit diesen beiden steht die Zeile + auf 3/1. Ohne so einen Stand waere auf dem Bild nicht zu sehen, ob + das „+2" ueberhaupt erscheint, und „kein Plus" sieht genauso aus wie + „Plus kaputt". + + MIT VERSCHIEDENEN LINKS: Derselbe Link zweimal faellt in den + Dublettenschutz, und dann stuenden hier zwei Zeilen in der Datenbank, + die es ueber die Oberflaeche nie gaebe. */ +setz.run(idScout, "werbung", MONAT, HEUTE, null, null, + "https://www.tiktok.com/@dogfather/video/7313", "tiktok.com/@dogfather/video/7313", + "eigen", null, jetzt); +setz.run(idScout, "werbung", MONAT, HEUTE, null, null, + "https://www.tiktok.com/@dogfather/video/7314", "tiktok.com/@dogfather/video/7314", + "agentur", null, jetzt); /* EINE UEBERFAELLIGE AUFGABE -- nicht fuer diese Seite, sondern fuer die KACHELWAND davor. @@ -192,7 +208,7 @@ async function bild(name, keks, breite, hoehe, vorher) { await seite.waitForTimeout(500); const blick = await seite.evaluate(nachmessen); - await seite.screenshot({ path: `bild-manager-ziele-${name}.png`, fullPage: true }); + await seite.screenshot({ path: `server/bild-manager-ziele-${name}.png`, fullPage: true }); console.log(`\n--- ${name} (${breite}px) ---`); console.log(` Ring: ${blick.ring}`); @@ -257,7 +273,7 @@ await bild("formular-handy", keksScout, 412, 915, async (seite) => { const nachher = await zahlVon("schulung"); const meldung = await seite.evaluate(() => document.querySelector(".mz-gelungen")?.textContent || "-"); - await seite.screenshot({ path: "bild-manager-ziele-einklick.png" }); + await seite.screenshot({ path: "server/bild-manager-ziele-einklick.png" }); /* Und zurueck. */ const zurueckDa = await seite.$(".mz-gelungen__zurueck"); @@ -310,7 +326,7 @@ await bild("formular-handy", keksScout, 412, 915, async (seite) => { const gewarnteKachel = await seite.$('.kachel[data-warn="ja"]'); if (gewarnteKachel) await gewarnteKachel.scrollIntoViewIfNeeded(); await seite.waitForTimeout(400); - await seite.screenshot({ path: "bild-manager-ziele-wand.png", fullPage: false }); + await seite.screenshot({ path: "server/bild-manager-ziele-wand.png", fullPage: false }); console.log("\n--- Die Kachelwand (412px) ---"); console.log(` Kachel da: ${wand.kachelDa ? "ja" : "NEIN — Befund!"} (${wand.name})`); console.log(` Abzeichen: ${wand.zahl}`); diff --git a/server/pruef-manager-ziele.mjs b/server/pruef-manager-ziele.mjs index c43d25ec..fc21405a 100644 --- a/server/pruef-manager-ziele.mjs +++ b/server/pruef-manager-ziele.mjs @@ -1291,6 +1291,96 @@ const spicyLoeschen = await hol("spicy", `${API}/eintrag/${lebtNoch}`, { method: ok(spicyLoeschen.status === 200, `Spicy Media kann ihn loeschen (${spicyLoeschen.status})`); +/* ===================================================================== + MEHR ALS DAS ZIEL — eintragen, zählen, und das Ziel bleibt stehen + ===================================================================== + + Filipe (07.10.2026): „soll jeder die möglichkeit auch mehr wie das + ziel einzutragen. die sollen auch gezählt werden. die zahl die die + leute erreichen sollen sollen aber während dems immer gleich bleiben. + mit der möglichkeit die grenzen zu übergehen." + + Drei Behauptungen, drei Messungen: + + 1. Eintragen über dem Ziel wird angenommen + 2. Das Ziel ändert sich dabei NICHT + 3. Was darüber liegt, wird gezählt (`extra`, `getan`) — und + `erledigt` bleibt gedeckelt, weil es die andere Frage + beantwortet + ===================================================================== */ +melde(""); +melde("=== Mehr als das Ziel: angenommen, gezaehlt, Ziel bleibt ==="); +{ + const vorher = await (await hol("scout", `${API}/stand`)).json(); + const v = vorher.aufgaben.find((a) => a.schluessel === "meeting"); + braucht(v && v.ziel > 0, "fuer diese Messung braucht meeting ein Ziel > 0"); + + /* So viele, dass es sicher ueber dem Ziel liegt -- und mit TAGEN AUS + DER VERGANGENHEIT. Ein Datum in der Zukunft wird abgewiesen + (`datum_zukunft`), und beim ersten Anlauf dieser Messung waren + prompt alle fuenf Versuche in der Zukunft: Die Meldung „0 von 5 + angenommen" sah nach einer Sperre aus und war der Kalender. */ + const tage = ["-01", "-02", "-03", "-04", "-05"]; + let angenommen = 0; + for (const t of tage) { + const a = await schick("scout", `${API}/eintrag`, "POST", + { aufgabe: "meeting", datum: vorher.monat + t }); + if (a.status === 201) angenommen++; + } + ok(angenommen === tage.length, + `alle ${tage.length} Eintraege ueber dem Ziel werden angenommen (${angenommen})`); + + const nach = await (await hol("scout", `${API}/stand`)).json(); + const n = nach.aufgaben.find((a) => a.schluessel === "meeting"); + + /* 2. DAS ZIEL BLEIBT. Das ist der Punkt, an dem eine naheliegende + „Loesung" falsch gewesen waere: das Ziel mitwachsen zu lassen. + Dann haette niemand je mehr als sein Ziel. */ + ok(n.ziel === v.ziel, + `das Ziel bleibt unveraendert (${v.ziel} -> ${n.ziel})`); + ok(n.zahl === v.zahl + tage.length, + `die Zahl waechst um genau ${tage.length} (${v.zahl} -> ${n.zahl})`); + ok(n.extra === n.zahl - n.ziel && n.extra > 0, + `was darueber liegt, steht als extra da (${n.extra} = ${n.zahl} - ${n.ziel})`); + ok(n.fehlt === 0, `und es fehlt nichts mehr (${n.fehlt})`); + + /* 3. DIE SUMMEN. `getan` zaehlt alles, `erledigt` deckelt je Aufgabe + -- und genau diese Trennung ist der Kern der Aenderung. */ + const summeRoh = nach.aufgaben.reduce((s, a) => s + a.zahl, 0); + const summeGedeckelt = nach.aufgaben.reduce((s, a) => s + Math.min(a.zahl, a.ziel), 0); + ok(nach.gesamt.getan === summeRoh, + `gesamt.getan zaehlt alles (${nach.gesamt.getan} = ${summeRoh})`); + ok(nach.gesamt.erledigt === summeGedeckelt, + `gesamt.erledigt bleibt je Aufgabe gedeckelt (${nach.gesamt.erledigt} = ${summeGedeckelt})`); + ok(nach.gesamt.getan > nach.gesamt.erledigt, + `und getan liegt jetzt ueber erledigt (${nach.gesamt.getan} > ${nach.gesamt.erledigt})`); + ok(nach.gesamt.extra === nach.gesamt.getan - nach.gesamt.erledigt, + `gesamt.extra ist die Differenz (${nach.gesamt.extra})`); + + /* WARUM DER RING NICHT UEBERLAEUFT -- die Gegenprobe zur Entscheidung, + `erledigt` NICHT zu entdeckeln: Der Anteil bleibt bei hoechstens + 100, obwohl `getan` darueber liegt. Ein voller Ring bei drei + unberuehrten Aufgaben waere eine falsche Auskunft. */ + ok(nach.gesamt.anteil <= 100, + `der Ring laeuft trotzdem nicht ueber (${nach.gesamt.anteil} %)`); + ok(nach.gesamt.offen > 0, + `und die offenen Aufgaben bleiben offen (${nach.gesamt.offen})`); + + /* GEGENPROBE ZUM MESSGERAET: Eine Aufgabe, die NICHT ueber dem Ziel + liegt, hat extra = 0. Sonst hiesse „extra ist gesetzt" nur, dass + das Feld existiert. */ + const ohne = nach.aufgaben.filter((a) => a.zahl <= a.ziel); + ok(ohne.length > 0 && ohne.every((a) => a.extra === 0), + `Aufgaben unter dem Ziel haben extra = 0 (${ohne.length} Stueck)`); + + /* UND IM EXPORT. Eine Zahl, die nur auf dem Bildschirm steht, fehlt + in der Liste, die am Monatsende weitergereicht wird. */ + const csv = await (await hol("admin", `${API}/export.csv`)).text(); + const kopfzeile = csv.split("\n")[0] || ""; + ok(kopfzeile.includes("Gesamt getan"), + `die Ausgabedatei nennt „Gesamt getan" (${kopfzeile.slice(0, 60)}…)`); +} + /* ===================================================================== SCHLUSS ===================================================================== */ diff --git a/server/workspace-manager-ziele.js b/server/workspace-manager-ziele.js index 7893c6bf..64dfe264 100644 --- a/server/workspace-manager-ziele.js +++ b/server/workspace-manager-ziele.js @@ -452,13 +452,41 @@ export function standFuer(person, monat = monatJetzt(), heute = tagBerlin()) { zahl: n, ziel, fehlt: Math.max(0, ziel - n), + /* WAS UEBER DEM ZIEL LIEGT (07.10.2026). + + Filipe: „soll jeder die möglichkeit auch mehr wie das ziel + einzutragen. die sollen auch gezählt werden. die zahl die die + leute erreichen sollen sollen aber immer gleich bleiben." + + Eintragen ging schon immer -- gemessen am 07.10.2026: fuenf von + fuenf Eintraegen wurden angenommen, es gab nie eine Sperre. Was + fehlte, war das ZAEHLEN: Ein Scout mit creator 2/3, meeting 1/2, + schulung 1/2 und werbung 2/1 hat sechs Dinge getan, und die + Seite sagte fuenf. Das eine Werbe-Video ueber dem Ziel fiel + durch `Math.min` heraus -- unsichtbar, ohne Hinweis. */ + extra: Math.max(0, n - ziel), stufe, text: stufenText(stufe, bisEnde), }; }); const zielSumme = aufgaben.reduce((s, a) => s + a.ziel, 0); + /* ZWEI ZAHLEN, WEIL ES ZWEI FRAGEN SIND -- und genau deshalb wird + `erledigt` NICHT einfach entdeckelt: + + erledigt Wie viel von der PFLICHT ist erfuellt? Je Aufgabe + hoechstens ihr Ziel. Daraus kommt der Ring. + getan Wie viel wurde WIRKLICH getan? Ohne Deckel. + + Wer nur `getan` fuehrte, bekaeme einen vollen Ring bei jemandem, + der zehn Werbevideos und sonst nichts gemacht hat: 10 von 8, Ring + voll, drei Aufgaben unberuehrt. Das waere keine Grosszuegigkeit, + sondern eine falsche Auskunft. Wer nur `erledigt` fuehrt -- der + Stand bis heute --, verschweigt die Mehrarbeit. + + Also beides, und jede Zahl beantwortet ihre eigene Frage. */ const erledigt = aufgaben.reduce((s, a) => s + Math.min(a.zahl, a.ziel), 0); + const getan = aufgaben.reduce((s, a) => s + a.zahl, 0); const offene = aufgaben.filter((a) => a.ziel > 0 && a.zahl < a.ziel); return { @@ -466,6 +494,10 @@ export function standFuer(person, monat = monatJetzt(), heute = tagBerlin()) { aufgaben, gesamt: { erledigt, ziel: zielSumme, + /* `getan` und `extra` kommen MIT, damit die Oberflaeche sie nicht + selbst zusammenrechnen muss. Dieselbe Summe an zwei Stellen + waere zwei Gelegenheiten, sie einmal anders zu bilden. */ + getan, extra: Math.max(0, getan - erledigt), anteil: zielSumme ? Math.round((erledigt / zielSumme) * 100) : 100, offen: offene.length, }, @@ -1082,7 +1114,7 @@ managerZieleRouter.get(`${BASIS}/verlauf`, (req, res) => { gesamt: s.gesamt, aufgaben: s.aufgaben.map((a) => ({ schluessel: a.schluessel, kurz: a.kurz, - zahl: a.zahl, ziel: a.ziel, + zahl: a.zahl, ziel: a.ziel, extra: a.extra, erreicht: a.ziel === 0 ? null : a.zahl >= a.ziel, })), }); @@ -1144,6 +1176,10 @@ function teamStand(monat) { gesamt: s.gesamt, aufgaben: s.aufgaben.map((a) => ({ schluessel: a.schluessel, zahl: a.zahl, ziel: a.ziel, stufe: a.stufe, + /* Mitgegeben, damit die Teamtabelle das Plus genauso zeigen kann + wie die eigene Seite -- sonst sieht DogFather die Mehrarbeit + seiner Leute nicht. */ + extra: a.extra, })), }; }); @@ -1177,8 +1213,14 @@ function teamStand(monat) { reihen.push({ id: null, name, rolle, rollenName: ROLLEN_NAME[rolle] || rolle, ehemalig: true, pflichtig: false, stufe: "aus", + /* Dieselben zwei Zahlen wie beim eigenen Stand -- `getan` ohne + Deckel, `erledigt` mit. Eine Reihe in der Teamtabelle, die + anders rechnet als die eigene Seite, waere zwei Wahrheiten + ueber dieselbe Person. */ gesamt: { erledigt: aufgaben.reduce((x, a) => x + Math.min(a.zahl, a.ziel), 0), + getan: aufgaben.reduce((x, a) => x + a.zahl, 0), + extra: aufgaben.reduce((x, a) => x + Math.max(0, a.zahl - a.ziel), 0), ziel: aufgaben.reduce((x, a) => x + a.ziel, 0), anteil: 100, offen: 0, }, @@ -1222,9 +1264,15 @@ managerZieleRouter.get(`${BASIS}/export.csv`, nurLeitung, (req, res) => { const t = String(w ?? ""); return /[";\n\r]/.test(t) ? `"${t.replace(/"/g, '""')}"` : t; }; + /* „Gesamt getan" NEBEN „Gesamt erledigt" (07.10.2026) -- nicht + statt. Die beiden beantworten verschiedene Fragen: `erledigt` + sagt, wie viel von der Pflicht erfuellt ist (je Aufgabe hoechstens + ihr Ziel), `getan` sagt, wie viel wirklich getan wurde. Wer die + Liste spaeter liest und nur eine Zahl findet, kann die Mehrarbeit + nicht mehr sehen -- und genau die soll gezaehlt werden. */ const kopf = ["Name", "Rolle", "Stand", ...AUFGABEN.flatMap((a) => [`${a.kurz} erreicht`, `${a.kurz} Ziel`]), - "Gesamt erledigt", "Gesamt Ziel", "Ampel"]; + "Gesamt erledigt", "Gesamt Ziel", "Gesamt getan", "Ampel"]; const zeilen = [kopf.map(feld).join(";")]; for (const p of leute) { /* DREI STAENDE, NICHT ZWEI. „nein" hiesse sonst dasselbe fuer @@ -1236,7 +1284,7 @@ managerZieleRouter.get(`${BASIS}/export.csv`, nurLeitung, (req, res) => { zeilen.push([ p.name, p.rollenName, stand, ...p.aufgaben.flatMap((a) => [a.zahl, a.ziel]), - p.gesamt.erledigt, p.gesamt.ziel, p.stufe, + p.gesamt.erledigt, p.gesamt.ziel, p.gesamt.getan, p.stufe, ].map(feld).join(";")); } diff --git a/workspace/anleitung.html b/workspace/anleitung.html index d5012fd1..4270a448 100644 --- a/workspace/anleitung.html +++ b/workspace/anleitung.html @@ -7,17 +7,17 @@ - - - - - + + + + + - - + +
@@ -289,16 +289,16 @@ Und anleitung.js braucht es doppelt: Symbol, Farbe, Name und Adresse jeder Karte kommen von dort, nicht vom Server. --> - + - - - - - - + + + + + +