feat: wip send invoice mail payment template

This commit is contained in:
Ahmed Bouhuolia
2024-10-28 18:33:16 +02:00
parent 0111b0e6ff
commit 12189f018d
15 changed files with 396 additions and 114 deletions

View File

@@ -0,0 +1,66 @@
import { Inject, Service } from 'typedi';
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
import { GetSaleInvoice } from './GetSaleInvoice';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import {
InvoicePaymentEmailProps,
renderInvoicePaymentEmail,
} from '@bigcapital/email-components';
import { GetInvoiceMailTemplateAttributesTransformer } from './GetInvoicePaymentMailAttributesTransformer';
@Service()
export class GetInvoicePaymentMail {
@Inject()
private getSaleInvoiceService: GetSaleInvoice;
@Inject()
private getBrandingTemplate: GetPdfTemplate;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieves the mail template attributes of the given invoice.
* @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Invoice id.
*/
public async getMailTemplateAttributes(tenantId: number, invoiceId: number) {
const invoice = await this.getSaleInvoiceService.getSaleInvoice(
tenantId,
invoiceId
);
const brandingTemplate = await this.getBrandingTemplate.getPdfTemplate(
tenantId,
invoice.pdfTemplateId
);
const mailTemplateAttributes = await this.transformer.transform(
tenantId,
invoice,
new GetInvoiceMailTemplateAttributesTransformer(),
{
invoice,
brandingTemplate,
}
);
return mailTemplateAttributes;
}
/**
* Retrieves the mail template html content.
* @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Invoice id.
*/
public async getMailTemplate(
tenantId: number,
invoiceId: number,
overrideAttributes?: Partial<InvoicePaymentEmailProps>
): Promise<string> {
const attributes = await this.getMailTemplateAttributes(
tenantId,
invoiceId
);
const mergedAttributes = { ...attributes, ...overrideAttributes };
return renderInvoicePaymentEmail(mergedAttributes);
}
}

View File

