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

@@ -179,10 +179,21 @@ export default class SaleInvoicesController extends BaseController {
'/:id/mail', '/:id/mail',
[ [
...this.specificSaleInvoiceValidation, ...this.specificSaleInvoiceValidation,
body('subject').isString().optional(),
body('subject').isString().optional({ nullable: true }),
body('message').isString().optional({ nullable: true }),
body('from').isString().optional(), body('from').isString().optional(),
body('to').isString().optional(),
body('body').isString().optional(), body('to').isArray().exists(),
body('to.*').isString().isEmail().optional(),
body('cc').isArray().optional({ nullable: true }),
body('cc.*').isString().isEmail().optional(),
body('bcc').isArray().optional({ nullable: true }),
body('bcc.*').isString().isEmail().optional(),
body('attach_invoice').optional().isBoolean().toBoolean(), body('attach_invoice').optional().isBoolean().toBoolean(),
], ],
this.validationResult, this.validationResult,

View File

@@ -4,6 +4,9 @@ export const SALE_INVOICE_DELETED = 'Sale invoice deleted';
export const SALE_INVOICE_MAIL_DELIVERED = 'Sale invoice mail delivered'; export const SALE_INVOICE_MAIL_DELIVERED = 'Sale invoice mail delivered';
export const SALE_INVOICE_VIEWED = 'Sale invoice viewed'; export const SALE_INVOICE_VIEWED = 'Sale invoice viewed';
export const SALE_INVOICE_PDF_VIEWED = 'Sale invoice PDF viewed'; export const SALE_INVOICE_PDF_VIEWED = 'Sale invoice PDF viewed';
export const SALE_INVOICE_MAIL_SENT = 'Sale invoice mail sent';
export const SALE_INVOICE_MAIL_REMINDER_SENT =
'Sale invoice reminder mail sent';
export const SALE_ESTIMATE_CREATED = 'Sale estimate created'; export const SALE_ESTIMATE_CREATED = 'Sale estimate created';
export const SALE_ESTIMATE_EDITED = 'Sale estimate edited'; export const SALE_ESTIMATE_EDITED = 'Sale estimate edited';

View File

@@ -30,18 +30,14 @@ export interface AddressItem {
} }
export interface CommonMailOptions { export interface CommonMailOptions {
toAddresses: AddressItem[]; from: Array<string>;
fromAddresses: AddressItem[];
from: string;
to: string | string[];
subject: string; subject: string;
body: string; message: string;
to: Array<string>;
cc?: Array<string>;
bcc?: Array<string>;
data?: Record<string, any>; data?: Record<string, any>;
} }
export interface CommonMailOptionsDTO { export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> {
to?: string | string[];
from?: string;
subject?: string;
body?: string;
} }

View File

@@ -234,7 +234,7 @@ export enum SaleInvoiceAction {
} }
export interface SaleInvoiceMailOptions extends CommonMailOptions { export interface SaleInvoiceMailOptions extends CommonMailOptions {
attachInvoice: boolean; attachInvoice?: boolean;
} }
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO { export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
@@ -251,6 +251,7 @@ export interface ISaleInvoiceMailSend {
tenantId: number; tenantId: number;
saleInvoiceId: number; saleInvoiceId: number;
messageOptions: SendInvoiceMailDTO; messageOptions: SendInvoiceMailDTO;
formattedMessageOptions: SaleInvoiceMailOptions;
} }
export interface ISaleInvoiceMailSent { export interface ISaleInvoiceMailSent {

View File

@@ -10,6 +10,7 @@ import {
SALE_INVOICE_CREATED, SALE_INVOICE_CREATED,
SALE_INVOICE_DELETED, SALE_INVOICE_DELETED,
SALE_INVOICE_EDITED, SALE_INVOICE_EDITED,
SALE_INVOICE_MAIL_SENT,
SALE_INVOICE_PDF_VIEWED, SALE_INVOICE_PDF_VIEWED,
SALE_INVOICE_VIEWED, SALE_INVOICE_VIEWED,
} from '@/constants/event-tracker'; } from '@/constants/event-tracker';
@@ -43,6 +44,10 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
events.saleInvoice.onPdfViewed, events.saleInvoice.onPdfViewed,
this.handleTrackPdfViewedInvoiceEvent this.handleTrackPdfViewedInvoiceEvent
); );
bus.subscribe(
events.saleInvoice.onMailSent,
this.handleTrackMailSentInvoiceEvent
);
} }
private handleTrackInvoiceCreatedEvent = ({ private handleTrackInvoiceCreatedEvent = ({
@@ -90,4 +95,12 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
properties: {}, properties: {},
}); });
}; };
private handleTrackMailSentInvoiceEvent = ({ tenantId }) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: SALE_INVOICE_MAIL_SENT,
properties: {},
});
};
} }

