feat(crm): add event_reminder_* templates to dev email tester

The pre-event reminder feature shipped with 5 seeded templates
(event_reminder_default + wedding/birthday/corporate/other) but the
CRM → Development "Send any CRM email to me" picker only listed the
quote/invoice/contract templates. Maintainer can now eyeball each
reminder category's body without staging a real event.

Backend:
- Extend TEMPLATES_KEYS in adminDev.js with all 5 reminder keys.
- Add event_date (today+2d), days_before (2), business_name (from
  business_profile.legal_name) to the common payload so the
  {{tokens}} in the reminder bodies resolve.

Frontend:
- Extend CrmEmailTemplateKey union.
- Add TEMPLATE_LABEL_KEYS entries.
- EN+DE i18n labels under crmDev.templates.label.event_reminder_*.

No PDF attachment — reminders are body-only emails (matches the
real flow).
This commit is contained in:
Luca
2026-05-26 20:44:11 +02:00
parent b064aab8ef
commit 6d302e7998
5 changed files with 99 additions and 1 deletions
+19
View File
@@ -98,6 +98,16 @@ const TEMPLATES_KEYS = [
'contract_sent',
'contract_signed_admin_notification',
'contract_fully_signed',
// Pre-event reminder emails (migration 143). The runtime resolver
// picks `event_reminder_<events.event_type>` with fallback to
// `event_reminder_default`. The dev tester exposes every seeded
// template so the maintainer can eyeball each category's body
// without staging a real event for each.
'event_reminder_default',
'event_reminder_wedding',
'event_reminder_birthday',
'event_reminder_corporate',
'event_reminder_other',
];
router.get(
@@ -355,11 +365,20 @@ async function buildPayloadFor(key, adminId, frontendUrl) {
const today = new Date();
const dueDate = new Date(today.getTime() - 5 * 86400000);
const validUntil = new Date(today.getTime() + 14 * 86400000);
// Pre-event reminder mock: pretend the event is 2 days out (the
// global default for `crm_event_reminders_days_before`). Renders
// {{event_date}} + {{days_before}} for the event_reminder_*
// templates.
const eventDate = new Date(today.getTime() + 2 * 86400000);
const businessProfile = await db('business_profile').first().catch(() => null);
const common = {
customer_name: 'Sample Customer',
customer_email: 'sample.customer@example.test',
event_name: 'Sample Event',
event_date: fakeShortDate(eventDate),
days_before: 2,
business_name: businessProfile?.legal_name || 'Sample Studio',
invoice_number: 'R-DEV-0001',
quote_number: 'Q-DEV-0001',
total_amount: fakeMoney(total, 'CHF'),
+20
View File
@@ -3317,6 +3317,26 @@
"contract_fully_signed": {
"title": "Vertrag beidseitig unterzeichnet",
"description": "Beidseitiger Versand, sobald beide Unterschriften vorliegen. Gestempeltes Vertrags-PDF im Anhang."
},
"event_reminder_default": {
"title": "Event-Erinnerung — Standard (Fallback)",
"description": "Vor-Event-Erinnerung, wenn keine kategoriespezifische Vorlage zutrifft."
},
"event_reminder_wedding": {
"title": "Event-Erinnerung — Hochzeit",
"description": "Vor-Event-Erinnerung für Events mit event_type=wedding."
},
"event_reminder_birthday": {
"title": "Event-Erinnerung — Geburtstag",
"description": "Vor-Event-Erinnerung für Events mit event_type=birthday."
},
"event_reminder_corporate": {
"title": "Event-Erinnerung — Firma",
"description": "Vor-Event-Erinnerung für Events mit event_type=corporate."
},
"event_reminder_other": {
"title": "Event-Erinnerung — Sonstiges",
"description": "Vor-Event-Erinnerung für Events mit event_type=other."
}
}
}
+20
View File
@@ -3317,6 +3317,26 @@
"contract_fully_signed": {
"title": "Contract fully signed (dual-party)",
"description": "Dual-party send once both signatures are in. Stamped contract PDF attached."
},
"event_reminder_default": {
"title": "Event reminder — default (catch-all)",
"description": "Pre-event nudge used when no per-category template matches."
},
"event_reminder_wedding": {
"title": "Event reminder — wedding",
"description": "Pre-event nudge for events with event_type=wedding."
},
"event_reminder_birthday": {
"title": "Event reminder — birthday",
"description": "Pre-event nudge for events with event_type=birthday."
},
"event_reminder_corporate": {
"title": "Event reminder — corporate",
"description": "Pre-event nudge for events with event_type=corporate."
},
"event_reminder_other": {
"title": "Event reminder — other",
"description": "Pre-event nudge for events with event_type=other."
}
}
}
@@ -98,6 +98,36 @@ const TEMPLATE_LABEL_KEYS: Record<
descKey: 'crmDev.templates.label.contract_fully_signed.description',
descFallback: 'Dual-party send once both signatures are in. Stamped contract PDF attached.',
},
event_reminder_default: {
titleKey: 'crmDev.templates.label.event_reminder_default.title',
titleFallback: 'Event reminder — default (catch-all)',
descKey: 'crmDev.templates.label.event_reminder_default.description',
descFallback: 'Pre-event nudge used when no per-category template matches.',
},
event_reminder_wedding: {
titleKey: 'crmDev.templates.label.event_reminder_wedding.title',
titleFallback: 'Event reminder — wedding',
descKey: 'crmDev.templates.label.event_reminder_wedding.description',
descFallback: 'Pre-event nudge for events with event_type=wedding.',
},
event_reminder_birthday: {
titleKey: 'crmDev.templates.label.event_reminder_birthday.title',
titleFallback: 'Event reminder — birthday',
descKey: 'crmDev.templates.label.event_reminder_birthday.description',
descFallback: 'Pre-event nudge for events with event_type=birthday.',
},
event_reminder_corporate: {
titleKey: 'crmDev.templates.label.event_reminder_corporate.title',
titleFallback: 'Event reminder — corporate',
descKey: 'crmDev.templates.label.event_reminder_corporate.description',
descFallback: 'Pre-event nudge for events with event_type=corporate.',
},
event_reminder_other: {
titleKey: 'crmDev.templates.label.event_reminder_other.title',
titleFallback: 'Event reminder — other',
descKey: 'crmDev.templates.label.event_reminder_other.description',
descFallback: 'Pre-event nudge for events with event_type=other.',
},
};
export const CrmDevelopmentPage: React.FC = () => {
+10 -1
View File
@@ -22,7 +22,16 @@ export type CrmEmailTemplateKey =
// template_key strings.
| 'contract_sent'
| 'contract_signed_admin_notification'
| 'contract_fully_signed';
| 'contract_fully_signed'
// Pre-event reminder emails (migration 143). One template per
// event_type with a catch-all default. Backend resolves
// `event_reminder_<event_type>` then falls back to
// `event_reminder_default`.
| 'event_reminder_default'
| 'event_reminder_wedding'
| 'event_reminder_birthday'
| 'event_reminder_corporate'
| 'event_reminder_other';
export interface DevEmailTemplateStatus {
key: CrmEmailTemplateKey;