mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 21:41:16 +00:00
see comments
This commit is contained in:
parent
14e1f03b66
commit
c700a1b0e2
4 changed files with 68 additions and 46 deletions
|
|
@ -63,17 +63,20 @@ export async function createCredential(
|
|||
|
||||
// Nur wenn wir den Zugang hinterlegen – legt der Kunde ihn selbst an, braucht er keine Mail.
|
||||
if (session.isAdmin) {
|
||||
const appUrl = await getAppUrl();
|
||||
const serviceLabel = getServiceById(validatedFields.data.service).label;
|
||||
after(async () => {
|
||||
const recipient = await getMailRecipient(profileId);
|
||||
if (!recipient) return;
|
||||
await sendMail({
|
||||
to: recipient.email,
|
||||
...credentialAddedMail({ name: recipient.name, serviceLabel, appUrl }),
|
||||
type: "credential",
|
||||
idempotencyKey: `credential-${credentialId}`,
|
||||
});
|
||||
try {
|
||||
const recipient = await getMailRecipient(profileId);
|
||||
if (!recipient) return;
|
||||
await sendMail({
|
||||
to: recipient.email,
|
||||
...credentialAddedMail({ name: recipient.name, serviceLabel, appUrl: await getAppUrl() }),
|
||||
type: "credential",
|
||||
idempotencyKey: `credential-${credentialId}`,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("[mail] Hinweis-Mail zum neuen Zugang fehlgeschlagen:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,34 +41,43 @@ async function deliverInvoiceMail(profileId: string, clientId: string, invoiceId
|
|||
if (!invoice) return { error: "Rechnung nicht gefunden." };
|
||||
if (invoice.status === "cancelled") return { error: "Stornierte Rechnungen werden nicht verschickt." };
|
||||
|
||||
const [pdfData] = await getInvoicesForPdf([invoiceId]);
|
||||
const recipient = await getMailRecipient(profileId);
|
||||
if (!pdfData || !recipient) return { error: "Rechnung oder Empfänger konnte nicht geladen werden." };
|
||||
// Die Rechnung ist beim Aufruf aus createInvoice schon gespeichert: Fehler hier dürfen nicht nach oben durchschlagen.
|
||||
try {
|
||||
const [pdfData] = await getInvoicesForPdf([invoiceId]);
|
||||
const recipient = await getMailRecipient(profileId);
|
||||
if (!pdfData || !recipient) return { error: "Rechnung oder Empfänger konnte nicht geladen werden." };
|
||||
|
||||
const pdf = await renderInvoicePdf(pdfData);
|
||||
const mail = invoiceMail({
|
||||
name: recipient.name,
|
||||
invoiceNumber: pdfData.invoiceNumber,
|
||||
invoiceDate: pdfData.invoiceDate,
|
||||
dueDate: pdfData.dueDate,
|
||||
grossTotal: invoiceTotals(pdfData.items).gross,
|
||||
appUrl: await getAppUrl(),
|
||||
});
|
||||
const [pdf, appUrl] = await Promise.all([renderInvoicePdf(pdfData), getAppUrl()]);
|
||||
const mail = invoiceMail({
|
||||
name: recipient.name,
|
||||
invoiceNumber: pdfData.invoiceNumber,
|
||||
invoiceDate: pdfData.invoiceDate,
|
||||
dueDate: pdfData.dueDate,
|
||||
grossTotal: invoiceTotals(pdfData.items).gross,
|
||||
appUrl,
|
||||
});
|
||||
|
||||
const sent = await sendMail({
|
||||
to: recipient.email,
|
||||
...mail,
|
||||
type: "invoice",
|
||||
attachments: [{ filename: invoiceFileName(pdfData.invoiceNumber), content: new Uint8Array(pdf), contentType: "application/pdf" }],
|
||||
});
|
||||
if (!sent.ok) return { error: sent.error };
|
||||
const sent = await sendMail({
|
||||
to: recipient.email,
|
||||
...mail,
|
||||
type: "invoice",
|
||||
attachments: [{ filename: invoiceFileName(pdfData.invoiceNumber), content: new Uint8Array(pdf), contentType: "application/pdf" }],
|
||||
});
|
||||
if (!sent.ok) return { error: sent.error };
|
||||
|
||||
await supabase
|
||||
.from("invoices")
|
||||
.update({ email_sent_at: new Date().toISOString(), email_sent_to: recipient.email })
|
||||
.eq("id", invoiceId);
|
||||
const { error: updateError } = await supabase
|
||||
.from("invoices")
|
||||
.update({ email_sent_at: new Date().toISOString(), email_sent_to: recipient.email })
|
||||
.eq("id", invoiceId);
|
||||
if (updateError) {
|
||||
console.error(`[mail] Rechnung ${invoiceId} wurde versendet, Versandstatus aber nicht gespeichert:`, updateError.message);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error(`[mail] Rechnung ${invoiceId} konnte nicht versendet werden:`, error);
|
||||
return { error: "E-Mail konnte nicht gesendet werden." };
|
||||
}
|
||||
}
|
||||
|
||||
export async function createInvoice(draft: InvoiceDraft, options: { sendEmail?: boolean } = {}): Promise<CreateInvoiceState> {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
import "server-only"
|
||||
import { headers } from "next/headers"
|
||||
|
||||
// Basis-URL für Links in Mails. APP_URL hat Vorrang, auf Vercel greift die Produktionsdomain, sonst der aktuelle Request.
|
||||
// Basis-URL für Links in Mails. Nie aus Request-Headern in Produktion: Host/X-Forwarded-Host sind vom Client fälschbar.
|
||||
// Reihenfolge: APP_URL, Vercel-Produktionsdomain, Vercel-Deployment-URL (Preview), lokal der Request-Host.
|
||||
export async function getAppUrl() {
|
||||
if (process.env.APP_URL) return process.env.APP_URL.replace(/\/$/, "")
|
||||
if (process.env.VERCEL_ENV === "production" && process.env.VERCEL_PROJECT_PRODUCTION_URL) {
|
||||
return `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
||||
}
|
||||
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`
|
||||
|
||||
const headerList = await headers()
|
||||
const host = headerList.get("x-forwarded-host") ?? headerList.get("host") ?? "localhost:3000"
|
||||
const protocol = headerList.get("x-forwarded-proto") ?? (host.startsWith("localhost") ? "http" : "https")
|
||||
return `${protocol}://${host}`
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const host = (await headers()).get("host") ?? "localhost:3000"
|
||||
return `http://${host}`
|
||||
}
|
||||
|
||||
throw new Error("APP_URL ist nicht gesetzt – ohne feste Basis-URL werden keine Mail-Links erzeugt.")
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue