diff --git a/docs/07-betrieb.md b/docs/07-betrieb.md index 26dfa48..eb17f09 100644 --- a/docs/07-betrieb.md +++ b/docs/07-betrieb.md @@ -1,159 +1,159 @@ -# 07 Betrieb - -## Deployment - -Gebaut wird **lokal**, auf den Server gehen nur `dist/` plus die Backend-Quellen. - -```powershell -$env:ICON_CRM_SSH_PASSWORD = '…' -powershell -File deploy/deploy.ps1 -powershell -File deploy/deploy.ps1 -SkipBuild # ohne neuen Build -powershell -File deploy/deploy.ps1 -Deps # bei neuen Backend-Paketen -``` - -Das Skript (`deploy/deploy.ps1`) macht in dieser Reihenfolge: - -```mermaid -flowchart LR - B["npm run build"] --> U1["pscp dist/"] - U1 --> U2["pscp server/src + package.json"] - U2 --> R["systemctl restart icon-crm-backend"] - R --> C["curl /api/health"] -``` - -Schlägt der Build fehl, wird **nichts** hochgeladen – sonst stünde auf dem -Server ein halber Stand. - -> **Falle aus der Praxis:** Wer `index.html` von Hand ändert und dann mit -> `-SkipBuild` hochlädt, spielt das alte `dist/` aus. Nach jeder Änderung an -> `src/` oder `index.html` gilt: erst bauen, dann hochladen. - -Alte Build-Dateien werden beim Hochladen **nicht** entfernt – `dist/assets/` -wächst mit jeder Auslieferung. Das ist Absicht: ein Browser mit offenem Tab holt -sonst ein Bruchstück, das es nicht mehr gibt. Gelegentlich aufräumen schadet -trotzdem nicht. - -## Dienste auf dem Server - -| Was | Befehl | -| --- | --- | -| Zustand | `systemctl status icon-crm-backend` | -| Neustart | `systemctl restart icon-crm-backend` | -| Log | `journalctl -u icon-crm-backend -n 100 --no-pager` | -| Gesundheit | `curl -s http://127.0.0.1:5007/api/health` → `{"ok":true,"db":"up"}` | -| Apache | `systemctl reload apache2`, Logs unter `/var/log/apache2/icon_crm_*.log` | - -Die systemd-Unit begrenzt den Speicher auf 1 GB (`MemoryMax=1024M`). Das ist -kein Sparzwang, sondern ein Sicherheitsnetz: ein Leck im Backend soll nicht -Apache und MariaDB mitreißen. - -## Umgebungsvariablen - -`server/.env`, chmod 600, gehört **nicht** ins Repository. Vorlage: -`server/.env.example`. - -| Variable | Bedeutung | -| --- | --- | -| `PORT`, `HOST` | `5007` auf `127.0.0.1` – von außen nur über Apache | -| `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME` | MariaDB, Datenbank `icon_CRM` | -| `JWT_SECRET`, `JWT_EXPIRES_IN` | Sitzungen, Vorgabe 12 h | -| `ADMIN_USERNAME`, `ADMIN_NAME`, `ADMIN_EMAIL`, `ADMIN_PASSWORD` | erster Administrator, **nur solange `icon_users` leer ist** | -| `CORS_ORIGINS` | erlaubte Herkunft für den lokalen Dev-Server | -| `UPLOAD_DIR` | Ablage der Lebensläufe | -| `OPENAI_API_KEY`, `OPENAI_MODEL` | KI; fehlt der Schlüssel, laufen die übrigen Teile weiter | -| `PERINCO_API_URL`, `PERINCO_API_TOKEN` | ICON liest perinco | -| `PERINCO_INBOUND_TOKEN` | perinco liest ICON | - -Ohne `ADMIN_PASSWORD` erzeugt das Backend eines und schreibt es **einmalig** ins -Log. Danach ist das Passwort nur noch über die Nutzerverwaltung bzw. *Mein -Konto* änderbar. - -## Erstaufsetzung (einmalig, bereits erledigt) - -```bash -apt-get install -y apache2 mariadb-server certbot python3-certbot-apache nodejs -a2enmod proxy proxy_http rewrite headers expires -mariadb -e "CREATE DATABASE icon_CRM CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" -mariadb -e "CREATE USER 'iconcrm'@'localhost' IDENTIFIED BY '…'; - GRANT ALL PRIVILEGES ON icon_CRM.* TO 'iconcrm'@'localhost';" -cd /var/www/icon_CRM/server && npm install --omit=dev -cp deploy/icon-crm-backend.service /etc/systemd/system/ -systemctl daemon-reload && systemctl enable --now icon-crm-backend -cp deploy/icrm.icon-experts.de.conf /etc/apache2/sites-available/ -a2ensite icrm.icon-experts.de && systemctl reload apache2 -certbot --apache -d icrm.icon-experts.de -``` - -Das Schema legt das Backend beim Start selbst an (`schema.sql` + -Migrationen in `db.js`). Die Firewall (`ufw`) lässt 22, 80 und 443 durch; das -Zertifikat erneuert certbot selbst. - -## Sicherung - -Der Bestand steht vollständig in MariaDB; hochgeladene Lebensläufe liegen unter -`/var/www/icon_CRM/uploads`. Beides gehört in die Sicherung: - -```bash -mariadb-dump --single-transaction icon_CRM | gzip > icon_CRM-$(date +%F).sql.gz -tar czf uploads-$(date +%F).tar.gz -C /var/www/icon_CRM uploads -``` - -## Der alte Name crm.icon-experts.de - -Das erste ICON CRM lief unter **crm.icon-experts.de** auf `212.227.135.41` -(`/var/www/ICON`, Supabase als Datenbank). Seit dem 14.09.2026 zeigt der Name -auf diesen Server und wird vom Vhost `icrm.icon-experts.de` als `ServerAlias` -mitbedient – dasselbe Zertifikat deckt beide Namen ab. Verschickte Links und -Lesezeichen laufen damit weiter, es gibt keinen zweiten Stand zu pflegen. - -Der alte Aufbau ist zurückgebaut: Vhosts (`icon`, `icon-le-ssl`) abgeschaltet, -`/var/www/ICON` gelöscht, das dortige Zertifikat für `crm.icon-experts.de` -entfernt (sonst hätte die Erneuerung ab sofort täglich Fehler gemeldet). - -Gesichert ist er als `icon-crm-alt-2026-09-14.zip` – Quellcode, Build, `.env` -und beide Vhost-Dateien, ohne `node_modules`. Die Datei liegt **auf beiden -Servern** unter `/root/backups/`, Prüfsumme -`252f7fa3226c2df7c4020a0234c3095c`. - -> Die Supabase-Instanz `icondb.perinco.de` läuft weiter. Sie war die Datenbank -> des alten CRM und ist die Quelle der Migration (`server/src/migrate.js`) – -> abzuschalten wäre eine eigene Entscheidung, nicht Teil dieses Rückbaus. - -## Störungssuche - -| Symptom | Erster Griff | -| --- | --- | -| Weiße Seite nach dem Rollout | `index.html` darf nicht aus dem Cache kommen – der Vhost setzt dafür `no-cache`; im Zweifel Strg+F5 | -| 502 auf `/api` | Backend aus: `systemctl status icon-crm-backend`, dann `journalctl -u …` | -| `{"db":"down"}` | MariaDB aus oder Zugangsdaten falsch | -| Anmeldung schlägt fehl | `icon_users` prüfen: existiert der Benutzer, ist `is_active = 1`? | -| Zertifikatswarnung | `certbot certificates`; deckt das Zertifikat **alle** Namen ab, auch `www`? | -| Website zeigt alte Ausschreibungen | `curl -s https://icon-experts.de/api/public/tenders | head` – kommt das durch den Proxy? | -| perinco-Kontakte fehlen | `GET /api/perinco/status`; Token beidseitig gesetzt? | -| Diktat lädt nicht | siehe [04 Diktat](04-diktat.md) – das ist eine Sache des Browsers, nicht des Servers | - -### Wenn Daten zu fehlen scheinen - -Erst messen, dann handeln. Die drei Griffe, die in der Praxis geholfen haben: - -```bash -mysql icon_CRM -e "SELECT COUNT(*) FROM icon_contacts" # steht es in der Datenbank? -curl -s .../api/contacts -H "Authorization: Bearer …" | wc -c # liefert die API es aus? -tail -n 50 /var/log/apache2/icon_crm_access.log # kam es beim Browser an? -``` - -Stimmen alle drei, liegt es an der Ansicht – meist an einem aktiven Filter. - -### DNS-Umstellungen - -Nach einem Wechsel des A-Records antworten fremde Resolver unterschiedlich -lange mit der alten Adresse. Zum Prüfen die **autoritativen** Nameserver und -mindestens einen öffentlichen Resolver befragen, nicht nur den eigenen: - -```bash -dig +short icon-experts.de @1.1.1.1 -dig +short icon-experts.de @8.8.8.8 -``` - -Ein lokaler Resolver (PiHole, Router) kann den alten Wert deutlich länger -halten – das sieht dann aus wie ein Serverfehler und ist keiner. +# 07 Betrieb + +## Deployment + +Gebaut wird **lokal**, auf den Server gehen nur `dist/` plus die Backend-Quellen. + +```powershell +$env:ICON_CRM_SSH_PASSWORD = '…' +powershell -File deploy/deploy.ps1 +powershell -File deploy/deploy.ps1 -SkipBuild # ohne neuen Build +powershell -File deploy/deploy.ps1 -Deps # bei neuen Backend-Paketen +``` + +Das Skript (`deploy/deploy.ps1`) macht in dieser Reihenfolge: + +```mermaid +flowchart LR + B["npm run build"] --> U1["pscp dist/"] + U1 --> U2["pscp server/src + package.json"] + U2 --> R["systemctl restart icon-crm-backend"] + R --> C["curl /api/health"] +``` + +Schlägt der Build fehl, wird **nichts** hochgeladen – sonst stünde auf dem +Server ein halber Stand. + +> **Falle aus der Praxis:** Wer `index.html` von Hand ändert und dann mit +> `-SkipBuild` hochlädt, spielt das alte `dist/` aus. Nach jeder Änderung an +> `src/` oder `index.html` gilt: erst bauen, dann hochladen. + +Alte Build-Dateien werden beim Hochladen **nicht** entfernt – `dist/assets/` +wächst mit jeder Auslieferung. Das ist Absicht: ein Browser mit offenem Tab holt +sonst ein Bruchstück, das es nicht mehr gibt. Gelegentlich aufräumen schadet +trotzdem nicht. + +## Dienste auf dem Server + +| Was | Befehl | +| --- | --- | +| Zustand | `systemctl status icon-crm-backend` | +| Neustart | `systemctl restart icon-crm-backend` | +| Log | `journalctl -u icon-crm-backend -n 100 --no-pager` | +| Gesundheit | `curl -s http://127.0.0.1:5007/api/health` → `{"ok":true,"db":"up"}` | +| Apache | `systemctl reload apache2`, Logs unter `/var/log/apache2/icon_crm_*.log` | + +Die systemd-Unit begrenzt den Speicher auf 1 GB (`MemoryMax=1024M`). Das ist +kein Sparzwang, sondern ein Sicherheitsnetz: ein Leck im Backend soll nicht +Apache und MariaDB mitreißen. + +## Umgebungsvariablen + +`server/.env`, chmod 600, gehört **nicht** ins Repository. Vorlage: +`server/.env.example`. + +| Variable | Bedeutung | +| --- | --- | +| `PORT`, `HOST` | `5007` auf `127.0.0.1` – von außen nur über Apache | +| `DB_HOST`, `DB_PORT`, `DB_USER`, `DB_PASSWORD`, `DB_NAME` | MariaDB, Datenbank `icon_CRM` | +| `JWT_SECRET`, `JWT_EXPIRES_IN` | Sitzungen, Vorgabe 7 Tage (`7d`) | +| `ADMIN_USERNAME`, `ADMIN_NAME`, `ADMIN_EMAIL`, `ADMIN_PASSWORD` | erster Administrator, **nur solange `icon_users` leer ist** | +| `CORS_ORIGINS` | erlaubte Herkunft für den lokalen Dev-Server | +| `UPLOAD_DIR` | Ablage der Lebensläufe | +| `OPENAI_API_KEY`, `OPENAI_MODEL` | KI; fehlt der Schlüssel, laufen die übrigen Teile weiter | +| `PERINCO_API_URL`, `PERINCO_API_TOKEN` | ICON liest perinco | +| `PERINCO_INBOUND_TOKEN` | perinco liest ICON | + +Ohne `ADMIN_PASSWORD` erzeugt das Backend eines und schreibt es **einmalig** ins +Log. Danach ist das Passwort nur noch über die Nutzerverwaltung bzw. *Mein +Konto* änderbar. + +## Erstaufsetzung (einmalig, bereits erledigt) + +```bash +apt-get install -y apache2 mariadb-server certbot python3-certbot-apache nodejs +a2enmod proxy proxy_http rewrite headers expires +mariadb -e "CREATE DATABASE icon_CRM CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" +mariadb -e "CREATE USER 'iconcrm'@'localhost' IDENTIFIED BY '…'; + GRANT ALL PRIVILEGES ON icon_CRM.* TO 'iconcrm'@'localhost';" +cd /var/www/icon_CRM/server && npm install --omit=dev +cp deploy/icon-crm-backend.service /etc/systemd/system/ +systemctl daemon-reload && systemctl enable --now icon-crm-backend +cp deploy/icrm.icon-experts.de.conf /etc/apache2/sites-available/ +a2ensite icrm.icon-experts.de && systemctl reload apache2 +certbot --apache -d icrm.icon-experts.de +``` + +Das Schema legt das Backend beim Start selbst an (`schema.sql` + +Migrationen in `db.js`). Die Firewall (`ufw`) lässt 22, 80 und 443 durch; das +Zertifikat erneuert certbot selbst. + +## Sicherung + +Der Bestand steht vollständig in MariaDB; hochgeladene Lebensläufe liegen unter +`/var/www/icon_CRM/uploads`. Beides gehört in die Sicherung: + +```bash +mariadb-dump --single-transaction icon_CRM | gzip > icon_CRM-$(date +%F).sql.gz +tar czf uploads-$(date +%F).tar.gz -C /var/www/icon_CRM uploads +``` + +## Der alte Name crm.icon-experts.de + +Das erste ICON CRM lief unter **crm.icon-experts.de** auf `212.227.135.41` +(`/var/www/ICON`, Supabase als Datenbank). Seit dem 14.09.2026 zeigt der Name +auf diesen Server und wird vom Vhost `icrm.icon-experts.de` als `ServerAlias` +mitbedient – dasselbe Zertifikat deckt beide Namen ab. Verschickte Links und +Lesezeichen laufen damit weiter, es gibt keinen zweiten Stand zu pflegen. + +Der alte Aufbau ist zurückgebaut: Vhosts (`icon`, `icon-le-ssl`) abgeschaltet, +`/var/www/ICON` gelöscht, das dortige Zertifikat für `crm.icon-experts.de` +entfernt (sonst hätte die Erneuerung ab sofort täglich Fehler gemeldet). + +Gesichert ist er als `icon-crm-alt-2026-09-14.zip` – Quellcode, Build, `.env` +und beide Vhost-Dateien, ohne `node_modules`. Die Datei liegt **auf beiden +Servern** unter `/root/backups/`, Prüfsumme +`252f7fa3226c2df7c4020a0234c3095c`. + +> Die Supabase-Instanz `icondb.perinco.de` läuft weiter. Sie war die Datenbank +> des alten CRM und ist die Quelle der Migration (`server/src/migrate.js`) – +> abzuschalten wäre eine eigene Entscheidung, nicht Teil dieses Rückbaus. + +## Störungssuche + +| Symptom | Erster Griff | +| --- | --- | +| Weiße Seite nach dem Rollout | `index.html` darf nicht aus dem Cache kommen – der Vhost setzt dafür `no-cache`; im Zweifel Strg+F5 | +| 502 auf `/api` | Backend aus: `systemctl status icon-crm-backend`, dann `journalctl -u …` | +| `{"db":"down"}` | MariaDB aus oder Zugangsdaten falsch | +| Anmeldung schlägt fehl | `icon_users` prüfen: existiert der Benutzer, ist `is_active = 1`? | +| Zertifikatswarnung | `certbot certificates`; deckt das Zertifikat **alle** Namen ab, auch `www`? | +| Website zeigt alte Ausschreibungen | `curl -s https://icon-experts.de/api/public/tenders | head` – kommt das durch den Proxy? | +| perinco-Kontakte fehlen | `GET /api/perinco/status`; Token beidseitig gesetzt? | +| Diktat lädt nicht | siehe [04 Diktat](04-diktat.md) – das ist eine Sache des Browsers, nicht des Servers | + +### Wenn Daten zu fehlen scheinen + +Erst messen, dann handeln. Die drei Griffe, die in der Praxis geholfen haben: + +```bash +mysql icon_CRM -e "SELECT COUNT(*) FROM icon_contacts" # steht es in der Datenbank? +curl -s .../api/contacts -H "Authorization: Bearer …" | wc -c # liefert die API es aus? +tail -n 50 /var/log/apache2/icon_crm_access.log # kam es beim Browser an? +``` + +Stimmen alle drei, liegt es an der Ansicht – meist an einem aktiven Filter. + +### DNS-Umstellungen + +Nach einem Wechsel des A-Records antworten fremde Resolver unterschiedlich +lange mit der alten Adresse. Zum Prüfen die **autoritativen** Nameserver und +mindestens einen öffentlichen Resolver befragen, nicht nur den eigenen: + +```bash +dig +short icon-experts.de @1.1.1.1 +dig +short icon-experts.de @8.8.8.8 +``` + +Ein lokaler Resolver (PiHole, Router) kann den alten Wert deutlich länger +halten – das sieht dann aus wie ein Serverfehler und ist keiner. diff --git a/docs/08-anleitung-benutzer.md b/docs/08-anleitung-benutzer.md index 08763fb..ec34b29 100644 --- a/docs/08-anleitung-benutzer.md +++ b/docs/08-anleitung-benutzer.md @@ -1,224 +1,224 @@ -# 08 Anleitung für Benutzer - -Diese Anleitung setzt nichts voraus. Adresse: . - -## Anmelden - -Benutzername oder E-Mail und Passwort. Die Anmeldung gilt 12 Stunden; danach -fragt das CRM erneut. Passwort ändern: *Einstellungen → Mein Konto*. - -Rechts oben stehen zwei Umschalter: die **Farbwelt** (ICON, Half-Life, Pixel -Art) und **hell/dunkel**. Beides hängt am Konto und gilt auf jedem Gerät. Ein -Doppelklick auf den Hell/Dunkel-Knopf stellt „wie das System" wieder her. - -## Suchen - -Oben in der Kopfleiste, oder mit `Strg+K`. Rechts im Feld zwei Symbole: - -* **Lupe** – die normale Suche. Läuft beim Tippen mit, durchsucht Kontakte, - Unternehmen, Ausschreibungen, Projekte, Leads und die Historie. Ein Klick auf - einen Treffer öffnet dessen Karte. -* **Funke** – die erweiterte Suche. Hier steht eine ganze Frage: - *„Wer arbeitet alles bei der KfW?"*, *„Wer hat den höchsten Satz?"*. Antwort - auf Enter, nicht beim Tippen. Die genannten Datensätze stehen darunter als - anklickbare Schaltflächen. - -## Dashboard - -Zahlen zum Bestand und die letzten Tätigkeiten. Von hier führt jeder Eintrag in -die zugehörige Karte. - -## Leads - -Die Pipeline als Kanban über acht Spalten, von **Backlog** bis **Absage**. -Kacheln lassen sich mit der Maus in eine andere Spalte ziehen; der Wechsel landet -in der Historie. - -Auf jeder Kachel: - -| Zeichen | Bedeutung | -| --- | --- | -| Aktenschrank | archivieren – landet unter *Leads → Archiviert* und lässt sich von dort zurückholen | -| roter Mülleimer | löschen, mit Rückfrage | -| Balken mit Prozentzahl | Stand der ToDo-Liste | - -Die Karte eines Leads hat drei Reiter: - -1. **Stammdaten** – Titel, Unternehmen, Ansprechpartner, Kontaktstand, - Zuständiger, Notizen und die Erinnerung. -2. **ToDos** – Checkliste mit Fälligkeitsdatum. Der Fortschritt steht als - Prozentwert auf der Kachel. -3. **Historie** – wer wann was geändert hat. - -### Erinnerungen - -Zwei Arten, beide als Pop-up, das sich unabhängig von der geöffneten Ansicht -meldet: - -* **gesetzt** – „in 2 Tagen anrufen". Eingegeben werden **Tage**, nicht ein - Datum; gerechnet wird auf dem Server. Quittieren heißt *Erledigt* oder *in n - Tagen erneut*. -* **automatisch** – wird ein Lead 30 Tage nicht angefasst, fragt das CRM, ob er - archiviert werden soll. Bleibt er stehen, läuft die Frist von vorn. - -## Kontakte - -Drei Ansichten, oben rechts umschaltbar: **Kacheln**, **Liste**, **Netzwerk**. - -* In der **Liste** sortiert ein Klick auf eine Spaltenüberschrift; ein zweiter - Klick dreht die Richtung um. Leere Felder stehen immer am Ende. -* Der Schalter **perinco** blendet zusätzlich die Kontakte des perinco CRM ein – - grün umrandet, nur zum Lesen. -* Filter (Art, Status, Unternehmen …) sind sichtbar ausgezeichnet. Steht die - Liste leer da, sagt der Text welche Filter aktiv sind; daneben liegt *Filter - zurücksetzen*. - -Die Karte eines Kontakts hat Stammdaten, Werdegang, Gespräche und Historie. - -Im Reiter **Werdegang & Interviews** lässt sich jede Station bearbeiten: -*Station hinzufügen*, der Stift ändert eine bestehende, der Mülleimer entfernt -sie. Das Unternehmen ist ein Textfeld mit Vorschlagsliste – ein Name, den es -noch nicht gibt, legt das Unternehmen an (wie im CV-Import). - -Der Haken **aktuelle Station** hängt mit dem Arbeitgeber im Reiter Stammdaten -zusammen: es kann immer nur eine Station laufen. Wird eine dazu erklärt, bekommt -die bisherige ein Enddatum, und der Arbeitgeber des Kontakts wechselt mit. Wird -die laufende Station gelöscht, hat der Kontakt danach keinen Arbeitgeber mehr – -die Rückfrage sagt das. - -| Feld | Hinweis | -| --- | --- | -| Art | Experte, Ansprechpartner, Lead – die Liste wird in den Stammdaten gepflegt | -| Status | Interessiert, Interview, Gebucht, Inaktiv | -| Stundensatz vor Ort / remote | in **Euro je Stunde**; leer heißt „nicht hinterlegt", nicht „null Euro" | -| Foto | hochladen oder mit *Von LinkedIn* aus dem öffentlichen Profil holen | -| CV | Link oder die hochgeladene Datei aus dem CV-Import | - -### CV-Import - -*Kontakte → CV-Import.* Lebensläufe hineinziehen (PDF, DOCX, Text; bis 20 auf -einmal). Danach drei Ordner: - -1. **Eingang** – je Datei entscheiden: *Ja, auslesen* oder *Nein*. -2. **Screening** – der Vorschlag der KI steht im Formular. Alles ist änderbar; - was hier korrigiert wird, gewinnt. -3. **Auswahl** – bestätigt, Kontakt angelegt, Lebenslauf hängt daran. - -Trägt der Lebenslauf ein **Bewerbungsfoto**, steht es oben im Formular, mit -gesetztem Haken. Es wird auf 400 px verkleinert (höchstens 512 kB) und beim -Übernehmen als Foto des Kontakts gespeichert. Hat der Automat statt des -Portraits das Firmenlogo erwischt, Haken entfernen – ein anderes Bild lässt sich -jederzeit in der Kontaktkarte setzen. - -Es wird **nichts automatisch angelegt**. Frühere Stationen werden als beendete -Anstellungen übernommen – der Kontakt erscheint dadurch beim alten Arbeitgeber -sofort unter „Ehemalige". - -## Unternehmen - -Ebenfalls Kacheln, Liste und Netzwerk, dazu der Reiter **Group** (Konzern). -Ein Unternehmen mit gesetztem Haken *Kunde* erscheint zusätzlich in der -Kundenansicht; der Zustand *aktiv* oder *alumni* unterscheidet laufende von -früheren Kunden. - -Fünf Sterne bewerten das Unternehmen, `Freelancer` sagt, ob dort überhaupt -freiberuflich gearbeitet wird. - -### Netzwerkansicht und Stammbaum - -Rechtecke sind Unternehmen, Kreise sind Kontakte. Ziehen verschiebt, das Mausrad -zoomt. Ein Klick auf ein Unternehmen mit Kontakten öffnet dessen **Stammbaum**: - -| Ast | Bedeutung | -| --- | --- | -| golden | Berater (Art „Experte") | -| hell | Ansprechpartner und übrige | -| grau, gestrichelt | Ehemalige – darunter steht, wo die Person heute ist | - -In den Kontakten lassen sich zusätzlich Verbindungen anlegen: *Verbindung -erstellen*, dann zwei Kontakte anklicken; Esc bricht ab. - -## Ausschreibungen - -Eine Tabelle; ein Klick klappt die Zeile auf. Links die zugewiesenen Experten -und der Kundenkontakt, rechts der **Ausschreibungstext**. - -> **Der Schalter „Online" entscheidet, ob die Ausschreibung auf der ICON Website -> in der Projektbörse steht.** Er sitzt deshalb direkt in der Liste. - -Der Text wird automatisch gespeichert, 800 ms nach dem letzten Tastendruck – es -gibt keinen Speichern-Knopf. Über dem Feld liegen die Knöpfe der Schreibhilfe -(*Text erzeugen*, *Verbessern*, *Kürzen*, *Stichpunkte*, *Anforderungsprofil*) -und der Diktat-Knopf. *Zurück* holt den Stand vor dem letzten KI-Eingriff. - -Zwei Untermenüs: - -* **Live!** – dieselben Ausschreibungen als Kanban über die Phasen, mit Ziehen - bearbeitbar. -* **Archiv** – archivierte Ausschreibungen; von dort zurückholbar. - -## Laufende Projekte - -Titel, Kunde, Experte, Zeitraum, Status, Tagessatz und Notizen. Die Notizen -sind ein WYSIWYG-Feld – im übernommenen Bestand steht dort ein Kopfblock aus dem -alten CRM und darunter der Ausschreibungstext. - -### Marge - -Jede Projektkachel weist die Marge aus, und in der Projektkarte steht die -Rechnung aufgeschlüsselt: - -| Zeile | Woher | -| --- | --- | -| Tagessatz Kunde | Feld **Tagessatz** am Projekt | -| Stundensatz Berater | Kontaktkarte des Experten | -| Kosten (8 h) | Stundensatz × 8 | -| Marge | Tagessatz − Kosten, dazu der Prozentsatz vom Tagessatz | - -**Ein Tag sind 8 Stunden.** Hat der Berater einen Remote- *und* einen -Vor-Ort-Satz, wird mit dem **Remote-Satz** gerechnet; fehlt der, mit dem -Vor-Ort-Satz. Welcher benutzt wurde, steht daneben. - -Die Farbe ist eine schnelle Ampel: grün ab 25 %, gelb darüber bis 0, rot bei -negativer Marge. - -Fehlt der Tagessatz oder hat der Berater keinen Stundensatz, steht statt einer -Zahl der Grund dort – eine leere Zeile ließe offen, ob die Marge null oder -unbekannt ist. Der Tagessatz wird am Projekt gepflegt, die Stundensätze beim -Kontakt (*Kontakt → Stundensatz vor Ort / remote*). - -## Historie - -Alles, was im CRM getan wurde, mit Zeitpunkt, Person und – bei Änderungen – den -betroffenen Feldern samt Vorher/Nachher. Filterbar nach Art des Datensatzes. - -**@-Erwähnungen:** In jedem Notizfeld öffnet `@` eine Vorschlagsliste über -Kontakte, Unternehmen und Benutzer. Im Lesezustand ist jede Erwähnung anklickbar -und führt auf das Profil; im Bearbeiten-Zustand steht dort die rohe Form -`@[Name](contact:12)` – anders ließe sie sich nicht ändern oder löschen. - -## Diktat - -*Einstellungen → Diktat*, einmal je Gerät: **Sprachmodell laden** (rund 300 MB, -bleibt danach im Browser). Anschließend steht in jedem längeren Textfeld ein -Mikrofon-Knopf. - -Aufnahme starten, sprechen, Knopf erneut drücken – der erkannte Text wird -**angehängt**, nie ersetzt. Das Kreuz daneben verwirft die Aufnahme. - -Die Erkennung läuft auf dem eigenen Rechner. Es wird nichts hochgeladen und -nichts gespeichert. Details: [04 Diktat](04-diktat.md). - -## Einstellungen - -| Reiter | Wer | Inhalt | -| --- | --- | --- | -| Nutzerverwaltung | Administratoren | Benutzer anlegen, Rolle, aktiv/inaktiv | -| Stammdaten | Administratoren | Auswahllisten: Branchen, Arten der Kontakte | -| KI | Administratoren | OpenAI-Schlüssel, Modell (Auswahlliste der für den Schlüssel verfügbaren Modelle), Verbindungstest | -| Mein Konto | alle | Foto, Farbe, Passwort, Design | -| Diktat | alle | Spracherkennung einrichten | - -Beim Pflegen der Stammdaten gelten zwei Regeln: **Umbenennen zieht den Bestand -mit**, und **Entfernen geht nur, solange ein Wert nirgends benutzt wird** – -hinter jedem Eintrag steht, wie oft er vorkommt. +# 08 Anleitung für Benutzer + +Diese Anleitung setzt nichts voraus. Adresse: . + +## Anmelden + +Benutzername oder E-Mail und Passwort. Die Anmeldung gilt 7 Tage; danach +fragt das CRM erneut. Passwort ändern: *Einstellungen → Mein Konto*. + +Rechts oben stehen zwei Umschalter: die **Farbwelt** (ICON, Half-Life, Pixel +Art) und **hell/dunkel**. Beides hängt am Konto und gilt auf jedem Gerät. Ein +Doppelklick auf den Hell/Dunkel-Knopf stellt „wie das System" wieder her. + +## Suchen + +Oben in der Kopfleiste, oder mit `Strg+K`. Rechts im Feld zwei Symbole: + +* **Lupe** – die normale Suche. Läuft beim Tippen mit, durchsucht Kontakte, + Unternehmen, Ausschreibungen, Projekte, Leads und die Historie. Ein Klick auf + einen Treffer öffnet dessen Karte. +* **Funke** – die erweiterte Suche. Hier steht eine ganze Frage: + *„Wer arbeitet alles bei der KfW?"*, *„Wer hat den höchsten Satz?"*. Antwort + auf Enter, nicht beim Tippen. Die genannten Datensätze stehen darunter als + anklickbare Schaltflächen. + +## Dashboard + +Zahlen zum Bestand und die letzten Tätigkeiten. Von hier führt jeder Eintrag in +die zugehörige Karte. + +## Leads + +Die Pipeline als Kanban über acht Spalten, von **Backlog** bis **Absage**. +Kacheln lassen sich mit der Maus in eine andere Spalte ziehen; der Wechsel landet +in der Historie. + +Auf jeder Kachel: + +| Zeichen | Bedeutung | +| --- | --- | +| Aktenschrank | archivieren – landet unter *Leads → Archiviert* und lässt sich von dort zurückholen | +| roter Mülleimer | löschen, mit Rückfrage | +| Balken mit Prozentzahl | Stand der ToDo-Liste | + +Die Karte eines Leads hat drei Reiter: + +1. **Stammdaten** – Titel, Unternehmen, Ansprechpartner, Kontaktstand, + Zuständiger, Notizen und die Erinnerung. +2. **ToDos** – Checkliste mit Fälligkeitsdatum. Der Fortschritt steht als + Prozentwert auf der Kachel. +3. **Historie** – wer wann was geändert hat. + +### Erinnerungen + +Zwei Arten, beide als Pop-up, das sich unabhängig von der geöffneten Ansicht +meldet: + +* **gesetzt** – „in 2 Tagen anrufen". Eingegeben werden **Tage**, nicht ein + Datum; gerechnet wird auf dem Server. Quittieren heißt *Erledigt* oder *in n + Tagen erneut*. +* **automatisch** – wird ein Lead 30 Tage nicht angefasst, fragt das CRM, ob er + archiviert werden soll. Bleibt er stehen, läuft die Frist von vorn. + +## Kontakte + +Drei Ansichten, oben rechts umschaltbar: **Kacheln**, **Liste**, **Netzwerk**. + +* In der **Liste** sortiert ein Klick auf eine Spaltenüberschrift; ein zweiter + Klick dreht die Richtung um. Leere Felder stehen immer am Ende. +* Der Schalter **perinco** blendet zusätzlich die Kontakte des perinco CRM ein – + grün umrandet, nur zum Lesen. +* Filter (Art, Status, Unternehmen …) sind sichtbar ausgezeichnet. Steht die + Liste leer da, sagt der Text welche Filter aktiv sind; daneben liegt *Filter + zurücksetzen*. + +Die Karte eines Kontakts hat Stammdaten, Werdegang, Gespräche und Historie. + +Im Reiter **Werdegang & Interviews** lässt sich jede Station bearbeiten: +*Station hinzufügen*, der Stift ändert eine bestehende, der Mülleimer entfernt +sie. Das Unternehmen ist ein Textfeld mit Vorschlagsliste – ein Name, den es +noch nicht gibt, legt das Unternehmen an (wie im CV-Import). + +Der Haken **aktuelle Station** hängt mit dem Arbeitgeber im Reiter Stammdaten +zusammen: es kann immer nur eine Station laufen. Wird eine dazu erklärt, bekommt +die bisherige ein Enddatum, und der Arbeitgeber des Kontakts wechselt mit. Wird +die laufende Station gelöscht, hat der Kontakt danach keinen Arbeitgeber mehr – +die Rückfrage sagt das. + +| Feld | Hinweis | +| --- | --- | +| Art | Experte, Ansprechpartner, Lead – die Liste wird in den Stammdaten gepflegt | +| Status | Interessiert, Interview, Gebucht, Inaktiv | +| Stundensatz vor Ort / remote | in **Euro je Stunde**; leer heißt „nicht hinterlegt", nicht „null Euro" | +| Foto | hochladen oder mit *Von LinkedIn* aus dem öffentlichen Profil holen | +| CV | Link oder die hochgeladene Datei aus dem CV-Import | + +### CV-Import + +*Kontakte → CV-Import.* Lebensläufe hineinziehen (PDF, DOCX, Text; bis 20 auf +einmal). Danach drei Ordner: + +1. **Eingang** – je Datei entscheiden: *Ja, auslesen* oder *Nein*. +2. **Screening** – der Vorschlag der KI steht im Formular. Alles ist änderbar; + was hier korrigiert wird, gewinnt. +3. **Auswahl** – bestätigt, Kontakt angelegt, Lebenslauf hängt daran. + +Trägt der Lebenslauf ein **Bewerbungsfoto**, steht es oben im Formular, mit +gesetztem Haken. Es wird auf 400 px verkleinert (höchstens 512 kB) und beim +Übernehmen als Foto des Kontakts gespeichert. Hat der Automat statt des +Portraits das Firmenlogo erwischt, Haken entfernen – ein anderes Bild lässt sich +jederzeit in der Kontaktkarte setzen. + +Es wird **nichts automatisch angelegt**. Frühere Stationen werden als beendete +Anstellungen übernommen – der Kontakt erscheint dadurch beim alten Arbeitgeber +sofort unter „Ehemalige". + +## Unternehmen + +Ebenfalls Kacheln, Liste und Netzwerk, dazu der Reiter **Group** (Konzern). +Ein Unternehmen mit gesetztem Haken *Kunde* erscheint zusätzlich in der +Kundenansicht; der Zustand *aktiv* oder *alumni* unterscheidet laufende von +früheren Kunden. + +Fünf Sterne bewerten das Unternehmen, `Freelancer` sagt, ob dort überhaupt +freiberuflich gearbeitet wird. + +### Netzwerkansicht und Stammbaum + +Rechtecke sind Unternehmen, Kreise sind Kontakte. Ziehen verschiebt, das Mausrad +zoomt. Ein Klick auf ein Unternehmen mit Kontakten öffnet dessen **Stammbaum**: + +| Ast | Bedeutung | +| --- | --- | +| golden | Berater (Art „Experte") | +| hell | Ansprechpartner und übrige | +| grau, gestrichelt | Ehemalige – darunter steht, wo die Person heute ist | + +In den Kontakten lassen sich zusätzlich Verbindungen anlegen: *Verbindung +erstellen*, dann zwei Kontakte anklicken; Esc bricht ab. + +## Ausschreibungen + +Eine Tabelle; ein Klick klappt die Zeile auf. Links die zugewiesenen Experten +und der Kundenkontakt, rechts der **Ausschreibungstext**. + +> **Der Schalter „Online" entscheidet, ob die Ausschreibung auf der ICON Website +> in der Projektbörse steht.** Er sitzt deshalb direkt in der Liste. + +Der Text wird automatisch gespeichert, 800 ms nach dem letzten Tastendruck – es +gibt keinen Speichern-Knopf. Über dem Feld liegen die Knöpfe der Schreibhilfe +(*Text erzeugen*, *Verbessern*, *Kürzen*, *Stichpunkte*, *Anforderungsprofil*) +und der Diktat-Knopf. *Zurück* holt den Stand vor dem letzten KI-Eingriff. + +Zwei Untermenüs: + +* **Live!** – dieselben Ausschreibungen als Kanban über die Phasen, mit Ziehen + bearbeitbar. +* **Archiv** – archivierte Ausschreibungen; von dort zurückholbar. + +## Laufende Projekte + +Titel, Kunde, Experte, Zeitraum, Status, Tagessatz und Notizen. Die Notizen +sind ein WYSIWYG-Feld – im übernommenen Bestand steht dort ein Kopfblock aus dem +alten CRM und darunter der Ausschreibungstext. + +### Marge + +Jede Projektkachel weist die Marge aus, und in der Projektkarte steht die +Rechnung aufgeschlüsselt: + +| Zeile | Woher | +| --- | --- | +| Tagessatz Kunde | Feld **Tagessatz** am Projekt | +| Stundensatz Berater | Kontaktkarte des Experten | +| Kosten (8 h) | Stundensatz × 8 | +| Marge | Tagessatz − Kosten, dazu der Prozentsatz vom Tagessatz | + +**Ein Tag sind 8 Stunden.** Hat der Berater einen Remote- *und* einen +Vor-Ort-Satz, wird mit dem **Remote-Satz** gerechnet; fehlt der, mit dem +Vor-Ort-Satz. Welcher benutzt wurde, steht daneben. + +Die Farbe ist eine schnelle Ampel: grün ab 25 %, gelb darüber bis 0, rot bei +negativer Marge. + +Fehlt der Tagessatz oder hat der Berater keinen Stundensatz, steht statt einer +Zahl der Grund dort – eine leere Zeile ließe offen, ob die Marge null oder +unbekannt ist. Der Tagessatz wird am Projekt gepflegt, die Stundensätze beim +Kontakt (*Kontakt → Stundensatz vor Ort / remote*). + +## Historie + +Alles, was im CRM getan wurde, mit Zeitpunkt, Person und – bei Änderungen – den +betroffenen Feldern samt Vorher/Nachher. Filterbar nach Art des Datensatzes. + +**@-Erwähnungen:** In jedem Notizfeld öffnet `@` eine Vorschlagsliste über +Kontakte, Unternehmen und Benutzer. Im Lesezustand ist jede Erwähnung anklickbar +und führt auf das Profil; im Bearbeiten-Zustand steht dort die rohe Form +`@[Name](contact:12)` – anders ließe sie sich nicht ändern oder löschen. + +## Diktat + +*Einstellungen → Diktat*, einmal je Gerät: **Sprachmodell laden** (rund 300 MB, +bleibt danach im Browser). Anschließend steht in jedem längeren Textfeld ein +Mikrofon-Knopf. + +Aufnahme starten, sprechen, Knopf erneut drücken – der erkannte Text wird +**angehängt**, nie ersetzt. Das Kreuz daneben verwirft die Aufnahme. + +Die Erkennung läuft auf dem eigenen Rechner. Es wird nichts hochgeladen und +nichts gespeichert. Details: [04 Diktat](04-diktat.md). + +## Einstellungen + +| Reiter | Wer | Inhalt | +| --- | --- | --- | +| Nutzerverwaltung | Administratoren | Benutzer anlegen, Rolle, aktiv/inaktiv | +| Stammdaten | Administratoren | Auswahllisten: Branchen, Arten der Kontakte | +| KI | Administratoren | OpenAI-Schlüssel, Modell (Auswahlliste der für den Schlüssel verfügbaren Modelle), Verbindungstest | +| Mein Konto | alle | Foto, Farbe, Passwort, Design | +| Diktat | alle | Spracherkennung einrichten | + +Beim Pflegen der Stammdaten gelten zwei Regeln: **Umbenennen zieht den Bestand +mit**, und **Entfernen geht nur, solange ein Wert nirgends benutzt wird** – +hinter jedem Eintrag steht, wie oft er vorkommt. diff --git a/docs/11-glossar.md b/docs/11-glossar.md index 7866a7a..86774de 100644 --- a/docs/11-glossar.md +++ b/docs/11-glossar.md @@ -60,7 +60,7 @@ angemeldeten Benutzer vorbelegt. **Diktat / Whisper** – Spracherkennung, die vollständig im Browser läuft. Siehe [04 Diktat](04-diktat.md). -**JWT** – das Token der Anmeldung, 12 Stunden gültig, im `localStorage`. Wird +**JWT** – das Token der Anmeldung, 7 Tage gültig, im `localStorage`. Wird bei jeder Anfrage zusätzlich gegen die Datenbank geprüft. **MentionField** – das Textfeld mit zwei Zuständen (lesen / bearbeiten) für diff --git a/server/.env.example b/server/.env.example index 4c54264..13cf4db 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,54 +1,54 @@ -# ICON CRM Backend -# Als .env kopieren und Werte setzen. .env gehoert nicht ins Repository. - -PORT=5007 -# Nur lokal binden - von aussen erreichbar ist die Anwendung ausschliesslich -# ueber den Apache-Reverse-Proxy auf icrm.icon-experts.de. -HOST=127.0.0.1 - -DB_HOST=localhost -DB_PORT=3306 -DB_USER=iconcrm -DB_PASSWORD=changeme -DB_NAME=icon_CRM - -# Signiert die JWT-Sitzungen. Langer Zufallswert, z. B.: -# node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))" -JWT_SECRET=changeme-langer-zufallswert -JWT_EXPIRES_IN=12h - -# Wird beim ersten Start angelegt, solange noch kein Benutzer existiert. -# Ohne ADMIN_PASSWORD erzeugt das Backend eines und schreibt es einmalig ins Log. -ADMIN_USERNAME=Admin -ADMIN_NAME=Administrator -ADMIN_EMAIL=admin@icon-experts.de -ADMIN_PASSWORD= - -# Erlaubte Herkunft fuer den lokalen Vite-Dev-Server. -CORS_ORIGINS=http://localhost:3101,http://127.0.0.1:3101 - -# Ablage der hochgeladenen Lebenslaeufe. Leer = /../uploads. -UPLOAD_DIR=/var/www/icon_CRM/uploads - -# --------------------------------------------------------------------------- -# KI (CV-Auslesung und Schreibhilfe fuer Ausschreibungen) -# --------------------------------------------------------------------------- -# Der Schluessel bleibt auf dem Server - der Browser spricht nie direkt mit -# OpenAI. Fehlt er, laeuft das CRM normal weiter und blendet die KI-Knoepfe aus. -# -# Hinweis zum Modell: gpt-5.6-luna akzeptiert KEIN eigenes "temperature"; -# ai.js schickt deshalb nur den Standardwert. -OPENAI_API_KEY= -OPENAI_MODEL=gpt-5.6-luna - -# --------------------------------------------------------------------------- -# Lesender Zugriff auf die Kontakte des perinco CRM -# --------------------------------------------------------------------------- -# Das ICON CRM zeigt die perinco-Kontakte zusaetzlich an - gekennzeichnet und -# ausschliesslich lesend. Gespeichert wird nichts. -# -# Der Token steht drueben in server/.env als ICON_API_TOKEN und muss -# uebereinstimmen. Fehlt einer der beiden Werte, bleibt der Schalter in der -# Oberflaeche aus und das CRM laeuft normal weiter. -PERINCO_API_URL=https://pcrm.perinco.de -PERINCO_API_TOKEN= +# ICON CRM Backend +# Als .env kopieren und Werte setzen. .env gehoert nicht ins Repository. + +PORT=5007 +# Nur lokal binden - von aussen erreichbar ist die Anwendung ausschliesslich +# ueber den Apache-Reverse-Proxy auf icrm.icon-experts.de. +HOST=127.0.0.1 + +DB_HOST=localhost +DB_PORT=3306 +DB_USER=iconcrm +DB_PASSWORD=changeme +DB_NAME=icon_CRM + +# Signiert die JWT-Sitzungen. Langer Zufallswert, z. B.: +# node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))" +JWT_SECRET=changeme-langer-zufallswert +JWT_EXPIRES_IN=7d + +# Wird beim ersten Start angelegt, solange noch kein Benutzer existiert. +# Ohne ADMIN_PASSWORD erzeugt das Backend eines und schreibt es einmalig ins Log. +ADMIN_USERNAME=Admin +ADMIN_NAME=Administrator +ADMIN_EMAIL=admin@icon-experts.de +ADMIN_PASSWORD= + +# Erlaubte Herkunft fuer den lokalen Vite-Dev-Server. +CORS_ORIGINS=http://localhost:3101,http://127.0.0.1:3101 + +# Ablage der hochgeladenen Lebenslaeufe. Leer = /../uploads. +UPLOAD_DIR=/var/www/icon_CRM/uploads + +# --------------------------------------------------------------------------- +# KI (CV-Auslesung und Schreibhilfe fuer Ausschreibungen) +# --------------------------------------------------------------------------- +# Der Schluessel bleibt auf dem Server - der Browser spricht nie direkt mit +# OpenAI. Fehlt er, laeuft das CRM normal weiter und blendet die KI-Knoepfe aus. +# +# Hinweis zum Modell: gpt-5.6-luna akzeptiert KEIN eigenes "temperature"; +# ai.js schickt deshalb nur den Standardwert. +OPENAI_API_KEY= +OPENAI_MODEL=gpt-5.6-luna + +# --------------------------------------------------------------------------- +# Lesender Zugriff auf die Kontakte des perinco CRM +# --------------------------------------------------------------------------- +# Das ICON CRM zeigt die perinco-Kontakte zusaetzlich an - gekennzeichnet und +# ausschliesslich lesend. Gespeichert wird nichts. +# +# Der Token steht drueben in server/.env als ICON_API_TOKEN und muss +# uebereinstimmen. Fehlt einer der beiden Werte, bleibt der Schalter in der +# Oberflaeche aus und das CRM laeuft normal weiter. +PERINCO_API_URL=https://pcrm.perinco.de +PERINCO_API_TOKEN= diff --git a/server/src/middleware/auth.js b/server/src/middleware/auth.js index 97e24f4..faecfa0 100644 --- a/server/src/middleware/auth.js +++ b/server/src/middleware/auth.js @@ -11,7 +11,7 @@ function secret() { export function signToken(user) { return jwt.sign({ sub: user.id, role: user.role }, secret(), { - expiresIn: process.env.JWT_EXPIRES_IN || '12h', + expiresIn: process.env.JWT_EXPIRES_IN || '7d', }); }