diff --git a/packages/server/src/api/controllers/Sales/SalesInvoices.ts b/packages/server/src/api/controllers/Sales/SalesInvoices.ts index 91c370273..69be4f4a8 100644 --- a/packages/server/src/api/controllers/Sales/SalesInvoices.ts +++ b/packages/server/src/api/controllers/Sales/SalesInvoices.ts @@ -10,6 +10,7 @@ import { ISaleInvoiceCreateDTO, SaleInvoiceAction, AbilitySubject, + SendInvoiceMailDTO, } from '@/interfaces'; import CheckPolicies from '@/api/middleware/CheckPolicies'; import { SaleInvoiceApplication } from '@/services/Sales/Invoices/SaleInvoicesApplication'; @@ -169,10 +170,10 @@ export default class SaleInvoicesController extends BaseController { '/:id/mail', [ ...this.specificSaleInvoiceValidation, - body('from').isString().exists(), - body('to').isString().exists(), - body('body').isString().exists(), - body('attach_invoice').exists().isBoolean().toBoolean(), + body('from').isString().optional(), + body('to').isString().optional(), + body('body').isString().optional(), + body('attach_invoice').optional().isBoolean().toBoolean(), ], this.validationResult, asyncMiddleware(this.sendSaleInvoiceMail.bind(this)), @@ -664,7 +665,7 @@ export default class SaleInvoicesController extends BaseController { }; /** - * + * Sends mail invoice of the given sale invoice. * @param {Request} req * @param {Response} res * @param {NextFunction} next @@ -676,11 +677,13 @@ export default class SaleInvoicesController extends BaseController { ) { const { tenantId } = req; const { id: invoiceId } = req.params; + const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req); try { await this.saleInvoiceApplication.sendSaleInvoiceMail( tenantId, - invoiceId + invoiceId, + invoiceMailDTO ); return res.status(200).send({}); } catch (error) { @@ -726,11 +729,13 @@ export default class SaleInvoicesController extends BaseController { ) { const { tenantId } = req; const { id: invoiceId } = req.params; + const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req); try { await this.saleInvoiceApplication.sendSaleInvoiceMailReminder( tenantId, - invoiceId + invoiceId, + invoiceMailDTO ); return res.status(200).send({}); } catch (error) { diff --git a/packages/server/src/interfaces/SaleInvoice.ts b/packages/server/src/interfaces/SaleInvoice.ts index 59090cd03..7d7633b87 100644 --- a/packages/server/src/interfaces/SaleInvoice.ts +++ b/packages/server/src/interfaces/SaleInvoice.ts @@ -194,3 +194,9 @@ export interface SendInvoiceMailDTO { body: string; attachInvoice?: boolean; } + +export interface ISaleInvoiceNotifyPayload { + tenantId: number; + saleInvoiceId: number; + messageDTO: SendInvoiceMailDTO; +} diff --git a/packages/server/src/loaders/jobs.ts b/packages/server/src/loaders/jobs.ts index 4fa3aadb1..9beb7a2bc 100644 --- a/packages/server/src/loaders/jobs.ts +++ b/packages/server/src/loaders/jobs.ts @@ -5,6 +5,8 @@ import RewriteInvoicesJournalEntries from 'jobs/WriteInvoicesJEntries'; import UserInviteMailJob from 'jobs/UserInviteMail'; import OrganizationSetupJob from 'jobs/OrganizationSetup'; import OrganizationUpgrade from 'jobs/OrganizationUpgrade'; +import { SendSaleInvoiceMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailJob'; +import { SendSaleInvoiceReminderMailJob } from '@/services/Sales/Invoices/SendSaleInvoiceMailReminderJob'; export default ({ agenda }: { agenda: Agenda }) => { new ResetPasswordMailJob(agenda); @@ -13,6 +15,8 @@ export default ({ agenda }: { agenda: Agenda }) => { new RewriteInvoicesJournalEntries(agenda); new OrganizationSetupJob(agenda); new OrganizationUpgrade(agenda); + new SendSaleInvoiceMailJob(agenda); + new SendSaleInvoiceReminderMailJob(agenda); agenda.start(); }; diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts index eb1a475aa..9ce79b656 100644 --- a/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts @@ -312,15 +312,20 @@ export class SaleInvoiceApplication { * @param {number} saleInvoiceId * @returns {} */ - public sendSaleInvoiceMailReminder(tenantId: number, saleInvoiceId: number) { - return this.sendInvoiceReminderService.sendInvoiceMailReminder( + public sendSaleInvoiceMailReminder( + tenantId: number, + saleInvoiceId: number, + messageDTO: SendInvoiceMailDTO + ) { + return this.sendInvoiceReminderService.triggerMail( tenantId, - saleInvoiceId + saleInvoiceId, + messageDTO ); } /** - * + * Sends the invoice mail of the given sale invoice. * @param {number} tenantId * @param {number} saleInvoiceId * @param {SendInvoiceMailDTO} messageDTO @@ -331,7 +336,7 @@ export class SaleInvoiceApplication { saleInvoiceId: number, messageDTO: SendInvoiceMailDTO ) { - return this.sendSaleInvoiceMailService.sendSaleInvoiceMail( + return this.sendSaleInvoiceMailService.sendMail( tenantId, saleInvoiceId, messageDTO diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts index 7d0b89b86..0fa1aeded 100644 --- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts @@ -1,12 +1,67 @@ -import { Service } from 'typedi'; -import { SendInvoiceMailDTO } from '@/interfaces'; - +import { Inject, Service } from 'typedi'; +import { ISaleInvoiceNotifyPayload, SendInvoiceMailDTO } from '@/interfaces'; +import Mail from '@/lib/Mail'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; +import events from '@/subscribers/events'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; @Service() export class SendSaleInvoiceMail { - public sendSaleInvoiceMail( + @Inject() + private tenancy: HasTenancyService; + + @Inject('agenda') + private agenda: any; + + /** + * Sends the invoice mail of the given sale invoice. + * @param {number} tenantId + * @param {number} saleInvoiceId + * @param {SendInvoiceMailDTO} messageDTO + */ + public async sendMail( tenantId: number, saleInvoiceId: number, messageDTO: SendInvoiceMailDTO - ) {} + ) { + const payload = { + tenantId, + saleInvoiceId, + messageDTO, + }; + await this.agenda.now('sale-invoice-mail-send', payload); + } + + /** + * Triggers the mail invoice. + * @param {number} tenantId + * @param {number} saleInvoiceId + * @param {SendInvoiceMailDTO} messageDTO + * @returns {Promise} + */ + public async triggerMail( + tenantId: number, + saleInvoiceId: number, + messageDTO: SendInvoiceMailDTO + ) { + const { SaleInvoice } = this.tenancy.models(tenantId); + + const saleInvoice = await SaleInvoice.query() + .findById(saleInvoiceId) + .withGraphFetched('customer'); + + const toEmail = messageDTO.to || saleInvoice.customer.email; + const subject = messageDTO.subject || saleInvoice.invoiceNo; + + if (!toEmail) { + return null; + } + const mail = new Mail() + .setSubject(subject) + .setView('mail/UserInvite.html') + .setTo(toEmail) + .setData({}); + + await mail.send(); + } } diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailJob.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailJob.ts new file mode 100644 index 000000000..2b73757f3 --- /dev/null +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailJob.ts @@ -0,0 +1,33 @@ +import Container, { Service } from 'typedi'; +import events from '@/subscribers/events'; +import { SendSaleInvoiceMail } from './SendSaleInvoiceMail'; + +@Service() +export class SendSaleInvoiceMailJob { + /** + * Constructor method. + */ + constructor(agenda) { + agenda.define( + 'sale-invoice-mail-send', + { priority: 'high', concurrency: 1 }, + this.handler + ); + } + + /** + * Triggers sending invoice mail. + */ + private handler = async (job, done: Function) => { + const { tenantId, saleInvoiceId, messageDTO } = job.attrs.data; + const sendInvoiceMail = Container.get(SendSaleInvoiceMail); + + try { + await sendInvoiceMail.triggerMail(tenantId, saleInvoiceId, messageDTO); + done(); + } catch (error) { + console.log(error); + done(error); + } + }; +} diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts index 6dcf49693..da210cf41 100644 --- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts @@ -1,11 +1,64 @@ -import { Service } from 'typedi'; +import { Inject, Service } from 'typedi'; +import { SendInvoiceMailDTO } from '@/interfaces'; +import Mail from '@/lib/Mail'; +import HasTenancyService from '@/services/Tenancy/TenancyService'; @Service() export class SendInvoiceMailReminder { + @Inject() + private tenancy: HasTenancyService; + + @Inject('agenda') + private agenda: any; + /** - * + * Triggers the reminder mail of the given sale invoice. * @param {number} tenantId * @param {number} saleInvoiceId */ - public sendInvoiceMailReminder(tenantId: number, saleInvoiceId: number) {} + public async triggerMail( + tenantId: number, + saleInvoiceId: number, + messageDTO: SendInvoiceMailDTO + ) { + const payload = { + tenantId, + saleInvoiceId, + messageDTO, + }; + await this.agenda.now('sale-invoice-reminder-mail-send', payload); + } + + /** + * Triggers the mail invoice. + * @param {number} tenantId + * @param {number} saleInvoiceId + * @param {SendInvoiceMailDTO} messageDTO + * @returns {Promise} + */ + public async sendMail( + tenantId: number, + saleInvoiceId: number, + messageDTO: SendInvoiceMailDTO + ) { + const { SaleInvoice } = this.tenancy.models(tenantId); + + const saleInvoice = await SaleInvoice.query() + .findById(saleInvoiceId) + .withGraphFetched('customer'); + + const toEmail = messageDTO.to || saleInvoice.customer.email; + const subject = messageDTO.subject || saleInvoice.invoiceNo; + + if (!toEmail) { + return null; + } + const mail = new Mail() + .setSubject(subject) + .setView('mail/UserInvite.html') + .setTo(toEmail) + .setData({}); + + await mail.send(); + } } diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminderJob.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminderJob.ts new file mode 100644 index 000000000..97a622c7d --- /dev/null +++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminderJob.ts @@ -0,0 +1,32 @@ +import Container, { Service } from 'typedi'; +import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder'; + +@Service() +export class SendSaleInvoiceReminderMailJob { + /** + * Constructor method. + */ + constructor(agenda) { + agenda.define( + 'sale-invoice-reminder-mail-send', + { priority: 'high', concurrency: 1 }, + this.handler + ); + } + + /** + * Triggers sending invoice mail. + */ + private handler = async (job, done: Function) => { + const { tenantId, saleInvoiceId, messageDTO } = job.attrs.data; + const sendInvoiceMail = Container.get(SendInvoiceMailReminder); + + try { + await sendInvoiceMail.sendMail(tenantId, saleInvoiceId, messageDTO); + done(); + } catch (error) { + console.log(error); + done(error); + } + }; +} diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index 24ca0a0a3..e54f48152 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -129,6 +129,9 @@ export default { onNotifySms: 'onSaleInvoiceNotifySms', onNotifiedSms: 'onSaleInvoiceNotifiedSms', + + onNotifyMail: 'onSaleInvoiceNotifyMail', + onNotifyReminderMail: 'onSaleInvoiceNotifyReminderMail' }, /** @@ -160,6 +163,8 @@ export default { onRejecting: 'onSaleEstimateRejecting', onRejected: 'onSaleEstimateRejected', + + onNotifyMail: 'onSaleEstimateNotifyMail' }, /**