View File

@@ -4,6 +4,7 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
import { MailTenancy } from '@/services/MailTenancy/MailTenancy'; import { MailTenancy } from '@/services/MailTenancy/MailTenancy';
import { formatSmsMessage } from '@/utils'; import { formatSmsMessage } from '@/utils';
import { Tenant } from '@/system/models'; import { Tenant } from '@/system/models';
import { castArray } from 'lodash';
@Service() @Service()
export class ContactMailNotification { export class ContactMailNotification {
@@ -14,76 +15,54 @@ export class ContactMailNotification {
private tenancy: HasTenancyService; private tenancy: HasTenancyService;
/** /**
* Parses the default message options. * Gets the default mail address of the given contact.
* @param {number} tenantId - * @param {number} tenantId - Tenant id.
* @param {number} invoiceId - * @param {number} invoiceId - Contact id.
* @param {string} subject - * @returns {Promise<Pick<CommonMailOptions, 'to' | 'from'>>}
* @param {string} body -
* @returns {Promise<SaleInvoiceMailOptions>}
*/ */
public async getDefaultMailOptions( public async getDefaultMailOptions(
tenantId: number, tenantId: number,
contactId: number, customerId: number
subject: string = '', ): Promise<Pick<CommonMailOptions, 'to' | 'from'>> {
body: string = ''
): Promise<CommonMailOptions> {
const { Customer } = this.tenancy.models(tenantId); const { Customer } = this.tenancy.models(tenantId);
const contact = await Customer.query() const customer = await Customer.query()
.findById(contactId) .findById(customerId)
.throwIfNotFound(); .throwIfNotFound();
const toAddresses = contact.contactAddresses; const toAddresses = customer.contactAddresses;
const fromAddresses = await this.mailTenancy.senders(tenantId); const fromAddresses = await this.mailTenancy.senders(tenantId);
const toAddress = toAddresses.find((a) => a.primary); const toAddress = toAddresses.find((a) => a.primary);
const fromAddress = fromAddresses.find((a) => a.primary); const fromAddress = fromAddresses.find((a) => a.primary);
const to = toAddress?.mail || ''; const to = toAddress?.mail ? castArray(toAddress?.mail) : [];
const from = fromAddress?.mail || ''; const from = fromAddress?.mail ? castArray(fromAddress?.mail) : [];
return { return { to, from };
subject,
body,
to,
from,
fromAddresses,
toAddresses,
};
} }
/** /**
* Retrieves the mail options of the given contact. * Retrieves the mail options of the given contact.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Invoice id.
* @param {string} defaultSubject - Default subject text.
* @param {string} defaultBody - Default body text.
* @returns {Promise<CommonMailOptions>} * @returns {Promise<CommonMailOptions>}
*/ */
public async getMailOptions( public async parseMailOptions(
tenantId: number, tenantId: number,
contactId: number, mailOptions: CommonMailOptions,
defaultSubject?: string, formatterArgs?: Record<string, any>
defaultBody?: string,
formatterData?: Record<string, any>
): Promise<CommonMailOptions> { ): Promise<CommonMailOptions> {
const mailOpts = await this.getDefaultMailOptions(
tenantId,
contactId,
defaultSubject,
defaultBody
);
const commonFormatArgs = await this.getCommonFormatArgs(tenantId); const commonFormatArgs = await this.getCommonFormatArgs(tenantId);
const formatArgs = { const formatArgs = {
...commonFormatArgs, ...commonFormatArgs,
...formatterData, ...formatterArgs,
}; };
const subject = formatSmsMessage(mailOpts.subject, formatArgs); const subjectFormatted = formatSmsMessage(mailOptions?.subject, formatArgs);
const body = formatSmsMessage(mailOpts.body, formatArgs); const messageFormatted = formatSmsMessage(mailOptions?.message, formatArgs);
return { return {
...mailOpts, ...mailOptions,
subject, subject: subjectFormatted,
body, message: messageFormatted,
}; };
} }

View File

@@ -1,33 +1,46 @@
import { isEmpty } from 'lodash'; import { castArray, isEmpty } from 'lodash';
import { ServiceError } from '@/exceptions'; import { ServiceError } from '@/exceptions';
import { CommonMailOptions, CommonMailOptionsDTO } from '@/interfaces'; import { CommonMailOptions } from '@/interfaces';
import { ERRORS } from './constants'; import { ERRORS } from './constants';
/** /**
* Merges the mail options with incoming options. * Merges the mail options with incoming options.
* @param {Partial<SaleInvoiceMailOptions>} mailOptions * @param {Partial<SaleInvoiceMailOptions>} mailOptions
* @param {Partial<SendInvoiceMailDTO>} overridedOptions * @param {Partial<SendInvoiceMailDTO>} overridedOptions
* @throws {ServiceError}
*/ */
export function parseAndValidateMailOptions( export function parseMailOptions(
mailOptions: Partial<CommonMailOptions>, mailOptions: CommonMailOptions,
overridedOptions: Partial<CommonMailOptionsDTO> overridedOptions: Partial<CommonMailOptions>
) { ): CommonMailOptions {
const mergedMessageOptions = { const mergedMessageOptions = {
...mailOptions, ...mailOptions,
...overridedOptions, ...overridedOptions,
}; };
if (isEmpty(mergedMessageOptions.from)) { const parsedMessageOptions = {
...mergedMessageOptions,
from: mergedMessageOptions?.from
? castArray(mergedMessageOptions?.from)
: [],
to: mergedMessageOptions?.to ? castArray(mergedMessageOptions?.to) : [],
cc: mergedMessageOptions?.cc ? castArray(mergedMessageOptions?.cc) : [],
bcc: mergedMessageOptions?.bcc ? castArray(mergedMessageOptions?.bcc) : [],
};
return parsedMessageOptions;
}
export function validateRequiredMailOptions(
mailOptions: Partial<CommonMailOptions>
) {
if (isEmpty(mailOptions.from)) {
throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND); throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND);
} }
if (isEmpty(mergedMessageOptions.to)) { if (isEmpty(mailOptions.to)) {
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND); throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
} }
if (isEmpty(mergedMessageOptions.subject)) { if (isEmpty(mailOptions.subject)) {
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND); throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
} }
if (isEmpty(mergedMessageOptions.body)) { if (isEmpty(mailOptions.message)) {
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND); throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
} }
return mergedMessageOptions;
} }

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

