Die Uhr laeuft erst, wenn die Anzahlung da ist
Bisher begann der Liefertermin mit der Annahme. Das ist unfair in beide Richtungen: Wer zehn Tage bis zur Zahlung braucht, verbraucht zehn Tage der zugesagten Zeit, ohne dass ein Handschlag Arbeit passiert waere -- und ich stehe am Ende als der da, der seinen Termin reisst. Ein Projekt hat jetzt drei Abschnitte statt zwei: 1. angenommen, wartet auf Anzahlung -> Uhr steht 2. Anzahlung da -> Uhr laeuft, Termin ab HEUTE neu 3. uebergeben oder abgebrochen -> Uhr steht wieder Der Zahlungseingang loest alles Weitere von selbst aus: Uhr starten, Termin neu rechnen, Status von briefing auf design, 'wer ist am Zug' auf mich, Benachrichtigung in der Verwaltung. Der bei der Annahme genannte Termin bleibt als termin_geplant_am erhalten, und die Meldung nennt BEIDE -- so sieht man, dass sich etwas verschoben hat, ohne nachrechnen zu muessen. Eingehaengt an der Stelle, an der beide Wege zusammenlaufen (PayPals Meldung UND das Vermerken von Hand). Nur am Webhook haenge sich die Seite verschieden verhalten, je nachdem WIE das Geld ankam -- eine von Hand verbuchte Zahlung startete die Uhr nie. Zwei Grundsaetze fuer die Automatik: Sie setzt Dinge in Gang, nimmt aber nie eine Entscheidung zurueck, die ein Mensch getroffen hat (ein von Hand pausiertes Projekt wird nicht kommentarlos wieder gestartet). Und jeder Schritt hinterlaesst eine Spur im Verlauf UND als Meldung -- eine Automatik, die stillschweigend arbeitet, ist kein Helfer, sondern ein Raetsel. ABBRECHEN Ein angenommener Auftrag bleibt abbrechbar: Der Kunde zahlt nicht, meldet sich nicht, springt ab. Vorschau und Ausfuehrung sind getrennt -- die Seite rechnet aus dem Aufgabenfortschritt vor, wie viel Leistung erbracht wurde, und schlaegt daraus einen Erstattungsbetrag vor. Der Betrag ist ein VORSCHLAG: Ob im Einzelfall mehr oder weniger angemessen ist, haengt an Dingen, die keine Tabelle kennt. Offene Rechnungen werden storniert (eine Zahlungsaufforderung ohne Gegenleistung), bezahlte bleiben unangetastet, und die Rueckzahlung loest die Seite bewusst NICHT selbst aus -- PayPal-Rueckzahlungen sind nicht umkehrbar. BENACHRICHTIGUNGEN Eigene Tabelle statt im Verlauf: Der Verlauf haelt fest, WAS geschehen ist -- vollstaendig, zum Nachschlagen. Eine Benachrichtigung ist ein Anstupsen, das gelesen und weggelegt wird. Beides in einer Tabelle hiesse: entweder ein Verlauf voller Rauschen oder Meldungen, die man nicht wegklicken kann. Wegklicken markiert nur als gelesen, loescht nichts. Dazu die Liste der Projekte, die seit ueber einer Woche auf ihre Anzahlung warten. Sie stehen in keiner anderen Zahl, weil ihre Uhr nie zu laufen begann -- ohne diesen Hinweis vergisst man sie. Geprueft: 44 gegen eine echte Datenbank. Darunter der Kern -- die Annahme wird zehn Tage zurueckdatiert, und der Termin muss danach trotzdem volle 20 Werktage entfernt liegen. Beim Bauen des Tests selbst ein Fehler gefunden: Die erste Fassung datierte nur die Annahme zurueck, nicht den damals errechneten Termin, und bildete damit genau den Fall nicht ab, um den es geht. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+1
-1
@@ -34,7 +34,7 @@
|
||||
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
|
||||
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
|
||||
Stand fest. */
|
||||
const CACHE_NAME = "dogfather-webdesign-v26";
|
||||
const CACHE_NAME = "dogfather-webdesign-v27";
|
||||
|
||||
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
|
||||
HTML-Datei in dieser Liste. */
|
||||
|
||||
+104
-1
@@ -93,6 +93,36 @@
|
||||
Sie waren doppelt so gross wie die Zahlen, um die es geht, und die
|
||||
Seite las sich wie ein Plakat statt wie ein Arbeitsplatz. Dieselbe
|
||||
Falle wie frueher bei `.wd a` und `.wd .wd-btn--haupt`. */
|
||||
/* ---------- Benachrichtigungen ---------- */
|
||||
.vw-meld {
|
||||
display: flex; align-items: flex-start; gap: .7rem;
|
||||
padding: .7rem .85rem; margin-bottom: .5rem;
|
||||
border-radius: var(--wd-radius-m, 14px);
|
||||
border: 1px solid rgb(var(--wd-blau-rgb) / .3);
|
||||
background: linear-gradient(rgb(var(--wd-blau-rgb) / .07), rgb(var(--wd-blau-rgb) / .07)),
|
||||
var(--wd-flaeche-2, #16191d);
|
||||
}
|
||||
/* Dringend heisst hier: Geld ist geflossen oder eine Frist laeuft.
|
||||
Nur DIESE bekommen Farbe -- bekaeme jede Meldung eine, waere keine
|
||||
mehr auffaellig. */
|
||||
.vw-meld--dringend {
|
||||
border-color: rgb(var(--wd-gold-rgb) / .5);
|
||||
background: linear-gradient(rgb(var(--wd-gold-rgb) / .08), rgb(var(--wd-gold-rgb) / .08)),
|
||||
var(--wd-flaeche-2, #16191d);
|
||||
}
|
||||
.vw-meld-inhalt { flex: 1; min-width: 0; cursor: pointer; text-align: left;
|
||||
background: none; border: 0; color: inherit; font: inherit; padding: 0; }
|
||||
.wd .vw-meld-titel { font-weight: 700; font-size: .95rem; margin: 0; }
|
||||
.wd .vw-meld-text { font-size: .84rem; color: var(--wd-text-soft); margin: .15rem 0 0; }
|
||||
.vw-meld-weg {
|
||||
flex-shrink: 0; min-width: 44px; min-height: 44px;
|
||||
background: none; border: 0; color: var(--wd-text-soft);
|
||||
cursor: pointer; font-size: 1.1rem; border-radius: 8px;
|
||||
}
|
||||
.vw-meld-weg:hover { background: rgb(255 255 255 / .07); color: var(--wd-text); }
|
||||
.vw-meld-weg:focus-visible { outline: 2px solid rgb(var(--wd-blau-rgb) / .95); outline-offset: 2px; }
|
||||
.vw-meld-alle { margin: .1rem 0 1rem; }
|
||||
|
||||
.wd .vw-ub-h {
|
||||
font-size: 1.05rem; font-weight: 650;
|
||||
margin: 1.9rem 0 .75rem;
|
||||
@@ -1113,6 +1143,14 @@
|
||||
<div class="vw-laedt" id="vw-ub-laedt">Wird geladen…</div>
|
||||
<div id="vw-ub-inhalt" hidden>
|
||||
|
||||
<!-- Benachrichtigungen stehen GANZ OBEN.
|
||||
|
||||
Sie sind das Einzige, was seit dem letzten Hinsehen neu
|
||||
ist -- alles andere sind Bestaende, die auch morgen noch
|
||||
da sind. Steht das Neue unter dem Bestand, sieht man es
|
||||
erst nach dem Scrollen, und der Sinn ist dahin. -->
|
||||
<div id="vw-meldungen" hidden></div>
|
||||
|
||||
<h2 class="vw-ub-h">Was auf dich wartet</h2>
|
||||
<div class="vw-ub-gitter" id="vw-ub-beimir"></div>
|
||||
|
||||
@@ -1735,6 +1773,50 @@
|
||||
}).join("") + '</ul>';
|
||||
}
|
||||
|
||||
/* ---- Benachrichtigungen ---- */
|
||||
var mk = document.getElementById("vw-meldungen");
|
||||
var meldungen = d.meldungen || [];
|
||||
if (!meldungen.length) {
|
||||
mk.hidden = true;
|
||||
mk.innerHTML = "";
|
||||
} else {
|
||||
mk.hidden = false;
|
||||
mk.innerHTML = meldungen.map(function (m) {
|
||||
return '<div class="vw-meld' + (m.dringend ? " vw-meld--dringend" : "") +
|
||||
'" data-meldung="' + s(m.id) + '">' +
|
||||
'<button type="button" class="vw-meld-inhalt"' +
|
||||
(m.ziel ? ' data-ziel="' + s(m.ziel) + '"' : "") + '>' +
|
||||
'<p class="vw-meld-titel">' + s(m.titel) + "</p>" +
|
||||
(m.text ? '<p class="vw-meld-text">' + s(m.text) + "</p>" : "") +
|
||||
"</button>" +
|
||||
'<button type="button" class="vw-meld-weg" data-weg="' + s(m.id) +
|
||||
'" aria-label="Benachrichtigung wegklicken">✕</button>' +
|
||||
"</div>";
|
||||
}).join("") +
|
||||
(meldungen.length > 1
|
||||
? '<div class="vw-meld-alle"><button type="button" class="wd-btn wd-btn--still wd-btn--klein" ' +
|
||||
'data-weg="alle">Alle ' + meldungen.length + ' wegklicken</button></div>'
|
||||
: "");
|
||||
}
|
||||
|
||||
/* Projekte, die auf ihre Anzahlung warten. Sie stehen in keiner
|
||||
anderen Zahl, weil ihre Uhr nie zu laufen begann -- ohne diesen
|
||||
Hinweis vergisst man sie schlicht. */
|
||||
var wartend = d.wartetAufAnzahlung || [];
|
||||
if (wartend.length) {
|
||||
mk.hidden = false;
|
||||
mk.insertAdjacentHTML("beforeend",
|
||||
'<div class="vw-meld vw-meld--dringend">' +
|
||||
'<button type="button" class="vw-meld-inhalt" data-ziel="projekte">' +
|
||||
'<p class="vw-meld-titel">' + wartend.length +
|
||||
(wartend.length === 1 ? " Projekt wartet" : " Projekte warten") +
|
||||
' seit über einer Woche auf die Anzahlung</p>' +
|
||||
'<p class="vw-meld-text">' +
|
||||
s(wartend.slice(0, 3).map(function (w) { return w.nummer + " · " + w.kunde_name; }).join(" · ")) +
|
||||
(wartend.length > 3 ? " …" : "") +
|
||||
' — die Uhr läuft dort noch nicht.</p></button></div>');
|
||||
}
|
||||
|
||||
/* Der Zähler am Anfragen-Reiter, damit man auch aus einer anderen
|
||||
Ansicht heraus sieht, dass etwas hereingekommen ist. */
|
||||
var az = document.getElementById("vw-anf-zaehler");
|
||||
@@ -1745,7 +1827,28 @@
|
||||
Ereignis-Delegation, weil die Kacheln bei jedem Laden neu entstehen
|
||||
— einzeln angehängte Zuhörer wären nach der ersten Aktualisierung
|
||||
verschwunden. */
|
||||
document.addEventListener("click", function (e) {
|
||||
document.addEventListener("click", async function (e) {
|
||||
var weg = e.target.closest ? e.target.closest("[data-weg]") : null;
|
||||
if (weg) {
|
||||
var welche = weg.dataset.weg;
|
||||
/* Sofort verschwinden lassen, nicht erst nach der Antwort.
|
||||
|
||||
Eine Benachrichtigung wegzuklicken ist folgenlos -- schlimmsten-
|
||||
falls taucht sie beim naechsten Laden wieder auf. Auf den Server
|
||||
zu warten, bevor sich etwas ruehrt, faehlt sich dagegen nach
|
||||
jedem Klick nach Haengen an. */
|
||||
if (welche === "alle") document.getElementById("vw-meldungen").hidden = true;
|
||||
else {
|
||||
var kasten = document.querySelector('[data-meldung="' + welche + '"]');
|
||||
if (kasten) kasten.remove();
|
||||
}
|
||||
try {
|
||||
await api("/webdesign/admin/meldungen/" + encodeURIComponent(welche) + "/gelesen",
|
||||
{ method: "POST", body: {} });
|
||||
} catch (f) { /* beim naechsten Laden erscheint sie wieder */ }
|
||||
return;
|
||||
}
|
||||
|
||||
var ziel = e.target.closest ? e.target.closest("[data-ziel]") : null;
|
||||
if (!ziel) return;
|
||||
var knopf = document.querySelector('[data-tab="' + ziel.dataset.ziel + '"]');
|
||||
|
||||
Reference in New Issue
Block a user