refactor: notification mail services

This commit is contained in:
Ahmed Bouhuolia
2024-11-02 14:59:57 +02:00
parent 0cc80bc179
commit d09aebcebb
19 changed files with 270 additions and 123 deletions

View File

@@ -13,15 +13,19 @@ export const SALE_ESTIMATE_EDITED = 'Sale estimate edited';
export const SALE_ESTIMATE_DELETED = 'Sale estimate deleted'; export const SALE_ESTIMATE_DELETED = 'Sale estimate deleted';
export const SALE_ESTIMATE_PDF_VIEWED = 'Sale estimate PDF viewed'; export const SALE_ESTIMATE_PDF_VIEWED = 'Sale estimate PDF viewed';
export const SALE_ESTIMATE_VIEWED = 'Sale estimate viewed'; export const SALE_ESTIMATE_VIEWED = 'Sale estimate viewed';
export const SALE_ESTIMATE_MAIL_SENT = 'Sale estimate mail sent';
export const PAYMENT_RECEIVED_CREATED = 'Payment received created'; export const PAYMENT_RECEIVED_CREATED = 'Payment received created';
export const PAYMENT_RECEIVED_EDITED = 'payment received edited'; export const PAYMENT_RECEIVED_EDITED = 'payment received edited';
export const PAYMENT_RECEIVED_DELETED = 'Payment received deleted'; export const PAYMENT_RECEIVED_DELETED = 'Payment received deleted';
export const PAYMENT_RECEIVED_PDF_VIEWED = 'Payment received PDF viewed'; export const PAYMENT_RECEIVED_PDF_VIEWED = 'Payment received PDF viewed';
export const PAYMENT_RECEIVED_MAIL_SENT = 'Payment received mail sent';
export const SALE_RECEIPT_PDF_VIEWED = 'Sale credit PDF viewed'; export const SALE_RECEIPT_PDF_VIEWED = 'Sale credit PDF viewed';
export const SALE_RECEIPT_MAIL_SENT = 'Sale credit mail sent';
export const CREDIT_NOTE_PDF_VIEWED = 'Credit note PDF viewed'; export const CREDIT_NOTE_PDF_VIEWED = 'Credit note PDF viewed';
export const CREDIT_NOTE_MAIL_SENT = 'Credit note mail sent';
export const BILL_CREATED = 'Bill created'; export const BILL_CREATED = 'Bill created';
export const BILL_EDITED = 'Bill edited'; export const BILL_EDITED = 'Bill edited';

View File

@@ -36,7 +36,7 @@ export interface CommonMailOptions {
to: Array<string>; to: Array<string>;
cc?: Array<string>; cc?: Array<string>;
bcc?: Array<string>; bcc?: Array<string>;
data?: Record<string, any>; formatArgs?: Record<string, any>;
} }
export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> { export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> {

View File

@@ -12,6 +12,7 @@ import {
PAYMENT_RECEIVED_EDITED, PAYMENT_RECEIVED_EDITED,
PAYMENT_RECEIVED_DELETED, PAYMENT_RECEIVED_DELETED,
PAYMENT_RECEIVED_PDF_VIEWED, PAYMENT_RECEIVED_PDF_VIEWED,
PAYMENT_RECEIVED_MAIL_SENT,
} from '@/constants/event-tracker'; } from '@/constants/event-tracker';
@Service() @Service()
@@ -39,6 +40,10 @@ export class PaymentReceivedEventsTracker extends EventSubscriber {
events.paymentReceive.onPdfViewed, events.paymentReceive.onPdfViewed,
this.handleTrackPdfViewedPaymentReceivedEvent this.handleTrackPdfViewedPaymentReceivedEvent
); );
bus.subscribe(
events.paymentReceive.onMailSent,
this.handleTrackMailSentPaymentReceivedEvent
);
} }
private handleTrackPaymentReceivedCreatedEvent = ({ private handleTrackPaymentReceivedCreatedEvent = ({
@@ -80,4 +85,14 @@ export class PaymentReceivedEventsTracker extends EventSubscriber {
properties: {}, properties: {},
}); });
}; };
private handleTrackMailSentPaymentReceivedEvent = ({
tenantId,
}: IPaymentReceivedDeletedPayload) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: PAYMENT_RECEIVED_MAIL_SENT,
properties: {},
});
};
} }

