Files
tessera-ctl/apps/api/src/user/welcome-mail-template.service.ts
T
schalli 714f731ac9
Tessera CI/CD / Lint & Type Check (push) Successful in 51s
Tessera CI/CD / Tests (push) Successful in 1m29s
Tessera CI/CD / Desktop-Pakete bauen (push) Successful in 19s
Tessera CI/CD / Build & Publish Images (push) Successful in 4m14s
feat(admin): Anmeldehinweise der Willkommensmail in der Vorlage bearbeitbar
Neue Felder loginHintDirectory/loginHintLocal (Platzhalter, Pflicht, max. 1000),
Migration 20260930170000 (nullable, leer = Standard aus @tessera/shared).
Knopf Passwort festlegen und Gueltigkeitshinweis bleiben fest; local-no-link
wird nie erzeugt und bleibt fest. Vorschau springt beim Bearbeiten auf die
passende Kontoart. Lokal im Browser nachgewiesen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 17:56:05 +02:00

126 lines
4.3 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { DEFAULT_WELCOME_MAIL_TEXTS, type WelcomeMailTexts } from '@tessera/shared';
import { PrismaService } from '../prisma/prisma.service';
import { forTenant } from '../prisma/prisma-tenant.extension';
/**
* WelcomeMailTemplateService — eigene Vorlage der Willkommensmail
* (Administrator → Willkommensmail).
*
* Hoechstens eine Vorlage je Mandant (Tabelle "WelcomeMailTemplate",
* `tenantId` eindeutig). Gespeichert werden nur die sechs Texte; Kopf,
* Zugangsdaten, Knoepfe und Fusszeile bleiben fest in
* `welcome-mail.template.ts`. Fehlt die Vorlage, gelten
* `DEFAULT_WELCOME_MAIL_TEXTS` aus `@tessera/shared`. Die zwei
* Anmeldehinweise kamen spaeter hinzu (Spalten nullable): fehlt ein Wert in
* einer aelteren Vorlage, gilt fuer ihn der Standardtext.
*
* Mandantenbindung: jeder Zugriff laeuft ueber `forTenant(prisma, tenantId)`.
* Beim Versand ist `tenantId` der Mandant des ZIEL-Benutzers
* (`WelcomeMailService.send`), in der Verwaltung der Mandant des
* angemeldeten Administrators (`WelcomeMailTemplateController`). Die
* Platzhalter-Pruefung liegt im DTO (400 bei unbekanntem Platzhalter).
*/
/** Antwort von GET /welcome-mail-template. */
export interface WelcomeMailTemplateState {
/** `true` = eigene Vorlage gespeichert, `false` = Standardtexte. */
custom: boolean;
/** Wirksame Texte (eigene Vorlage oder Standard) — Vorbelegung im Formular. */
texts: WelcomeMailTexts;
/** Standardtexte fuer "Auf Standard zuruecksetzen". */
defaults: WelcomeMailTexts;
updatedAt: Date | null;
updatedBy: string | null;
}
const TEXT_SELECT = {
subject: true,
heading: true,
intro: true,
loginHintDirectory: true,
loginHintLocal: true,
closing: true,
updatedAt: true,
updatedBy: true,
} as const;
@Injectable()
export class WelcomeMailTemplateService {
constructor(private readonly prisma: PrismaService) {}
async getState(tenantId: string): Promise<WelcomeMailTemplateState> {
const tenantPrisma = forTenant(this.prisma, tenantId);
const row = await tenantPrisma.welcomeMailTemplate.findUnique({
where: { tenantId },
select: TEXT_SELECT,
});
const defaults = { ...DEFAULT_WELCOME_MAIL_TEXTS };
if (!row) {
return { custom: false, texts: defaults, defaults, updatedAt: null, updatedBy: null };
}
return {
custom: true,
texts: {
subject: row.subject,
heading: row.heading,
intro: row.intro,
loginHintDirectory: row.loginHintDirectory?.trim()
? row.loginHintDirectory
: defaults.loginHintDirectory,
loginHintLocal: row.loginHintLocal?.trim() ? row.loginHintLocal : defaults.loginHintLocal,
closing: row.closing,
},
defaults,
updatedAt: row.updatedAt,
updatedBy: row.updatedBy,
};
}
/** Texte fuer den Versand: eigene Vorlage des Mandanten, sonst `null` (= Standard). */
async getCustomTexts(tenantId: string): Promise<WelcomeMailTexts | null> {
const state = await this.getState(tenantId);
return state.custom ? state.texts : null;
}
/** Name des Mandanten fuer `{{firma}}`; leer, wenn der Mandant fehlt. */
async getTenantName(tenantId: string): Promise<string> {
const tenantPrisma = forTenant(this.prisma, tenantId);
const tenant = await tenantPrisma.tenant.findUnique({
where: { id: tenantId },
select: { name: true },
});
return tenant?.name ?? '';
}
async save(
tenantId: string,
texts: WelcomeMailTexts,
updatedBy: string,
): Promise<WelcomeMailTemplateState> {
const tenantPrisma = forTenant(this.prisma, tenantId);
const data = {
subject: texts.subject,
heading: texts.heading,
intro: texts.intro,
loginHintDirectory: texts.loginHintDirectory,
loginHintLocal: texts.loginHintLocal,
closing: texts.closing,
updatedBy,
};
await tenantPrisma.welcomeMailTemplate.upsert({
where: { tenantId },
create: { tenantId, ...data },
update: data,
});
return this.getState(tenantId);
}
/** "Auf Standard zuruecksetzen": loescht die eigene Vorlage (idempotent). */
async reset(tenantId: string): Promise<WelcomeMailTemplateState> {
const tenantPrisma = forTenant(this.prisma, tenantId);
await tenantPrisma.welcomeMailTemplate.deleteMany({ where: { tenantId } });
return this.getState(tenantId);
}
}