Commit Graph
46 Commits
Author SHA1 Message Date
ClaudeandClaude Opus 5.5 054b04d5b6 Abo-Angebote: Admin sieht, bis wann der Rabatt läuft
"Gültig bis" ist nur die Frist zum Annehmen. Jetzt steht auch das
Rabattende da:
- Formular "Neues Angebot", Kunde ohne Abo: Schätzung "Rabatt läuft
  N Monate ab Abschluss und endet voraussichtlich zwischen <heute + N>
  und <gültig bis + N>", mit Hinweis auf den späteren Start nach der
  Widerrufsfrist (gut 2 Wochen)
- Formular, laufendes Abo: genaues Ende aus den nächsten Abbuchungen
  (Anker = Leistungsbeginn, Monatsende wie bei Stripe), auch in der
  Erfolgsmeldung
- Angebotsliste: offen -> Schätzung, aktiv -> "Rabatt endet am ..."
  aus dem Abo-Dokument (bzw. "endete", "endet mit dem Abo am ...",
  "unbekannt (Abo nicht gefunden)")

Rechnung in server/services/offerDiscount.js: Der Coupon deckt die
N Abbuchungen ab dem Anwenden. Beim Checkout ist das der Abo-Start, bei
laufendem Abo und Testkonto (Coupon per Webhook) der Zeitpunkt des
Anhängens; Testkonten laufen deshalb einen Monat länger.

