Annehmen war bisher nur ein Etikett: Man stellte den Status um, und es passierte nichts. Kunde, Projekt, Aufgabenliste, Termin und Zugang musste man danach von Hand in fünf Schritten nachbauen. Der alte Code gab das im Kommentar selbst zu -- schlug der zweite von zwei Aufrufen fehl, stand der Kunde schon in der Datenbank und man durfte es nicht noch einmal versuchen. Jetzt macht das EIN Aufruf, ganz oder gar nicht: Kunde finden oder anlegen, Projekt anlegen, die komplette Aufgabenliste des Pakets einsetzen, Liefertermin berechnen, Einladungslink erzeugen. Der Kunde verfolgt ab diesem Moment alles in seinem Portal. Die Zeit läuft wirklich: - Eigenes Datumsfeld (termin_am) neben dem freien Text. Aus 'Mitte Oktober' kann man keine verbleibenden Tage rechnen. - Werktage statt Kalendertage, inklusive luxemburgischer Feiertage. Die beweglichen werden über die Osterformel berechnet statt gepflegt -- eine Liste ist im übernächsten Jahr lautlos falsch. - Vorschlag je Paket (Onepager 10, Website 20, Shop 30 Werktage), überschreibbar vor dem Bestätigen. - Verbleibende Zeit wird SERVERSEITIG gerechnet. Der Browser kennt die Feiertage nicht; zwei verschiedene Zahlen für denselben Termin wären schlimmer als gar keine. Ablehnen mit Grund und vorbereiteter Absage in fünf Sprachen, Text serverseitig erzeugt und vor dem Abschicken lesbar. Ein laufendes Projekt lässt sich nicht nachträglich als Anfrage ablehnen. Sechs Fehler dabei gefunden: - Ein unbekanntes Paket hätte GAR KEINEN Termin bekommen statt des Ersatzwerts. Zwei Funktionen lasen dieselbe Tabelle, nur eine hatte einen Rückfallwert. Eine fehlende Zahl fällt nirgends auf. - wd_anfragen hat keine Spalte 'firma' -- better-sqlite3 weist undefined ab, die ganze Annahme wäre gescheitert. - Migrationen stehen in einer ausdrücklichen Liste; 0015 fehlte darin. - datumKurz() wurde aufgerufen, gab es aber nicht. Der Fehler wäre erst NACH dem Anlegen aufgetreten. - Der Installations-Hinweis lag fest über dem Annehmen-Knopf und machte ihn auf dem Handy untreffbar. Die Seite reserviert jetzt Platz dafür. - 'richttermin' ist freier Text, lief aber durch einen Datumsformatierer: Der Kunde las 'Invalid Date' an der wichtigsten Stelle seines Projekts. Geprüft: 49 Prüfungen der Terminrechnung gegen nachschlagbare Osterdaten und Wochentage, 70 gegen eine echte Datenbank (darunter: zweiter Klick legt nichts doppelt an, Abbruch mittendrin lässt NICHTS zurück, gesperrter Kunde wird nicht still entsperrt), 42 im Browser auf Computer und Handy, 40 im Portal. Alle bestehenden Prüfungen weiter grün. Co-Authored-By: Claude Opus 5 <[email protected]>
139 lines
6.9 KiB
JavaScript
139 lines
6.9 KiB
JavaScript
/* =====================================================================
|
|
test-termine.mjs — die Terminrechnung
|
|
|
|
Diese Datei rechnet die Zahl aus, die der Kunde als Zusage liest.
|
|
Sie hat keine Oberfläche, an der ein Fehler auffiele: Ein Termin, der
|
|
drei Tage zu früh liegt, sieht genauso aus wie ein richtiger. Deshalb
|
|
wird hier gegen NACHPRÜFBARE Daten geprüft — Osterdaten und
|
|
Wochentage kann jeder in einem Kalender nachschlagen.
|
|
|
|
Läuft ohne Datenbank, also auch auf dem Windows-Rechner.
|
|
===================================================================== */
|
|
import {
|
|
terminVorschlag, werktageAddieren, werktageBis, istWerktag,
|
|
feiertage, hatLiefertermin, laufzeitFuer, LAUFZEIT_WERKTAGE,
|
|
} from "./lib/webdesign-termine.js";
|
|
|
|
let ok = 0, fehler = 0;
|
|
const pruefe = (n, b, z) => b
|
|
? (ok++, console.log(" ok " + n))
|
|
: (fehler++, console.log(" FEHL " + n + (z !== undefined ? " -> " + z : "")));
|
|
|
|
console.log("\n1. OSTERN — gegen den Kalender nachgeschlagen");
|
|
/* Wenn diese Formel falsch ist, sind Ostermontag, Christi Himmelfahrt
|
|
und Pfingstmontag ALLE falsch. Deshalb zuerst und über mehrere Jahre. */
|
|
const OSTERN = {
|
|
2024: "2024-03-31", 2025: "2025-04-20", 2026: "2026-04-05",
|
|
2027: "2027-03-28", 2028: "2028-04-16", 2030: "2030-04-21",
|
|
};
|
|
for (const [jahr, datum] of Object.entries(OSTERN)) {
|
|
/* Ostersonntag selbst ist kein Eintrag in der Feiertagsliste (er ist
|
|
ein Sonntag). Geprüft wird deshalb der Ostermontag: einen Tag
|
|
später. */
|
|
const montag = new Date(new Date(datum + "T00:00:00Z").getTime() + 864e5)
|
|
.toISOString().slice(0, 10);
|
|
pruefe("Ostermontag " + jahr + " ist der " + montag,
|
|
feiertage(Number(jahr)).has(montag),
|
|
Array.from(feiertage(Number(jahr))).sort().join(" "));
|
|
}
|
|
|
|
console.log("\n2. DIE FESTEN FEIERTAGE LUXEMBURGS");
|
|
const f26 = feiertage(2026);
|
|
for (const [name, tag] of [
|
|
["Neujahr", "2026-01-01"], ["Tag der Arbeit", "2026-05-01"],
|
|
["Europatag", "2026-05-09"], ["Nationalfeiertag", "2026-06-23"],
|
|
["Mariä Himmelfahrt", "2026-08-15"], ["Allerheiligen", "2026-11-01"],
|
|
["Weihnachten", "2026-12-25"], ["Zweiter Weihnachtstag", "2026-12-26"],
|
|
]) pruefe(name + " (" + tag + ")", f26.has(tag));
|
|
|
|
pruefe("Christi Himmelfahrt 2026 ist der 14.05.", f26.has("2026-05-14"));
|
|
pruefe("Pfingstmontag 2026 ist der 25.05.", f26.has("2026-05-25"));
|
|
pruefe("2026 hat elf Feiertage", f26.size === 11, f26.size);
|
|
|
|
console.log("\n3. WAS IST EIN WERKTAG?");
|
|
pruefe("Montag ist einer", istWerktag(new Date("2026-08-24T00:00:00Z")));
|
|
pruefe("Samstag nicht", !istWerktag(new Date("2026-08-22T00:00:00Z")));
|
|
pruefe("Sonntag nicht", !istWerktag(new Date("2026-08-23T00:00:00Z")));
|
|
pruefe("der Nationalfeiertag nicht, obwohl er ein Dienstag ist",
|
|
!istWerktag(new Date("2026-06-23T00:00:00Z")));
|
|
|
|
console.log("\n4. WERKTAGE ADDIEREN");
|
|
/* Der 21.08.2026 ist ein Freitag. Ein Werktag weiter ist Montag, nicht
|
|
Samstag -- genau der Fehler, den eine Kalendertagsrechnung macht. */
|
|
pruefe("Freitag + 1 Werktag = Montag",
|
|
werktageAddieren("2026-08-21", 1) === "2026-08-24",
|
|
werktageAddieren("2026-08-21", 1));
|
|
pruefe("Freitag + 5 Werktage = Freitag darauf",
|
|
werktageAddieren("2026-08-21", 5) === "2026-08-28",
|
|
werktageAddieren("2026-08-21", 5));
|
|
/* Über Feiertage hinweg: Vom 19.06.2026 (Freitag) sind fünf Werktage
|
|
normalerweise der 26.06. Der 23.06. ist aber Nationalfeiertag, also
|
|
verschiebt sich alles um einen Tag auf den 29.06. (Montag). */
|
|
pruefe("ein Feiertag dazwischen verschiebt den Termin",
|
|
werktageAddieren("2026-06-19", 5) === "2026-06-29",
|
|
werktageAddieren("2026-06-19", 5) + " (ohne Feiertag waere es der 26.06.)");
|
|
pruefe("der Starttag selbst zaehlt nicht mit",
|
|
werktageAddieren("2026-08-24", 1) === "2026-08-25",
|
|
werktageAddieren("2026-08-24", 1));
|
|
pruefe("null Werktage ergeben keinen Termin", werktageAddieren("2026-08-24", 0) === null);
|
|
pruefe("Unsinn ergibt keinen Termin", werktageAddieren("2026-08-24", "viele") === null);
|
|
|
|
console.log("\n5. DIE UHRZEIT DARF DAS ERGEBNIS NICHT VERSCHIEBEN");
|
|
/* Wer morgens annimmt, muss denselben Termin bekommen wie jemand, der
|
|
abends annimmt. Ohne Normierung auf Mitternacht springt das Datum. */
|
|
const frueh = werktageAddieren("2026-08-24T06:00:00Z", 10);
|
|
const spaet = werktageAddieren("2026-08-24T23:30:00Z", 10);
|
|
pruefe("frueh und spaet am selben Tag ergeben denselben Termin",
|
|
frueh === spaet, frueh + " vs. " + spaet);
|
|
|
|
console.log("\n6. VERBLEIBENDE ZEIT");
|
|
pruefe("heute ist null",
|
|
werktageBis("2026-08-24", "2026-08-24T10:00:00Z") === 0,
|
|
werktageBis("2026-08-24", "2026-08-24T10:00:00Z"));
|
|
pruefe("Montag bis Freitag sind vier Werktage",
|
|
werktageBis("2026-08-28", "2026-08-24T10:00:00Z") === 4,
|
|
werktageBis("2026-08-28", "2026-08-24T10:00:00Z"));
|
|
/* Das Wochenende zaehlt nicht mit: Von Freitag bis Montag ist genau
|
|
EIN Werktag, nicht drei Tage. */
|
|
pruefe("Freitag bis Montag ist ein Werktag",
|
|
werktageBis("2026-08-24", "2026-08-21T10:00:00Z") === 1,
|
|
werktageBis("2026-08-24", "2026-08-21T10:00:00Z"));
|
|
pruefe("ein ueberschrittener Termin ergibt eine negative Zahl",
|
|
werktageBis("2026-08-20", "2026-08-24T10:00:00Z") === -2,
|
|
werktageBis("2026-08-20", "2026-08-24T10:00:00Z"));
|
|
pruefe("ohne Termin kommt nichts heraus", werktageBis(null) === null);
|
|
|
|
console.log("\n7. VORSCHLAG JE PAKET");
|
|
const v = terminVorschlag("website", "2026-08-24");
|
|
pruefe("Website bekommt 20 Werktage", v.werktage === 20, v.werktage);
|
|
pruefe("und daraus ein echtes Datum", /^\d{4}-\d{2}-\d{2}$/.test(v.termin || ""), v.termin);
|
|
pruefe("das Datum liegt auf einem Werktag",
|
|
istWerktag(new Date(v.termin + "T00:00:00Z")), v.termin);
|
|
pruefe("20 Werktage sind rund vier Wochen, nicht 20 Tage",
|
|
(new Date(v.termin) - new Date("2026-08-24")) / 864e5 >= 26,
|
|
Math.round((new Date(v.termin) - new Date("2026-08-24")) / 864e5) + " Kalendertage");
|
|
|
|
const b = terminVorschlag("betreuung", "2026-08-24");
|
|
pruefe("eine laufende Betreuung bekommt KEINEN Liefertermin", b.termin === null && b.laufend);
|
|
pruefe("und wird auch so gekennzeichnet", hatLiefertermin("betreuung") === false);
|
|
|
|
const u = terminVorschlag("gibtesnicht", "2026-08-24");
|
|
pruefe("ein unbekanntes Paket faellt auf den mittleren Wert zurueck",
|
|
u.werktage === LAUFZEIT_WERKTAGE.unklar, u.werktage);
|
|
pruefe("und bekommt trotzdem ein Datum -- kein stilles Projekt ohne Zusage",
|
|
!!u.termin && u.laufend === false, String(u.termin));
|
|
pruefe("nur die laufende Betreuung ergibt null", laufzeitFuer("betreuung") === 0);
|
|
pruefe("ein Tippfehler im Paketnamen ergibt NICHT null",
|
|
laufzeitFuer("websiet") > 0, laufzeitFuer("websiet"));
|
|
|
|
console.log("\n8. JEDES PAKET IST HINTERLEGT");
|
|
/* Fehlt eines, bekaeme es beim Annehmen still den Ersatzwert -- und
|
|
niemand merkte, dass die Zusage geraten war. */
|
|
for (const p of ["onepager", "website", "shop", "verwaltung", "betreuung", "unklar"]) {
|
|
pruefe("Laufzeit fuer " + p + " ist hinterlegt",
|
|
Object.prototype.hasOwnProperty.call(LAUFZEIT_WERKTAGE, p), LAUFZEIT_WERKTAGE[p]);
|
|
}
|
|
|
|
console.log(`\n===== ${ok} bestanden, ${fehler} fehlgeschlagen =====`);
|
|
process.exitCode = fehler ? 1 : 0;
|