Files
perinco-CRM/docs/08-anleitung-entwickler.md
T
Sascha WolfandClaude Opus 5 4d90f14d6b docs: Anleitungen fuer Benutzer und Entwickler, Quellcode-Referenz, Glossar
Die bisherige Dokumentation beschrieb die Architektur, half aber niemandem, der
die Anwendung zum ersten Mal oeffnet. Dazu jetzt vier Kapitel:

- 07 Anleitung fuer Benutzer: was die Anwendung tut, jeder Bereich Schritt fuer
  Schritt, warum der Signalton nur im Dashboard klingelt, was Loeschen eines
  Suchagenten mitnimmt
- 08 Anleitung fuer Entwickler: einrichten, pruefen, Feld/Menuepunkt/Reiter/
  Endpunkt hinzufuegen, dazu eine Tabelle der Fallen, die real Zeit gekostet
  haben (Routen-Reihenfolge, FormData, Leerzeichen in Pfaden, Zeitstempel
  koennen nur vorziehen, Kaskaden beim Loeschen)
- 09 Quellcode-Referenz: jede Datei mit ihren Exporten und den Zusagen, auf die
  sich der Rest verlaesst
- 10 Glossar: Agent, Match, Lead, Opportunity und die Statuswerte der Datenbank

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 15:34:00 +02:00

8.2 KiB

08 Anleitung fuer Entwickler

Fuer jemanden, der das Projekt zum ersten Mal oeffnet. Was man braucht, wie man es startet, wo man was aendert - und welche Fallen es gibt.

In zwei Minuten zum Bild

  • Ein Vite-Frontend (React 19, TypeScript, Tailwind 4), ein Express-Backend, zwei MariaDB-Datenbanken.
  • Kein Router im Frontend: App.tsx haelt die Ansicht in einem useState. Die App liegt hinter einer Anmeldung, tiefe Links braucht sie nicht.
  • Kein ORM im CRM-Teil - dort schreibt das Backend SQL mit mysql2. Der Scanner-Teil benutzt Sequelize, weil er 1:1 aus einer anderen Anwendung uebernommen wurde.
  • Alles auf Deutsch: Bezeichner, Kommentare, Meldungen. Kommentare erklaeren das Warum, nicht das Was.

Voraussetzungen

Version
Node 20 oder neuer (Server laeuft 20.20)
MariaDB 10.11 oder neuer, erreichbar mit Benutzer und Passwort
Browser Chrome/Edge fuer WebGPU beim Diktat (sonst laeuft es auf der CPU)

Lokal starten

git clone http://192.168.1.199:3000/sascha/perinco-CRM.git
cd perinco-CRM

npm install                      # Frontend
cd server && npm install         # Backend
cp .env.example .env             # Werte eintragen, mindestens DB_* und JWT_SECRET
npm start                        # Backend auf :5006
cd .. && npm run dev             # Frontend auf :3100, /api wird geproxied

Was dabei von selbst passiert:

  • perinco_CRM muss existieren; die Tabellen legt server/src/schema.sql beim Start an (idempotent).
  • perinco_scanner legt das Backend komplett selbst an - Datenbank, Tabellen, Portale, Standardeinstellungen.
  • Fehlt ADMIN_PASSWORD, wird beim ersten Start ein Zufallspasswort erzeugt und einmalig ins Log geschrieben. Dort nachsehen, sonst kommt man nicht rein.

Ohne MariaDB startet das Backend nicht. Das Frontend laesst sich trotzdem ansehen - siehe Oberflaeche pruefen ohne Datenbank weiter unten.

Pruefen

npm run lint          # = tsc --noEmit, das ist die Wahrheit
npm run build         # muss ohne Fehler durchlaufen

Es gibt keine automatischen Tests. Wer etwas aendert, prueft mit tsc, einem Build und einem Blick in die Oberflaeche.

Aufbau

src/                  Frontend
  App.tsx             Ansicht, zentrale Daten, Layout
  types.ts            Typen, Konstanten, Rechenhilfen (Volumen, Farben)
  lib/                API-Zugriff und Kontexte
  components/         je Bereich ein Ordner
server/src/           Backend
  server.js           Express-App, Reihenfolge der Routen
  db.js               Verbindungspool, Schema-Init, Admin-Seed
  routes/             ein Router je Ressource
  middleware/auth.js  JWT pruefen, Adminrechte
  scanner/            portierter Projekt Scanner (Sequelize, Playwright, OpenAI)
docs/                 diese Dokumentation
deploy/               Apache-Vhosts, systemd-Unit, Deployskript

Wie eine Anfrage laeuft