View File

@@ -13,6 +13,7 @@ import {
SALE_ESTIMATE_DELETED, SALE_ESTIMATE_DELETED,
SALE_ESTIMATE_PDF_VIEWED, SALE_ESTIMATE_PDF_VIEWED,
SALE_ESTIMATE_VIEWED, SALE_ESTIMATE_VIEWED,
SALE_ESTIMATE_MAIL_SENT,
} from '@/constants/event-tracker'; } from '@/constants/event-tracker';
@Service() @Service()
@@ -44,6 +45,10 @@ export class SaleEstimateEventsTracker extends EventSubscriber {
events.saleEstimate.onViewed, events.saleEstimate.onViewed,
this.handleTrackViewedEstimateEvent this.handleTrackViewedEstimateEvent
); );
bus.subscribe(
events.saleEstimate.onMailSent,
this.handleTrackMailSentEstimateEvent
);
} }
private handleTrackEstimateCreatedEvent = ({ private handleTrackEstimateCreatedEvent = ({
@@ -93,4 +98,12 @@ export class SaleEstimateEventsTracker extends EventSubscriber {
properties: {}, properties: {},
}); });
}; };
private handleTrackMailSentEstimateEvent = ({ tenantId }) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: SALE_ESTIMATE_MAIL_SENT,
properties: {},
});
};
} }

View File

@@ -46,7 +46,7 @@ export class ContactMailNotification {
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @returns {Promise<CommonMailOptions>} * @returns {Promise<CommonMailOptions>}
*/ */
public async parseMailOptions( public async formatMailOptions(
tenantId: number, tenantId: number,
mailOptions: CommonMailOptions, mailOptions: CommonMailOptions,
formatterArgs?: Record<string, any> formatterArgs?: Record<string, any>

View File

@@ -44,3 +44,13 @@ export function validateRequiredMailOptions(
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND); throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
} }
} }
export const mergeAndValidateMailOptions = (
mailOptions: CommonMailOptions,
overridedOptions: Partial<CommonMailOptions>
): CommonMailOptions => {
const parsedMessageOptions = parseMailOptions(mailOptions, overridedOptions);
validateRequiredMailOptions(parsedMessageOptions);
return parsedMessageOptions;
};

View File

