mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* feat: add proration invoice and reminder email templates Add email templates for monthly proration billing notifications: - ProrationInvoiceEmail: Sent when invoice is created for additional seats - ProrationReminderEmail: Sent 7 days later if invoice remains unpaid Includes: - React email templates using V2BaseEmailHtml - BaseEmail classes for rendering - Billing email service functions - Translation keys for email content Co-Authored-By: Claude Opus 4.5 <[email protected]> * feat: add tasker integration for proration email notifications Add task handlers for proration billing email flow: - sendProrationInvoiceEmail: Sends invoice email and schedules reminder - sendProrationReminderEmail: Sends reminder if invoice still unpaid - cancelProrationReminder: Cancels scheduled reminder on payment success The calling job should trigger these tasks after: 1. MonthlyProrationService.createProrationForTeam() succeeds with invoice 2. handleProrationPaymentSuccess() is called (to cancel reminder) Co-Authored-By: Claude Opus 4.5 <[email protected]> * feat: i18n + feedback from cubic + morgan * fix: use default tasker import instead of non-existent getTasker Co-Authored-By: unknown <> * fix: add trigger tasks plus DI * use for await * fix query * fix: remove duplicate JOIN alias in findTeamMembersWithPermission query The raw SQL query had two INNER JOINs using the same alias 'u': - INNER JOIN "users" u ON m."userId" = u.id - INNER JOIN "User" u ON m."userId" = u.id This would cause a SQL error at runtime. Removed the duplicate JOIN with incorrect table name ("User" instead of "users"). Fixes issue identified by Cubic AI review. Co-Authored-By: unknown <> --------- Co-authored-by: Claude Opus 4.5 <[email protected]> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
74 lines
1.8 KiB
TypeScript
74 lines
1.8 KiB
TypeScript
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
|
|
import type { TFunction } from "i18next";
|
|
import renderEmail from "../src/renderEmail";
|
|
import BaseEmail from "./_base-email";
|
|
|
|
export interface ProrationReminderEmailParams {
|
|
user: {
|
|
name: string | null;
|
|
email: string;
|
|
t: TFunction;
|
|
};
|
|
team: {
|
|
id: number;
|
|
name: string | null;
|
|
};
|
|
proration: {
|
|
monthKey: string;
|
|
netSeatIncrease: number;
|
|
proratedAmount: number;
|
|
};
|
|
invoiceUrl?: string | null;
|
|
}
|
|
|
|
export default class ProrationReminderEmail extends BaseEmail {
|
|
user: {
|
|
name: string;
|
|
email: string;
|
|
t: TFunction;
|
|
};
|
|
team: {
|
|
id: number;
|
|
name: string;
|
|
};
|
|
proration: {
|
|
monthKey: string;
|
|
netSeatIncrease: number;
|
|
proratedAmount: number;
|
|
};
|
|
invoiceUrl?: string | null;
|
|
|
|
constructor(params: ProrationReminderEmailParams) {
|
|
super();
|
|
this.user = { ...params.user, name: params.user.name || "" };
|
|
this.team = { ...params.team, name: params.team.name || "" };
|
|
this.proration = params.proration;
|
|
this.invoiceUrl = params.invoiceUrl;
|
|
}
|
|
|
|
protected async getNodeMailerPayload(): Promise<Record<string, unknown>> {
|
|
return {
|
|
from: `${EMAIL_FROM_NAME} <${this.getMailerOptions().from}>`,
|
|
to: this.user.email,
|
|
subject: this.user.t("proration_reminder_subject", {
|
|
teamName: this.team.name,
|
|
}),
|
|
html: await renderEmail("ProrationReminderEmail", {
|
|
user: this.user,
|
|
team: this.team,
|
|
proration: this.proration,
|
|
invoiceUrl: this.invoiceUrl,
|
|
}),
|
|
text: this.getTextBody(),
|
|
};
|
|
}
|
|
|
|
protected getTextBody(): string {
|
|
const formattedAmount = (this.proration.proratedAmount / 100).toFixed(2);
|
|
return this.user.t("proration_reminder_text", {
|
|
amount: formattedAmount,
|
|
teamName: this.team.name,
|
|
});
|
|
}
|
|
}
|