fix(audit): wire 6 missing email subject overrides (R2-H14)

Admin-editable subject overrides at /admin/email-templates were no-ops
for 6 of 8 templates — only portal_activation and portal_reset called
loadSubjectOverride. Added a shared resolveSubject() helper and wired
it into the missing senders:

- crm_invite + portal_invite_resend (crm-invite.service.ts)
- inquiry_client_confirmation (email worker via portId on job payload)
- inquiry_sales_notification (email worker via portId on job payload)
- residential_inquiry_client_confirmation (residential-inquiries route)
- residential_inquiry_sales_alert (residential-inquiries route)

The inquiry email worker payloads now carry portId + portName so the
worker can resolve the per-port override; producers in inquiry-
notifications.service.ts pass them through.

1175/1175 vitest passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-05-06 22:26:41 +02:00
parent 59b9e8f177
commit c312cd3685
5 changed files with 126 additions and 17 deletions

View File

@@ -0,0 +1,34 @@
/**
* Helper that turns a template key + per-port settings into a final
* subject line. Centralises the override-resolution + token-substitution
* pattern so every transactional email sender is one call away from
* honoring the admin's `email_template_<key>_subject` override.
*
* Wire-up (per send site):
* const subject = await resolveSubject({
* key: 'crm_invite',
* portId,
* fallback: result.subject,
* tokens: { portName, recipientName: data.recipientName, ttlHours: 48 },
* });
*
* The override is read from `system_settings.email_template_<key>_subject`.
* If unset / empty / non-string, the `fallback` is returned as-is.
*/
import { loadSubjectOverride, applySubjectTokens } from '@/lib/email/template-overrides';
import type { TemplateKey } from '@/lib/email/template-catalog';
export async function resolveSubject(args: {
key: TemplateKey;
/** Optional — when omitted (e.g. system-level emails with no port
* context), only the fallback subject is returned. */
portId?: string | null;
fallback: string;
tokens?: Record<string, string | number | undefined>;
}): Promise<string> {
if (!args.portId) return args.fallback;
const override = await loadSubjectOverride(args.portId, args.key);
if (!override) return args.fallback;
return args.tokens ? applySubjectTokens(override, args.tokens) : override;
}