diff --git a/packages/server/src/services/Sales/PaymentReceives/GetPaymentReeceivePdf.ts b/packages/server/src/services/Sales/PaymentReceives/GetPaymentReeceivePdf.ts index e05937f76..e3d3cfb26 100644 --- a/packages/server/src/services/Sales/PaymentReceives/GetPaymentReeceivePdf.ts +++ b/packages/server/src/services/Sales/PaymentReceives/GetPaymentReeceivePdf.ts @@ -1,7 +1,7 @@ import { Inject, Service } from 'typedi'; import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy'; import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable'; -import { IPaymentReceive } from '@/interfaces'; +import { GetPaymentReceive } from './GetPaymentReceive'; @Service() export default class GetPaymentReceivePdf { @@ -11,6 +11,9 @@ export default class GetPaymentReceivePdf { @Inject() private templateInjectable: TemplateInjectable; + @Inject() + private getPaymentService: GetPaymentReceive; + /** * Retrieve sale invoice pdf content. * @param {number} tenantId - @@ -19,8 +22,12 @@ export default class GetPaymentReceivePdf { */ async getPaymentReceivePdf( tenantId: number, - paymentReceive: IPaymentReceive + paymentReceiveId: number ): Promise { + const paymentReceive = await this.getPaymentService.getPaymentReceive( + tenantId, + paymentReceiveId + ); const htmlContent = await this.templateInjectable.render( tenantId, 'modules/payment-receive-standard', diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts new file mode 100644 index 000000000..3b92c2ece --- /dev/null +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts @@ -0,0 +1,131 @@ +import { Inject, Service } from 'typedi'; +import * as R from 'ramda'; +import { IPaymentReceiveMailOpts, SendInvoiceMailDTO } from '@/interfaces'; +import Mail from '@/lib/Mail'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import { + DEFAULT_PAYMENT_MAIL_CONTENT, + DEFAULT_PAYMENT_MAIL_SUBJECT, + ERRORS, +} from './constants'; +import { ServiceError } from '@/exceptions'; +import { formatSmsMessage } from '@/utils'; +import { Tenant } from '@/system/models'; +import { GetPaymentReceive } from './GetPaymentReceive'; + +@Service() +export class SendPaymentReceiveMailNotification { + @Inject() + private tenancy: HasTenancyService; + + @Inject() + private getPaymentService: GetPaymentReceive; + + @Inject('agenda') + private agenda: any; + + /** + * Sends the mail of the given payment receive. + * @param {number} tenantId + * @param {number} paymentReceiveId + * @param {SendInvoiceMailDTO} messageDTO + */ + public async triggerMail( + tenantId: number, + paymentReceiveId: number, + messageDTO: IPaymentReceiveMailOpts + ) { + const payload = { + tenantId, + paymentReceiveId, + messageDTO, + }; + await this.agenda.now('payment-receive-mail-send', payload); + } + + /** + * Retrieves the default payment mail options. + * @param {number} tenantId + * @param {number} invoiceId + * @returns {Promise} + */ + public getDefaultMailOpts = async (tenantId: number, invoiceId: number) => { + const { PaymentReceive } = this.tenancy.models(tenantId); + const paymentReceive = await PaymentReceive.query() + .findById(invoiceId) + .withGraphFetched('customer') + .throwIfNotFound(); + + return { + attachInvoice: true, + subject: DEFAULT_PAYMENT_MAIL_SUBJECT, + body: DEFAULT_PAYMENT_MAIL_CONTENT, + to: paymentReceive.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 payment = await this.getPaymentService.getPaymentReceive( + tenantId, + invoiceId + ); + const organization = await Tenant.query() + .findById(tenantId) + .withGraphFetched('metadata'); + + return formatSmsMessage(text, { + CompanyName: organization.metadata.name, + CustomerName: payment.customer.displayName, + PaymentNumber: payment.invoiceNo, + PaymentDate: payment.dueAmountFormatted, + PaymentAmount: payment.dueAmountFormatted, + }); + }; + + /** + * Triggers the mail invoice. + * @param {number} tenantId + * @param {number} saleInvoiceId + * @param {SendInvoiceMailDTO} messageDTO + * @returns {Promise} + */ + public async sendMail( + tenantId: number, + paymentReceiveId: number, + messageDTO: SendInvoiceMailDTO + ): Promise { + const defaultMessageOpts = await this.getDefaultMailOpts( + tenantId, + paymentReceiveId + ); + // Parsed message opts with default options. + const parsedMessageOpts = { + ...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.textFormatter)(tenantId, paymentReceiveId); + const subject = await formatter(parsedMessageOpts.subject); + const body = await formatter(parsedMessageOpts.body); + + await new Mail() + .setSubject(subject) + .setTo(parsedMessageOpts.to) + .setContent(body) + .send(); + } +} diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob.ts new file mode 100644 index 000000000..236a33758 --- /dev/null +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob.ts @@ -0,0 +1,35 @@ +import Container, { Service } from 'typedi'; +import events from '@/subscribers/events'; +import { SendPaymentReceiveMailNotification } from './PaymentReceiveMailNotification'; + +@Service() +export class PaymentReceiveMailNotificationJob { + /** + * Constructor method. + */ + constructor(agenda) { + agenda.define( + 'payment-receive-mail-send', + { priority: 'high', concurrency: 2 }, + this.handler + ); + } + + /** + * Triggers sending payment notification via mail. + */ + private handler = async (job, done: Function) => { + const { tenantId, paymentReceiveId, messageDTO } = job.attrs.data; + const paymentMail = Container.get(SendPaymentReceiveMailNotification); + + console.log(tenantId, paymentReceiveId, messageDTO); + + try { + await paymentMail.sendMail(tenantId, paymentReceiveId, messageDTO); + done(); + } catch (error) { + console.log(error); + done(error); + } + }; +} diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceivesApplication.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceivesApplication.ts index afeca6010..bbf88b1ca 100644 --- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceivesApplication.ts +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceivesApplication.ts @@ -4,6 +4,7 @@ import { IPaymentReceive, IPaymentReceiveCreateDTO, IPaymentReceiveEditDTO, + IPaymentReceiveMailOpts, IPaymentReceiveSmsDetails, IPaymentReceivesFilter, ISystemUser, @@ -17,7 +18,7 @@ import { GetPaymentReceive } from './GetPaymentReceive'; import { GetPaymentReceiveInvoices } from './GetPaymentReceiveInvoices'; import { PaymentReceiveNotifyBySms } from './PaymentReceiveSmsNotify'; import GetPaymentReceivePdf from './GetPaymentReeceivePdf'; -import { PaymentReceive } from '@/models'; +import { SendPaymentReceiveMailNotification } from './PaymentReceiveMailNotification'; @Service() export class PaymentReceivesApplication { @@ -42,6 +43,9 @@ export class PaymentReceivesApplication { @Inject() private paymentSmsNotify: PaymentReceiveNotifyBySms; + @Inject() + private paymentMailNotify: SendPaymentReceiveMailNotification; + @Inject() private getPaymentReceivePdfService: GetPaymentReceivePdf; @@ -176,18 +180,37 @@ export class PaymentReceivesApplication { }; /** - * Retrieve PDF content of the given payment receive. + * Notify customer via mail about payment receive details. + * @param {number} tenantId + * @param {number} paymentReceiveId + * @param {IPaymentReceiveMailOpts} messageOpts + * @returns {Promise} + */ + public notifyPaymentByMail( + tenantId: number, + paymentReceiveId: number, + messageOpts: IPaymentReceiveMailOpts + ) { + return this.paymentMailNotify.triggerMail( + tenantId, + paymentReceiveId, + messageOpts + ); + } + + /** + * Retrieve pdf content of the given payment receive. * @param {number} tenantId * @param {PaymentReceive} paymentReceive * @returns */ public getPaymentReceivePdf = ( tenantId: number, - paymentReceive: PaymentReceive + paymentReceiveId: number ) => { return this.getPaymentReceivePdfService.getPaymentReceivePdf( tenantId, - paymentReceive + paymentReceiveId ); }; } diff --git a/packages/server/src/services/Sales/PaymentReceives/constants.ts b/packages/server/src/services/Sales/PaymentReceives/constants.ts index ccd8d75ee..e11060670 100644 --- a/packages/server/src/services/Sales/PaymentReceives/constants.ts +++ b/packages/server/src/services/Sales/PaymentReceives/constants.ts @@ -1,3 +1,15 @@ +export const DEFAULT_PAYMENT_MAIL_SUBJECT = + 'Invoice {InvoiceNumber} from {CompanyName}'; +export const DEFAULT_PAYMENT_MAIL_CONTENT = ` +

Dear {CustomerName}

+

Thank you for your business, You can view or print your invoice from attachements.

+

+Invoice #{InvoiceNumber}
+Due Date : {InvoiceDueDate}
+Amount : {InvoiceAmount}
+

+`; + export const ERRORS = { PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS', PAYMENT_RECEIVE_NOT_EXISTS: 'PAYMENT_RECEIVE_NOT_EXISTS', @@ -12,6 +24,7 @@ export const ERRORS = { PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE: 'PAYMENT_CUSTOMER_SHOULD_NOT_UPDATE', CUSTOMER_HAS_PAYMENT_RECEIVES: 'CUSTOMER_HAS_PAYMENT_RECEIVES', PAYMENT_ACCOUNT_CURRENCY_INVALID: 'PAYMENT_ACCOUNT_CURRENCY_INVALID', + NO_INVOICE_CUSTOMER_EMAIL_ADDR: 'NO_INVOICE_CUSTOMER_EMAIL_ADDR' }; export const DEFAULT_VIEWS = [];