/* ===================================================================== WANN EIN ZETTEL EINEN TERMIN HAT (17.09.2026) EINE Regel, zwei Verwender -- und deshalb eine eigene Datei. Seit heute zeigt der Kalender, was auf den Brettern steht und einen Termin hat. Die Bedingung dafuer stand als SQL mitten in workspace-kalender.js. Sobald die Brettkarte dasselbe sagen soll ("steht im Kalender am 24."), braeuchte sie eine zweite Fassung -- und zwei Fassungen derselben Regel laufen auseinander. Nicht sofort, sondern beim naechsten Nachschaerfen, und dann zeigt die Karte etwas anderes als der Kalender. Das ist schlimmer als beides wegzulassen: Man glaubt der Anzeige ja. Dieselbe Krankheit gab es im Haus schon zweimal -- die abgeschriebene Spaltenliste (11.09., drei Spalten samt Inhalt verloren) und die zweite Artenliste neben ARTNAME (im Kalender, ein BigMatch liess sich anlegen und war danach unsichtbar). DIE DATEI IMPORTIERT NICHTS. Muster wie treff-tabellen.js und workspace-bewerbung-rollen.js: Ein Blatt ohne eigene Importe kann von ueberall gelesen werden, ohne einen Ring zu bauen -- und ein Ring aeussert sich in JavaScript nicht als Fehler, sondern als `undefined` an einer Stelle, an der niemand damit rechnet. ------------------------------------------------------------------- WARUM GENAU DIESE VIER FAELLE `eintraege.datum` ist ein PFLICHTFELD und faellt auf HEUTE zurueck, wenn niemand eins waehlt. Alle Eintraege in den Kalender zu nehmen hiesse also: jeder je getippte Zettel steht an dem Tag, an dem ihn jemand getippt hat. Nach einem Monat waere der Kalender ein Protokoll und kein Plan -- und einer, in dem alles steht, sagt nichts mehr. geplant Eine Zusage: "Wird gemacht -- am 24.09." Das ist ein Termin, den jemand jemandem gegeben hat. event_ende Ein Zeitraum. Wer ein Ende eintraegt, meint eine Veranstaltung, keine Notiz. uhrzeit Wer eine Uhrzeit tippt, meint einen Zeitpunkt. Niemand traegt aus Versehen 20:00 ein. datum > heute Der Rueckfall ist IMMER heute oder frueher. Ein Datum in der Zukunft kann nur Absicht sein. Der letzte Fall ist der wichtigste und der unauffaelligste: Er braucht kein neues Feld und keine Umgewoehnung. ===================================================================== */ /** Der massgebliche Tag eines Eintrags -- als SQL-Ausdruck. * * Eine Zusage schlaegt das Erfassungsdatum: Wer "am 24." sagt, meint * den 24., auch wenn der Zettel am 3. entstanden ist. */ export const TERMIN_TAG_SQL = "COALESCE(e.geplant, e.datum)"; /** Der LETZTE Tag, an dem ein Eintrag noch laeuft -- als SQL-Ausdruck. * * Gebraucht seit dem 20.09.2026. Filipe: „wenn ich termine eintrage * mit von bis, will ich dass die ganze zeit markiert ist und nicht * nur der anfang." * * DER FEHLER SASS TIEFER ALS IN DER ANZEIGE. Der Kalender suchte * Eintraege, deren ANFANG im sichtbaren Fenster liegt. Eine Aktion * vom 28.09. bis zum 05.10. kam damit im Oktober ueberhaupt nicht an * -- nicht „nur am ersten Tag markiert", sondern gar nicht da. Wer im * Oktober plante, sah eine freie Woche, die belegt war. * * `MAX` und nicht `COALESCE(e.event_ende, ...)`: Ein Ende, das VOR * dem Anfang liegt (vertippt, Anfang spaeter verschoben), wuerde * sonst den Eintrag aus seinem eigenen Zeitraum werfen. Der leere * Text sortiert vor jedem Datum -- ohne Ende gewinnt damit immer der * Anfang, und das ist genau richtig. */ export const TERMIN_ENDE_SQL = "MAX(COALESCE(e.event_ende, ''), COALESCE(e.geplant, e.datum))"; /** Die Bedingung als SQL. Erwartet GENAU EINEN Platzhalter: den * heutigen Tag in Ortszeit (`heuteLokal()`). * * UTC waere hier falsch: Zwischen Mitternacht und zwei Uhr morgens * liefert es in Deutschland den Vortag, und ein Zettel von heute * staende dann ploetzlich im Kalender. */ export const TERMIN_BEDINGUNG_SQL = `( e.geplant IS NOT NULL OR e.event_ende IS NOT NULL OR e.uhrzeit IS NOT NULL OR e.datum > ? )`; /** Warum er drinsteht -- als SQL-Ausdruck. Die Reihenfolge ist die * Rangfolge: Eine Zusage ist mehr als ein Zeitraum, ein Zeitraum mehr * als eine Uhrzeit. */ export const TERMIN_GRUND_SQL = `CASE WHEN e.geplant IS NOT NULL THEN 'zugesagt' WHEN e.event_ende IS NOT NULL THEN 'zeitraum' WHEN e.uhrzeit IS NOT NULL THEN 'uhrzeit' ELSE 'termin' END`; /** Welche Zustaende NICHT in den Kalender gehoeren. * * Ein Kalender zeigt, was ansteht. Was gemacht oder abgelehnt ist, * gehoert auf sein Brett -- nicht in die Planung der naechsten Woche. */ export const OHNE_ZUSTAENDE = ["erledigt", "abgelehnt"]; /** Dieselbe Frage in JavaScript -- fuer einen einzelnen Eintrag. * * Sie wird beim Ausliefern der Brettkarten gebraucht: Dort steht seit * heute "im Kalender am 24.", und diese Auskunft MUSS dieselbe sein * wie die des Kalenders. Beide Fassungen stehen deshalb in dieser * Datei nebeneinander, damit eine Aenderung an der einen die andere * ins Auge springen laesst -- und pruef-terminregel.mjs weist nach, * dass sie fuer dieselben Zeilen dasselbe sagen. * * @param e Zeile aus `eintraege` (mit geplant, event_ende, uhrzeit, * datum, status) * @param heute Heutiger Tag in Ortszeit, "JJJJ-MM-TT" * @returns {null|{tag, grund}} */ export function terminVon(e, heute) { if (!e) return null; if (OHNE_ZUSTAENDE.includes(e.status)) return null; const grund = e.geplant ? "zugesagt" : e.event_ende ? "zeitraum" : e.uhrzeit ? "uhrzeit" : (e.datum && heute && e.datum > heute) ? "termin" : null; if (!grund) return null; return { tag: e.geplant || e.datum, grund }; }