diff --git a/docs/superpowers/specs/2026-09-30-projektarten-services-design.md b/docs/superpowers/specs/2026-09-30-projektarten-services-design.md new file mode 100644 index 0000000..f6d41eb --- /dev/null +++ b/docs/superpowers/specs/2026-09-30-projektarten-services-design.md @@ -0,0 +1,143 @@ +# Projektarten, Zuordnungs-Ablauf und Bereich „Services“ – Design + +Stand: 2026-09-30 · Status: vom Nutzer im Chat freigegeben (Abschnitte 1–3), Spec zur Durchsicht + +Gesamtvorhaben in drei Teilprojekten, **nacheinander**: jedes wird gebaut, geht live und wird vom Nutzer +live getestet, **bevor** das nächste beginnt. + +1. **Projektarten + Services** (diese Spec) +2. Preview 2.0 (statisch / Build / Dev-Server bei Bedarf, Build ohne Portal-Geheimnisse) – eigene Spec +3. Funktion „Website-Analyse“ (Einwilligung, eigene Auswertung, Heatmap, Rechtstexte) – eigene Spec + +Recherchen dazu: `/root/reports/analyse-funktion-2026-09-30/` (projekttypen-preview.md, technik-analyse-heatmap.md, +recht-tracking-deutschland.md). + +## Ziel + +Jedes Repo hat eine **Art** (Website, Web-App, …). Egal ob es in Gitea oder im WOMS angelegt wird, wird +die anlegende Person durch einen Ablauf geführt, bis Art, Kunde und Ticket feststehen. Es entstehen +**keine leeren Tickets** und **keine verwaisten Kunden-Repos**. Im WOMS zeigt ein Bereich „Services“ unsere +Leistungsarten, was je Art möglich ist und welche Projekte welche Art haben. + +## Entscheidungen (vom Nutzer) + +| # | Entscheidung | +|---|---| +| P1 | Arten: **Website, Web-App, App, Migration/Umzug, Automation/Skript, Sonstiges**. Landingpage und Shop laufen als Website; Wartung ist ein Abo, keine Art; Template bleibt eigene Markierung. | +| P2 | Bestehende Repos bekommen **keine automatische Art**; sie stehen auf „Art fehlt“, bis jemand sie von Hand setzt. | +| P3 | Gitea bleibt erlaubt. Wer ein Repo in Gitea anlegt, bekommt eine **Mail** (Muster Push-Zeitmail) mit Link in den Zuordnungs-Ablauf. | +| P4 | Der Ablauf gilt **auch** für den Mail-Link, **mit WOMS-Login** (inkl. 2FA). | +| P5 | Ablauf: Art → Für einen Kunden? → Kunde → Ticket (vorhandenes oder neu) → Abschließen. Ein neues Ticket entsteht **erst beim Abschließen**. | +| P6 | Bereich „Services“ = Variante **B**: Katalog + Projektlisten je Art. Funktionen werden **nur am Projekt** geschaltet. | +| P7 | Jetzt nur das Gerüst und als Funktion „Website-Analyse“ (Status *In Entwicklung*). Weitere Funktionen fragt Claude am Ende ab. | + +## 1. Datenmodell und Erkennung + +**`websiteProjects` – neue Attribute** (Schema-Skript `scripts/projektarten-setup.mjs`, idempotent, Muster +`portal-setup.mjs`): +- `kind` (string 20, optional): `website` | `webapp` | `app` | `migration` | `automation` | `other`; leer = Art fehlt +- `forCustomer` (boolean, optional; leer = noch nicht beantwortet) +- `kindSetBy` (string 64), `kindSetAt` (ISO-String) +- `createdByGitea` (string 64): Gitea-Login der anlegenden Person (für die Mail) +- `assignMailSentAt`, `assignReminderSentAt` (ISO-Strings) + +`projectType` (preview/website/template/repo) bleibt unverändert und regelt weiter nur das Hosting. + +**Katalog** `server/services/projectKinds.js` (einzige Quelle der Wahrheit): +```js +KINDS = [ + { key: 'website', label: 'Website', preview: 'yes', features: ['analyse'], description: '…' }, + { key: 'webapp', label: 'Web-App', preview: 'possible', features: [], … }, + { key: 'app', label: 'App', preview: 'no', … }, + { key: 'migration', label: 'Migration/Umzug', preview: 'no', ticketType: 'Migration', … }, + { key: 'automation', label: 'Automation/Skript', preview: 'no', … }, + { key: 'other', label: 'Sonstiges', preview: 'no', … }, +] +FEATURES = { analyse: { label: 'Website-Analyse', status: 'in_development' } } +``` +Ausgeliefert über `GET /api/admin/services` (Admin-JWT wie die anderen WOMS-Aufrufe). WOMS hat keine eigene Kopie. + +**Erkennung neuer Repos** +1. Org-Webhook WEBklar (Hook 112) bekommt zusätzlich das Ereignis `repository` (created). Der Handler in + `server/routes/webhook/gitea.js` legt das Projekt-Dokument an (`upsertWebsiteProjectByRepo`), setzt + `createdByGitea = sender.login` und stößt die Mail an. `deleted` wird ignoriert (Verhalten wie heute). +2. Job alle 10 Minuten in `server/index.js`: `importNewRepos()` (dieselbe Funktion wie „Suche neue Repos“) + findet Repos in persönlichen Konten. `createdByGitea` = Repo-Owner (bei Org-Repos ohne Hook-Ereignis leer + ⇒ keine Mail, erscheint nur in „Art fehlt“). +3. **Nur neu angelegte** Projekt-Dokumente lösen eine Mail aus (Dokument in diesem Lauf erzeugt **und** + Repo `created_at` jünger als 24 h). Beim ersten Lauf nach dem Livegang entsteht so keine Mail-Welle. +4. Empfänger = Mitarbeiter, dessen Kürzel/Anzeigename zum Gitea-Login passt (`findEmployeeForPusher` aus + `pushTimeMail.js` wiederverwenden). Kein Treffer ⇒ keine Mail, Log-Zeile. +5. Mail (neue Staff-Vorlage **T16 „Neues Repo zuordnen“**, Stil wie T15): Repo-Name, Gitea-Link, wer, wann, + Knopf „Jetzt zuordnen“ ⇒ `https://ticket.webklar.com/projects/zuordnen/`. +6. Erinnerung: 1× nach 24 h, falls `kind` noch leer (derselbe 10-Minuten-Job). Danach nichts mehr. + +## 2. Zuordnungs-Ablauf (WOMS) + +Route **`/projects/zuordnen/:id`** (ProtectedRoute, also WOMS-Login + 2FA). Kopf: Repo-Name, Gitea-Link, +angelegt von/am. Schritte mit „Zurück“/„Weiter“: + +1. **Was ist das?** sechs Knöpfe aus dem Katalog. +2. **Für einen Kunden?** Ja / Nein (internes Projekt ⇒ weiter zu 5). +3. **Welcher Kunde?** Suche über Kunden; „Neuer Kunde“ öffnet das vorhandene Kunden-Formular und kehrt mit + dem neuen Kunden zurück. +4. **Welches Ticket?** offene Tickets (Status ≠ Closed/Cancelled) dieses Kunden, oder „Neues Ticket anlegen“ + mit vorausgefüllten, änderbaren Feldern: Thema „ – “, Ticket-Art `Migration` bei Art + Migration sonst `Webpage`, Angefragt von = Ansprechpartner des Kunden, Start heute, Details = Repo-Link. +5. **Zusammenfassung + „Abschließen“.** Bei Art Website mit Kunde: Preview-Schalter an, Subdomain-Vorschlag + aus dem Repo-Namen (änderbar, Prüfung wie heute). + +**Abschließen** = ein Aufruf `POST /api/admin/website-projects/:id/zuordnen` +`{ kind, forCustomer, customerId?, ticketId? | newTicket?, preview?: { subdomain } }`. Der Server: +1. prüft Eingaben (Art aus Katalog, Kunde existiert, Ticket gehört zum Kunden und ist offen); +2. legt ggf. das Ticket an (neue Funktion `createTicketForProject` in `ticketAdmin.js`, Felder wie das + WOMS-Formular); +3. ruft `assignProject(projectId, { customerId, ticketId })` (Umzug in die Org, Pushes nachtragen); +4. setzt `kind`, `forCustomer`, `kindSetBy` (Mitarbeiter aus dem JWT), `kindSetAt`; +5. bei Website + Preview: `updateWebsiteProject(id, { subdomain, projectType: 'preview' })`. +Scheitert Schritt 3 nach einem neu angelegten Ticket, wird das Ticket wieder gelöscht (kein leeres Ticket). +Antwort `{ ticketId }` ⇒ WOMS springt ins Ticket. + +Weitere Einstiege: +- WOMS „Neues Repo anlegen“ (Projekte): nach Name/Vorlage direkt weiter in diesen Ablauf. +- Ticket ⇒ „Projekt zuweisen“: Kunde/Ticket bekannt; fehlt die Art, fragt ein kleiner Dialog nur Schritt 1. +- Bereits zugeordnet (`kind` gesetzt) ⇒ Seite zeigt „Bereits erledigt von am “ mit Link zum + Ticket; Art bleibt dort änderbar. + +## 3. Bereich „Services“ (WOMS) + +Menüpunkt **„Services“** neben „Projekte“, Route `/services`. Daten: `GET /api/admin/services` liefert +Katalog + Projekte gruppiert nach `kind` (Repo, Kunde, Ticket, Preview-URL, Funktionszustände). + +- Eine Karte je Art: Name, Beschreibung, Anzahl, **mögliche Funktionen** mit Status (Website-Analyse: + *In Entwicklung*), **Projekte dieser Art** als Liste (Klick ⇒ Ticket bzw. Projekt). +- Karte **„Art fehlt“**: alle Repos ohne `kind`, je mit Knopf „Zuordnen“ ⇒ Ablauf aus Abschnitt 2. +- Kein Umschalten hier. Umschalten der Funktionen kommt mit Teilprojekt 3 **am Projekt** (Ticket ⇒ Projekte). +- Kundenportal: Tab „Funktionen“ zeigt dem Kunden nur Funktionen, die laut Katalog zur Art seines Projekts + passen (Anbindung an die Funktionen-Spec, sobald diese live ist). + +## Fehlerfälle + +- Gitea-Login ohne passenden Mitarbeiter ⇒ keine Mail, Projekt steht in „Art fehlt“. +- SMTP-Fehler ⇒ `assignMailSentAt` bleibt leer, nächster Job-Lauf versucht es erneut (höchstens 3 Versuche). +- Zwei Personen schließen gleichzeitig ab ⇒ zweiter Aufruf bekommt 409 „bereits zugeordnet“. +- Appwrite nicht erreichbar ⇒ Job überspringt den Lauf, keine Teil-Zustände. + +## Tests + +- Unit (Portal, `node --test`): Katalog, Eingabeprüfung, Mail-Auswahl „nur neue Repos“, Erinnerung, + Rückbau des Tickets bei Fehler, 409 bei Doppel-Abschluss. +- WOMS (vitest): Schritt-Logik des Ablaufs, Vorbelegung des neuen Tickets. +- Playwright gegen Fixture-Daten: Ablauf komplett (intern, Kunde + vorhandenes Ticket, Kunde + neues Ticket), + Services-Seite. +- Live-Test mit dem Nutzer: Repo in Gitea anlegen ⇒ Mail ⇒ Ablauf ⇒ Ticket/Preview prüfen; danach aufräumen. + +## Livegang (je Schritt mit OK des Nutzers) + +L1 Schema-Skript · L2 Portal pushen (main) · L3 Hook 112 um `repository` erweitern · L4 WOMS pushen (test) · +L5 Live-Test mit dem Nutzer · danach erst Teilprojekt 2. + +## Nicht Teil dieser Spec + +Preview-Modi/Dev-Server (Teilprojekt 2), Analyse und deren Rechtstexte (Teilprojekt 3), weitere Funktionen +im Katalog (werden am Ende beim Nutzer abgefragt), Umbenennen/Abschaffen von `projectType`.