feat(akquise): Berater-Mail sagt "beworben" und zeigt den Link

Betreff "Du wurdest beworben: <Titel>", Text "wir haben dich auf folgendes
Projekt beworben", Link zum Projekt als ausgeschriebene Zeile in der
Tabelle statt versteckt hinter "Zur Ausschreibung".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sascha Wolf
2026-10-21 09:49:41 +02:00
co-authored by Claude Opus 5.5
parent eaef454442
commit 6772f34b4e
4 changed files with 22 additions and 12 deletions
+2 -1
View File
@@ -988,7 +988,8 @@ 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 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 Anlegen und auch, wenn er später beim Bearbeiten dazukommt. Wer schon
zugeordnet war, bekommt beim erneuten Speichern keine zweite Mail. Die Mail zugeordnet war, bekommt beim erneuten Speichern keine zweite Mail. Die Mail
enthält die Projektdaten und den Link zur Ausschreibung; eine Antwort darauf sagt, dass der Berater auf das Projekt beworben wurde, und enthält die
Projektdaten samt ausgeschriebenem Link zum Projekt; eine Antwort darauf
geht an den, der das Projekt eingetragen hat. Ohne hinterlegte Adresse geht geht an den, der das Projekt eingetragen hat. Ohne hinterlegte Adresse geht
nichts heraus, der Haken zeigt dann einen Hinweis. nichts heraus, der Haken zeigt dann einen Hinweis.
+4 -2
View File
@@ -393,8 +393,10 @@ Fremdschlüssel nicht.
Seit 21.10.2026 verschickt das CRM selbst Mails - bisher nur eine Sorte: Wird in 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 der Akquise ein Projekt eingetragen und dabei ein Berater ausgewählt, bekommt
dieser eine Mail mit Titel, Endkunde, Anbieter, Tagessatz, Zeitraum, Link zur dieser eine Mail: Betreff „Du wurdest beworben: ‹Titel›", im Text „wir haben
Ausschreibung und Beschreibung (gekürzt auf 1500 Zeichen). dich auf folgendes Projekt beworben", dazu Titel, Endkunde, Anbieter, Kanal,
Tagessatz, Zeitraum, der **Link zum Projekt** ausgeschrieben, wer beworben hat
und die Beschreibung (gekürzt auf 1500 Zeichen).
```mermaid ```mermaid
sequenceDiagram sequenceDiagram
+10 -7
View File
@@ -65,14 +65,20 @@ export function buildMessage(acquisition, consultantName) {
['Projektpreis', formatEuro(acquisition.fixed_price)], ['Projektpreis', formatEuro(acquisition.fixed_price)],
['Zeitraum', period || null], ['Zeitraum', period || null],
['Laufzeit', acquisition.duration_days ? `${acquisition.duration_days} Tage` : null], ['Laufzeit', acquisition.duration_days ? `${acquisition.duration_days} Tage` : null],
['Eingetragen von', acquisition.created_by_name], ['Link zum Projekt', acquisition.link],
['Beworben von', acquisition.created_by_name],
].filter(([, value]) => value !== null && value !== undefined && value !== ''); ].filter(([, value]) => value !== null && value !== undefined && value !== '');
// Der Link steht ausgeschrieben da - auch wer die Mail als Text liest, sieht ihn.
const cell = (label, value) =>
label === 'Link zum Projekt'
? `<a href="${escapeHtml(value)}">${escapeHtml(value)}</a>`
: escapeHtml(value);
const table = rows const table = rows
.map( .map(
([label, value]) => ([label, value]) =>
`<tr><td style="padding:4px 16px 4px 0;color:#666;vertical-align:top">${escapeHtml(label)}</td>` + `<tr><td style="padding:4px 16px 4px 0;color:#666;vertical-align:top">${escapeHtml(label)}</td>` +
`<td style="padding:4px 0">${escapeHtml(value)}</td></tr>` `<td style="padding:4px 0;word-break:break-all">${cell(label, value)}</td></tr>`
) )
.join(''); .join('');
@@ -85,11 +91,8 @@ export function buildMessage(acquisition, consultantName) {
const html = [ const html = [
'<div style="font-family:Segoe UI,Arial,sans-serif;font-size:14px;color:#222">', '<div style="font-family:Segoe UI,Arial,sans-serif;font-size:14px;color:#222">',
`<p>Hallo ${escapeHtml(firstName)},</p>`, `<p>Hallo ${escapeHtml(firstName)},</p>`,
'<p>in der Akquise wurde ein neues Projekt eingetragen und dir zugeordnet:</p>', '<p>wir haben dich auf folgendes Projekt <strong>beworben</strong>:</p>',
`<table style="border-collapse:collapse;font-size:14px">${table}</table>`, `<table style="border-collapse:collapse;font-size:14px">${table}</table>`,
acquisition.link
? `<p><a href="${escapeHtml(acquisition.link)}">Zur Ausschreibung</a></p>`
: '',
description description
? `<p style="white-space:pre-wrap;border-left:3px solid #ddd;padding-left:12px;color:#444">${escapeHtml(description)}</p>` ? `<p style="white-space:pre-wrap;border-left:3px solid #ddd;padding-left:12px;color:#444">${escapeHtml(description)}</p>`
: '', : '',
@@ -99,7 +102,7 @@ export function buildMessage(acquisition, consultantName) {
'</div>', '</div>',
].join(''); ].join('');
return { subject: `Neues Projekt für dich: ${acquisition.title}`, html }; return { subject: `Du wurdest beworben: ${acquisition.title}`, html };
} }
/** /**
+6 -2
View File
@@ -43,12 +43,16 @@ const message = buildMessage(
}, },
'Jörg Müller' 'Jörg Müller'
); );
check('Betreff mit Titel', message.subject === 'Neues Projekt für dich: SAP <script>'); check('Betreff mit Titel', message.subject === 'Du wurdest beworben: SAP <script>');
check('Sagt, dass beworben wurde', message.html.includes('<strong>beworben</strong>'));
check('Titel escaped', message.html.includes('SAP &lt;script&gt;') && !message.html.includes('<script>')); check('Titel escaped', message.html.includes('SAP &lt;script&gt;') && !message.html.includes('<script>'));
check('Firma escaped', message.html.includes('ACME &amp; Co')); check('Firma escaped', message.html.includes('ACME &amp; Co'));
check('Anrede mit Vorname', message.html.includes('Hallo Jörg,')); check('Anrede mit Vorname', message.html.includes('Hallo Jörg,'));
check('Datum deutsch', message.html.includes('01.11.2026 bis 31.12.2026')); check('Datum deutsch', message.html.includes('01.11.2026 bis 31.12.2026'));
check('Link enthalten', message.html.includes('href="https://example.com/p?a=1&amp;b=2"')); check(
'Link sichtbar',
message.html.includes('<a href="https://example.com/p?a=1&amp;b=2">https://example.com/p?a=1&amp;b=2</a>')
);
check('Leere Felder fehlen', !message.html.includes('Projektpreis')); check('Leere Felder fehlen', !message.html.includes('Projektpreis'));
console.log( console.log(