fix: mail services

This commit is contained in:
Ahmed Bouhuolia
2024-11-09 22:23:52 +02:00
parent 94223b6ebf
commit aa7e5d4ae9
15 changed files with 168 additions and 64 deletions

View File

@@ -37,7 +37,8 @@ export interface CommonMailOptions {
cc?: Array<string>; cc?: Array<string>;
bcc?: Array<string>; bcc?: Array<string>;
formatArgs?: Record<string, any>; formatArgs?: Record<string, any>;
toOptions: Array<AddressItem>;
fromOptions: Array<AddressItem>;
} }
export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> { export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> {}
}

View File

@@ -58,7 +58,7 @@ export class PdfTemplate extends TenantModel {
* @returns {string} * @returns {string}
*/ */
get companyLogoUri() { get companyLogoUri() {
return this.attributes.companyLogoKey return this.attributes?.companyLogoKey
? getUploadedObjectUri(this.attributes.companyLogoKey) ? getUploadedObjectUri(this.attributes.companyLogoKey)
: ''; : '';
} }

View File

@@ -23,22 +23,24 @@ export class ContactMailNotification {
public async getDefaultMailOptions( public async getDefaultMailOptions(
tenantId: number, tenantId: number,
customerId: number customerId: number
): Promise<Pick<CommonMailOptions, 'to' | 'from'>> { ): Promise<
Pick<CommonMailOptions, 'to' | 'from' | 'toOptions' | 'fromOptions'>
> {
const { Customer } = this.tenancy.models(tenantId); const { Customer } = this.tenancy.models(tenantId);
const customer = await Customer.query() const customer = await Customer.query()
.findById(customerId) .findById(customerId)
.throwIfNotFound(); .throwIfNotFound();
const toAddresses = customer.contactAddresses; const toOptions = customer.contactAddresses;
const fromAddresses = await this.mailTenancy.senders(tenantId); const fromOptions = await this.mailTenancy.senders(tenantId);
const toAddress = toAddresses.find((a) => a.primary); const toAddress = toOptions.find((a) => a.primary);
const fromAddress = fromAddresses.find((a) => a.primary); const fromAddress = fromOptions.find((a) => a.primary);
const to = toAddress?.mail ? castArray(toAddress?.mail) : []; const to = toAddress?.mail ? castArray(toAddress?.mail) : [];
const from = fromAddress?.mail ? castArray(fromAddress?.mail) : []; const from = fromAddress?.mail ? castArray(fromAddress?.mail) : [];
return { to, from }; return { to, from, toOptions, fromOptions };
} }
/** /**

View File

@@ -129,8 +129,8 @@ export class SendSaleInvoiceMailCommon {
...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 Due Amount': invoice.dueAmountFormatted,
'Invoice DueDate': invoice.dueDateFormatted, 'Invoice Due Date': 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

@@ -1,6 +1,10 @@
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 { mergeAndValidateMailOptions } from '@/services/MailNotification/utils'; import { mergeAndValidateMailOptions } from '@/services/MailNotification/utils';
@@ -47,6 +51,34 @@ export class SendSaleInvoiceMail {
} as ISaleInvoiceMailSend); } as ISaleInvoiceMailSend);
} }
/**
* Retrieves the formatted mail options.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {SendInvoiceMailDTO} messageOptions
* @returns {Promise<SaleInvoiceMailOptions>}
*/
async getFormattedMailOptions(
tenantId: number,
saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO
): Promise<SaleInvoiceMailOptions> {
const defaultMessageOptions = await this.invoiceMail.getInvoiceMailOptions(
tenantId,
saleInvoiceId
);
// Merges message options with default options and parses the options values.
const parsedMessageOptions = mergeAndValidateMailOptions(
defaultMessageOptions,
messageOptions
);
return this.invoiceMail.formatInvoiceMailOptions(
tenantId,
saleInvoiceId,
parsedMessageOptions
);
}
/** /**
* Triggers the mail invoice. * Triggers the mail invoice.
* @param {number} tenantId * @param {number} tenantId
@@ -59,21 +91,11 @@ export class SendSaleInvoiceMail {
saleInvoiceId: number, saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO messageOptions: SendInvoiceMailDTO
) { ) {
const defaultMessageOptions = await this.invoiceMail.getInvoiceMailOptions( const formattedMessageOptions = await this.getFormattedMailOptions(
tenantId, tenantId,
saleInvoiceId saleInvoiceId,
);
// Merges message options with default options and parses the options values.
const parsedMessageOptions = mergeAndValidateMailOptions(
defaultMessageOptions,
messageOptions messageOptions
); );
const formattedMessageOptions =
await this.invoiceMail.formatInvoiceMailOptions(
tenantId,
saleInvoiceId,
parsedMessageOptions
);
const mail = new Mail() const mail = new Mail()
.setSubject(formattedMessageOptions.subject) .setSubject(formattedMessageOptions.subject)
.setTo(formattedMessageOptions.to) .setTo(formattedMessageOptions.to)

View File

@@ -112,17 +112,20 @@ export class SendPaymentReceiveMailNotification {
}; };
/** /**
* Triggers the mail invoice. * Retrieves the formatted mail options of the given payment receive.
* @param {number} tenantId * @param {number} tenantId
* @param {number} saleInvoiceId * @param {number} paymentReceiveId
* @param {SendInvoiceMailDTO} messageDTO * @param {SendInvoiceMailDTO} messageDTO
* @returns {Promise<void>} * @returns {Promise<PaymentReceiveMailOpts>}
*/ */
public async sendMail( public getFormattedMailOptions = async (
tenantId: number, tenantId: number,
paymentReceiveId: number, paymentReceiveId: number,
messageDTO: SendInvoiceMailDTO messageDTO: SendInvoiceMailDTO
): Promise<void> { ) => {
const formatterArgs = await this.textFormatter(tenantId, paymentReceiveId);
// Default message options.
const defaultMessageOpts = await this.getMailOptions( const defaultMessageOpts = await this.getMailOptions(
tenantId, tenantId,
paymentReceiveId paymentReceiveId
@@ -132,17 +135,43 @@ export class SendPaymentReceiveMailNotification {
defaultMessageOpts, defaultMessageOpts,
messageDTO messageDTO
); );
// Formats the message options.
return this.contactMailNotification.formatMailOptions(
tenantId,
parsedMessageOpts,
formatterArgs
);
};
/**
* Triggers the mail invoice.
* @param {number} tenantId
* @param {number} saleInvoiceId - Invoice id.
* @param {SendInvoiceMailDTO} messageDTO - Message options.
* @returns {Promise<void>}
*/
public async sendMail(
tenantId: number,
paymentReceiveId: number,
messageDTO: SendInvoiceMailDTO
): Promise<void> {
// Retrieves the formatted mail options.
const formattedMessageOptions = await this.getFormattedMailOptions(
tenantId,
paymentReceiveId,
messageDTO
);
const mail = new Mail() const mail = new Mail()
.setSubject(parsedMessageOpts.subject) .setSubject(formattedMessageOptions.subject)
.setTo(parsedMessageOpts.to) .setTo(formattedMessageOptions.to)
.setCC(parsedMessageOpts.cc) .setCC(formattedMessageOptions.cc)
.setBCC(parsedMessageOpts.bcc) .setBCC(formattedMessageOptions.bcc)
.setContent(parsedMessageOpts.message); .setContent(formattedMessageOptions.message);
const eventPayload = { const eventPayload = {
tenantId, tenantId,
paymentReceiveId, paymentReceiveId,
messageOptions: parsedMessageOpts, messageOptions: formattedMessageOptions,
}; };
// Triggers `onPaymentReceiveMailSend` event. // Triggers `onPaymentReceiveMailSend` event.
await this.eventPublisher.emitAsync( await this.eventPublisher.emitAsync(

View File

@@ -1,15 +1,16 @@
export const DEFAULT_PAYMENT_MAIL_SUBJECT = 'Payment Received by {CompanyName}'; export const DEFAULT_PAYMENT_MAIL_SUBJECT =
'Payment Received for {Customer Name} from {Company Name}';
export const DEFAULT_PAYMENT_MAIL_CONTENT = ` export const DEFAULT_PAYMENT_MAIL_CONTENT = `
<p>Dear {CustomerName}</p> <p>Dear {Customer Name}</p>
<p>Thank you for your payment. It was a pleasure doing business with you. We look forward to work together again!</p> <p>Thank you for your payment. It was a pleasure doing business with you. We look forward to work together again!</p>
<p> <p>
Payment Date : <strong>{PaymentDate}</strong><br /> Payment Date : <strong>{Payment Date}</strong><br />
Amount : <strong>{PaymentAmount}</strong></br /> Amount : <strong>{Payment 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

@@ -114,6 +114,58 @@ export class SaleReceiptMailNotification {
return transformReceiptToMailDataArgs(receipt); return transformReceiptToMailDataArgs(receipt);
}; };
/**
* Formats the mail options of the given sale receipt.
* @param {number} tenantId
* @param {number} receiptId
* @param {SaleReceiptMailOpts} mailOptions
* @returns {Promise<SaleReceiptMailOpts>}
*/
public async formatEstimateMailOptions(
tenantId: number,
receiptId: number,
mailOptions: SaleReceiptMailOpts
): Promise<SaleReceiptMailOpts> {
const formatterArgs = await this.textFormatterArgs(tenantId, receiptId);
const formattedOptions =
(await this.contactMailNotification.formatMailOptions(
tenantId,
mailOptions,
formatterArgs
)) as SaleReceiptMailOpts;
return formattedOptions;
}
/**
* Retrieves the formatted mail options of the given sale receipt.
* @param {number} tenantId
* @param {number} saleReceiptId
* @param {SaleReceiptMailOptsDTO} messageOpts
* @returns {Promise<SaleReceiptMailOpts>}
*/
public getFormatMailOptions = async (
tenantId: number,
saleReceiptId: number,
messageOpts: SaleReceiptMailOptsDTO
): Promise<SaleReceiptMailOpts> => {
const defaultMessageOptions = await this.getMailOptions(
tenantId,
saleReceiptId
);
// Merges message opts with default options.
const parsedMessageOpts = mergeAndValidateMailOptions(
defaultMessageOptions,
messageOpts
) as SaleReceiptMailOpts;
// Formats the message options.
return this.formatEstimateMailOptions(
tenantId,
saleReceiptId,
parsedMessageOpts
);
};
/** /**
* 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.
@@ -126,25 +178,21 @@ export class SaleReceiptMailNotification {
saleReceiptId: number, saleReceiptId: number,
messageOpts: SaleReceiptMailOptsDTO messageOpts: SaleReceiptMailOptsDTO
) { ) {
const defaultMessageOptions = await this.getMailOptions( // Formats the message options.
const formattedMessageOptions = await this.getFormatMailOptions(
tenantId, tenantId,
saleReceiptId saleReceiptId,
);
// Merges message opts with default options.
const parsedMessageOpts = mergeAndValidateMailOptions(
defaultMessageOptions,
messageOpts messageOpts
) as SaleReceiptMailOpts; );
const mail = new Mail() const mail = new Mail()
.setSubject(parsedMessageOpts.subject) .setSubject(formattedMessageOptions.subject)
.setTo(parsedMessageOpts.to) .setTo(formattedMessageOptions.to)
.setCC(parsedMessageOpts.cc) .setCC(formattedMessageOptions.cc)
.setBCC(parsedMessageOpts.bcc) .setBCC(formattedMessageOptions.bcc)
.setContent(parsedMessageOpts.message); .setContent(formattedMessageOptions.message);
// Attaches the receipt pdf document. // Attaches the receipt pdf document.
if (parsedMessageOpts.attachReceipt) { if (formattedMessageOptions.attachReceipt) {
// Retrieves document buffer of the receipt pdf document. // Retrieves document buffer of the receipt pdf document.
const [receiptPdfBuffer, filename] = const [receiptPdfBuffer, filename] =
await this.receiptPdfService.saleReceiptPdf(tenantId, saleReceiptId); await this.receiptPdfService.saleReceiptPdf(tenantId, saleReceiptId);

View File

@@ -32,7 +32,7 @@ function EstimateMailDialogFormRoot({
closeDialog, closeDialog,
}) { }) {
const { mutateAsync: sendEstimateMail } = useSendSaleEstimateMail(); const { mutateAsync: sendEstimateMail } = useSendSaleEstimateMail();
const { mailOptions, saleEstimateId, redirectToEstimatesList } = const { mailOptions, saleEstimateId, } =
useEstimateMailDialogBoot(); useEstimateMailDialogBoot();
const initialValues = transformMailFormToInitialValues( const initialValues = transformMailFormToInitialValues(

View File

@@ -25,8 +25,8 @@ export function EstimateMailDialogFormContent({
<Form> <Form>
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<MailNotificationForm <MailNotificationForm
fromAddresses={mailOptions.from_addresses} fromAddresses={mailOptions.from_options}
toAddresses={mailOptions.to_addresses} toAddresses={mailOptions.to_options}
/> />
<AttachFormGroup name={'attachEstimate'} inline> <AttachFormGroup name={'attachEstimate'} inline>
<FSwitch name={'attachEstimate'} label={'Attach Estimate'} /> <FSwitch name={'attachEstimate'} label={'Attach Estimate'} />

View File

@@ -22,6 +22,7 @@ interface PaymentMailDialogBootProps {
*/ */
function PaymentMailDialogBoot({ function PaymentMailDialogBoot({
paymentReceiveId, paymentReceiveId,
redirectToPaymentsList,
...props ...props
}: PaymentMailDialogBootProps) { }: PaymentMailDialogBootProps) {
const { data: mailOptions, isLoading: isMailOptionsLoading } = const { data: mailOptions, isLoading: isMailOptionsLoading } =

View File

@@ -25,8 +25,8 @@ export function PaymentMailDialogFormContent({
<Form> <Form>
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<MailNotificationForm <MailNotificationForm
fromAddresses={mailOptions.from_addresses} fromAddresses={mailOptions.from_options}
toAddresses={mailOptions.to_addresses} toAddresses={mailOptions.to_options}
/> />
<AttachFormGroup name={'attachPayment'} inline> <AttachFormGroup name={'attachPayment'} inline>
<FSwitch name={'attachPayment'} label={'Attach Payment'} /> <FSwitch name={'attachPayment'} label={'Attach Payment'} />

View File

@@ -25,8 +25,8 @@ export function ReceiptMailDialogFormContent({
<Form> <Form>
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
<MailNotificationForm <MailNotificationForm
fromAddresses={mailOptions.from_addresses} fromAddresses={mailOptions.from_options}
toAddresses={mailOptions.to_addresses} toAddresses={mailOptions.to_options}
/> />
<AttachFormGroup name={'attachReceipt:'} inline> <AttachFormGroup name={'attachReceipt:'} inline>
<FSwitch name={'attachReceipt:'} label={'Attach Receipt'} /> <FSwitch name={'attachReceipt:'} label={'Attach Receipt'} />

View File

@@ -66,7 +66,7 @@ export function MailNotificationForm({
</FFormGroup> </FFormGroup>
</HeaderBox> </HeaderBox>
<MailMessageEditor name={'body'} /> <MailMessageEditor name={'message'} />
</Box> </Box>
); );
} }

View File

@@ -5,7 +5,7 @@ export const initialMailNotificationValues = {
from: [], from: [],
to: [], to: [],
subject: '', subject: '',
body: '', message: '',
}; };
export interface MailNotificationFormValues { export interface MailNotificationFormValues {
@@ -26,7 +26,7 @@ export const transformMailFormToRequest = (
}; };
/** /**
* Transformes the mail options response values to form initial values. * Transforms the mail options response values to form initial values.
* @param {any} mailOptions * @param {any} mailOptions
* @param {MailNotificationFormValues} initialValues * @param {MailNotificationFormValues} initialValues
* @returns {MailNotificationFormValues} * @returns {MailNotificationFormValues}