Beides lag bisher nur als nicht eingecheckte Arbeit im Arbeitsverzeichnis,
waehrend auf Gitea nur die Doku dazu stand (docs/12-suche.md). Damit ist
der Code jetzt versioniert.
Globale Suche:
- server/src/routes/search.js: Volltext ueber Kontakte, Opportunities und
Akquise, dazu die erweiterte Suche mit dem Modell des Scanners
- src/components/GlobalSearch.tsx im Kopf der Anwendung; ein Treffer
springt in die Ansicht und oeffnet den Datensatz (navigation.tsx)
ICON CRM:
- server/src/routes/integrations.js liefert die Kontakte mit gesetztem
Haekchen hinaus, server/src/routes/iconCrm.js holt die Kontakte von
drueben herein - beide nur lesend, mit festem Token statt JWT
- server/src/contactBridge.js und src/lib/contactMatch.ts erkennen
Dubletten, src/components/leads/IconMergeDialog.tsx laesst sie
zusammenfuehren
- server/src/ai.js buendelt die OpenAI-Aufrufe
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Kopier-Symbol auf Kachel, Pipeline-Karte und Listenzeile sowie "Kopieren"
im Dialog. Ein Dialog fragt nach einem oder mehreren Beratern (aus der
Beraterliste der Akquise, ohne den eigenen) und der Phase der Kopie
(Standard: Neu). Neuer Endpoint POST /api/opportunities/:id/copy legt je
Berater eine Kopie mit Projektdaten, Ansprechpartnern, Zustaendigem und
acquisition_id an; Interaktion und naechster Schritt bleiben beim Original.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bei der Uebernahme aus der Akquise verschwand die wichtigste Angabe nach
dem Titel: fuer wen das Projekt laeuft. Die Opportunity hatte kein Feld
dafuer, also schrieb die Uebernahme den Namen in den Herkunftsvermerk der
Notizen - in Pipeline, Liste und Kacheln war er damit nicht zu sehen, und
suchen liess sich nur im Notiztext.
crm_opportunities.consultant traegt ihn jetzt selbst. Genau ein Name je
Opportunity: passt ein Fund zu drei Beratern, entstehen bei der
Uebernahme drei Opportunities. Sichtbar in Pipeline-Karte, Kachel,
Papierkorb und als sortierbare Spalte direkt hinter dem Titel; die Suche
fasst ihn mit ein, und im Dialog kommt die Auswahl aus derselben Liste
wie in der Akquise (Einstellungen -> Akquise), damit nicht "Nico" und
"Nico N." nebeneinander entstehen.
Bestehende Opportunities holt migrateOpportunityConsultant() beim Start
aus ihren Notizen nach - nur dort, wo die Spalte leer ist. Damit ist der
Lauf wiederholbar und ueberschreibt nichts, was seither von Hand gesetzt
wurde; ein Merker in crm_settings ist dafuer nicht noetig. Der Text in
den Notizen bleibt stehen: er ist der Herkunftsnachweis samt Anbieter,
Kanal und Link.
Der Namenszusatz im Titel entfaellt wieder. Er war gestern dazugekommen,
damit sich mehrere Opportunities aus einem Fund unterscheiden liessen -
mit einer eigenen Spalte ist er nur noch Rauschen.
Nebenbei: der Block, der won_at nachzieht, stand zweimal in schema.sql.
Idempotent und damit harmlos, aber eine Kopie - jetzt einmal.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
So wird bei perinco nicht gerechnet: die Summe "Gewichtet" aenderte sich beim
Schieben der Abschlusswahrscheinlichkeit und behauptete damit eine Groesse, die
es im Haus nicht gibt.
Weg sind die Kennzahl ueber der Liste, die Spalte samt Sortierung, die zweite
Zahl in der Vorschau des Formulars und weightedVolume() selbst. Die
Wahrscheinlichkeit bleibt an der Opportunity - Schieberegler im Formular, Prozent
in Liste und Pipeline-Karte; sie geht nur in keine Summe mehr ein.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Drei Dinge:
- Die Phasen sind jetzt die des Vermittlungsprozesses: Neu, Beworben,
Erstgespraech Berater, Kundenvorstellung, Interview Kunde, Zusage, Beauftragt,
Absage. Offen ist alles bis einschliesslich Zusage - dort ist der Einsatz
bestaetigt, die Beauftragung steht aber noch aus. migrateOpportunityStages()
stellt bestehende Datenbanken um und setzt alle vorhandenen Opportunities auf
"Neu": in welcher der neuen Phasen ein Projekt steckt, sagt der alte Trichter
nicht (so mit dem Nutzer entschieden).
Der Datenbankwert heisst 'Erstgespraech Berater' ohne Umlaut - Projektkonvention
fuer Datenbankwerte. Angezeigt wird er ueber STAGE_LABELS; dazu gibt es
STAGE_HINTS mit der Erlaeuterung je Phase als Titelhinweis an Auswahl, Badge
und Spaltenkopf.
- Kanban ist die erste Ansicht der Opportunities. Die Pipeline ist die
Arbeitsansicht, Kachel und Liste sind zum Nachschauen.
- Hinter "Archiv", beiden "Papierkorb"-Punkten und "Unpassend" steht die Anzahl,
wie schon bei "Aus Mails". Die Zahlen kommen aus einem eigenen Endpunkt
(/api/badges), weil sie alle ins Menue gehoeren und zusammen kommen sollen;
Inhalte laedt weiter jede Ansicht selbst. Der Scanner liegt in einer eigenen
Datenbank und kann fehlen - sein Zaehler ist dann null und die Leiste zeigt
dort einfach keine Zahl. Der Aufruf haengt bewusst hinter den Daten und nicht
im Promise.all: faellt er aus, bleibt das Menue ohne Zahlen statt die Ansicht
mit einem Fehler.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Das Loeschsymbol gab es in der Kachelansicht schon, es war aber bis zum
Ueberfahren durchsichtig - ein Knopf, den man nur findet, wenn man ihn kennt.
Jetzt ist er sichtbar, in der Liste ebenso, und die Pipeline-Karte hat ihn
ueberhaupt zum ersten Mal.
Geloescht wird nicht mehr endgueltig: DELETE setzt deleted_at, die Zeile bleibt.
An einer Opportunity haengen Ansprechpartner, Verlauf und Mailzuordnungen, und
ein Fehlklick soll das nicht kosten. Der Papierkorb ist ein Unterpunkt unter
Opportunities und zeigt, wann und von wem geloescht wurde und wie lange der
Eintrag noch liegt. Zurueckholen darf, wer auch loeschen darf; das Leeren jeder
Angemeldete - was dort liegt, hat schon jemand mit den noetigen Rechten
weggeworfen. Beides fragt vorher nach.
Nach 90 Tagen raeumt das Backend selbst auf, beim Abruf der Liste und hoechstens
einmal pro Stunde je Prozess - dasselbe Muster wie in der Akquise. Ein eigener
Zeitplan waere eine weitere Stelle, die ausfallen kann, und die Abfragen filtern
Faelliges ohnehin heraus.
Im Papierkorb liegende Opportunities sind aus allen Listen, Summen und
Zuordnungsdialogen heraus und lassen sich nicht bearbeiten (409) - erst
zurueckholen, dann aendern.
Nebenbei: der Header bekommt seinen Titel jetzt ueber viewTitle(). Der
Ansichtsname musste eindeutig sein ("Opportunity-Papierkorb", weil die Mails
schon einen Papierkorb haben), angezeigt wird beides als "Papierkorb".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Menüs, Dialoge, Schaltflächen, Platzhalter, Hinweise und die
Fehlermeldungen der API standen in Ersatzschreibweise (oe/ae/ue). Sie
erscheinen als Toast oder Beschriftung direkt in der Oberfläche und
gehören damit richtig geschrieben; sechs sichtbare Texte mit ss statt ß
sind gleich mit erledigt ("Schließen", "Straße", "zu groß").
Unangetastet bleibt bewusst:
- Kommentare im Quelltext - die sind durchgängig ASCII, und der Diff wäre
dreimal so groß ohne Nutzen für den Benutzer
- Protokollzeilen (console.*) - die landen im journal, nicht in der App
- Datenbankwerte und interne Kennungen: 'uebernommen', 'empfaenger',
'vorschlaege', 'laeuft-bereits' stecken in Enums, SQL und
Zustandsvergleichen. Ein Umlaut darin hätte still falsche Ergebnisse
geliefert
- server/src/mail/extract.js - die Muster decken absichtlich beide
Schreibweisen ab: /gr(ü|ue)(ß|ss)e/
- scanner/utils/crypto.js - der Ersatzschlüssel darf sich nicht ändern,
sonst sind gespeicherte Portal-Passwörter nicht mehr lesbar
Geprüft mit einem Rundgang durch die Ansichten und Dialoge: sichtbarer
Text plus title/aria-label/placeholder enthalten keine
Ersatzschreibweisen mehr.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Der eigenstaendige Projekt Scanner zieht komplett ins CRM ein und laeuft
parallel zur bestehenden App weiter - jede geteilte Ressource ist verdoppelt
statt geteilt, damit die alte Installation unberuehrt bleibt:
- eigene Datenbank perinco_scanner, legt sich beim Start selbst an
- eigener SCANNER_ENCRYPTION_KEY, eigenes Upload-Verzeichnis
- eigener Scheduler mit Zeitversatz, damit nicht beide Instanzen gleichzeitig
mit denselben Portal-Cookies scrapen (Rate-Limits, CAPTCHA, doppelter RAM)
- Sperre gegen ueberlappende Laeufe: Scheduler und "Jetzt scannen" teilen sie,
ein zweiter Aufruf bekommt 409 statt doppelter OpenAI-Kosten
Backend unter /api/scanner hinter der CRM-Anmeldung; die HTTP-Basic-Auth des
Originals entfaellt. CV-PDFs kommen als Stream mit Token statt aus einem
statischen Verzeichnis. Bestandsdaten holt "npm run scanner:import" aus der
alten Datenbank, ohne sie zu veraendern.
Oberflaeche in Tailwind und CI-Farben neu gebaut: Menuepunkt Scanner mit
aufklappbaren Unterpunkten, Einstellungen als eigener Reiter.
Whisper hat keinen eigenen Menuepunkt mehr, sondern ist ein Reiter in den
Einstellungen. Damit diese Geraeteeinstellung jeder Rolle offensteht, sind die
Einstellungen jetzt fuer alle sichtbar - welche Reiter erscheinen, entscheidet
die Rolle.
Ausserdem: kein festes Admin-Passwort mehr im Quelltext, playwright exakt auf
1.60.0 gepinnt (nutzt den vorhandenen Chromium-Build des Servers), technische
Dokumentation mit Funktionszeichnungen unter docs/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>