From 50d5ddba8ef4f2cfe1563ad89a1cbb9928fd7b12 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 22 Dec 2023 02:45:44 +0200 Subject: [PATCH] feat: send invoice notifications --- .../Invoices/SaleInvoiceMailFormatter.ts | 43 ------- .../Sales/Invoices/SendSaleInvoiceMail.ts | 107 ++++++++++++------ .../Invoices/SendSaleInvoiceMailReminder.ts | 99 ++++++++++------ 3 files changed, 138 insertions(+), 111 deletions(-) delete mode 100644 packages/server/src/services/Sales/Invoices/SaleInvoiceMailFormatter.ts diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoiceMailFormatter.ts b/packages/server/src/services/Sales/Invoices/SaleInvoiceMailFormatter.ts deleted file mode 100644 index a8c85f260..000000000 --- a/packages/server/src/services/Sales/Invoices/SaleInvoiceMailFormatter.ts +++ /dev/null @@ -1,43 +0,0 @@ -import HasTenancyService from '@/services/Tenancy/TenancyService'; -import { GetSaleInvoice } from './GetSaleInvoice'; -import { Inject, Service } from 'typedi'; -import { Tenant } from '@/system/models'; - -@Service() -export class SaleInvoiceMailFormatter { - @Inject() - private tenancy: HasTenancyService; - - @Inject() - private getSaleInvoiceService: GetSaleInvoice; - - /** - * Retrieves the formatted text of the given sale invoice. - * @param {number} tenantId - Tenant id. - * @param {number} invoiceId - Sale invoice id. - * @param {string} text - The given text. - * @returns {Promise} - */ - public formatText = async ( - tenantId: number, - invoiceId: number, - text: string - ): Promise => { - const invoice = await this.getSaleInvoiceService.getSaleInvoice( - tenantId, - invoiceId - ); - const organization = await Tenant.query() - .findById(tenantId) - .withGraphFetched('metadata'); - - return text - .replace('{CompanyName}', organization.metadata.name) - .replace('{CustomerName}', invoice.customer.displayName) - .replace('{InvoiceNumber}', invoice.invoiceNo) - .replace('{InvoiceDueAmount}', invoice.dueAmountFormatted) - .replace('{InvoiceDueDate}', invoice.dueDateFormatted) - .replace('{InvoiceDate}', invoice.invoiceDateFormatted) - .replace('{InvoiceAmount}', invoice.totalFormatted); - }; -} diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts index 4f3792256..cc8269c56 100644 --- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts @@ -4,31 +4,29 @@ import { SendInvoiceMailDTO } from '@/interfaces'; import Mail from '@/lib/Mail'; import HasTenancyService from '@/services/Tenancy/TenancyService'; import { SaleInvoicePdf } from './SaleInvoicePdf'; -import { SaleInvoiceMailFormatter } from './SaleInvoiceMailFormatter'; import { DEFAULT_INVOICE_MAIL_CONTENT, DEFAULT_INVOICE_MAIL_SUBJECT, ERRORS, } from './constants'; -import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators'; import { ServiceError } from '@/exceptions'; +import { formatSmsMessage } from '@/utils'; +import { GetSaleInvoice } from './GetSaleInvoice'; +import { Tenant } from '@/system/models'; @Service() export class SendSaleInvoiceMail { @Inject() private tenancy: HasTenancyService; - @Inject('agenda') - private agenda: any; - + @Inject() + private getSaleInvoiceService: GetSaleInvoice; + @Inject() private invoicePdf: SaleInvoicePdf; - @Inject() - private invoiceFormatter: SaleInvoiceMailFormatter; - - @Inject() - private commandInvoiceValidator: CommandSaleInvoiceValidators; + @Inject('agenda') + private agenda: any; /** * Sends the invoice mail of the given sale invoice. @@ -49,6 +47,58 @@ export class SendSaleInvoiceMail { await this.agenda.now('sale-invoice-mail-send', payload); } + /** + * Retrieves the default invoice mail options. + * @param {number} tenantId + * @param {number} invoiceId + * @returns {Promise} + */ + public getDefaultMailOpts = async (tenantId: number, invoiceId: number) => { + const { SaleInvoice } = this.tenancy.models(tenantId); + const saleInvoice = await SaleInvoice.query() + .findById(invoiceId) + .withGraphFetched('customer') + .throwIfNotFound(); + + return { + attachInvoice: true, + subject: DEFAULT_INVOICE_MAIL_SUBJECT, + body: DEFAULT_INVOICE_MAIL_CONTENT, + to: saleInvoice.customer.email, + }; + }; + + /** + * Retrieves the formatted text of the given sale invoice. + * @param {number} tenantId - Tenant id. + * @param {number} invoiceId - Sale invoice id. + * @param {string} text - The given text. + * @returns {Promise} + */ + public textFormatter = async ( + tenantId: number, + invoiceId: number, + text: string + ): Promise => { + const invoice = await this.getSaleInvoiceService.getSaleInvoice( + tenantId, + invoiceId + ); + const organization = await Tenant.query() + .findById(tenantId) + .withGraphFetched('metadata'); + + return formatSmsMessage(text, { + CompanyName: organization.metadata.name, + CustomerName: invoice.customer.displayName, + InvoiceNumber: invoice.invoiceNo, + InvoiceDueAmount: invoice.dueAmountFormatted, + InvoiceDueDate: invoice.dueDateFormatted, + InvoiceDate: invoice.invoiceDateFormatted, + InvoiceAmount: invoice.totalFormatted, + }); + }; + /** * Triggers the mail invoice. * @param {number} tenantId @@ -61,31 +111,20 @@ export class SendSaleInvoiceMail { saleInvoiceId: number, messageDTO: SendInvoiceMailDTO ) { - const { SaleInvoice } = this.tenancy.models(tenantId); - - const saleInvoice = await SaleInvoice.query() - .findById(saleInvoiceId) - .withGraphFetched('customer'); - - this.commandInvoiceValidator.validateInvoiceExistance(saleInvoice); - + const defaultMessageOpts = await this.getDefaultMailOpts( + tenantId, + saleInvoiceId + ); // Parsed message opts with default options. const parsedMessageOpts = { - attachInvoice: true, - subject: DEFAULT_INVOICE_MAIL_SUBJECT, - body: DEFAULT_INVOICE_MAIL_CONTENT, - to: saleInvoice.customer.email, + ...defaultMessageOpts, ...messageDTO, }; // In case there is no email address from the customer or from options, throw an error. if (!parsedMessageOpts.to) { throw new ServiceError(ERRORS.NO_INVOICE_CUSTOMER_EMAIL_ADDR); } - const formatter = R.curry(this.invoiceFormatter.formatText)( - tenantId, - saleInvoiceId - ); - const toEmail = parsedMessageOpts.to; + const formatter = R.curry(this.textFormatter)(tenantId, saleInvoiceId); const subject = await formatter(parsedMessageOpts.subject); const body = await formatter(parsedMessageOpts.body); const attachments = []; @@ -96,17 +135,13 @@ export class SendSaleInvoiceMail { tenantId, saleInvoiceId ); - attachments.push({ - filename: 'invoice.pdf', - content: invoicePdfBuffer, - }); + attachments.push({ filename: 'invoice.pdf', content: invoicePdfBuffer }); } - const mail = new Mail() + await new Mail() .setSubject(subject) - .setTo(toEmail) + .setTo(parsedMessageOpts.to) .setContent(body) - .setAttachments(attachments); - - await mail.send(); + .setAttachments(attachments) + .send(); } } diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts index 16baf1273..886916260 100644 --- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts @@ -1,11 +1,8 @@ import { Inject, Service } from 'typedi'; import * as R from 'ramda'; -import { assign } from 'lodash'; import { SendInvoiceMailDTO } from '@/interfaces'; import Mail from '@/lib/Mail'; import HasTenancyService from '@/services/Tenancy/TenancyService'; -import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators'; -import { SaleInvoiceMailFormatter } from './SaleInvoiceMailFormatter'; import { DEFAULT_INVOICE_REMINDER_MAIL_CONTENT, DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, @@ -13,6 +10,9 @@ import { } from './constants'; import { SaleInvoicePdf } from './SaleInvoicePdf'; import { ServiceError } from '@/exceptions'; +import { GetSaleInvoice } from './GetSaleInvoice'; +import { Tenant } from '@/system/models'; +import { formatSmsMessage } from '@/utils'; @Service() export class SendInvoiceMailReminder { @@ -22,15 +22,12 @@ export class SendInvoiceMailReminder { @Inject('agenda') private agenda: any; - @Inject() - private commandInvoiceValidator: CommandSaleInvoiceValidators; - - @Inject() - private invoiceFormatter: SaleInvoiceMailFormatter; - @Inject() private invoicePdf: SaleInvoicePdf; + @Inject() + private getSaleInvoiceService: GetSaleInvoice; + /** * Triggers the reminder mail of the given sale invoice. * @param {number} tenantId @@ -49,6 +46,59 @@ export class SendInvoiceMailReminder { await this.agenda.now('sale-invoice-reminder-mail-send', payload); } + /** + * Parses the default message options. + * @param {number} tenantId + * @param {number} invoiceId + * @returns {Promise} + */ + public async getDefaultMailOpts(tenantId: number, invoiceId: number) { + const { SaleInvoice } = this.tenancy.models(tenantId); + + const saleInvoice = await SaleInvoice.query() + .findById(invoiceId) + .withGraphFetched('customer') + .throwIfNotFound(); + + return { + attachInvoice: true, + subject: DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, + body: DEFAULT_INVOICE_REMINDER_MAIL_CONTENT, + to: saleInvoice.customer.email, + }; + } + + /** + * Retrieves the formatted text of the given sale invoice. + * @param {number} tenantId - Tenant id. + * @param {number} invoiceId - Sale invoice id. + * @param {string} text - The given text. + * @returns {Promise} + */ + public formatText = async ( + tenantId: number, + invoiceId: number, + text: string + ): Promise => { + const invoice = await this.getSaleInvoiceService.getSaleInvoice( + tenantId, + invoiceId + ); + const organization = await Tenant.query() + .findById(tenantId) + .withGraphFetched('metadata'); + + return formatSmsMessage(text, { + CompanyName: organization.metadata.name, + CustomerName: invoice.customer.displayName, + InvoiceNumber: invoice.invoiceNo, + InvoiceDueAmount: invoice.dueAmountFormatted, + InvoiceDueDate: invoice.dueDateFormatted, + InvoiceDate: invoice.invoiceDateFormatted, + InvoiceAmount: invoice.totalFormatted, + }); + }; + /** * Triggers the mail invoice. * @param {number} tenantId @@ -61,31 +111,19 @@ export class SendInvoiceMailReminder { saleInvoiceId: number, messageOptions: SendInvoiceMailDTO ) { - const { SaleInvoice } = this.tenancy.models(tenantId); - - const saleInvoice = await SaleInvoice.query() - .findById(saleInvoiceId) - .withGraphFetched('customer'); - - // Validates the invoice existance. - this.commandInvoiceValidator.validateInvoiceExistance(saleInvoice); - + const defaultMessageOpts = await this.getDefaultMailOpts( + tenantId, + saleInvoiceId + ); const parsedMessageOptions = { - attachInvoice: true, - subject: DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT, - body: DEFAULT_INVOICE_REMINDER_MAIL_CONTENT, - to: saleInvoice.customer.email, + ...defaultMessageOpts, ...messageOptions, }; // In case there is no email address from the customer or from options, throw an error. if (!parsedMessageOptions.to) { throw new ServiceError(ERRORS.NO_INVOICE_CUSTOMER_EMAIL_ADDR); } - const formatter = R.curry(this.invoiceFormatter.formatText)( - tenantId, - saleInvoiceId - ); - const toEmail = parsedMessageOptions.to; + const formatter = R.curry(this.formatText)(tenantId, saleInvoiceId); const subject = await formatter(parsedMessageOptions.subject); const body = await formatter(parsedMessageOptions.body); const attachments = []; @@ -96,14 +134,11 @@ export class SendInvoiceMailReminder { tenantId, saleInvoiceId ); - attachments.push({ - filename: 'invoice.pdf', - content: invoicePdfBuffer, - }); + attachments.push({ filename: 'invoice.pdf', content: invoicePdfBuffer }); } const mail = new Mail() .setSubject(subject) - .setTo(toEmail) + .setTo(parsedMessageOptions.to) .setContent(body) .setAttachments(attachments);