Tests: npm test (37 Fälle, davon 8 neu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:01:45 +00:00
ClaudeandClaude Opus 5.5 c600118374 Abo-Angebote: Gültigkeit überall sichtbar
Das "Angebot gültig bis" stand bisher nur in der Angebotsliste, auf der
Bestellseite und in der Mail. Jetzt auch:
- Admin, Formular "Neues Angebot": eigene Zeile "Angebot gültig bis
  TT.MM.JJJJ" in der Vorschau, in der Bestätigungsabfrage und in der
  Erfolgsmeldung (bei laufendem Abo nicht, dort gilt das Angebot sofort)
- Kundenbereich, Abo-Karte (Seitenleiste bzw. Tab "Wartung"): Zeile
  "Angebot gültig bis TT.MM.JJJJ" unter dem Preis

Das Datum aus dem Datumsfeld wird ohne Date-Umweg formatiert, im Portal
nach Berliner Kalender (wie bisher). Nebenbei: "danach 50 €" mit
geschütztem Leerzeichen, damit das € auf dem Handy nicht allein umbricht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 22:42:37 +00:00
ClaudeandClaude Opus 5.5 d5eb908cb6 Preview-Router: öffentliche Seiten überstehen Server-Neustart
Nach einem Neustart startet der Kundenbereich oft vor Appwrite. Bisher
lieferte getPublicSubdomains dann ein leeres Set, syncPreviewTls setzte
alle Preview-Hosts hinter die Login-Weiche (preview-chain) und wiederholte
das nie. Öffentliche Seiten wie anelia-wolf und emmas-schatzkiste waren
bis zum nächsten Deploy nur noch mit Login erreichbar bzw. 403.

- Antwortet Appwrite nicht, übernimmt syncPreviewTls die öffentlichen
  Seiten aus der zuletzt geschriebenen preview-hosts.yml. Neue Subdomains
  bleiben login-geschützt, ohne alte Datei ist alles geschützt.
- Beim Start wird der Abgleich alle 15 s wiederholt (max. 10 min), bis er
  mit Appwrite-Daten geklappt hat.
- Tests: Datei lesen, Abgleich mit/ohne Appwrite, Startup-Wiederholung.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 22:19:33 +00:00
ClaudeandClaude Opus 5.5 ba9916cfe6 Projektdetails und Fortschritt kundenverständlich
Der Server bereitet Commits, Tickets und Arbeitseinträge für Kunden auf
(server/services/customerProjectView.js), die Rohdaten bleiben intern:
- keine Co-Authored-By-/Signed-off-by-Zeilen und KI-Hinweise, keine
  Commit-Kürzel, Kontonamen, Git-Links oder Branch
- technische Commits (Preview-Config, Merges) ausgeblendet, bekannte
  Automatik-Texte übersetzt, "fix"/"upload" -> "Website aktualisiert"
- Ticketstatus und Arbeitsarten auf Deutsch (Open -> Eingegangen,
  Remote -> Online erledigt, ...), Fortschritt in 4 Schritten
- Akquise-Tickets auch in den Projektdetails verborgen
- /api/projects ohne Gitea-URL, Repo- und Vorlagennamen

Darstellung: Auftragskarte mit Status und Fortschrittsleiste, Zeitleiste
"Letzte Änderungen" nach Tagen, Arbeiten mit Farbe/Symbol je Art und
Arbeitszeit; der Tab "Fortschritt" nutzt dieselben Bausteine. In der
App-Ansicht öffnen sich die Projektdetails als eigene Seite mit Zurück
zu "Projekte".

Tests: npm test (node --test, 22 Fälle).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:46:22 +00:00
ClaudeandClaude Opus 5.5 e54f97144c Abo: Widerrufsfrist nach §§ 187, 188, 193 BGB statt 14 x 24 Stunden
Bisher endete die Frist rechnerisch 14 x 24 h nach Vertragsschluss, und der spaetere
Beginn lag pauschal 15 Tage danach. Gesetzlich beginnt die Frist am Folgetag und endet
um 24 Uhr des 14. Tages; faellt der auf Sa/So/Feiertag, am naechsten Werktag. Beispiel:
Abschluss Sa 26.09. -> Frist bis Mo 12.10., 24 Uhr. Vorher verschwand "Vertrag widerrufen"
schon Sa 10.10. vormittags, und "Beginn nach Ablauf der Frist" buchte am So 11.10. ab.

- server/services/deadlines.js: Fristende und Leistungsbeginn, deutscher Kalender,
  Feiertage aller Bundeslaender (zugunsten des Kunden, § 193 BGB: Erklaerungsort).
- Portal: "Vertrag widerrufen · bis TT.MM." bleibt bis zum echten Fristende sichtbar.
- Beginn nach Ablauf der Frist: Stripe trial_end = 6 Uhr am Tag nach dem spaetesten
  moeglichen Fristende (Checkout gilt hoechstens 24 h). Die Bestellseite zeigt genau
  dieses Datum; hat es sich um Mitternacht verschoben, kommt "Seite neu laden".
- Team-Mail beim Widerruf nennt das Fristende und ob der Widerruf rechtzeitig kam.
- Datumsanzeige im Portal nach deutscher Zeit (auch bei Kunden im Ausland).

Vertragstexte unveraendert (termsVersion bleibt v3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 18:30:27 +00:00
ClaudeandClaude Opus 5.5 93381a81c5 Kundenbereich: Verträge und Abmelden nur noch im Profilbild-Menü
- "Abo verwalten & Rechnungen" (Stripe-Kundenportal) steht nur noch im Konto-Menü
  (Gruppe "Verträge", nur mit Abo). In der Wartungskarte bleibt nur bei fehlgeschlagener
  Abbuchung "Zahlungsart aktualisieren".
- "Verträge hier kündigen" und "Vertrag widerrufen": raus aus Seitenfuß, Wartungskarte und
  Abo-Seite; im Kundenbereich nur noch im Konto-Menü. Ausnahme: während der Widerrufsfrist
  zeigt die Wartungskarte weiter "Vertrag widerrufen · bis TT.MM." (§ 356a BGB: hervorgehoben
  platziert). Öffentlich bleiben beide auf der Login-Seite und im Footer von webklar.com.
- "Abmelden" nicht mehr in der Kopfzeile, nur noch im Konto-Menü.
- Hinweis unter den Rechnungen verweist aufs Profilbild und öffnet das Menü.
- Browser ohne <dialog>: Menü klappt als Box auf (statt Abmelden in der Kopfzeile).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 16:43:21 +00:00
ClaudeandClaude Opus 5.5 d72d30dd2a App-Ansicht iOS-App: lila bis in die Notch, Tab-Leiste trotzdem ganz unten
Die Statusleiste ist wieder durchsichtig (black-translucent), damit der
lila Hintergrund hinter Uhrzeit und Notch weitergeht. Dass die Tab-Leiste
damit auf kurzen Seiten 59 px zu hoch hing, liegt an einem bekannten
iOS-Verhalten: In der installierten App bekommen fixierte Elemente die
volle Bildschirmhoehe nur, solange die Seite scrollbar ist.

- Installierte App auf iOS (display-mode standalone, nur iOS): Die Seite
  ist immer mindestens 1 px hoeher als der Bildschirm (100lvh + 1px).
  100lvh ist dort die volle Hoehe, 100%, 100svh und 100dvh sind zu kurz.
- Die Chat-Hoehe kommt aus 100lvh.
- Das Konto-Sheet sperrt die Seite dort nicht mehr (overflow bleibt
  sichtbar); es ist von oben mit voller Hoehe verankert, das Panel sitzt
  unten.
- Bild-Grossansicht und Kamera des Chats sind von oben mit voller Hoehe
  verankert statt mit inset: 0.
- Kuendigen/Widerrufen: dunkler Streifen hinter der weissen Statusleiste.

Browser, Android und Desktop unveraendert (pixelgleich).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:58:06 +00:00
ClaudeandClaude Opus 5.5 57ba2c06f9 App-Ansicht iOS-App: undurchsichtige Statusleiste, Tab-Leiste wieder ganz unten
Der Fix aus bd7b1fc (Seite mindestens bildschirmhoch) hat nicht gewirkt
und wird zurueckgenommen. Die Ursache ist nicht die Seitenhoehe:
Bei black-translucent ist der Bereich fuer fixierte Elemente in der
installierten iOS-App um die Statusleiste (59 pt) kuerzer als der
Bildschirm. Solange die Seite oben steht, klebt dieser Bereich oben, und
die Tab-Leiste haengt ueber dem unteren Rand. Nach dem Herunterscrollen
sitzt sie richtig, deshalb sah es nur im Chat gut aus.

Mit undurchsichtiger Statusleiste (black) beginnt die App unterhalb der
Statusleiste, und der Bereich ist korrekt bemessen. Nachteil: Der lila
Schimmer reicht nicht mehr hinter die Uhrzeit. Die Abstaende oben kommen
weiter aus env(safe-area-inset-top), das dann 0 ist.

Browser, Android und Desktop unveraendert (pixelgleich).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:43:37 +00:00
ClaudeandClaude Opus 5.5 bd7b1fc671 App-Ansicht iOS-App: Tab-Leiste sitzt wieder ganz unten
In der installierten App auf dem iPhone hing die Tab-Leiste auf kurzen
Seiten (z. B. leere Tabs Projekte, Rechnungen, Fortschritt) 59 px ueber
dem unteren Rand, darunter ein schwarzer Streifen; im Chat nicht.
Das ist ein bekannter WebKit-Fehler bei black-translucent: Der
Layout-Bereich ist um die Hoehe der Statusleiste kuerzer als der
Bildschirm, und auf Seiten, die kuerzer als der Bildschirm sind, docken
fixierte Leisten an dieses zu kurze Ende. Der Chat ist hoeher als der
Bildschirm und war deshalb nicht betroffen.

- Installierte App auf iOS: Seite mindestens bildschirmhoch. Die Hoehe
  kommt aus screen.* (--kb-screen-h), weil innerHeight, 100svh und 100dvh
  in diesem Zustand selbst zu kurz sein koennen. Das gilt auch fuer den
  Login und die Chat-Hoehe.
- Safari im Browser, Android und Desktop unveraendert (pixelgleich).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:35:48 +00:00
ClaudeandClaude Opus 5.5 d5bfd1be3d App-Ansicht iOS: Chat-Eingabe springt nicht mehr weg, kein Zoom beim Tippen
Auf dem iPhone sprang die Seite beim Tippen ins Chatfeld weg (nur die
Fusszeile war zu sehen), zoomte hinein und liess sich danach seitlich
schieben. Beides sind bekannte Safari-Effekte:

- window.scrollTo(0, 0) bei offener Tastatur kaempft gegen Safaris eigenes
  Hochschieben des Feldes und ist entfernt, ebenso das Verkleinern des
  Chats auf die sichtbare Hoehe, das das schon hochgeschobene Feld wieder
  aus dem Bild rutschen liess. Der Chat nutzt jetzt 100dvh: auf Android
  folgt das der Tastatur (interactive-widget=resizes-content), auf iOS
  bleibt die Hoehe stabil.
- Auf iOS aendert sich beim Fokus keine Hoehe mehr (die Tab-Leiste wird
  nur optisch ausgeblendet). Ist die Chat-Eingabe nach der
  Tastatur-Animation doch verdeckt, wird sie gezielt nachgeschoben
  (gemessen am visualViewport).
- iOS zoomt beim Fokus, wenn die Schrift effektiv unter 16 px liegt, z. B.
  bei Safari-Seitenzoom unter 100 %. maximum-scale=1 nur auf iOS verhindert
  diesen automatischen Zoom; mit zwei Fingern zoomen bleibt moeglich
  (WebKit ignoriert die Sperre dafuer seit iOS 10). Android bleibt
  unveraendert, dort wuerde es das Zoomen sperren.

Im Ruhezustand pixelgleich zu main (28 Ansichten).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 15:17:36 +00:00
ClaudeandClaude Opus 5.5 163ab88c35 App-Ansicht: ueberholte Chat-Regeln entfernt, Verlauf bleibt ueber data-stick unten
Der Chat-Composer bringt den runden Senden-Knopf fuer die App-Ansicht selbst
mit (0d47b7c). Die alten Regeln der App-Ansicht fuer .chat-send und
.chat-input-row waren dadurch wirkungslos und sind entfernt; die
Eingabe-Pille bleibt.

- Verlauf: der ResizeObserver nutzt data-stick aus ChatComposer.trackScroll
  statt eines eigenen Scroll-Zaehlers
- Hinweis unter dem Chat nur noch in der App-Ansicht mit Touch ausgeblendet
  (vorher auf allen Touch-Geraeten, also auch auf Tablets quer)
- Handy quer: feste Hoehe der Eingabe entfernt, der Composer waechst selbst mit

Handy, Tablet hochkant und Desktop sind pixelgleich zu main (28 Ansichten).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:53:27 +00:00
ClaudeandClaude Opus 5.5 0d47b7ca80 Chat: Senden-Knopf in der App-Ansicht (Handy quer, Tablet hochkant) rund
Die App-Ansicht (kundenbereich.css) gilt bis 880 px und fuer Handys quer,
der runde Senden-Knopf mit Symbol galt bisher erst unter 640 px. Dazwischen
stand "Senden" in einem 46-px-Kreis und lief ueber den Rand (auch waehrend
der Sprachaufnahme). Jetzt gilt der runde Knopf unter derselben Bedingung
wie die App-Ansicht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:44:30 +00:00
ClaudeandClaude Opus 5.5 20bf5afe28 Kundenbereich: App-Ansicht fuers Handy (Tab-Leiste, Konto-Sheet, installierbar)
Auf dem Handy (unter 880 px und Handys quer) sieht der Kundenbereich aus wie
eine App; der Desktop bleibt pixelgleich.

- Kopfzeile als App-Leiste: Logo, beim Scrollen der Seitentitel, Avatar
- Navigation als Tab-Leiste unten mit Symbolen und Zaehlern; neuer Tab
  "Wartung" mit der Abo-Karte, Stundenkonto/Bestellung und
  Uebernahmegutschrift als Unterseiten mit Zurueck (auch Android-Geste)
- Konto als Bottom-Sheet (Ansprechpartner, Vertraege kuendigen/widerrufen,
  Rechtliches, Abmelden), am Desktop als Popover unter dem Avatar
- Rechnungen als Karten, Fortschritt als Zeitleiste, Chat fuellt den
  Bildschirm; bei offener Tastatur verschwindet die Tab-Leiste (kommt auch
  nach der Android-Zurueck-Geste wieder)
- Installierbar: Manifest, App-Icons (WEBklar-Logo), Favicon, Hinweis
  "Zum Home-Bildschirm"; Service Worker nur fuer die Offline-Seite,
  Kundendaten werden nicht zwischengespeichert
- Installierte App: Kuendigen/Widerrufen mit Link zurueck in den
  Kundenbereich (nur dort sichtbar, Texte unveraendert); Login-Links zu
  Bedingungen/Datenschutz/Impressum oeffnen wie im Dashboard ein neues Fenster
- Login mit App-Icon und Abstaenden fuer Statusleiste/Home-Balken
- Hinweise "in der Seitenleiste" haben auf dem Handy eine eigene Fassung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:36:29 +00:00
ClaudeandClaude Opus 5.5 06e9a0fe8d Chat-Anhaenge: Korrekturen aus dem Code-Review
- Kamerafotos in anderen Formaten (z. B. HEIF von Android-Kameras) werden
  wie Dateien angenommen statt abgelehnt
- Dateinamen nach Zeichen kuerzen: ein halbiertes Emoji machte den Anhang
  unabrufbar (encodeURIComponent)
- Endet eine Aufnahme von selbst (Headset getrennt, Anruf), bleibt die
  Sprachnachricht erhalten statt "zu kurz"
- Kamera-App nur auf iOS/iPadOS/Android; Touch-Laptops bekommen die Kamera
  im Browser statt eines Dateidialogs
- Knapp neben dem Chat abgelegte Dateien oeffnet der Browser nicht mehr
  selbst (Portal bliebe sonst stehen); Datei-Felder bleiben Ablagen
- Fehler beim Ausliefern kommt als JSON statt mit dem Dateityp
- Server liest hoechstens 4 Uploads gleichzeitig ein (Speicher), weitere
  warten; abgebrochene Wartende geben ihren Platz frei

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:01:17 +00:00
ClaudeandClaude Opus 5.5 35d98da977 Chat: Bilder, Kamerafoto, Dateien und Sprachnachrichten
- Kundenbereich und Admin-Chat: Bueroklammer-Menue (Bild, Kamera, Datei),
  Mikrofon fuer Sprachnachrichten, Vorschau vor dem Senden mit
  Upload-Fortschritt, Einfuegen (Strg+V) und Hineinziehen von Dateien
- Kamera: am Handy/Tablet die Kamera-App, am Rechner die Kamera im Browser
  mit Vorschau, Kamerawechsel und "Nochmal"
- Sprachnachricht: Aufnahme mit Zeit und Pegel, vorher anhoerbar oder
  direkt senden, hoechstens 5 Minuten
- Verlauf: Bilder (einzeln bzw. Raster, Grossansicht), Player fuer
  Sprachnachrichten, Datei-Kacheln mit Download
- Server: jede Datei einzeln roh hochgeladen (max. 25 MB, 10 je Nachricht,
  max. 3 gleichzeitig), Typ aus dem Inhalt, nur erlaubte Endungen, keine
  Programme; Ablage unter upload-data/chat/<Kunde>/ ausserhalb von public/,
  Auslieferung nur mit Login; Bilder, Audio und PDF inline, alles andere
  als Download (nosniff, CSP sandbox)
- Nicht gesendete Anhaenge werden nach 24 Stunden geloescht
- Benachrichtigungs-Mail nennt nur Art und Namen der Anhaenge
- Appwrite: neues Attribut portalMessages.attachments
  (scripts/setup-chat-collection.mjs, vor dem Deploy ausfuehren)
- Datenschutzerklaerung Abschnitt 6 ergaenzt (Stand 26.09.2026), PDF neu

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 14:01:17 +00:00
ClaudeandClaude Opus 5.5 6e18a70a5d Kundenbereich: Abo-Karte heisst "Ihre Wartung", Status nur bei Kuendigung
- Seitenleiste: Ueberschrift "Ihre Wartung" statt "Ihr Wartungs-Abo"
- Kein "aktiv"-Label mehr; ein Status steht nur noch da, wenn gekuendigt
- Zahlungsproblem bleibt ueber den roten Hinweis sichtbar, spaeterer
  Beginn ueber die Datumszeile ("Beginn und erste Abbuchung am ...")
- Bei Kuendigung vor Leistungsbeginn keine "erste Abbuchung" mehr
- Ungenutzte Status-Styles entfernt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 12:50:24 +00:00
ClaudeandClaude Opus 5.5 4593065f79 Kundenbereich: neues Design mit Seitenleiste (Design "Kundenbereich Neu")
- Kopfzeile mit Firmenname, Kundennr., Initialen-Avatar und Abmelden
- Seitenleiste: Navigation Projekte/Fortschritt/Rechnungen/Chat mit Zaehlern
  (offene Rechnungen, ungelesene Nachrichten), "Dein Ansprechpartner"
  (Mitarbeiter des juengsten zugewiesenen Tickets, sonst WEBklar-Team) und
  Wartungs-Abo-Karte (Angebot bzw. Status, Stunden, naechste Abbuchung,
  Abo verwalten, Uebernahmegutschrift, Widerruf, Mindestlaufzeit, Kuendigen)
- Projektkarte: Vorschau mit "Website oeffnen", Status, Vorschau-Adresse,
  Live-Domain, letzte Aenderung, Schnellzugriffe, Projektdetails
- Abo-Bestellung und Stundenkonto/Verlauf sowie Uebernahmegutschrift als
  eigene Ansichten; Direktlinks #abo/#gutschrift (auch ueber den Login)
- Pflichtlinks (Kuendigungsbutton, Widerruf, Bedingungen, Datenschutz,
  Impressum) als Fusszeile; Systemschriften statt externer Fonts
- Mobil: Navigation im 2x2-Raster, kein horizontales Scrollen
- API: /api/contact, updatedAt in /api/projects; Mail-Links auf Ansichten

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 21:03:14 +00:00
ClaudeandClaude Opus 5.5 bb2f46e191 Abo: individuelle Angebote (z. B. erste 2 Monate je 25 EUR) mit Stripe-Coupon
- Admin-Tab "Abo-Angebote" und Button "Angebot" in der Kundenliste: Preis, Monate,
  Gueltigkeit, Hinweis fuer den Kunden, interne Notiz, Vorschau vor dem Anlegen
- Je Angebot sofort ein Stripe-Coupon (amount_off, repeating ueber die
  Angebotsmonate, max. 1 Einloesung); danach automatisch wieder Normalpreis
- Kunde ohne Abo: Angebot im Portal, Angebotspreis in der Bestelluebersicht,
  Coupon im Checkout; Angebot schaltet die Bestellung auch ausserhalb der
  Pilotliste frei; Bestellung prueft, dass das angezeigte Angebot noch gilt
- Laufendes Abo: Coupon sofort am Abo fuer die naechsten Abbuchungen
- Testkonten: Testrabatt im Checkout, Angebot per Webhook zusaetzlich am Abo
- Angebot in Abo-Metadaten, Vertragsbestaetigung, Admin-Mail und Abo-Karte
- Zurueckziehen loescht den Coupon; abgelaufene Angebote nicht mehr bestellbar
- Stundenkontingent nach invoice.subtotal: auch voll rabattierte oder per
  Guthaben bezahlte Monate bringen die Stunde, die Probezeit nicht
- setup-abo-collections.mjs: aboOffers, subscriptions.offerId/offerText,
  Attribut-Abfrage mit Limit (sonst Haenger ab 26 Attributen)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:32:08 +00:00
ClaudeandClaude Opus 5.5 48b13f4488 Abo: Uebernahmegutschrift fuer beim Altanbieter bezahlte Leistungen
- Kunde (Dashboard, nur mit laufendem Abo): Antrag mit Leistung, Anbieter,
  Domain, Rechnungsbetrag/-zeitraum, Grund, Rechnungsnachweis (PDF/JPG/PNG)
  und E-Mail fuer die Antwort; Eingangsbestaetigung per Mail, Status und
  Antwort auf Rueckfragen im Portal
- Admin-Mail mit Link admin.html?gutschrift=<id> (Login leitet zurueck)
- Admin-Tab "Gutschriften": Kunde und Vertrag, Nachweise, Verlauf,
  automatische Pruefungen (Duplikate per Datei-Hash/Domain, Abo, Zeitraum,
  DNS-Abgleich Nameserver/MX mit Anbieter), Pflicht-Checkliste, Rechner
  (tagesgenau anteilig), Genehmigen/Rueckfrage/Ablehnen mit Kundenmail
- Genehmigte Gutschrift als Stripe-Kundenguthaben (idempotent, Nachbuchen
  bei Stripe-Stoerung)
- Nachweise in upload-data/ (gitignored), Loeschung abgelehnter nach 6 Monaten
- Stundenkontingent auch fuer Monate, die ein Guthaben komplett deckt
  (invoice.total statt amount_paid)
- Datenschutzerklaerung Abschnitt 7 ergaenzt, PDF neu erzeugt
- Collection takeoverCredits in setup-abo-collections.mjs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:34:33 +00:00
ClaudeandClaude Opus 5.5 dbabbf1c0b Kontakt-E-Mail: support@webklar.com statt privater Adresse (Bedingungen, Datenschutz, Bestellseite, Mails, PDFs)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:30:19 +00:00
ClaudeandClaude Opus 5.5 2bd166adf3 Abo: Mindestlaufzeit nur fuer Vertraege ab Bedingungen Version 3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 18:00:54 +00:00
ClaudeandClaude Opus 5.5 9b563482ff Wartungs-Abo v3: Mindestlaufzeit 12 Monate
- Bedingungen § 8: 12 Monate ab Leistungsbeginn, Kuendigung zum Ende bis
  1 Monat vorher, danach monatlich; Sonderkuendigung bei Geschaeftsaufgabe
  (1 Monat Frist), einvernehmliche Beendigung jederzeit; gilt beidseitig
- Kuendigungslogik computeCancellationDate (Mindestlaufzeit, verpasste
  Frist, Wunschtermin, Geschaeftsaufgabe, Monatsende) + cancelAt gespiegelt
- Stripe-Kundenportal: Kuendigung aus (Stripe kennt keine Mindestlaufzeit)
- Bestellseite, Abo-Karte, Vertragsbestaetigung, Checkout-Text, PDF,
  Leitfaden angepasst; termsVersion 2026-09-25-v3

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:59:49 +00:00
ClaudeandClaude Opus 5.5 9e21aa0690 Abo: Kuendigungsbestaetigung auch bei Kuendigung ueber das Stripe-Kundenportal
Webhook customer.subscription.updated erkennt neu gesetzte Kuendigungen.
Kuendigungs- und Widerrufsseite markieren ihre Kuendigung per Metadatum
cancelSource, damit keine doppelte Bestaetigung verschickt wird.
Admin-Hinweis auch bei zurueckgenommener Kuendigung.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:45:04 +00:00
ClaudeandClaude Opus 5.5 90e85f5989 Abo: Testkonten zahlen per Rabatt nur 0,50 EUR (ABO_TEST_EMAILS)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 17:09:52 +00:00
ClaudeandClaude Opus 5.5 7237ec729d Stripe-Produkt mit Produktbild; Portal-Konfiguration exportiert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:18:52 +00:00
ClaudeandClaude Opus 5.5 986ef9ebb9 Abo: Pilotliste mit '*' fuer Regelbetrieb
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:17:05 +00:00
ClaudeandClaude Opus 5.5 4ab154bfa2 Wartungs-Abo v2: Rechtsrunde umgesetzt
- Bestellseite: Pflichtangaben direkt ueber dem Button, Zahlungsarten,
  Wahl des Leistungsbeginns (sofort / nach Widerrufsfrist via 15 Tage
  Stripe-Probezeit), keine Vorauswahl
- Zustimmungsnachweis: Zeitpunkt, Version, Wahl, IP, Browser in Stripe-
  Metadaten und Appwrite (neue Attribute, Setup-Skript ergaenzt)
- Checkout: Karte + SEPA (Fallback nur Karte), korrigierter Hinweistext
- Vertragsbestaetigung mit PDF der Vertragsunterlagen statt HTML
- Kuendigen/Widerrufen: Formular direkt (keine Zwischenseite), Wunsch-
  termin, Druck/PDF der Bestaetigung, Vorbefuellung bei Login
- Widerrufsbutton im Abo waehrend der Frist, 'beginnt am'-Status
- Pilotbetrieb per ABO_PILOT_EMAILS
- Vertragsbedingungen v2, Datenschutzerklaerung, Kalkulator ohne MwSt
  und mit 100/80 EUR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 14:10:21 +00:00
ClaudeandClaude Opus 5.5 c0214725c0 Stripe-Produkt: Name, Beschreibung und Abrechnungstext wie im Dashboard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:37:26 +00:00
ClaudeandClaude Opus 5.5 6c92615869 ENTWURF: Postfaecher und Domains bei Hetzner (5 GB je Postfach)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:25:22 +00:00
ClaudeandClaude Opus 5.5 7849cb0e64 ENTWURF Vertragsunterlagen v2, Datenschutzerklaerung, Leitfaden Empfehlungsgeber
Noch nicht freigegeben, nicht nach main pushen. Enthaelt amtliche
Muster-Widerrufsbelehrung (Fassung ab 19.06.2026), AVV mit TOM und
Unterauftragsverarbeitern, Stundenkontingent statt Guthaben, Wahl des
Leistungsbeginns, Sperre erst ab 100 EUR, Hetzner-Backup-Formulierung.
Platzhalter: E-Mail-Anbieter und Postfachgroesse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 13:06:51 +00:00
ClaudeandClaude Opus 5.5 e8c2139268 Abo-Karte: Leistungen im 2x2-Raster, mobil einspaltig
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:05:38 +00:00
ClaudeandClaude Opus 5.5 65429aa3ac Wartungs-Abo (50 EUR/Monat) ueber Stripe mit Stundenkonto im Portal
- Bestellung auf der Portal-Startseite: Zusammenfassung, AGB- und
  Vorab-Leistungs-Zustimmung, Button 'Zahlungspflichtig bestellen',
  danach Stripe Checkout (Abo, Kleinunternehmer-Fusszeile)
- Stripe-Webhook /api/abo/webhook spiegelt Abos nach Appwrite
  (subscriptions) und schreibt je bezahlter Rechnung 60 min gut
  (hourCredits, idempotent ueber Invoice-ID, 12 Monate gueltig)
- Stundenkonto: FIFO-Verbrauch aus Worksheets mit countsToPlan,
  15-Minuten-Takt, Mehraufwand separat ausgewiesen
- Stripe-Kundenportal fuer Zahlungsart/Rechnungen/Kuendigung
- Oeffentlicher Kuendigungsbutton (§ 312k) und Widerrufsbutton
  (§ 356a) mit Eingangsbestaetigung per Mail
- Abo-Bedingungen inkl. Leistungsbeschreibung und Widerrufsbelehrung,
  als Anhang der Vertragsbestaetigung

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 12:04:45 +00:00
ClaudeandClaude Opus 5.5 c3f56ba290 Kundenliste: alle Kunden laden statt Appwrite-Default von 25
Ohne Query.limit liefert Appwrite nur 25 Dokumente. Bei 110 Kunden fehlten
in WOMS alle Kunden ab 'Autowerkstatt ...' alphabetisch - neu angelegte
Leads wirkten dadurch, als waeren sie nie angelegt worden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-22 18:58:03 +00:00
ClaudeandClaude Opus 5 ac264dc9ba Rechnungsanalyse: Termin landet im Akquise-Ticket und damit im Planboard
Der im Gespraech vereinbarte Termin wird mit einem Klick am Akquise-Ticket
eingetragen (startDate/startTime/endTime/assignedTo/assignedName) - genau die
Felder, die das WOMS-Planboard liest. Kein zweiter Datensatz, damit Termin und
Ticket nicht auseinanderlaufen koennen.

- server/services/appointments.js: Ticket des Kunden finden, Termin schreiben,
  belegte Zeiten lesen; Umrechnung ISO <-> "dd.mm.yyyy" und "hh:mm" <-> "hhmm"
- Routen GET /api/portal-admin/appointments und
  POST /api/portal-admin/customers/:id/appointment
- Bereits vergebene Zeiten sind in der Terminauswahl gesperrt ("belegt"), der
  eigene Termin bleibt waehlbar; steht am Ticket schon einer, ist er beim
  Oeffnen vorausgewaehlt
- Zeiten, die jemand von Hand in WOMS gesetzt hat, tauchen mit auf

Sicherung gegen Datenverlust: bevorzugt wird das Akquise-Ticket. Gibt es keins,
kommt nur ein anderes offenes Ticket OHNE Datum in Frage - bei Webpage- und
Projekttickets bedeutet startDate den Projektstart, den wuerden wir sonst still
ueberschreiben. Die Rueckmeldung nennt immer Nummer und Art des Tickets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 22:09:51 +00:00
Claude c4a6c90306 Rechnungsanalyse: Kopfzeile aufgeraeumt, Zurueck-Weg steckt im Logo
Der Button "← Admin" und die Lead-Zeile in der Kopfleiste sind raus. Das
webklar-Logo ist jetzt selbst der Link zurueck ins Admin-Panel (mit title
als Hinweis). Der Lead steht weiterhin ueber dem Preis ("Angebot für …").
2026-08-08 21:47:31 +00:00
ClaudeandClaude Opus 5 9108c90a53 Rechnungsanalyse: Kalkulationsseite fuer Leads im Admin-Panel
Bei Leads (customerStatus=lead) steht in der Kundenliste jetzt der Button
"Rechnungsanalyse". Er oeffnet /kalkulation.html?customer=<id> - die Seite,
die im Gespraech vor Ort oder am Telefon ausgefuellt wird.

- Positionen nach Bereich (Website, Onlineshop, Softwareprojekt,
  Automatisierung/KI, laufende Kosten) mit Stundenwerten und Mengen
- Preis ueber die Stundenstaffel (150/130/115/100), Brutto mit 19 % MwSt.,
  Zahlungsmodalitaeten ab 1.000 / 3.000 EUR
- Hinweis, ob selbst zusagbar (<= 5 Std), Termin abzustimmen oder ob Kenso
  freigeben muss (Shop, Software, Automatisierung)
- Termin mit Justin/Kenso aus den hinterlegten Zeitfenstern, Notizen,
  Kundenportal-Zugang mit QR-Code
- Zusammenfassung als Text zum Kopieren -> gehoert ins Akquise-Ticket

Bewusst ohne eigenen Datensatz: dokumentiert wird im Ticket. Der Stand liegt
pro Kunde nur im localStorage, damit ein angefangenes Gespraech nicht
verloren geht.

Fonts (Caprasimo, Figtree) liegen lokal unter public/fonts, kein Abruf beim
Google-CDN. Neue Route GET /api/portal-admin/customers/:customerId liefert
die Stammdaten zum Vorbefuellen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 21:42:13 +00:00
ClaudeandClaude Fable 5 a4b4974431 Selbstheilung: /api/auth/me stellt abgelaufenes/fehlendes preview_session-Cookie neu aus
Portal-Session (webklar_portal_session) lebt unabhaengig vom preview_session-JWT.
Fehlte das Preview-Cookie (WebView-Datenverlust, Ablauf), liefen alle
*.project.webklar.com-Hosts in eine 302-Login-Schleife (schwarze Seite im
Kundenportal), obwohl der Nutzer eingeloggt war - Re-Login war der einzige
Ausweg. Jetzt heilt jeder Aufruf der Projektuebersicht das Cookie automatisch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 20:13:44 +00:00
ClaudeandClaude Fable 5 55991c9008 previewDeploy: node_build-Autodetect fuer App-Repos + Build-Output-Erkennung
- runDeploy: Repos mit package.json-build-Script ohne index.html werden
  automatisch als node_build gebaut (Expo/Vite/React zeigen sonst nur den
  Platzhalter); Build-Fehler fallen auf static+Platzhalter zurueck
- deployNodeBuild: npm-ci-Fallback auf npm install, Output-Erkennung
  dist/build/out/web-build, maxBuffer+Timeout
- updateWebsiteProject: bestehenden type/index aus .webklar-preview.json
  erhalten statt bei jedem Edit auf static zurueckzusetzen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 13:37:34 +00:00
ClaudeandClaude Fable 5 f3bde44b3d previewDeploy: vorhandene HTML-Datei statt Platzhalter als Startseite befoerdern
Fehlt im Repo eine index.html, zeigte die Preview nur die
'wird vorbereitet'-Platzhalterseite, obwohl das eigentliche Projekt
(z.B. prototyp-app.html in knso/videogen) laengst deployt war.
ensureIndexHtml befoerdert jetzt die beste vorhandene HTML-Datei
(bevorzugte Namen, sonst einzige/groesste) per Kopie zur index.html;
der Platzhalter kommt nur noch, wenn gar keine HTML-Datei existiert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 09:26:04 +00:00
ClaudeandClaude Opus 4.8 b6f5ea11ec Portal-Admin: archivierte Projekte/Kunden ausblenden, Kundentabelle ohne Horizontal-Scroll
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 19:30:16 +00:00
ClaudeandClaude Opus 4.8 1b4262b09f fix: 403-Schutz + Selbstheilung fuer geloeschte Preview-Config
- runDeploy schreibt Platzhalter-index.html, wenn das Deploy-Verzeichnis
  keine index.html enthaelt (leergeraeumtes Repo/Fehlbuild) -> nie mehr
  nginx 403 Forbidden, Subdomain bleibt erreichbar.
- deployStatic: Fallback auf Repo-Wurzel bei fehlendem index-Unterordner,
  .git wird nicht mehr mit ausgeliefert.
- Webhook: fehlt .webklar-preview.json im Repo, wird die Hosting-Config aus
  dem Appwrite-Projektstand rekonstruiert, ins Repo zurueckgeschrieben und
  normal deployt (ensureRepoPreviewConfig).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 18:05:00 +00:00
ClaudeandClaude Opus 4.8 07ebcc7e62 feat(portal): Live-Vorschaufenster pro Projektkarte im Kundenbereich
Statt nur Name + Link zeigt jede Projektkarte jetzt ein kleines,
skaliertes Live-Fenster der echten Webseite (iframe der previewUrl,
responsiv per ResizeObserver auf 1280px-Basisbreite herunterskaliert).
Klick aufs Fenster oeffnet die Seite im neuen Tab. Nicht-fertige
Projekte zeigen einen Platzhalter statt iframe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 12:42:35 +00:00
ClaudeandClaude Opus 4.8 bed738d8bd fix: verwaiste Projekte archivierbar (Gitea-Repo 404 nicht mehr hart abbrechen)
- archiveRepo: fehlt das Gitea-Repo (404), wird nur das Appwrite-Dokument
  (ent)archiviert statt hart zu scheitern -> orphaned Projekte lassen sich
  aus der Liste entfernen
- Reaktivierungs-Redeploy wird bei fehlendem Repo uebersprungen

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 09:19:25 +00:00
ClaudeandClaude Opus 4.8 376a8ae34d feat: Projekt-Typ Vorlage (template) - login-geschuetzt, isTemplate-Flag
- neuer Typ template: hasWebsite wie preview, isPublic=false, isTemplate=true
- deriveProjectType erkennt isTemplate; Appwrite-Update schreibt isTemplate
- .webklar-preview.json bekommt template-Flag

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 09:09:57 +00:00
ClaudeandClaude Opus 4.8 617d463a5a fix: updateWebsiteProject - kein hasPreview-Attribut, TLS-Sync nach Appwrite-Update
- hasPreview existiert im Live-Schema nicht -> Appwrite lehnte Update ab
- syncPreviewTls muss NACH updateDocument laufen, sonst liest es isPublic
  noch im alten Zustand (oeffentlicher Router bekam faelschlich preview-auth)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 00:30:24 +00:00
ClaudeandClaude Opus 4.8 b474c4aa48 feat: Website-Projekte bearbeiten (Name/Subdomain/Typ) + Archivierung
- updateWebsiteProject: synct Name/Subdomain/Typ nach Gitea (.webklar-preview.json,
  Repo-Beschreibung), Preview-Deploy (Redeploy+Teardown+TLS) und Appwrite
- Projekt-Typen: preview (Login), website (oeffentlich ohne Login), project (kein Hosting)
- syncPreviewTls erzeugt fuer isPublic-Projekte Router ohne preview-auth
- archiveRepo: Gitea archived + Appwrite-Flag + Preview-Teardown; Reaktivieren redeployt
- syncReposToAppwrite uebernimmt repo.archived
- Routen: PATCH /:id, POST /:id/archive, POST /:id/unarchive

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 00:21:50 +00:00