feat: Interviews, Mail-Papierkorb, Kontaktart

Drei Aenderungen aus dem Alltagsgebrauch:

- Der Kalender heisst jetzt "Interviews" - dafuer wird er benutzt. Der Wert in
  ViewState wandert mit, damit Menue und Kopfzeile nicht auseinanderlaufen.
  Sichtbare Reste ("Kalenderfarbe") heissen jetzt Terminfarbe.

- Mails haben einen Papierkorb, der unter dem Menuepunkt aufklappt. Loeschen
  heisst dabei ausschliesslich: im CRM. Im Postfach crm@perinco.de bleibt die
  Mail unberuehrt - Schreibrechte darauf hat die Anwendung nicht. Damit der
  Delta-Abgleich geloeschte Mails nicht wieder einliest, bleibt nach dem Leeren
  eine leere Zeile als Merker stehen (crm_purged); runMailSync() ueberspringt
  alles, was im CRM geloescht ist. Zurueckholen und Leeren darf jeder
  Angemeldete, Leeren nach einer Warnung.

- Der Kontaktstatus (Neu .. Verloren) wird zur Kontaktart: Recruiter, Partner,
  Endkunde. Die Spalte heisst jetzt kind, weil "Status" das Feld falsch
  benannte. migrateLeadKind() stellt bestehende Datenbanken um und setzt alle
  vorhandenen Kontakte auf Recruiter - welche Art jemand ist, sagt der alte
  Status nicht (so mit dem Nutzer entschieden).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-08-18 11:12:49 +02:00
co-authored by Claude Opus 5
parent 025390e18a
commit 8741a9ae0c
39 changed files with 719 additions and 199 deletions
+44 -3
View File
@@ -124,7 +124,8 @@ sequenceDiagram
end
G-->>Y: Seite mit Metadaten + nextLink/deltaLink
loop je Nachricht
Y->>D: bekannt? Text vorhanden?
Y->>D: bekannt? Text vorhanden? im CRM gelöscht?
Note over Y: im Papierkorb oder endgültig gelöscht? dann weiter
Note over Y: unbekannt und älter als die Grenze? dann weiter
opt Text fehlt
Y->>G: /messages/{id}?$select=body
@@ -224,7 +225,7 @@ nur `mail_pool = 0`, der Vorbereich steht als Unterpunkt *Aus Mails* unter dem
Menüpunkt *Kontakte*.
Der Grund ist Pflegbarkeit: die Kontaktliste ist eine gepflegte Liste, an der
Opportunities, Kalender und Netzwerk hängen. Ein Abgleich, der ungefragt jede
Opportunities, Interviews und Netzwerk hängen. Ein Abgleich, der ungefragt jede
Adresse aus jeder Mail hineinschreibt, macht sie unbrauchbar - genau daran ist
das Altsystem gescheitert. Deshalb sind Funde aus Mails Rohmaterial, bis jemand
entscheidet.
@@ -288,13 +289,16 @@ Alle unter `/api/mail`, alle hinter `requireAuth`.
| Methode | Pfad | Rolle | Zweck |
| --- | --- | --- | --- |
| GET | `/status` | alle | Betriebszustand, Zähler, letzter Fehler |
| GET | `/messages` | alle | Liste; `search`, `filter`, `limit`, `offset` |
| GET | `/messages` | alle | Liste; `search`, `filter`, `limit`, `offset`, `trash` |
| GET | `/messages/:id` | alle | Mail mit Text, Zuordnungen, Vorschlägen |
| POST | `/messages/:id/links` | alle | an Kontakt und/oder Opportunity hängen |
| DELETE | `/links/:id` | alle | Zuordnung lösen |
| GET | `/candidates` | alle | Kontakt-Vorschläge, `status` wählbar |
| POST | `/candidates/:id/adopt` | alle | Kontakt anlegen bzw. ergänzen; `target: 'pool'` legt in „Aus Mails" ab |
| POST | `/candidates/:id/reject` | alle | Vorschlag verwerfen |
| DELETE | `/messages/:id` | alle | Mail in den Papierkorb legen |
| POST | `/messages/:id/restore` | alle | Mail aus dem Papierkorb zurückholen |
| POST | `/trash/empty` | alle | Papierkorb endgültig leeren |
Der Vorbereich hängt an den Kontakten und hat seine Endpunkte deshalb unter
`/api/leads`:
@@ -319,6 +323,43 @@ vorbehalten.
`filter` in der Liste: `unlinked` (noch nirgends zugeordnet - die Arbeitsliste),
`candidates` (offene Kontakt-Vorschläge), `linked` (bereits zugeordnet).
`trash=true` zeigt genau den Papierkorb; ohne den Parameter ist er ausgeblendet.
## Papierkorb
Löschen heißt im CRM: aus dem CRM verschwinden lassen. Das Postfach bleibt
unberührt - Schreibrechte darauf hat die Anwendung nicht und soll sie nicht
haben. Zwei Spalten in `crm_mail_messages` tragen das:
| Spalte | Bedeutung |
| --- | --- |
| `crm_deleted_at` (+ `crm_deleted_by`) | liegt im Papierkorb, lässt sich zurückholen |
| `crm_purged` | Papierkorb geleert: Zeile bleibt als leerer Merker stehen |
Der Merker ist der Kern der Sache. Im Postfach liegt die Mail weiter, der
Delta-Abgleich liefert sie also erneut aus. `runMailSync()` prüft deshalb bei
jeder Nachricht zuerst, ob sie im CRM gelöscht ist, und lässt sie dann in Ruhe -
kein Text wird nachgeladen, keine Zeile aktualisiert. Ohne den Merker käme jede
endgültig gelöschte Mail beim nächsten Lauf zurück. Der Lauf zählt die
übersprungenen Mails in `skippedTrashed` und schreibt sie ins Protokoll.
Beim Leeren fällt zusätzlich weg:
* alle `crm_mail_links` der betroffenen Mails - was es nicht mehr gibt, kann an
keiner Opportunity und keinem Kontakt hängen,
* deren **offene** Kontakt-Vorschläge. Entschiedene bleiben: sie sind der
Nachweis, woher ein Kontakt stammt, und tragen die Herkunftsanzeige in
„Aus Mails".
Vom Inhalt bleibt nichts: Betreff, Absender, Empfänger, Vorschautext und Text
werden geleert. In der Zeile stehen danach nur noch die Graph-Kennung, ihr Hash,
der Empfangszeitpunkt und der Löschvermerk.
In den Papierkorb legen und leeren darf **jeder angemeldete Benutzer** - beim
Leeren nach einer Warnung. Der erste Schritt ist umkehrbar, der zweite nicht.
`crm_deleted_by` trägt bewusst keinen Fremdschlüssel auf `crm_users`: Spalten
lassen sich mit `ADD COLUMN IF NOT EXISTS` idempotent nachziehen, ein
Fremdschlüssel nicht.
## Was anders ist als im Altsystem