feat(kontakte): Einverständnis für Mails und Mailingliste
Am Kontakt gibt es unter der E-Mail-Adresse das Kästchen "Einverständnis für Mails". Ohne Adresse ist es gesperrt. Der Zeitpunkt wird in `mail_consent_at` mitgeschrieben und im Kontakt angezeigt: bei einer Beschwerde ist nicht die Frage, ob ein Einverständnis vorliegt, sondern seit wann. Beim Widerruf wird er geleert; ein erneutes Speichern verschiebt ihn nicht. Der Knopf "Mailingliste erstellen" in der Kontaktleiste sammelt alle Adressen mit Haken, durch Komma getrennt, mit Zähler am Knopf. Rechts im Feld ein Kopiersymbol (Zwischenablage, mit Fallback für Browser ohne die Schnittstelle) und ein Mailsymbol. Zum Mailsymbol zwei Entscheidungen: - Die Adressen gehen ins BCC, nicht ins An-Feld. Im An-Feld sähe jeder Empfänger die vollständige Kundenliste. - Windows gibt an das Mailprogramm nur rund 2000 Zeichen weiter. Ab 1900 Zeichen ist das Symbol deshalb gesperrt und der Dialog erklärt es - sonst wäre die Empfängerliste unbemerkt abgeschnitten. Nur für richtige Kontakte: Der Vorbereich "Aus Mails" hat das Kästchen nicht, und beim Übernehmen wird der Haken nicht mitgesetzt - dass jemand eine Mail geschrieben hat, ist keine Einwilligung in Werbung. Das Backend weist ein `mail_consent` für Einträge mit `mail_pool = 1` ab. Sichtbar ohne Öffnen: grünes Briefsymbol in der Kachel, Häkchen hinter der Adresse in der Liste. Geprüft im Browser gegen das Stub-Backend: Haken setzen und widerrufen, Zähler, Komma-Liste, Zwischenablage, mailto mit BCC, kein Kästchen im Vorbereich (15 Prüfungen). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
9a4266ae7e
commit
41e9f6898f
@@ -64,11 +64,25 @@ function mapLead(row) {
|
||||
tags: parseTags(row.tags),
|
||||
owner_id: row.owner_id,
|
||||
created_by: row.created_by,
|
||||
// TINYINT(1) kommt als 0/1 - im Frontend soll es ein Schalter sein.
|
||||
// TINYINT(1) kommt als 0/1 - im Frontend sollen es Schalter sein.
|
||||
mail_pool: Boolean(row.mail_pool),
|
||||
mail_consent: Boolean(row.mail_consent),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Einverstaendnis in Werbemails setzen. Der Zeitpunkt wird nur beim Wechsel von
|
||||
* "nein" auf "ja" gestempelt - ein zweites Speichern desselben Kontakts darf den
|
||||
* Nachweis nicht auf heute verschieben. Beim Widerruf wird er geleert.
|
||||
*/
|
||||
function consentUpdate(incoming, previous) {
|
||||
const next = incoming === true || incoming === 'true' || incoming === 1 || incoming === '1';
|
||||
if (next === Boolean(previous?.mail_consent)) return {};
|
||||
return next
|
||||
? { mail_consent: 1, mail_consent_at: new Date() }
|
||||
: { mail_consent: 0, mail_consent_at: null };
|
||||
}
|
||||
|
||||
const SELECT_LEADS = `
|
||||
SELECT l.*,
|
||||
creator.name AS created_by_name,
|
||||
@@ -118,7 +132,9 @@ router.post('/', async (req, res, next) => {
|
||||
return res.status(400).json({ error: 'Zuständiger Benutzer existiert nicht.' });
|
||||
}
|
||||
|
||||
const columns = [...TEXT_FIELDS, 'status', 'tags', 'owner_id', 'created_by'];
|
||||
const consent = consentUpdate(req.body?.mail_consent, null);
|
||||
|
||||
const columns = [...TEXT_FIELDS, 'status', 'tags', 'owner_id', 'created_by', ...Object.keys(consent)];
|
||||
const values = [
|
||||
...TEXT_FIELDS.map((field) =>
|
||||
field === 'first_name' ? firstName : field === 'last_name' ? lastName : emptyToNull(req.body?.[field])
|
||||
@@ -127,6 +143,7 @@ router.post('/', async (req, res, next) => {
|
||||
serializeTags(req.body?.tags) ?? '[]',
|
||||
ownerId,
|
||||
req.user.id,
|
||||
...Object.values(consent),
|
||||
];
|
||||
|
||||
try {
|
||||
@@ -439,6 +456,22 @@ router.patch('/:id', async (req, res, next) => {
|
||||
values.push(ownerId);
|
||||
}
|
||||
|
||||
/*
|
||||
* Einverstaendnis: nur fuer richtige Kontakte. Im Vorbereich "Aus Mails" gibt
|
||||
* es das Kennzeichen nicht - dort steht niemand, der eingewilligt haette.
|
||||
*/
|
||||
if (req.body?.mail_consent !== undefined) {
|
||||
const [rows] = await pool.query('SELECT mail_consent, mail_pool FROM crm_leads WHERE id = ? LIMIT 1', [id]);
|
||||
if (!rows[0]) return res.status(404).json({ error: 'Lead nicht gefunden.' });
|
||||
if (rows[0].mail_pool) {
|
||||
return res.status(400).json({ error: 'Einträge in „Aus Mails" haben kein Einverständnis für Mails.' });
|
||||
}
|
||||
for (const [column, value] of Object.entries(consentUpdate(req.body.mail_consent, rows[0]))) {
|
||||
fields.push(`${column} = ?`);
|
||||
values.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.length === 0) return res.status(400).json({ error: 'Keine Änderungen übergeben.' });
|
||||
|
||||
try {
|
||||
|
||||
@@ -74,6 +74,18 @@ CREATE TABLE IF NOT EXISTS crm_leads (
|
||||
ALTER TABLE crm_leads ADD COLUMN IF NOT EXISTS mail_pool TINYINT(1) NOT NULL DEFAULT 0 AFTER source;
|
||||
ALTER TABLE crm_leads ADD INDEX IF NOT EXISTS idx_crm_leads_mail_pool (mail_pool);
|
||||
|
||||
/*
|
||||
* Einverstaendnis in Werbemails. Ohne Haken kommt die Adresse in keine
|
||||
* Mailingliste - Vorgabe ist deshalb 0, auch bei Kontakten, die aus dem
|
||||
* Vorbereich uebernommen werden: eine Mail von jemandem ist keine Einwilligung.
|
||||
*
|
||||
* `mail_consent_at` haelt fest, wann der Haken gesetzt wurde. Bei einer
|
||||
* Beschwerde ist die Frage nicht "gibt es ein Einverstaendnis", sondern "seit
|
||||
* wann" - ohne Zeitpunkt ist das Kennzeichen als Nachweis wertlos.
|
||||
*/
|
||||
ALTER TABLE crm_leads ADD COLUMN IF NOT EXISTS mail_consent TINYINT(1) NOT NULL DEFAULT 0 AFTER mail_pool;
|
||||
ALTER TABLE crm_leads ADD COLUMN IF NOT EXISTS mail_consent_at DATETIME NULL AFTER mail_consent;
|
||||
|
||||
-- Wer steht mit wem in Kontakt. Ungerichtet gedacht; Duplikate werden ueber
|
||||
-- den Unique-Key auf dem sortierten Paar verhindert (siehe Backend).
|
||||
CREATE TABLE IF NOT EXISTS crm_lead_connections (
|
||||
|
||||
Reference in New Issue
Block a user