@@ -13,9 +13,10 @@ import {
SaleEstimateMailOptionsDTO, SaleEstimateMailOptionsDTO,
} from '@/interfaces'; } from '@/interfaces';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
import { transformEstimateToMailDataArgs } from './utils';
@Service() @Service()
export class SendSaleEstimateMail { export class SendSaleEstimateMail {
@@ -65,23 +66,17 @@ export class SendSaleEstimateMail {
} }
/** /**
* Formates the text of the mail. * Formate the text of the mail.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {number} estimateId - Estimate id. * @param {number} estimateId - Estimate id.
* @returns {Promise<Record<string, any>>} * @returns {Promise<Record<string, any>>}
*/ */
public formatterData = async (tenantId: number, estimateId: number) => { public formatterArgs = async (tenantId: number, estimateId: number) => {
const estimate = await this.getSaleEstimateService.getEstimate( const estimate = await this.getSaleEstimateService.getEstimate(
tenantId, tenantId,
estimateId estimateId
); );
return { return transformEstimateToMailDataArgs(estimate);
CustomerName: estimate.customer.displayName,
EstimateNumber: estimate.estimateNumber,
EstimateDate: estimate.formattedEstimateDate,
EstimateAmount: estimate.formattedAmount,
EstimateExpirationDate: estimate.formattedExpirationDate,
};
}; };
/** /**
@@ -92,7 +87,9 @@ export class SendSaleEstimateMail {
*/ */
public getMailOptions = async ( public getMailOptions = async (
tenantId: number, tenantId: number,
saleEstimateId: number saleEstimateId: number,
defaultSubject: string = DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT,
defaultMessage: string = DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT
): Promise<SaleEstimateMailOptions> => { ): Promise<SaleEstimateMailOptions> => {
const { SaleEstimate } = this.tenancy.models(tenantId); const { SaleEstimate } = this.tenancy.models(tenantId);
@@ -100,22 +97,45 @@ export class SendSaleEstimateMail {
.findById(saleEstimateId) .findById(saleEstimateId)
.throwIfNotFound(); .throwIfNotFound();
const formatterData = await this.formatterData(tenantId, saleEstimateId); const formatArgs = await this.formatterArgs(tenantId, saleEstimateId);
const mailOptions = await this.contactMailNotification.getMailOptions( const mailOptions =
tenantId, await this.contactMailNotification.getDefaultMailOptions(
saleEstimate.customerId, tenantId,
DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT, saleEstimate.customerId
DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT, );
formatterData
);
return { return {
...mailOptions, ...mailOptions,
data: formatterData, message: defaultMessage,
subject: defaultSubject,
attachEstimate: true, attachEstimate: true,
formatArgs,
}; };
}; };
/**
* Formats the given mail options.
* @param {number} tenantId
* @param {number} saleEstimateId
* @param {SaleEstimateMailOptions} mailOptions
* @returns {Promise<SaleEstimateMailOptions>}
*/
public formatMailOptions = async (
tenantId: number,
saleEstimateId: number,
mailOptions: SaleEstimateMailOptions
): Promise<SaleEstimateMailOptions> => {
const formatterArgs = await this.formatterArgs(tenantId, saleEstimateId);
const formattedOptions =
await this.contactMailNotification.formatMailOptions(
tenantId,
mailOptions,
formatterArgs
);
return { ...formattedOptions };
};
/** /**
* Sends the mail notification of the given sale estimate. * Sends the mail notification of the given sale estimate.
* @param {number} tenantId * @param {number} tenantId
@@ -133,27 +153,52 @@ export class SendSaleEstimateMail {
saleEstimateId saleEstimateId
); );
// Overrides and validates the given mail options. // Overrides and validates the given mail options.
const messageOpts = parseAndValidateMailOptions( const parsedMessageOptions = mergeAndValidateMailOptions(
localMessageOpts, localMessageOpts,
messageOptions messageOptions
) as SaleEstimateMailOptions;
const formattedOptions = await this.formatMailOptions(
tenantId,
saleEstimateId,
parsedMessageOptions
); );
const mail = new Mail() const mail = new Mail()
.setSubject(messageOpts.subject) .setSubject(formattedOptions.subject)
.setTo(messageOpts.to) .setTo(formattedOptions.to)
.setContent(messageOpts.body); .setContent(formattedOptions.message);
// Attaches the estimate pdf to the mail.
if (formattedOptions.attachEstimate) {
// Retrieves the estimate pdf and attaches it to the mail.
const [estimatePdfBuffer, estimateFilename] =
await this.estimatePdf.getSaleEstimatePdf(tenantId, saleEstimateId);
if (messageOpts.attachEstimate) {
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
tenantId,
saleEstimateId
);
mail.setAttachments([ mail.setAttachments([
{ {
filename: messageOpts.data?.EstimateNumber || 'estimate.pdf', filename: `${estimateFilename}.pdf`,
content: estimatePdfBuffer, content: estimatePdfBuffer,
}, },
]); ]);
} }
const eventPayload = {
tenantId,
saleEstimateId,
messageOptions,
formattedOptions,
};
// Triggers `onSaleEstimateMailSend` event.
await this.eventPublisher.emitAsync(
events.saleEstimate.onMailSend,
eventPayload as ISaleEstimateMailPresendEvent
);
await mail.send(); await mail.send();
// Triggers `onSaleEstimateMailSent` event.
await this.eventPublisher.emitAsync(
events.saleEstimate.onMailSent,
eventPayload as ISaleEstimateMailPresendEvent
);
} }
} }

View File

@@ -1,16 +1,16 @@
export const DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT = export const DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT =
'Estimate {EstimateNumber} is awaiting your approval'; 'Estimate {Estimate Number} is awaiting your approval';
export const DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT = `<p>Dear {CustomerName}</p> export const DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT = `<p>Dear {Customer Name}</p>
<p>Thank you for your business, You can view or print your estimate from attachements.</p> <p>Thank you for your business, You can view or print your estimate from attachements.</p>
<p> <p>
Estimate <strong>#{EstimateNumber}</strong><br /> Estimate <strong>#{Estimate Number}</strong><br />
Expiration Date : <strong>{EstimateExpirationDate}</strong><br /> Expiration Date : <strong>{Estimate Expiration Date}</strong><br />
Amount : <strong>{EstimateAmount}</strong></br /> Amount : <strong>{Estimate Amount}</strong></br />
</p> </p>
<p> <p>
<i>Regards</i><br /> <i>Regards</i><br />
<i>{CompanyName}</i> <i>{Company Name}</i>
</p> </p>
`; `;

View File

@@ -22,3 +22,13 @@ export const transformEstimateToPdfTemplate = (
customerAddress: contactAddressTextFormat(estimate.customer), customerAddress: contactAddressTextFormat(estimate.customer),
}; };
}; };
export const transformEstimateToMailDataArgs = (estimate: any) => {
return {
'Customer Name': estimate.customer.displayName,
'Estimate Number': estimate.estimateNumber,
'Estimate Date': estimate.formattedEstimateDate,
'Estimate Amount': estimate.formattedAmount,
'Estimate Expiration Date': estimate.formattedExpirationDate,
};
};

View File

@@ -21,6 +21,7 @@ export class GetInvoicePaymentMail {
/** /**
* Retrieves the mail template attributes of the given invoice. * Retrieves the mail template attributes of the given invoice.
* Invoice template attributes are composed of the invoice and branding template attributes.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Invoice id. * @param {number} invoiceId - Invoice id.
*/ */

View File

@@ -75,20 +75,22 @@ export class SendSaleInvoiceMailCommon {
tenantId, tenantId,
invoiceId invoiceId
); );
const parsedOptions = await this.contactMailNotification.parseMailOptions( const formattedOptions =
tenantId, await this.contactMailNotification.formatMailOptions(
mailOptions, tenantId,
formatterArgs mailOptions,
); formatterArgs
);
const message = await this.getInvoicePaymentMail.getMailTemplate( const message = await this.getInvoicePaymentMail.getMailTemplate(
tenantId, tenantId,
invoiceId, invoiceId,
{ {
// # Invoice message // # Invoice message
invoiceMessage: parsedOptions.message, invoiceMessage: formattedOptions.message,
preview: formattedOptions.message,
} }
); );
return { ...parsedOptions, message }; return { ...formattedOptions, message };
} }
/** /**
@@ -111,13 +113,13 @@ export class SendSaleInvoiceMailCommon {
); );
return { return {
...commonArgs, ...commonArgs,
['Customer Name']: invoice.customer.displayName, 'Customer Name': invoice.customer.displayName,
['Invoice Number']: invoice.invoiceNo, 'Invoice Number': invoice.invoiceNo,
['Invoice DueAmount']: invoice.dueAmountFormatted, 'Invoice DueAmount': invoice.dueAmountFormatted,
['Invoice DueDate']: invoice.dueDateFormatted, 'Invoice DueDate': invoice.dueDateFormatted,
['Invoice Date']: invoice.invoiceDateFormatted, 'Invoice Date': invoice.invoiceDateFormatted,
['Invoice Amount']: invoice.totalFormatted, 'Invoice Amount': invoice.totalFormatted,
['Overdue Days']: invoice.overdueDays, 'Overdue Days': invoice.overdueDays,
}; };
}; };
} }

View File

@@ -3,10 +3,7 @@ import Mail from '@/lib/Mail';
import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces'; import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces';
import { SaleInvoicePdf } from './SaleInvoicePdf'; import { SaleInvoicePdf } from './SaleInvoicePdf';
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon'; import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
import { import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
parseMailOptions,
validateRequiredMailOptions,
} from '@/services/MailNotification/utils';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
@@ -18,12 +15,12 @@ export class SendSaleInvoiceMail {
@Inject() @Inject()
private invoiceMail: SendSaleInvoiceMailCommon; private invoiceMail: SendSaleInvoiceMailCommon;
@Inject('agenda')
private agenda: any;
@Inject() @Inject()
private eventPublisher: EventPublisher; private eventPublisher: EventPublisher;
@Inject('agenda')
private agenda: any;
/** /**
* Sends the invoice mail of the given sale invoice. * Sends the invoice mail of the given sale invoice.
* @param {number} tenantId * @param {number} tenantId
@@ -67,13 +64,10 @@ export class SendSaleInvoiceMail {
saleInvoiceId saleInvoiceId
); );
// Merges message options with default options and parses the options values. // Merges message options with default options and parses the options values.
const parsedMessageOptions = parseMailOptions( const parsedMessageOptions = mergeAndValidateMailOptions(
defaultMessageOptions, defaultMessageOptions,
messageOptions messageOptions
); );
// Validates the required mail options.
validateRequiredMailOptions(parsedMessageOptions);
const formattedMessageOptions = const formattedMessageOptions =
await this.invoiceMail.formatInvoiceMailOptions( await this.invoiceMail.formatInvoiceMailOptions(
tenantId, tenantId,

View File

@@ -13,9 +13,10 @@ import {
} from './constants'; } from './constants';
import { GetPaymentReceived } from './GetPaymentReceived'; import { GetPaymentReceived } from './GetPaymentReceived';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
import { transformPaymentReceivedToMailDataArgs } from './utils';
@Service() @Service()
export class SendPaymentReceiveMailNotification { export class SendPaymentReceiveMailNotification {
@@ -77,15 +78,19 @@ export class SendPaymentReceiveMailNotification {
.findById(paymentId) .findById(paymentId)
.throwIfNotFound(); .throwIfNotFound();
const formatterData = await this.textFormatter(tenantId, paymentId); const formatArgs = await this.textFormatter(tenantId, paymentId);
return this.contactMailNotification.getMailOptions( const mailOptions =
tenantId, await this.contactMailNotification.getDefaultMailOptions(
paymentReceive.customerId, tenantId,
DEFAULT_PAYMENT_MAIL_SUBJECT, paymentReceive.customerId
DEFAULT_PAYMENT_MAIL_CONTENT, );
formatterData return {
); ...mailOptions,
subject: DEFAULT_PAYMENT_MAIL_SUBJECT,
message: DEFAULT_PAYMENT_MAIL_CONTENT,
...formatArgs,
};
}; };
/** /**
@@ -103,12 +108,7 @@ export class SendPaymentReceiveMailNotification {
tenantId, tenantId,
invoiceId invoiceId
); );
return { return transformPaymentReceivedToMailDataArgs(payment);
CustomerName: payment.customer.displayName,
PaymentNumber: payment.payment_receive_no,
PaymentDate: payment.formattedPaymentDate,
PaymentAmount: payment.formattedAmount,
};
}; };
/** /**
@@ -128,14 +128,31 @@ export class SendPaymentReceiveMailNotification {
paymentReceiveId paymentReceiveId
); );
// Parsed message opts with default options. // Parsed message opts with default options.
const parsedMessageOpts = parseAndValidateMailOptions( const parsedMessageOpts = mergeAndValidateMailOptions(
defaultMessageOpts, defaultMessageOpts,
messageDTO messageDTO
); );
await new Mail() const mail = new Mail()
.setSubject(parsedMessageOpts.subject) .setSubject(parsedMessageOpts.subject)
.setTo(parsedMessageOpts.to) .setTo(parsedMessageOpts.to)
.setContent(parsedMessageOpts.body) .setContent(parsedMessageOpts.message);
.send();
const eventPayload = {
tenantId,
paymentReceiveId,
messageOptions: parsedMessageOpts,
};
// Triggers `onPaymentReceiveMailSend` event.
await this.eventPublisher.emitAsync(
events.paymentReceive.onMailSend,
eventPayload
);
await mail.send();
// Triggers `onPaymentReceiveMailSent` event.
await this.eventPublisher.emitAsync(
events.paymentReceive.onMailSent,
eventPayload
);
} }
} }

View File

@@ -21,3 +21,12 @@ export const transformPaymentReceivedToPdfTemplate = (
customerAddress: contactAddressTextFormat(payment.customer), customerAddress: contactAddressTextFormat(payment.customer),
}; };
}; };
export const transformPaymentReceivedToMailDataArgs = (payment: any) => {
return {
'Customer Name': payment.customer.displayName,
'Payment Number': payment.paymentReceiveNo,
'Payment Date': payment.formattedPaymentDate,
'Payment Amount': payment.formattedAmount,
};
};

View File

@@ -13,9 +13,10 @@ import {
SaleReceiptMailOptsDTO, SaleReceiptMailOptsDTO,
} from '@/interfaces'; } from '@/interfaces';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification'; import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils'; import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import events from '@/subscribers/events'; import events from '@/subscribers/events';
import { transformReceiptToMailDataArgs } from './utils';
@Service() @Service()
export class SaleReceiptMailNotification { export class SaleReceiptMailNotification {
@@ -79,18 +80,19 @@ export class SaleReceiptMailNotification {
.findById(saleReceiptId) .findById(saleReceiptId)
.throwIfNotFound(); .throwIfNotFound();
const formattedData = await this.textFormatter(tenantId, saleReceiptId); const formatArgs = await this.textFormatterArgs(tenantId, saleReceiptId);
const mailOpts = await this.contactMailNotification.getMailOptions( const mailOptions =
tenantId, await this.contactMailNotification.getDefaultMailOptions(
saleReceipt.customerId, tenantId,
DEFAULT_RECEIPT_MAIL_SUBJECT, saleReceipt.customerId
DEFAULT_RECEIPT_MAIL_CONTENT, );
formattedData
);
return { return {
...mailOpts, ...mailOptions,
message: DEFAULT_RECEIPT_MAIL_CONTENT,
subject: DEFAULT_RECEIPT_MAIL_SUBJECT,
attachReceipt: true, attachReceipt: true,
formatArgs,
}; };
} }
@@ -101,7 +103,7 @@ export class SaleReceiptMailNotification {
* @param {string} text - The given text. * @param {string} text - The given text.
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
public textFormatter = async ( public textFormatterArgs = async (
tenantId: number, tenantId: number,
receiptId: number receiptId: number
): Promise<Record<string, string>> => { ): Promise<Record<string, string>> => {
@@ -109,19 +111,14 @@ export class SaleReceiptMailNotification {
tenantId, tenantId,
receiptId receiptId
); );
return { return transformReceiptToMailDataArgs(receipt);
CustomerName: receipt.customer.displayName,
ReceiptNumber: receipt.receiptNumber,
ReceiptDate: receipt.formattedReceiptDate,
ReceiptAmount: receipt.formattedAmount,
};
}; };
/** /**
* Triggers the mail notification of the given sale receipt. * Triggers the mail notification of the given sale receipt.
* @param {number} tenantId - Tenant id. * @param {number} tenantId - Tenant id.
* @param {number} saleReceiptId - Sale receipt id. * @param {number} saleReceiptId - Sale receipt id.
* @param {SaleReceiptMailOpts} messageDTO - Overrided message options. * @param {SaleReceiptMailOpts} messageDTO - message options.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public async sendMail( public async sendMail(
@@ -129,30 +126,45 @@ export class SaleReceiptMailNotification {
saleReceiptId: number, saleReceiptId: number,
messageOpts: SaleReceiptMailOptsDTO messageOpts: SaleReceiptMailOptsDTO
) { ) {
const defaultMessageOpts = await this.getMailOptions( const defaultMessageOptions = await this.getMailOptions(
tenantId, tenantId,
saleReceiptId saleReceiptId
); );
// Merges message opts with default options. // Merges message opts with default options.
const parsedMessageOpts = parseAndValidateMailOptions( const parsedMessageOpts = mergeAndValidateMailOptions(
defaultMessageOpts, defaultMessageOptions,
messageOpts messageOpts
); ) as SaleReceiptMailOpts;
const mail = new Mail() const mail = new Mail()
.setSubject(parsedMessageOpts.subject) .setSubject(parsedMessageOpts.subject)
.setTo(parsedMessageOpts.to) .setTo(parsedMessageOpts.to)
.setContent(parsedMessageOpts.body); .setContent(parsedMessageOpts.message);
// Attaches the receipt pdf document.
if (parsedMessageOpts.attachReceipt) { if (parsedMessageOpts.attachReceipt) {
// Retrieves document buffer of the receipt pdf document. // Retrieves document buffer of the receipt pdf document.
const receiptPdfBuffer = await this.receiptPdfService.saleReceiptPdf( const [receiptPdfBuffer, filename] =
tenantId, await this.receiptPdfService.saleReceiptPdf(tenantId, saleReceiptId);
saleReceiptId
);
mail.setAttachments([ mail.setAttachments([
{ filename: 'receipt.pdf', content: receiptPdfBuffer }, { filename: `${filename}.pdf`, content: receiptPdfBuffer },
]); ]);
} }
const eventPayload = {
tenantId,
saleReceiptId,
messageOptions: {},
};
await this.eventPublisher.emitAsync(
events.saleReceipt.onMailSend,
eventPayload
);
await mail.send(); await mail.send();
await this.eventPublisher.emitAsync(
events.saleReceipt.onMailSent,
eventPayload
);
} }
} }

View File

@@ -35,7 +35,10 @@ export class SaleReceiptsPdf {
* @param {number} saleInvoiceId - * @param {number} saleInvoiceId -
* @returns {Promise<Buffer>} * @returns {Promise<Buffer>}
*/ */
public async saleReceiptPdf(tenantId: number, saleReceiptId: number) { public async saleReceiptPdf(
tenantId: number,
saleReceiptId: number
): Promise<[Buffer, string]> {
const filename = await this.getSaleReceiptFilename(tenantId, saleReceiptId); const filename = await this.getSaleReceiptFilename(tenantId, saleReceiptId);
const brandingAttributes = await this.getReceiptBrandingAttributes( const brandingAttributes = await this.getReceiptBrandingAttributes(

View File

@@ -1,16 +1,16 @@
export const DEFAULT_RECEIPT_MAIL_SUBJECT = export const DEFAULT_RECEIPT_MAIL_SUBJECT =
'Receipt {ReceiptNumber} from {CompanyName}'; 'Receipt {Receipt Number} from {Company Name}';
export const DEFAULT_RECEIPT_MAIL_CONTENT = ` export const DEFAULT_RECEIPT_MAIL_CONTENT = `
<p>Dear {CustomerName}</p> <p>Dear {Customer Name}</p>
<p>Thank you for your business, You can view or print your receipt from attachements.</p> <p>Thank you for your business, You can view or print your receipt from attachements.</p>
<p> <p>
Receipt <strong>#{ReceiptNumber}</strong><br /> Receipt <strong>#{Receipt Number}</strong><br />
Amount : <strong>{ReceiptAmount}</strong></br /> Amount : <strong>{Receipt Amount}</strong></br />
</p> </p>
<p> <p>
<i>Regards</i><br /> <i>Regards</i><br />
<i>{CompanyName}</i> <i>{Company Name}</i>
</p> </p>
`; `;

View File

@@ -1,9 +1,12 @@
import { ISaleReceipt, ISaleReceiptBrandingTemplateAttributes } from "@/interfaces"; import {
import { contactAddressTextFormat } from "@/utils/address-text-format"; ISaleReceipt,
ISaleReceiptBrandingTemplateAttributes,
} from '@/interfaces';
import { contactAddressTextFormat } from '@/utils/address-text-format';
export const transformReceiptToBrandingTemplateAttributes = (
saleReceipt: ISaleReceipt
export const transformReceiptToBrandingTemplateAttributes = (saleReceipt: ISaleReceipt): Partial<ISaleReceiptBrandingTemplateAttributes> => { ): Partial<ISaleReceiptBrandingTemplateAttributes> => {
return { return {
total: saleReceipt.formattedAmount, total: saleReceipt.formattedAmount,
subtotal: saleReceipt.formattedSubtotal, subtotal: saleReceipt.formattedSubtotal,
@@ -18,4 +21,13 @@ export const transformReceiptToBrandingTemplateAttributes = (saleReceipt: ISaleR
receiptDate: saleReceipt.formattedReceiptDate, receiptDate: saleReceipt.formattedReceiptDate,
customerAddress: contactAddressTextFormat(saleReceipt.customer), customerAddress: contactAddressTextFormat(saleReceipt.customer),
}; };
} };
export const transformReceiptToMailDataArgs = (saleReceipt: any) => {
return {
'Customer Name': saleReceipt.customer.displayName,
'Receipt Number': saleReceipt.receiptNumber,
'Receipt Date': saleReceipt.formattedReceiptDate,
'Receipt Amount': saleReceipt.formattedAmount,
};
};

View File

@@ -213,7 +213,7 @@ export default {
onPreMailSend: 'onSaleEstimatePreMailSend', onPreMailSend: 'onSaleEstimatePreMailSend',
onMailSend: 'onSaleEstimateMailSend', onMailSend: 'onSaleEstimateMailSend',
onMailSent: 'onSaleEstimateMailSend', onMailSent: 'onSaleEstimateMailSent',
}, },
/** /**