feat: mail notifications of sales transactions

This commit is contained in:
Ahmed Bouhuolia
2023-12-30 17:49:02 +02:00
parent 0d15c16d40
commit ab7abfea35
25 changed files with 336 additions and 221 deletions

View File

@@ -1,9 +1,9 @@
import { Inject, Service } from 'typedi';
import * as R from 'ramda';
import { SaleInvoiceMailOptions } from '@/interfaces';
import { CommonMailOptions } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { MailTenancy } from '@/services/MailTenancy/MailTenancy';
import { formatSmsMessage } from '@/utils';
import { Tenant } from '@/system/models';
@Service()
export class ContactMailNotification {
@@ -15,8 +15,10 @@ export class ContactMailNotification {
/**
* Parses the default message options.
* @param {number} tenantId
* @param {number} invoiceId
* @param {number} tenantId -
* @param {number} invoiceId -
* @param {string} subject -
* @param {string} body -
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async getDefaultMailOptions(
@@ -24,9 +26,11 @@ export class ContactMailNotification {
contactId: number,
subject: string = '',
body: string = ''
): Promise<any> {
const { Contact, Customer } = this.tenancy.models(tenantId);
const contact = await Customer.query().findById(contactId).throwIfNotFound();
): Promise<CommonMailOptions> {
const { Customer } = this.tenancy.models(tenantId);
const contact = await Customer.query()
.findById(contactId)
.throwIfNotFound();
const toAddresses = contact.contactAddresses;
const fromAddresses = await this.mailTenancy.senders(tenantId);
@@ -48,10 +52,12 @@ export class ContactMailNotification {
}
/**
* Retrieves the mail options.
* @param {number}
* @param {number} invoiceId
* @returns {}
* Retrieves the mail options of the given contact.
* @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>}
*/
public async getMailOptions(
tenantId: number,
@@ -59,15 +65,20 @@ export class ContactMailNotification {
defaultSubject?: string,
defaultBody?: string,
formatterData?: Record<string, any>
): Promise<SaleInvoiceMailOptions> {
): Promise<CommonMailOptions> {
const mailOpts = await this.getDefaultMailOptions(
tenantId,
contactId,
defaultSubject,
defaultBody
);
const subject = formatSmsMessage(mailOpts.subject, formatterData);
const body = formatSmsMessage(mailOpts.body, formatterData);
const commonFormatArgs = await this.getCommonFormatArgs(tenantId);
const formatArgs = {
...commonFormatArgs,
...formatterData,
};
const subject = formatSmsMessage(mailOpts.subject, formatArgs);
const body = formatSmsMessage(mailOpts.body, formatArgs);
return {
...mailOpts,
@@ -75,4 +86,21 @@ export class ContactMailNotification {
body,
};
}
/**
* Retrieves the common format args.
* @param {number} tenantId
* @returns {Promise<Record<string, string>>}
*/
public async getCommonFormatArgs(
tenantId: number
): Promise<Record<string, string>> {
const organization = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
return {
CompanyName: organization.metadata.name,
};
}
}

View File

@@ -0,0 +1,6 @@
export const ERRORS = {
MAIL_FROM_NOT_FOUND: 'Mail from address not found',
MAIL_TO_NOT_FOUND: 'Mail to address not found',
MAIL_SUBJECT_NOT_FOUND: 'Mail subject not found',
MAIL_BODY_NOT_FOUND: 'Mail body not found',
};

View File

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

View File

@@ -8,6 +8,7 @@ import {
ISaleEstimateDTO,
ISalesEstimatesFilter,
SaleEstimateMailOptions,
SaleEstimateMailOptionsDTO,
} from '@/interfaces';
import { EditSaleEstimate } from './EditSaleEstimate';
import { DeleteSaleEstimate } from './DeleteSaleEstimate';
@@ -224,8 +225,8 @@ export class SaleEstimatesApplication {
public sendSaleEstimateMail(
tenantId: number,
saleEstimateId: number,
saleEstimateMailOpts: SaleEstimateMailOptions
) {
saleEstimateMailOpts: SaleEstimateMailOptionsDTO
): Promise<void> {
return this.sendEstimateMailService.triggerMail(
tenantId,
saleEstimateId,
@@ -235,11 +236,14 @@ export class SaleEstimatesApplication {
/**
* Retrieves the default mail options of the given sale estimate.
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {}
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns {Promise<SaleEstimateMailOptions>}
*/
public getSaleEstimateMail(tenantId: number, saleEstimateId: number) {
public getSaleEstimateMail(
tenantId: number,
saleEstimateId: number
): Promise<SaleEstimateMailOptions> {
return this.sendEstimateMailService.getMailOptions(
tenantId,
saleEstimateId

View File

@@ -7,8 +7,12 @@ import {
} from './constants';
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
import { GetSaleEstimate } from './GetSaleEstimate';
import { SaleEstimateMailOptions } from '@/interfaces';
import {
SaleEstimateMailOptions,
SaleEstimateMailOptionsDTO,
} from '@/interfaces';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
@Service()
export class SendSaleEstimateMail {
@@ -31,13 +35,14 @@ export class SendSaleEstimateMail {
* Triggers the reminder mail of the given sale estimate.
* @param {number} tenantId -
* @param {number} saleEstimateId -
* @param {SaleEstimateMailOptions} messageOptions -
* @param {SaleEstimateMailOptionsDTO} messageOptions -
* @returns {Promise<void>}
*/
public async triggerMail(
tenantId: number,
saleEstimateId: number,
messageOptions: SaleEstimateMailOptions
) {
messageOptions: SaleEstimateMailOptionsDTO
): Promise<void> {
const payload = {
tenantId,
saleEstimateId,
@@ -48,9 +53,9 @@ export class SendSaleEstimateMail {
/**
* Formates the text of the mail.
* @param {number} tenantId
* @param {number} estimateId
* @param {string} text
* @param {number} tenantId - Tenant id.
* @param {number} estimateId - Estimate id.
* @returns {Promise<Record<string, any>>}
*/
public formatterData = async (tenantId: number, estimateId: number) => {
const estimate = await this.getSaleEstimateService.getEstimate(
@@ -70,9 +75,12 @@ export class SendSaleEstimateMail {
* Retrieves the mail options.
* @param {number} tenantId
* @param {number} saleEstimateId
* @returns
* @returns {Promise<SaleEstimateMailOptions>}
*/
public getMailOptions = async (tenantId: number, saleEstimateId: number) => {
public getMailOptions = async (
tenantId: number,
saleEstimateId: number
): Promise<SaleEstimateMailOptions> => {
const { SaleEstimate } = this.tenancy.models(tenantId);
const saleEstimate = await SaleEstimate.query()
@@ -91,6 +99,7 @@ export class SendSaleEstimateMail {
return {
...mailOptions,
data: formatterData,
attachEstimate: true
};
};
@@ -99,26 +108,28 @@ export class SendSaleEstimateMail {
* @param {number} tenantId
* @param {number} saleEstimateId
* @param {SaleEstimateMailOptions} messageOptions
* @returns {Promise<void>}
*/
public async sendMail(
tenantId: number,
saleEstimateId: number,
messageOptions: SaleEstimateMailOptions
) {
messageOptions: SaleEstimateMailOptionsDTO
): Promise<void> {
const localMessageOpts = await this.getMailOptions(
tenantId,
saleEstimateId
);
const messageOpts = {
...localMessageOpts,
...messageOptions,
};
// Overrides and validates the given mail options.
const messageOpts = parseAndValidateMailOptions(
localMessageOpts,
messageOptions
);
const mail = new Mail()
.setSubject(messageOpts.subject)
.setTo(messageOpts.to)
.setContent(messageOpts.body);
if (messageOpts.to) {
if (messageOpts.attachEstimate) {
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
tenantId,
saleEstimateId

View File

@@ -300,7 +300,10 @@ export class SaleInvoiceApplication {
* @returns {}
*/
public getSaleInvoiceMailReminder(tenantId: number, saleInvoiceId: number) {
return this.sendInvoiceReminderService.getMailOpts(tenantId, saleInvoiceId);
return this.sendInvoiceReminderService.getMailOption(
tenantId,
saleInvoiceId
);
}
/**
@@ -347,6 +350,9 @@ export class SaleInvoiceApplication {
* @returns {Promise<SendInvoiceMailDTO>}
*/
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
return this.sendSaleInvoiceMailService.getMailOpts(tenantId, saleInvoiceid);
return this.sendSaleInvoiceMailService.getMailOption(
tenantId,
saleInvoiceid
);
}
}

View File

@@ -1,15 +1,12 @@
import { Inject, Service } from 'typedi';
import { isEmpty } from 'lodash';
import { SaleInvoiceMailOptions } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { GetSaleInvoice } from './GetSaleInvoice';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import {
DEFAULT_INVOICE_MAIL_CONTENT,
DEFAULT_INVOICE_MAIL_SUBJECT,
} from './constants';
import { GetSaleInvoice } from './GetSaleInvoice';
import { Tenant } from '@/system/models';
import { ServiceError } from '@/exceptions';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
@Service()
export class SendSaleInvoiceMailCommon {
@@ -28,9 +25,9 @@ export class SendSaleInvoiceMailCommon {
* @param {number} invoiceId - Invoice id.
* @param {string} defaultSubject - Subject text.
* @param {string} defaultBody - Subject body.
* @returns {}
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async getMailOpts(
public async getMailOption(
tenantId: number,
invoiceId: number,
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
@@ -44,13 +41,17 @@ export class SendSaleInvoiceMailCommon {
const formatterData = await this.formatText(tenantId, invoiceId);
return this.contactMailNotification.getMailOptions(
const mailOptions = await this.contactMailNotification.getMailOptions(
tenantId,
saleInvoice.customerId,
defaultSubject,
defaultBody,
formatterData
);
return {
...mailOptions,
attachInvoice: true,
};
}
/**
@@ -68,12 +69,8 @@ export class SendSaleInvoiceMailCommon {
tenantId,
invoiceId
);
const organization = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
return {
CompanyName: organization.metadata.name,
CustomerName: invoice.customer.displayName,
InvoiceNumber: invoice.invoiceNo,
InvoiceDueAmount: invoice.dueAmountFormatted,
@@ -83,33 +80,4 @@ export class SendSaleInvoiceMailCommon {
OverdueDays: invoice.overdueDays,
};
};
/**
* Validates the mail notification options before sending it.
* @param {Partial<SaleInvoiceMailOptions>} mailNotificationOpts
* @throws {ServiceError}
*/
public validateMailNotification(
mailNotificationOpts: Partial<SaleInvoiceMailOptions>
) {
if (isEmpty(mailNotificationOpts.from)) {
throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND);
}
if (isEmpty(mailNotificationOpts.to)) {
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
}
if (isEmpty(mailNotificationOpts.subject)) {
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
}
if (isEmpty(mailNotificationOpts.body)) {
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
}
}
}
const ERRORS = {
MAIL_FROM_NOT_FOUND: 'Mail from address not found',
MAIL_TO_NOT_FOUND: 'Mail to address not found',
MAIL_SUBJECT_NOT_FOUND: 'Mail subject not found',
MAIL_BODY_NOT_FOUND: 'Mail body not found',
};

View File

@@ -7,6 +7,7 @@ import {
DEFAULT_INVOICE_MAIL_CONTENT,
DEFAULT_INVOICE_MAIL_SUBJECT,
} from './constants';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
@Service()
export class SendSaleInvoiceMail {
@@ -44,8 +45,8 @@ export class SendSaleInvoiceMail {
* @param {number} saleInvoiceId
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async getMailOpts(tenantId: number, saleInvoiceId: number) {
return this.invoiceMail.getMailOpts(
public async getMailOption(tenantId: number, saleInvoiceId: number) {
return this.invoiceMail.getMailOption(
tenantId,
saleInvoiceId,
DEFAULT_INVOICE_MAIL_SUBJECT,
@@ -65,15 +66,15 @@ export class SendSaleInvoiceMail {
saleInvoiceId: number,
messageDTO: SendInvoiceMailDTO
) {
const defaultMessageOpts = await this.getMailOpts(tenantId, saleInvoiceId);
// Parsed message opts with default options.
const messageOpts = {
...defaultMessageOpts,
...messageDTO,
};
this.invoiceMail.validateMailNotification(messageOpts);
const defaultMessageOpts = await this.getMailOption(
tenantId,
saleInvoiceId
);
// Merge message opts with default options and validate the incoming options.
const messageOpts = parseAndValidateMailOptions(
defaultMessageOpts,
messageDTO
);
const mail = new Mail()
.setSubject(messageOpts.subject)
.setTo(messageOpts.to)

View File

@@ -43,8 +43,8 @@ export class SendInvoiceMailReminder {
* @param {number} saleInvoiceId
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async getMailOpts(tenantId: number, saleInvoiceId: number) {
return this.invoiceCommonMail.getMailOpts(
public async getMailOption(tenantId: number, saleInvoiceId: number) {
return this.invoiceCommonMail.getMailOption(
tenantId,
saleInvoiceId,
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
@@ -64,7 +64,7 @@ export class SendInvoiceMailReminder {
saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO
) {
const localMessageOpts = await this.getMailOpts(tenantId, saleInvoiceId);
const localMessageOpts = await this.getMailOption(tenantId, saleInvoiceId);
const messageOpts = {
...localMessageOpts,

View File

@@ -1,14 +1,18 @@
import { Inject, Service } from 'typedi';
import { IPaymentReceiveMailOpts, SendInvoiceMailDTO } from '@/interfaces';
import {
PaymentReceiveMailOpts,
PaymentReceiveMailOptsDTO,
SendInvoiceMailDTO,
} from '@/interfaces';
import Mail from '@/lib/Mail';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import {
DEFAULT_PAYMENT_MAIL_CONTENT,
DEFAULT_PAYMENT_MAIL_SUBJECT,
} from './constants';
import { Tenant } from '@/system/models';
import { GetPaymentReceive } from './GetPaymentReceive';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
@Service()
export class SendPaymentReceiveMailNotification {
@@ -28,13 +32,14 @@ export class SendPaymentReceiveMailNotification {
* Sends the mail of the given payment receive.
* @param {number} tenantId
* @param {number} paymentReceiveId
* @param {SendInvoiceMailDTO} messageDTO
* @param {PaymentReceiveMailOptsDTO} messageDTO
* @returns {Promise<void>}
*/
public async triggerMail(
tenantId: number,
paymentReceiveId: number,
messageDTO: IPaymentReceiveMailOpts
) {
messageDTO: PaymentReceiveMailOptsDTO
): Promise<void> {
const payload = {
tenantId,
paymentReceiveId,
@@ -45,18 +50,21 @@ export class SendPaymentReceiveMailNotification {
/**
* Retrieves the default payment mail options.
* @param {number} tenantId
* @param {number} invoiceId
* @returns {Promise<SendInvoiceMailDTO>}
* @param {number} tenantId - Tenant id.
* @param {number} paymentReceiveId - Payment receive id.
* @returns {Promise<PaymentReceiveMailOpts>}
*/
public getMailOptions = async (tenantId: number, invoiceId: number) => {
public getMailOptions = async (
tenantId: number,
paymentId: number
): Promise<PaymentReceiveMailOpts> => {
const { PaymentReceive } = this.tenancy.models(tenantId);
const paymentReceive = await PaymentReceive.query()
.findById(invoiceId)
.findById(paymentId)
.throwIfNotFound();
const formatterData = await this.textFormatter(tenantId, invoiceId);
const formatterData = await this.textFormatter(tenantId, paymentId);
return this.contactMailNotification.getMailOptions(
tenantId,
@@ -82,12 +90,7 @@ export class SendPaymentReceiveMailNotification {
tenantId,
invoiceId
);
const organization = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
return {
CompanyName: organization.metadata.name,
CustomerName: payment.customer.displayName,
PaymentNumber: payment.payment_receive_no,
PaymentDate: payment.formattedPaymentDate,
@@ -112,10 +115,10 @@ export class SendPaymentReceiveMailNotification {
paymentReceiveId
);
// Parsed message opts with default options.
const parsedMessageOpts = {
...defaultMessageOpts,
...messageDTO,
};
const parsedMessageOpts = parseAndValidateMailOptions(
defaultMessageOpts,
messageDTO
);
await new Mail()
.setSubject(parsedMessageOpts.subject)
.setTo(parsedMessageOpts.to)

View File

@@ -4,10 +4,10 @@ import {
IPaymentReceive,
IPaymentReceiveCreateDTO,
IPaymentReceiveEditDTO,
IPaymentReceiveMailOpts,
IPaymentReceiveSmsDetails,
IPaymentReceivesFilter,
ISystemUser,
PaymentReceiveMailOptsDTO,
} from '@/interfaces';
import { Inject, Service } from 'typedi';
import { CreatePaymentReceive } from './CreatePaymentReceive';
@@ -189,8 +189,8 @@ export class PaymentReceivesApplication {
public notifyPaymentByMail(
tenantId: number,
paymentReceiveId: number,
messageOpts: IPaymentReceiveMailOpts
) {
messageOpts: PaymentReceiveMailOptsDTO
): Promise<void> {
return this.paymentMailNotify.triggerMail(
tenantId,
paymentReceiveId,
@@ -204,7 +204,7 @@ export class PaymentReceivesApplication {
* @param {number} paymentReceiveId
* @returns {Promise<void>}
*/
public getPaymentDefaultMail(tenantId: number, paymentReceiveId: number) {
public getPaymentMailOptions(tenantId: number, paymentReceiveId: number) {
return this.paymentMailNotify.getMailOptions(tenantId, paymentReceiveId);
}

View File

@@ -6,6 +6,7 @@ import {
ISaleReceipt,
ISalesReceiptsFilter,
SaleReceiptMailOpts,
SaleReceiptMailOptsDTO,
} from '@/interfaces';
import { EditSaleReceipt } from './EditSaleReceipt';
import { GetSaleReceipt } from './GetSaleReceipt';
@@ -176,12 +177,13 @@ export class SaleReceiptApplication {
* Sends the receipt mail of the given sale receipt.
* @param {number} tenantId
* @param {number} saleReceiptId
* @returns {Promise<void>}
*/
public sendSaleReceiptMail(
tenantId: number,
saleReceiptId: number,
messageOpts: SaleReceiptMailOpts
) {
messageOpts: SaleReceiptMailOptsDTO
): Promise<void> {
return this.saleReceiptNotifyByMailService.triggerMail(
tenantId,
saleReceiptId,
@@ -193,9 +195,12 @@ export class SaleReceiptApplication {
* Retrieves the default mail options of the given sale receipt.
* @param {number} tenantId
* @param {number} saleReceiptId
* @returns
* @returns {Promise<SaleReceiptMailOpts>}
*/
public getSaleReceiptMail(tenantId: number, saleReceiptId: number) {
public getSaleReceiptMail(
tenantId: number,
saleReceiptId: number
): Promise<SaleReceiptMailOpts> {
return this.saleReceiptNotifyByMailService.getMailOptions(
tenantId,
saleReceiptId

View File

@@ -1,7 +1,5 @@
import * as R from 'ramda';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import { Tenant } from '@/system/models';
import Mail from '@/lib/Mail';
import { GetSaleReceipt } from './GetSaleReceipt';
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
@@ -9,8 +7,9 @@ import {
DEFAULT_RECEIPT_MAIL_CONTENT,
DEFAULT_RECEIPT_MAIL_SUBJECT,
} from './constants';
import { SaleReceiptMailOpts } from '@/interfaces';
import { SaleReceiptMailOpts, SaleReceiptMailOptsDTO } from '@/interfaces';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
@Service()
export class SaleReceiptMailNotification {
@@ -32,13 +31,13 @@ export class SaleReceiptMailNotification {
/**
* Sends the receipt mail of the given sale receipt.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {SendInvoiceMailDTO} messageDTO
* @param {number} saleReceiptId
* @param {SaleReceiptMailOptsDTO} messageDTO
*/
public async triggerMail(
tenantId: number,
saleReceiptId: number,
messageOpts: SaleReceiptMailOpts
messageOpts: SaleReceiptMailOptsDTO
) {
const payload = {
tenantId,
@@ -52,9 +51,12 @@ export class SaleReceiptMailNotification {
* Retrieves the mail options of the given sale receipt.
* @param {number} tenantId
* @param {number} saleReceiptId
* @returns
* @returns {Promise<SaleReceiptMailOptsDTO>}
*/
public async getMailOptions(tenantId: number, saleReceiptId: number) {
public async getMailOptions(
tenantId: number,
saleReceiptId: number
): Promise<SaleReceiptMailOpts> {
const { SaleReceipt } = this.tenancy.models(tenantId);
const saleReceipt = await SaleReceipt.query()
@@ -63,17 +65,21 @@ export class SaleReceiptMailNotification {
const formattedData = await this.textFormatter(tenantId, saleReceiptId);
return this.contactMailNotification.getMailOptions(
const mailOpts = await this.contactMailNotification.getMailOptions(
tenantId,
saleReceipt.customerId,
DEFAULT_RECEIPT_MAIL_SUBJECT,
DEFAULT_RECEIPT_MAIL_CONTENT,
formattedData
);
return {
...mailOpts,
attachReceipt: true,
};
}
/**
* Retrieves the formatted text of the given sale invoice.
* Retrieves the formatted text of the given sale receipt.
* @param {number} tenantId - Tenant id.
* @param {number} receiptId - Sale receipt id.
* @param {string} text - The given text.
@@ -83,58 +89,52 @@ export class SaleReceiptMailNotification {
tenantId: number,
receiptId: number
): Promise<Record<string, string>> => {
const invoice = await this.getSaleReceiptService.getSaleReceipt(
const receipt = await this.getSaleReceiptService.getSaleReceipt(
tenantId,
receiptId
);
const organization = await Tenant.query()
.findById(tenantId)
.withGraphFetched('metadata');
return {
CompanyName: organization.metadata.name,
CustomerName: invoice.customer.displayName,
ReceiptNumber: invoice.receiptNumber,
ReceiptDate: invoice.formattedReceiptDate,
ReceiptAmount: invoice.formattedAmount,
CustomerName: receipt.customer.displayName,
ReceiptNumber: receipt.receiptNumber,
ReceiptDate: receipt.formattedReceiptDate,
ReceiptAmount: receipt.formattedAmount,
};
};
/**
* Triggers the mail invoice.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @param {SendInvoiceMailDTO} messageDTO
* Triggers the mail notification of the given sale receipt.
* @param {number} tenantId - Tenant id.
* @param {number} saleReceiptId - Sale receipt id.
* @param {SaleReceiptMailOpts} messageDTO - Overrided message options.
* @returns {Promise<void>}
*/
public async sendMail(
tenantId: number,
saleReceiptId: number,
messageOpts: SaleReceiptMailOpts
messageOpts: SaleReceiptMailOptsDTO
) {
const defaultMessageOpts = await this.getMailOptions(
tenantId,
saleReceiptId
);
// Parsed message opts with default options.
const parsedMessageOpts = {
...defaultMessageOpts,
...messageOpts,
};
// Merges message opts with default options.
const parsedMessageOpts = parseAndValidateMailOptions(
defaultMessageOpts,
messageOpts
);
const mail = new Mail()
.setSubject(parsedMessageOpts.subject)
.setTo(parsedMessageOpts.to)
.setContent(parsedMessageOpts.body);
if (parsedMessageOpts.attachInvoice) {
// Retrieves document buffer of the invoice pdf document.
if (parsedMessageOpts.attachReceipt) {
// Retrieves document buffer of the receipt pdf document.
const receiptPdfBuffer = await this.receiptPdfService.saleReceiptPdf(
tenantId,
saleReceiptId
);
mail.setAttachments([
{ filename: 'invoice.pdf', content: receiptPdfBuffer },
{ filename: 'receipt.pdf', content: receiptPdfBuffer },
]);
}
await mail.send();

View File

@@ -1,5 +1,5 @@
export const DEFAULT_RECEIPT_MAIL_SUBJECT =
'Invoice {InvoiceNumber} from {CompanyName}';
'Receipt {ReceiptNumber} from {CompanyName}';
export const DEFAULT_RECEIPT_MAIL_CONTENT = `
<p>Dear {CustomerName}</p>
<p>Thank you for your business, You can view or print your receipt from attachements.</p>