View File

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

View File

@@ -406,7 +406,7 @@ export const runningAmount = (amount: number) => {
}; };
}; };
export const formatSmsMessage = (message, args) => { export const formatSmsMessage = (message: string, args) => {
let formattedMessage = message; let formattedMessage = message;
Object.keys(args).forEach((key) => { Object.keys(args).forEach((key) => {

View File

@@ -29,7 +29,13 @@ export const useStripeIntegrationEditBoot = () => {
return context; return context;
}; };
export const StripeIntegrationEditBoot: React.FC = ({ children }) => { interface StripeIntegrationEditBootProps {
children: React.ReactNode;
}
export const StripeIntegrationEditBoot: React.FC<
StripeIntegrationEditBootProps
> = ({ children }) => {
const { const {
payload: { stripePaymentMethodId }, payload: { stripePaymentMethodId },
} = useDrawerContext(); } = useDrawerContext();

View File

@@ -357,7 +357,6 @@ export function useSendSaleInvoiceMail(
(value) => apiRequest.post(`sales/invoices/${value.id}/mail`, value.values), (value) => apiRequest.post(`sales/invoices/${value.id}/mail`, value.values),
{ {
onSuccess: (res) => { onSuccess: (res) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient); commonInvalidateQueries(queryClient);
}, },
...options, ...options,

View File

@@ -2,7 +2,6 @@
"name": "@bigcapital/email-components", "name": "@bigcapital/email-components",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module",
"scripts": { "scripts": {
"build": "tsc && vite build", "build": "tsc && vite build",
"lint": "eslint .", "lint": "eslint .",