sequenceDiagram
    participant K as Komponente
    participant A as lib/api.ts
    participant E as Express
    participant M as middleware/auth
    participant R as routes/*.js
    participant D as MariaDB

    K->>A: leadsApi.update(7, { status: 'Gewonnen' })
    A->>A: Token anhaengen, Content-Type setzen
    A->>E: PATCH /api/leads/7
    E->>M: requireAuth
    M->>D: Benutzer laden, aktiv?
    M->>R: req.user gesetzt
    R->>R: Eingaben pruefen und normalisieren
    R->>D: UPDATE ... ; SELECT (angereichert)
    R-->>A: 200 JSON
    A-->>K: typisiertes Objekt

Jede Route liefert den vollstaendigen, angereicherten Datensatz zurueck (mit created_by_name, owner_name, Ansprechpartnern, Teilnehmern). Das Frontend muss danach nichts nachladen.

Etwas hinzufuegen

Neues Feld an einem Kontakt

  1. server/src/schema.sql: Spalte mit ALTER TABLE ... ADD COLUMN IF NOT EXISTS ergaenzen - das Statement muss beim naechsten Start wiederholbar sein.
  2. server/src/routes/leads.js: Feld in die Listen TEXT_FIELDS bzw. in INSERT/UPDATE und in den SELECT aufnehmen.
  3. src/types.ts: Feld ins Lead-Interface.
  4. src/components/leads/LeadModal.tsx: Eingabefeld, inputClass benutzen.
  5. Wenn es in Kachel oder Liste sichtbar sein soll: LeadGrid.tsx, LeadList.tsx.

Neuer Menuepunkt

  1. src/types.ts: Wert in ViewState ergaenzen.
  2. src/components/Sidebar.tsx: Eintrag in CATEGORY_ITEMS (Name + SVG-Pfad).
  3. src/App.tsx: Komponente rendern und einen Untertitel setzen.

Der Name in ViewState ist gleichzeitig die Beschriftung und der Titel in der Kopfzeile. Wer eine abweichende Beschriftung braucht, setzt label am MenuItem (so macht es der Unterpunkt Dashboard, der auf die Ansicht Scanner zeigt).

Neuer Reiter in den Einstellungen

  1. src/types.ts: Wert in SettingsTab; nur fuer Admins zusaetzlich in ADMIN_SETTINGS_TABS.
  2. src/components/settings/Settings.tsx: Eintrag in TABS und Rendering.

Ein Sprung dorthin aus einer beliebigen Tiefe: goTo('Einstellungen', { tab: 'whisper' }).

Neuer Endpunkt

  1. Router in server/src/routes/ anlegen, router.use(requireAuth) nicht vergessen, requireAdmin wo noetig.
  2. In server/src/server.js einhaengen - vor dem /api-404-Fallback.
  3. In src/lib/api.ts eine typisierte Funktion ergaenzen.

Fallen

Diese Punkte haben real Zeit gekostet:

Falle Was passiert
Reihenfolge in server.js Der 404-Fallback app.use('/api', ...) faengt alles, was danach registriert wird. Neue Router davor einhaengen.
Content-Type bei FormData apiRequest setzt JSON nur, wenn der Body keine FormData ist - sonst fehlt die multipart-Grenze und der Upload scheitert.
Dateien in neuen Tabs Ein <a href> schickt kein Bearer-Token. Deshalb holt openAgentCv() das PDF per fetch und oeffnet einen Blob.
Leerzeichen in Pfaden Der alte Scanner liegt unter /var/www/Projekt Scanner/…. In der .env muss der Wert gequotet sein, sonst schneidet systemd ihn am Leerzeichen ab.
.env ist gleichzeitig EnvironmentFile Also kein export, keine Shell-Ausdruecke, Werte mit Leerzeichen in Anfuehrungszeichen.
Zeitstempel koennen nur vorziehen Ein „vorgealterter" lastRunTime verzoegert keinen Cronlauf. Wer verzoegern will, braucht eine eigene Frist.
Sperrfristen bei jedem Start neu Frueher schob jedes Deployment den faelligen Scan um 30 Minuten - er fand nie statt. Deshalb liegt last_scan_at in der Datenbank.
sequelize.sync({ alter: true }) Legt bei jedem Start Unique-Indizes neu an und sprengt irgendwann MySQLs Grenze von 64 Keys. Nur sync() benutzen.
Kaskaden beim Loeschen Einen Suchagenten loeschen loescht alle seine Treffer mit. Inaktiv setzen ist meist gemeint.
Doppelte Scanner-Laeufe Scheduler und Jetzt scannen teilen eine Sperre. Ohne sie zahlt man jede KI-Bewertung zweimal.

Oberflaeche pruefen ohne Datenbank

Fuer Bildschirmfotos und Klickwege genuegt ein Stub: ein kleiner node:http-Server, der dist/ ausliefert und auf /api/... erfundene Daten zurueckgibt. Token vorab setzen, dann ist die Anmeldung uebersprungen:

await page.goto(BASE);
await page.evaluate(() => localStorage.setItem('perinco_crm_token', 'stub-token'));
await page.goto(BASE);

Playwright startet dabei am besten das installierte Chrome (chromium.launch({ channel: 'chrome' })) - dann braucht es keinen Browser-Download.

Konventionen

  • Deutsch in Bezeichnern, Kommentaren, Meldungen. Fachbegriffe bleiben englisch (Match, Opportunity, Lead).
  • Kommentare erklaeren das Warum. Was der Code tut, steht im Code.
  • Kein dark: im Markup. Nur die Skalen ink-*, brand-*, canvas, surface benutzen, den Rest macht index.css.
  • Gemeinsame Klassen statt kopierter Utility-Ketten: inputClass, labelClass aus components/leads/leadUi.tsx, dazu cardClass, primaryButtonClass aus components/scanner/scannerUi.tsx.
  • Meldungen als Toast, Loeschabfragen ueber ConfirmDialog - nie alert() oder confirm().
  • Rechte immer doppelt: Oberflaeche blendet aus, Backend antwortet mit 403.

Deployment

Siehe 06 Betrieb. Kurzform: lokal bauen, deploy/deploy.ps1 laedt dist/ und server/src hoch und startet den Dienst neu. Auf dem Server wird nicht gebaut - dort sind RAM und Swap knapp.