@@ -0,0 +1,136 @@
import { Transformer } from '@/lib/Transformer/Transformer';
export class GetInvoiceMailTemplateAttributesTransformer extends Transformer {
/**
* Include these attributes to item entry object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return [
'companyLogoUri',
'companyName',
'invoiceAmount',
'primaryColor',
'invoiceAmount',
'invoiceMessage',
'dueDate',
'dueDateLabel',
'invoiceNumber',
'invoiceNumberLabel',
'total',
'totalLabel',
'dueAmount',
'dueAmountLabel',
'viewInvoiceButtonLabel',
'viewInvoiceButtonUrl',
'items',
];
};
public excludeAttributes = (): string[] => {
return ['*'];
};
public companyLogoUri(): string {
return this.options.brandingTemplate?.attributes?.companyLogoUri;
}
public companyName(): string {
return this.context.organization.name;
}
public invoiceAmount(): string {
return this.options.invoice.totalFormatted;
}
public primaryColor(): string {
return this.options?.brandingTemplate?.attributes?.primaryColor;
}
public invoiceMessage(): string {
return '';
}
public dueDate(): string {
return this.options?.invoice?.dueDateFormatted;
}
public dueDateLabel(): string {
return 'Due {dueDate}';
}
public invoiceNumber(): string {
return this.options?.invoice?.invoiceNo;
}
public invoiceNumberLabel(): string {
return 'Invoice # {invoiceNumber}';
}
public total(): string {
return this.options.invoice?.totalFormatted;
}
public totalLabel(): string {
return 'Total';
}
public dueAmount(): string {
return this.options?.invoice.dueAmountFormatted;
}
public dueAmountLabel(): string {
return 'Due Amount';
}
public viewInvoiceButtonLabel(): string {
return 'View Invoice';
}
public viewInvoiceButtonUrl(): string {
return '';
}
public items(): Array<any> {
return this.item(
this.options.invoice?.entries,
new GetInvoiceMailTemplateItemAttrsTransformer()
);
}
}
class GetInvoiceMailTemplateItemAttrsTransformer extends Transformer {
/**
* Include these attributes to item entry object.
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['quantity', 'label', 'rate'];
};
public excludeAttributes = (): string[] => {
return ['*'];
};
public quantity(entry): string {
return entry?.quantity;
}
public label(entry): string {
console.log(entry);
return entry?.item?.name;
}
public rate(entry): string {
return entry?.rateFormatted;
}
}

View File

@@ -7,6 +7,7 @@ import {
DEFAULT_INVOICE_MAIL_CONTENT,
DEFAULT_INVOICE_MAIL_SUBJECT,
} from './constants';
import { GetInvoicePaymentMail } from './GetInvoicePaymentMail';
@Service()
export class SendSaleInvoiceMailCommon {
@@ -19,6 +20,9 @@ export class SendSaleInvoiceMailCommon {
@Inject()
private contactMailNotification: ContactMailNotification;
@Inject()
private getInvoicePaymentMail: GetInvoicePaymentMail;
/**
* Retrieves the mail options.
* @param {number} tenantId - Tenant id.
@@ -27,11 +31,11 @@ export class SendSaleInvoiceMailCommon {
* @param {string} defaultBody - Subject body.
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async getMailOption(
public async getInvoiceMailOptions(
tenantId: number,
invoiceId: number,
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
defaultBody: string = DEFAULT_INVOICE_MAIL_CONTENT
defaultMessage: string = DEFAULT_INVOICE_MAIL_CONTENT
): Promise<SaleInvoiceMailOptions> {
const { SaleInvoice } = this.tenancy.models(tenantId);
@@ -39,21 +43,51 @@ export class SendSaleInvoiceMailCommon {
.findById(invoiceId)
.throwIfNotFound();
const formatterData = await this.formatText(tenantId, invoiceId);
const mailOptions = await this.contactMailNotification.getMailOptions(
tenantId,
saleInvoice.customerId,
defaultSubject,
defaultBody,
formatterData
);
const contactMailDefaultOptions =
await this.contactMailNotification.getDefaultMailOptions(
tenantId,
saleInvoice.customerId
);
return {
...mailOptions,
...contactMailDefaultOptions,
attachInvoice: true,
subject: defaultSubject,
message: defaultMessage,
};
}
/**
* Formats the given invoice mail options.
* @param {number} tenantId
* @param {number} invoiceId
* @param {SaleInvoiceMailOptions} mailOptions
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async formatInvoiceMailOptions(
tenantId: number,
invoiceId: number,
mailOptions: SaleInvoiceMailOptions
): Promise<SaleInvoiceMailOptions> {
const formatterArgs = await this.getInvoiceFormatterArgs(
tenantId,
invoiceId
);
const parsedOptions = await this.contactMailNotification.parseMailOptions(
tenantId,
mailOptions,
formatterArgs
);
const message = await this.getInvoicePaymentMail.getMailTemplate(
tenantId,
invoiceId,
{
// # Invoice message
invoiceMessage: parsedOptions.message,
}
);
return { ...parsedOptions, message };
}
/**
* Retrieves the formatted text of the given sale invoice.
* @param {number} tenantId - Tenant id.
@@ -61,7 +95,7 @@ export class SendSaleInvoiceMailCommon {
* @param {string} text - The given text.
* @returns {Promise<string>}
*/
public formatText = async (
public getInvoiceFormatterArgs = async (
tenantId: number,
invoiceId: number
): Promise<Record<string, string | number>> => {
@@ -69,7 +103,6 @@ export class SendSaleInvoiceMailCommon {
tenantId,
invoiceId
);
return {
CustomerName: invoice.customer.displayName,
InvoiceNumber: invoice.invoiceNo,

View File

@@ -1,15 +1,23 @@
import { Inject, Service } from 'typedi';
import Mail from '@/lib/Mail';
import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces';
import {
ISaleInvoiceMailSend,
SaleInvoiceMailOptions,
SendInvoiceMailDTO,
} from '@/interfaces';
import { SaleInvoicePdf } from './SaleInvoicePdf';
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
import {
DEFAULT_INVOICE_MAIL_CONTENT,
DEFAULT_INVOICE_MAIL_SUBJECT,
} from './constants';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
import {
parseMailOptions,
validateRequiredMailOptions,
} from '@/services/MailNotification/utils';
import events from '@/subscribers/events';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { ParsedNumberSearch } from 'libphonenumber-js';
@Service()
export class SendSaleInvoiceMail {
@@ -57,12 +65,17 @@ export class SendSaleInvoiceMail {
* @param {number} saleInvoiceId
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async getMailOption(tenantId: number, saleInvoiceId: number) {
return this.invoiceMail.getMailOption(
public async getMailOption(
tenantId: number,
saleInvoiceId: number,
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
defaultMessage: string = DEFAULT_INVOICE_MAIL_CONTENT
): Promise<SaleInvoiceMailOptions> {
return this.invoiceMail.getInvoiceMailOptions(
tenantId,
saleInvoiceId,
DEFAULT_INVOICE_MAIL_SUBJECT,
DEFAULT_INVOICE_MAIL_CONTENT
defaultSubject,
defaultMessage
);
}
@@ -78,44 +91,58 @@ export class SendSaleInvoiceMail {
saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO
) {
const defaultMessageOpts = await this.getMailOption(
const defaultMessageOptions = await this.getMailOption(
tenantId,
saleInvoiceId
);
// Merge message opts with default options and validate the incoming options.
const messageOpts = parseAndValidateMailOptions(
defaultMessageOpts,
// Merges message options with default options and parses the options values.
const parsedMessageOptions = parseMailOptions(
defaultMessageOptions,
messageOptions
);
const mail = new Mail()
.setSubject(messageOpts.subject)
.setTo(messageOpts.to)
.setContent(messageOpts.body);
// Validates the required mail options.
validateRequiredMailOptions(parsedMessageOptions);
if (messageOpts.attachInvoice) {
// Retrieves document buffer of the invoice pdf document.
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
const formattedMessageOptions =
await this.invoiceMail.formatInvoiceMailOptions(
tenantId,
saleInvoiceId
saleInvoiceId,
parsedMessageOptions
);
const mail = new Mail()
.setSubject(formattedMessageOptions.subject)
.setTo(formattedMessageOptions.to)
.setContent(formattedMessageOptions.message);
// Attach invoice document.
if (formattedMessageOptions.attachInvoice) {
// Retrieves document buffer of the invoice pdf document.
const [invoicePdfBuffer, invoiceFilename] =
await this.invoicePdf.saleInvoicePdf(tenantId, saleInvoiceId);
mail.setAttachments([
{ filename: 'invoice.pdf', content: invoicePdfBuffer },
{ filename: `${invoiceFilename}.pdf`, content: invoicePdfBuffer },
]);
}
// Triggers the event `onSaleInvoiceSend`.
await this.eventPublisher.emitAsync(events.saleInvoice.onMailSend, {
const eventPayload = {
tenantId,
saleInvoiceId,
messageOptions,
} as ISaleInvoiceMailSend);
formattedMessageOptions,
} as ISaleInvoiceMailSend;
// Triggers the event `onSaleInvoiceSend`.
await this.eventPublisher.emitAsync(
events.saleInvoice.onMailSend,
eventPayload
);
await mail.send();
// Triggers the event `onSaleInvoiceSend`.
await this.eventPublisher.emitAsync(events.saleInvoice.onMailSent, {
tenantId,
saleInvoiceId,
messageOptions,
} as ISaleInvoiceMailSend);
await this.eventPublisher.emitAsync(
events.saleInvoice.onMailSent,
eventPayload
);
}
}