feat(akquise): Mail an Berater bei neuem Projekt
Wird in der Akquise ein Projekt mit Berater angelegt (oder ein Berater beim Bearbeiten hinzugefuegt), bekommt er eine Mail ueber Graph sendMail aus crm@perinco.de; Reply-To ist der eintragende Benutzer. Je Berater abschaltbar unter Einstellungen -> Akquise (Spalte notify, Vorgabe an). Versand laeuft nach der Antwort und kippt das Speichern nie. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
907b010f3c
commit
eaef454442
@@ -104,6 +104,7 @@ erDiagram
|
||||
enum field "sender | channel | consultant"
|
||||
varchar value "Inhalt des Dropdowns"
|
||||
varchar email "nur bei consultant"
|
||||
tinyint notify "Mail bei neuem Projekt, Vorgabe 1"
|
||||
}
|
||||
crm_acquisition_history {
|
||||
date found_on PK
|
||||
|
||||
@@ -137,6 +137,7 @@ entfallen.
|
||||
| Ton bleibt aus | Schwellenwert, Dashboard-Ansicht offen, einmal auf der Seite geklickt? |
|
||||
| Keine Mails im Postfachreiter | `journalctl -u perinco-crm-backend \| grep '\[Mail\]'`, dann Verbindungstest in den Einstellungen |
|
||||
| Mail-Abgleich meldet `ErrorAccessDenied` | Administratorzustimmung für `Mail.Read` fehlt, oder die ApplicationAccessPolicy erlaubt das Postfach nicht |
|
||||
| Berater bekommt keine Mail bei neuem Projekt | Adresse und Schalter unter *Einstellungen → Akquise*; Log `journalctl -u perinco-crm-backend \| grep 'Akquise] Mail'`; bei `ErrorAccessDenied` fehlt die Zustimmung für `Mail.Send` |
|
||||
|
||||
Nützliche Abfragen:
|
||||
|
||||
|
||||
@@ -983,6 +983,15 @@ wurden die vorhandenen Berater einmalig mit `vorname.nachname@perinco.de`
|
||||
vorbelegt - Umlaute ausgeschrieben, bei mehrteiligen Namen erster und letzter
|
||||
Teil. Berater mit nur einem Namen blieben leer und brauchen eine Adresse von Hand.
|
||||
|
||||
Darunter steht der Haken **Mail bei neuem Projekt** (seit 21.10.2026, anfangs
|
||||
bei allen an). Ist er gesetzt, bekommt der Berater eine Mail von
|
||||
crm@perinco.de, sobald er in der Akquise einem Projekt zugeordnet wird - beim
|
||||
Anlegen und auch, wenn er später beim Bearbeiten dazukommt. Wer schon
|
||||
zugeordnet war, bekommt beim erneuten Speichern keine zweite Mail. Die Mail
|
||||
enthält die Projektdaten und den Link zur Ausschreibung; eine Antwort darauf
|
||||
geht an den, der das Projekt eingetragen hat. Ohne hinterlegte Adresse geht
|
||||
nichts heraus, der Haken zeigt dann einen Hinweis.
|
||||
|
||||
Im Reiter **Scanner** steht das **Suchintervall (Minuten)** - also wie oft der
|
||||
Hintergrund-Scanner die Börsen durchsucht.
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ Mal etwas:
|
||||
* `addConsultantEmail()` legt `crm_acquisition_options.email` an und belegt die
|
||||
vorhandenen Berater einmalig mit `vorname.nachname@perinco.de` vor
|
||||
(`guessPerincoEmail()`, Merker `consultant_email_seeded`). Namen ohne
|
||||
Nachnamen bleiben leer.
|
||||
Nachnamen bleiben leer. Dieselbe Funktion legt `notify` (Vorgabe 1) an.
|
||||
|
||||
`ensureAdminUser()` legt nur an, wenn `crm_users` **leer** ist. Ohne
|
||||
`ADMIN_PASSWORD` erzeugt es ein Zufallspasswort und schreibt es einmalig ins Log -
|
||||
@@ -106,6 +106,7 @@ Alle unter `router.use(requireAuth)`.
|
||||
| `opportunities.js` | `GET /?trash=true`, `POST /`, `PATCH /:id`, `DELETE /:id` (Papierkorb), `POST /:id/restore`, `POST /:id/acquisition`, `POST /:id/copy`, `POST /trash/empty` | ändern, löschen und wiederherstellen nur Ersteller, Zuständiger oder Admin; Leeren jeder Angemeldete. `/:id/acquisition` nimmt eine Übernahme zurück: alle lebenden Opportunities mit derselben `acquisition_id` in den Papierkorb, beim Eintrag `converted_at`/`opportunity_id` leeren (Funddatum bleibt), in einer Transaktion. `/restore` hängt den Eintrag wieder an die Opportunity, wenn er frei ist. `/:id/copy` (`{ consultants, stage }`) legt je Berater eine Kopie an - Projektdaten, Ansprechpartner, Zuständiger und `acquisition_id` kommen mit, Interaktion und nächster Schritt nicht; jeder Angemeldete |
|
||||
| `acquisitions.js` | `GET /?view=eingang\|archiv\|papierkorb\|alle`, `POST /`, `PATCH /:id`, `DELETE /:id` (Papierkorb), `DELETE /:id/permanent`, `POST /trash/empty`, `POST /:id/opportunity`, `POST /:id/reopen`, `POST /:id/restore`; Auswahllisten: `GET/POST /options`, `PATCH/DELETE /options/:id` | Einträge ändern, löschen und zurückholen nur Ersteller oder Admin; Auswahllisten und Leeren jeder Angemeldete. Zwei verschiedene Rückwege, nicht zu verwechseln: `/reopen` holt aus dem **Archiv** in den Eingang (setzt `found_at` auf heute und leert `converted_at`/`opportunity_id` - sonst hält die Übernahme den Eintrag im Archiv fest), `/restore` holt aus dem **Papierkorb** zurück (leert nur `deleted_at`/`deleted_by`, das Funddatum bleibt). Beide antworten 409, wenn der Eintrag im falschen Zustand ist |
|
||||
| `badges.js` | `GET /` | Zahlen für die Seitenleiste: Akquise-Archiv und dessen Papierkorb, Papierkorb der Opportunities und der Mails, verworfene Scanner-Treffer. Der Scanner-Zähler ist `null`, wenn dessen Datenbank fehlt. Für Administratoren zusätzlich `ms_secret`, sobald das Client-Secret in weniger als 30 Tagen abläuft |
|
||||
| `acquisitionNotify.js` (kein Router) | Berater-Mail bei neuem Projekt: `addedConsultants()` (wer ist neu), `buildMessage()` (Betreff + escaptes HTML), `notifyConsultants()` (Versand über Graph `sendMail` aus `MS_MAILBOX`, wirft nie). Aufgerufen von `POST`/`PATCH` in `acquisitions.js` nach der Antwort. Siehe [11 Postfach](11-postfach.md#berater-mail-bei-neuem-projekt) |
|
||||
| `link.js` (kein Router) | `normalizeLink()` für den Link zur Ausschreibung: nur http/https, ohne Schema wird `https://` ergänzt, max. 1024 Zeichen. Geteilt von Akquise und Opportunities, damit ein übernommener Link nicht an einer anderen Prüfung scheitert |
|
||||
| `acquisitionRules.js` (kein Router) | Fristen und die SQL-Bedingungen `IN_INBOX`, `IN_ARCHIVE`, `RETURNED`, `IS_OPEN` (nur noch für den Löschlauf), `NOT_DUE`, `OPPORTUNITY_GONE`, `NOT_DELETED`, `IN_TRASH`. Geteilt von Liste, Zählern, Aufräumlauf und Menüzahl - zwei Kopien derselben Regel sind einmal auseinandergelaufen. Dazu `splitConsultants` / `joinConsultants` / `normalizeConsultants` für das Feld *Berater*, das mehrere Namen kommagetrennt hält (Gegenstück im Frontend: `src/types.ts`) |
|
||||
| `dashboard.js` | `GET /?from=&to=&bucket=tag\|monat\|quartal\|jahr` | drei Reihen über der Zeit plus Bewerbungen je Berater. Liest Akquise **und** `crm_acquisition_history`; Termine und Phasenlisten stehen bewusst nicht drin - die lädt die App ohnehin |
|
||||
|
||||
+40
-2
@@ -66,6 +66,9 @@ Login.
|
||||
3. **API-Berechtigung** Microsoft Graph → *Anwendungsberechtigungen* →
|
||||
`Mail.Read`, danach Administratorzustimmung erteilen.
|
||||
`Mail.ReadBasic` genügt nicht, es liefert keinen Mailtext.
|
||||
Seit 21.10.2026 zusätzlich `Mail.Send` (erteilt) - damit verschickt das CRM
|
||||
die [Berater-Mail bei neuem Projekt](#berater-mail-bei-neuem-projekt) aus
|
||||
diesem Postfach.
|
||||
4. **Zugriff auf dieses eine Postfach begrenzen.** Anwendungsberechtigungen
|
||||
gelten tenant-weit; ohne Richtlinie darf die App jedes Postfach lesen:
|
||||
|
||||
@@ -386,10 +389,45 @@ Fremdschlüssel nicht.
|
||||
| HTML-Anzeige | ungefiltert | serverseitig entschärft, `sandbox=""`, externe Bilder gesperrt |
|
||||
| Zeitangaben | gemischt | durchgehend UTC in der Datenbank, Umrechnung im Frontend |
|
||||
|
||||
## Berater-Mail bei neuem Projekt
|
||||
|
||||
Seit 21.10.2026 verschickt das CRM selbst Mails - bisher nur eine Sorte: Wird in
|
||||
der Akquise ein Projekt eingetragen und dabei ein Berater ausgewählt, bekommt
|
||||
dieser eine Mail mit Titel, Endkunde, Anbieter, Tagessatz, Zeitraum, Link zur
|
||||
Ausschreibung und Beschreibung (gekürzt auf 1500 Zeichen).
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant B as Browser
|
||||
participant API as POST/PATCH /api/acquisitions
|
||||
participant N as acquisitionNotify.js
|
||||
participant G as Graph sendMail
|
||||
B->>API: Eintrag mit Berater speichern
|
||||
API-->>B: 201 / 200 (sofort)
|
||||
API->>N: notifyConsultants(eintrag, neue Berater, benutzer)
|
||||
N->>N: Adresse + Schalter je Berater aus crm_acquisition_options
|
||||
N->>G: POST /users/crm@perinco.de/sendMail
|
||||
```
|
||||
|
||||
* **Absender** ist `MS_MAILBOX` (crm@perinco.de), Kopie in *Gesendete
|
||||
Elemente*. **Reply-To** ist der Benutzer, der den Eintrag gespeichert hat -
|
||||
eine Antwort des Beraters landet also bei ihm, nicht im CRM-Postfach.
|
||||
* **Wer** eine Mail bekommt: Beim Anlegen alle ausgewählten Berater, beim
|
||||
Bearbeiten nur die neu hinzugekommenen. Voraussetzung je Berater: Adresse
|
||||
hinterlegt und Schalter *Mail bei neuem Projekt* an (*Einstellungen →
|
||||
Akquise*). Der Schalter war bei Einführung für alle an.
|
||||
* **Fehler** brechen das Speichern nie ab. Der Versand läuft nach der Antwort an
|
||||
den Browser; Erfolg und Fehler stehen im Log:
|
||||
`journalctl -u perinco-crm-backend | grep 'Akquise] Mail'`.
|
||||
* **Ohne M365-Anbindung** (Secret fehlt) wird nur eine Warnung geloggt.
|
||||
* Die `ApplicationAccessPolicy` (siehe oben) gilt auch für `Mail.Send` - die App
|
||||
darf also nur aus crm@perinco.de senden, nicht im Namen anderer.
|
||||
|
||||
## Was noch nicht drin ist
|
||||
|
||||
* **Senden, Antworten, Weiterleiten.** Braucht `Mail.ReadWrite` und `Mail.Send`
|
||||
und damit eine erneute Administratorzustimmung.
|
||||
* **Antworten, Weiterleiten aus dem Postfachreiter.** Senden geht seit der
|
||||
Berater-Mail (`Mail.Send`); Antworten auf eine konkrete Mail bräuchte
|
||||
zusätzlich `Mail.ReadWrite`.
|
||||
* **Anhänge.** `has_attachments` wird geführt und angezeigt, die Dateien
|
||||
selbst werden nicht geholt.
|
||||
* **Automatische Zuordnung zu Opportunities.** Die Zuordnung erfolgt per Hand;
|
||||
|
||||
Reference in New Issue
Block a user