diff --git a/packages/server/src/api/controllers/Sales/SalesInvoices.ts b/packages/server/src/api/controllers/Sales/SalesInvoices.ts
index cd7f5bfec..9e5ac8d25 100644
--- a/packages/server/src/api/controllers/Sales/SalesInvoices.ts
+++ b/packages/server/src/api/controllers/Sales/SalesInvoices.ts
@@ -696,7 +696,10 @@ export default class SaleInvoicesController extends BaseController {
invoiceId,
invoiceMailDTO
);
- return res.status(200).send({});
+ return res.status(200).send({
+ code: 200,
+ message: 'The sale invoice mail has been sent successfully.',
+ });
} catch (error) {
next(error);
}
@@ -717,18 +720,18 @@ export default class SaleInvoicesController extends BaseController {
const { id: invoiceId } = req.params;
try {
- await this.saleInvoiceApplication.getSaleInvoiceMailReminder(
+ const data = await this.saleInvoiceApplication.getSaleInvoiceMailReminder(
tenantId,
invoiceId
);
- return res.status(200).send({});
+ return res.status(200).send(data);
} catch (error) {
next(error);
}
}
/**
- *
+ * Sends mail invoice of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
@@ -749,7 +752,10 @@ export default class SaleInvoicesController extends BaseController {
invoiceId,
invoiceMailDTO
);
- return res.status(200).send({});
+ return res.status(200).send({
+ code: 200,
+ message: 'The sale invoice mail reminder has been sent successfully.',
+ });
} catch (error) {
next(error);
}
diff --git a/packages/server/src/config/index.ts b/packages/server/src/config/index.ts
index bc6833130..0dc9d9676 100644
--- a/packages/server/src/config/index.ts
+++ b/packages/server/src/config/index.ts
@@ -58,6 +58,7 @@ module.exports = {
secure: !!parseInt(process.env.MAIL_SECURE, 10),
username: process.env.MAIL_USERNAME,
password: process.env.MAIL_PASSWORD,
+ from: process.env.MAIL_FROM_ADDRESS,
},
/**
diff --git a/packages/server/src/interfaces/SaleInvoice.ts b/packages/server/src/interfaces/SaleInvoice.ts
index 7d7633b87..d660b17ca 100644
--- a/packages/server/src/interfaces/SaleInvoice.ts
+++ b/packages/server/src/interfaces/SaleInvoice.ts
@@ -1,5 +1,5 @@
import { Knex } from 'knex';
-import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
+import { ISystemUser, IAccount, ITaxTransaction, AddressItem } from '@/interfaces';
import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
@@ -187,8 +187,18 @@ export enum SaleInvoiceAction {
NotifyBySms = 'NotifyBySms',
}
+export interface SaleInvoiceMailOptions {
+ toAddresses: AddressItem[];
+ fromAddresses: AddressItem[];
+ from: string;
+ to: string | string[];
+ subject: string;
+ body: string;
+ attachInvoice: boolean;
+}
+
export interface SendInvoiceMailDTO {
- to: string;
+ to: string | string[];
from: string;
subject: string;
body: string;
diff --git a/packages/server/src/models/Customer.ts b/packages/server/src/models/Customer.ts
index 690b77d55..631763b71 100644
--- a/packages/server/src/models/Customer.ts
+++ b/packages/server/src/models/Customer.ts
@@ -24,6 +24,9 @@ export default class Customer extends mixin(TenantModel, [
CustomViewBaseModel,
ModelSearchable,
]) {
+ email: string;
+ displayName: string;
+
/**
* Query builder.
*/
@@ -76,6 +79,19 @@ export default class Customer extends mixin(TenantModel, [
return 'debit';
}
+ /**
+ *
+ */
+ get contactAddresses() {
+ return [
+ {
+ mail: this.email,
+ label: this.displayName,
+ primary: true
+ },
+ ].filter((c) => c.mail);
+ }
+
/**
* Model modifiers.
*/
diff --git a/packages/server/src/services/MailNotification/ContactMailNotification.ts b/packages/server/src/services/MailNotification/ContactMailNotification.ts
new file mode 100644
index 000000000..21c745a96
--- /dev/null
+++ b/packages/server/src/services/MailNotification/ContactMailNotification.ts
@@ -0,0 +1,78 @@
+import { Inject, Service } from 'typedi';
+import * as R from 'ramda';
+import { SaleInvoiceMailOptions } from '@/interfaces';
+import HasTenancyService from '@/services/Tenancy/TenancyService';
+import { MailTenancy } from '@/services/MailTenancy/MailTenancy';
+import { formatSmsMessage } from '@/utils';
+
+@Service()
+export class ContactMailNotification {
+ @Inject()
+ private mailTenancy: MailTenancy;
+
+ @Inject()
+ private tenancy: HasTenancyService;
+
+ /**
+ * Parses the default message options.
+ * @param {number} tenantId
+ * @param {number} invoiceId
+ * @returns {Promise}
+ */
+ public async getDefaultMailOptions(
+ tenantId: number,
+ contactId: number,
+ subject: string = '',
+ body: string = ''
+ ): Promise {
+ const { Contact, Customer } = this.tenancy.models(tenantId);
+ const contact = await Customer.query().findById(contactId).throwIfNotFound();
+
+ const toAddresses = contact.contactAddresses;
+ const fromAddresses = await this.mailTenancy.senders(tenantId);
+
+ const toAddress = toAddresses.find((a) => a.primary);
+ const fromAddress = fromAddresses.find((a) => a.primary);
+
+ const to = toAddress?.mail || '';
+ const from = fromAddress?.mail || '';
+
+ return {
+ subject,
+ body,
+ to,
+ from,
+ fromAddresses,
+ toAddresses,
+ };
+ }
+
+ /**
+ * Retrieves the mail options.
+ * @param {number}
+ * @param {number} invoiceId
+ * @returns {}
+ */
+ public async getMailOptions(
+ tenantId: number,
+ contactId: number,
+ defaultSubject?: string,
+ defaultBody?: string,
+ formatterData?: Record
+ ): Promise {
+ const mailOpts = await this.getDefaultMailOptions(
+ tenantId,
+ contactId,
+ defaultSubject,
+ defaultBody
+ );
+ const subject = formatSmsMessage(mailOpts.subject, formatterData);
+ const body = formatSmsMessage(mailOpts.body, formatterData);
+
+ return {
+ ...mailOpts,
+ subject,
+ body,
+ };
+ }
+}
diff --git a/packages/server/src/services/MailTenancy/MailTenancy.ts b/packages/server/src/services/MailTenancy/MailTenancy.ts
new file mode 100644
index 000000000..6f8e82e11
--- /dev/null
+++ b/packages/server/src/services/MailTenancy/MailTenancy.ts
@@ -0,0 +1,25 @@
+import config from '@/config';
+import { Tenant } from "@/system/models";
+import { Service } from 'typedi';
+
+
+@Service()
+export class MailTenancy {
+ /**
+ * Retrieves the senders mails of the given tenant.
+ * @param {number} tenantId
+ */
+ public async senders(tenantId: number) {
+ const tenant = await Tenant.query()
+ .findById(tenantId)
+ .withGraphFetched('metadata');
+
+ return [
+ {
+ mail: config.mail.from,
+ label: tenant.metadata.name,
+ primary: true,
+ }
+ ].filter((item) => item.mail)
+ }
+}
\ No newline at end of file
diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimatesApplication.ts b/packages/server/src/services/Sales/Estimates/SaleEstimatesApplication.ts
index 68cd8e601..1ceb2bbcc 100644
--- a/packages/server/src/services/Sales/Estimates/SaleEstimatesApplication.ts
+++ b/packages/server/src/services/Sales/Estimates/SaleEstimatesApplication.ts
@@ -240,7 +240,7 @@ export class SaleEstimatesApplication {
* @returns {}
*/
public getSaleEstimateMail(tenantId: number, saleEstimateId: number) {
- return this.sendEstimateMailService.getDefaultMailOpts(
+ return this.sendEstimateMailService.getMailOptions(
tenantId,
saleEstimateId
);
diff --git a/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts b/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts
index 9ae6fa2f8..5777ab55a 100644
--- a/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts
+++ b/packages/server/src/services/Sales/Estimates/SendSaleEstimateMail.ts
@@ -1,5 +1,4 @@
import { Inject, Service } from 'typedi';
-import * as R from 'ramda';
import Mail from '@/lib/Mail';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import {
@@ -8,28 +7,31 @@ import {
} from './constants';
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
import { GetSaleEstimate } from './GetSaleEstimate';
-import { formatSmsMessage } from '@/utils';
import { SaleEstimateMailOptions } from '@/interfaces';
+import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
@Service()
export class SendSaleEstimateMail {
@Inject()
private tenancy: HasTenancyService;
- @Inject('agenda')
- private agenda: any;
-
@Inject()
private estimatePdf: SaleEstimatesPdf;
@Inject()
private getSaleEstimateService: GetSaleEstimate;
+ @Inject()
+ private contactMailNotification: ContactMailNotification;
+
+ @Inject('agenda')
+ private agenda: any;
+
/**
* Triggers the reminder mail of the given sale estimate.
- * @param {number} tenantId
- * @param {number} saleEstimateId
- * @param {SaleEstimateMailOptions} messageOptions
+ * @param {number} tenantId -
+ * @param {number} saleEstimateId -
+ * @param {SaleEstimateMailOptions} messageOptions -
*/
public async triggerMail(
tenantId: number,
@@ -50,51 +52,50 @@ export class SendSaleEstimateMail {
* @param {number} estimateId
* @param {string} text
*/
- public formatText = async (
- tenantId: number,
- estimateId: number,
- text: string
- ) => {
+ public formatterData = async (tenantId: number, estimateId: number) => {
const estimate = await this.getSaleEstimateService.getEstimate(
tenantId,
estimateId
);
- return formatSmsMessage(text, {
+ return {
CustomerName: estimate.customer.displayName,
EstimateNumber: estimate.estimateNumber,
EstimateDate: estimate.formattedEstimateDate,
EstimateAmount: estimate.formattedAmount,
EstimateExpirationDate: estimate.formattedExpirationDate,
- });
- };
-
- /**
- * Retrieves the default mail options.
- * @param {number} tenantId
- * @param {number} saleEstimateId
- * @returns {Promise}
- */
- public getDefaultMailOpts = async (
- tenantId: number,
- saleEstimateId: number
- ) => {
- const { SaleEstimate } = this.tenancy.models(tenantId);
-
- const saleEstimate = await SaleEstimate.query()
- .findById(saleEstimateId)
- .withGraphFetched('customer')
- .throwIfNotFound();
-
- return {
- attachPdf: true,
- subject: DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT,
- body: DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT,
- to: saleEstimate.customer.email,
};
};
/**
- * Sends the mail.
+ * Retrieves the mail options.
+ * @param {number} tenantId
+ * @param {number} saleEstimateId
+ * @returns
+ */
+ public getMailOptions = async (tenantId: number, saleEstimateId: number) => {
+ const { SaleEstimate } = this.tenancy.models(tenantId);
+
+ const saleEstimate = await SaleEstimate.query()
+ .findById(saleEstimateId)
+ .throwIfNotFound();
+
+ const formatterData = await this.formatterData(tenantId, saleEstimateId);
+
+ const mailOptions = await this.contactMailNotification.getMailOptions(
+ tenantId,
+ saleEstimate.customerId,
+ DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT,
+ DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT,
+ formatterData
+ );
+ return {
+ ...mailOptions,
+ data: formatterData,
+ };
+ };
+
+ /**
+ * Sends the mail notification of the given sale estimate.
* @param {number} tenantId
* @param {number} saleEstimateId
* @param {SaleEstimateMailOptions} messageOptions
@@ -104,34 +105,31 @@ export class SendSaleEstimateMail {
saleEstimateId: number,
messageOptions: SaleEstimateMailOptions
) {
- const defaultMessageOpts = await this.getDefaultMailOpts(
+ const localMessageOpts = await this.getMailOptions(
tenantId,
saleEstimateId
);
- const parsedMessageOpts = {
- ...defaultMessageOpts,
+ const messageOpts = {
+ ...localMessageOpts,
...messageOptions,
};
- const formatter = R.curry(this.formatText)(tenantId, saleEstimateId);
- const subject = await formatter(parsedMessageOpts.subject);
- const body = await formatter(parsedMessageOpts.body);
- const attachments = [];
+ const mail = new Mail()
+ .setSubject(messageOpts.subject)
+ .setTo(messageOpts.to)
+ .setContent(messageOpts.body);
- if (parsedMessageOpts.to) {
+ if (messageOpts.to) {
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
tenantId,
saleEstimateId
);
- attachments.push({
- filename: 'estimate.pdf',
- content: estimatePdfBuffer,
- });
+ mail.setAttachments([
+ {
+ filename: messageOpts.data?.EstimateNumber || 'estimate.pdf',
+ content: estimatePdfBuffer,
+ },
+ ]);
}
- await new Mail()
- .setSubject(subject)
- .setTo(parsedMessageOpts.to)
- .setContent(body)
- .setAttachments(attachments)
- .send();
+ await mail.send();
}
}
diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts
index 9b3c19d33..b175d9546 100644
--- a/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts
+++ b/packages/server/src/services/Sales/Invoices/SaleInvoicesApplication.ts
@@ -300,10 +300,7 @@ export class SaleInvoiceApplication {
* @returns {}
*/
public getSaleInvoiceMailReminder(tenantId: number, saleInvoiceId: number) {
- return this.getSaleInvoiceReminderService.getInvoiceMailReminder(
- tenantId,
- saleInvoiceId
- );
+ return this.sendInvoiceReminderService.getMailOpts(tenantId, saleInvoiceId);
}
/**
@@ -345,14 +342,11 @@ export class SaleInvoiceApplication {
/**
* Retrieves the default mail options of the given sale invoice.
- * @param {number} tenantId
- * @param {number} saleInvoiceid
+ * @param {number} tenantId
+ * @param {number} saleInvoiceid
* @returns {Promise}
*/
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
- return this.sendInvoiceReminderService.getDefaultMailOpts(
- tenantId,
- saleInvoiceid
- );
+ return this.sendSaleInvoiceMailService.getMailOpts(tenantId, saleInvoiceid);
}
}
diff --git a/packages/server/src/services/Sales/Invoices/SendInvoiceInvoiceMailCommon.ts b/packages/server/src/services/Sales/Invoices/SendInvoiceInvoiceMailCommon.ts
new file mode 100644
index 000000000..97be8de87
--- /dev/null
+++ b/packages/server/src/services/Sales/Invoices/SendInvoiceInvoiceMailCommon.ts
@@ -0,0 +1,115 @@
+import { Inject, Service } from 'typedi';
+import { isEmpty } from 'lodash';
+import { SaleInvoiceMailOptions } from '@/interfaces';
+import HasTenancyService from '@/services/Tenancy/TenancyService';
+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 {
+ @Inject()
+ private tenancy: HasTenancyService;
+
+ @Inject()
+ private getSaleInvoiceService: GetSaleInvoice;
+
+ @Inject()
+ private contactMailNotification: ContactMailNotification;
+
+ /**
+ * Retrieves the mail options.
+ * @param {number} tenantId - Tenant id.
+ * @param {number} invoiceId - Invoice id.
+ * @param {string} defaultSubject - Subject text.
+ * @param {string} defaultBody - Subject body.
+ * @returns {}
+ */
+ public async getMailOpts(
+ tenantId: number,
+ invoiceId: number,
+ defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
+ defaultBody: string = DEFAULT_INVOICE_MAIL_CONTENT
+ ): Promise {
+ const { SaleInvoice } = this.tenancy.models(tenantId);
+
+ const saleInvoice = await SaleInvoice.query()
+ .findById(invoiceId)
+ .throwIfNotFound();
+
+ const formatterData = await this.formatText(tenantId, invoiceId);
+
+ return this.contactMailNotification.getMailOptions(
+ tenantId,
+ saleInvoice.customerId,
+ defaultSubject,
+ defaultBody,
+ formatterData
+ );
+ }
+
+ /**
+ * 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
+ ): Promise> => {
+ const invoice = await this.getSaleInvoiceService.getSaleInvoice(
+ 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,
+ InvoiceDueDate: invoice.dueDateFormatted,
+ InvoiceDate: invoice.invoiceDateFormatted,
+ InvoiceAmount: invoice.totalFormatted,
+ OverdueDays: invoice.overdueDays,
+ };
+ };
+
+ /**
+ * Validates the mail notification options before sending it.
+ * @param {Partial} mailNotificationOpts
+ * @throws {ServiceError}
+ */
+ public validateMailNotification(
+ mailNotificationOpts: Partial
+ ) {
+ 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',
+};
diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts
index cc8269c56..a5bc744e1 100644
--- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts
+++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMail.ts
@@ -1,29 +1,20 @@
import { Inject, Service } from 'typedi';
-import * as R from 'ramda';
-import { SendInvoiceMailDTO } from '@/interfaces';
import Mail from '@/lib/Mail';
-import HasTenancyService from '@/services/Tenancy/TenancyService';
+import { SendInvoiceMailDTO } from '@/interfaces';
import { SaleInvoicePdf } from './SaleInvoicePdf';
+import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
import {
DEFAULT_INVOICE_MAIL_CONTENT,
DEFAULT_INVOICE_MAIL_SUBJECT,
- ERRORS,
} from './constants';
-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;
+ private invoicePdf: SaleInvoicePdf;
@Inject()
- private getSaleInvoiceService: GetSaleInvoice;
-
- @Inject()
- private invoicePdf: SaleInvoicePdf;
+ private invoiceMail: SendSaleInvoiceMailCommon;
@Inject('agenda')
private agenda: any;
@@ -48,56 +39,19 @@ export class SendSaleInvoiceMail {
}
/**
- * Retrieves the default invoice mail options.
+ * Retrieves the mail options of the given sale invoice.
* @param {number} tenantId
- * @param {number} invoiceId
- * @returns {Promise}
+ * @param {number} saleInvoiceId
+ * @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(
+ public async getMailOpts(tenantId: number, saleInvoiceId: number) {
+ return this.invoiceMail.getMailOpts(
tenantId,
- invoiceId
+ saleInvoiceId,
+ DEFAULT_INVOICE_MAIL_SUBJECT,
+ DEFAULT_INVOICE_MAIL_CONTENT
);
- 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.
@@ -111,37 +65,30 @@ export class SendSaleInvoiceMail {
saleInvoiceId: number,
messageDTO: SendInvoiceMailDTO
) {
- const defaultMessageOpts = await this.getDefaultMailOpts(
- tenantId,
- saleInvoiceId
- );
+ const defaultMessageOpts = await this.getMailOpts(tenantId, saleInvoiceId);
+
// Parsed message opts with default options.
- const parsedMessageOpts = {
+ const messageOpts = {
...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, saleInvoiceId);
- const subject = await formatter(parsedMessageOpts.subject);
- const body = await formatter(parsedMessageOpts.body);
- const attachments = [];
+ this.invoiceMail.validateMailNotification(messageOpts);
- if (parsedMessageOpts.attachInvoice) {
+ const mail = new Mail()
+ .setSubject(messageOpts.subject)
+ .setTo(messageOpts.to)
+ .setContent(messageOpts.body);
+
+ if (messageOpts.attachInvoice) {
// Retrieves document buffer of the invoice pdf document.
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
tenantId,
saleInvoiceId
);
- attachments.push({ filename: 'invoice.pdf', content: invoicePdfBuffer });
+ mail.setAttachments([
+ { filename: 'invoice.pdf', content: invoicePdfBuffer },
+ ]);
}
- await new Mail()
- .setSubject(subject)
- .setTo(parsedMessageOpts.to)
- .setContent(body)
- .setAttachments(attachments)
- .send();
+ await mail.send();
}
}
diff --git a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts
index 886916260..09463bddc 100644
--- a/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts
+++ b/packages/server/src/services/Sales/Invoices/SendSaleInvoiceMailReminder.ts
@@ -1,24 +1,15 @@
import { Inject, Service } from 'typedi';
-import * as R from 'ramda';
import { SendInvoiceMailDTO } from '@/interfaces';
import Mail from '@/lib/Mail';
-import HasTenancyService from '@/services/Tenancy/TenancyService';
+import { SaleInvoicePdf } from './SaleInvoicePdf';
+import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
import {
DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
- ERRORS,
} 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 {
- @Inject()
- private tenancy: HasTenancyService;
-
@Inject('agenda')
private agenda: any;
@@ -26,7 +17,7 @@ export class SendInvoiceMailReminder {
private invoicePdf: SaleInvoicePdf;
@Inject()
- private getSaleInvoiceService: GetSaleInvoice;
+ private invoiceCommonMail: SendSaleInvoiceMailCommon;
/**
* Triggers the reminder mail of the given sale invoice.
@@ -47,57 +38,19 @@ export class SendInvoiceMailReminder {
}
/**
- * Parses the default message options.
+ * Retrieves the mail options of the given sale invoice.
* @param {number} tenantId
- * @param {number} invoiceId
- * @returns {Promise}
+ * @param {number} saleInvoiceId
+ * @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(
+ public async getMailOpts(tenantId: number, saleInvoiceId: number) {
+ return this.invoiceCommonMail.getMailOpts(
tenantId,
- invoiceId
+ saleInvoiceId,
+ DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
+ DEFAULT_INVOICE_REMINDER_MAIL_CONTENT
);
- 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.
@@ -111,37 +64,27 @@ export class SendInvoiceMailReminder {
saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO
) {
- const defaultMessageOpts = await this.getDefaultMailOpts(
- tenantId,
- saleInvoiceId
- );
- const parsedMessageOptions = {
- ...defaultMessageOpts,
+ const localMessageOpts = await this.getMailOpts(tenantId, saleInvoiceId);
+
+ const messageOpts = {
+ ...localMessageOpts,
...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.formatText)(tenantId, saleInvoiceId);
- const subject = await formatter(parsedMessageOptions.subject);
- const body = await formatter(parsedMessageOptions.body);
- const attachments = [];
+ const mail = new Mail()
+ .setSubject(messageOpts.subject)
+ .setTo(messageOpts.to)
+ .setContent(messageOpts.body);
- if (parsedMessageOptions.attachInvoice) {
+ if (messageOpts.attachInvoice) {
// Retrieves document buffer of the invoice pdf document.
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
tenantId,
saleInvoiceId
);
- attachments.push({ filename: 'invoice.pdf', content: invoicePdfBuffer });
+ mail.setAttachments([
+ { filename: 'invoice.pdf', content: invoicePdfBuffer },
+ ]);
}
- const mail = new Mail()
- .setSubject(subject)
- .setTo(parsedMessageOptions.to)
- .setContent(body)
- .setAttachments(attachments);
-
await mail.send();
}
}
diff --git a/packages/server/src/services/Sales/Invoices/constants.ts b/packages/server/src/services/Sales/Invoices/constants.ts
index 79bf67c0a..404b7e613 100644
--- a/packages/server/src/services/Sales/Invoices/constants.ts
+++ b/packages/server/src/services/Sales/Invoices/constants.ts
@@ -8,6 +8,11 @@ Invoice #{InvoiceNumber}
Due Date : {InvoiceDueDate}
Amount : {InvoiceAmount}
+
+
+Regards
+{CompanyName}
+
`;
export const DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT =
@@ -18,6 +23,11 @@ export const DEFAULT_INVOICE_REMINDER_MAIL_CONTENT = `
Invoice #{InvoiceNumber}
Due Date : {InvoiceDueDate}
Amount : {InvoiceAmount}
+
+
+Regards
+{CompanyName}
+
`;
export const ERRORS = {
diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts
index 42069c5f3..79dfe2392 100644
--- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts
+++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceiveMailNotification.ts
@@ -1,17 +1,14 @@
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';
+import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
@Service()
export class SendPaymentReceiveMailNotification {
@@ -21,6 +18,9 @@ export class SendPaymentReceiveMailNotification {
@Inject()
private getPaymentService: GetPaymentReceive;
+ @Inject()
+ private contactMailNotification: ContactMailNotification;
+
@Inject('agenda')
private agenda: any;
@@ -49,19 +49,22 @@ export class SendPaymentReceiveMailNotification {
* @param {number} invoiceId
* @returns {Promise}
*/
- public getDefaultMailOpts = async (tenantId: number, invoiceId: number) => {
+ public getMailOptions = 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,
- };
+ const formatterData = await this.textFormatter(tenantId, invoiceId);
+
+ return this.contactMailNotification.getMailOptions(
+ tenantId,
+ paymentReceive.customerId,
+ DEFAULT_PAYMENT_MAIL_SUBJECT,
+ DEFAULT_PAYMENT_MAIL_CONTENT,
+ formatterData
+ );
};
/**
@@ -73,9 +76,8 @@ export class SendPaymentReceiveMailNotification {
*/
public textFormatter = async (
tenantId: number,
- invoiceId: number,
- text: string
- ): Promise => {
+ invoiceId: number
+ ): Promise> => {
const payment = await this.getPaymentService.getPaymentReceive(
tenantId,
invoiceId
@@ -84,13 +86,13 @@ export class SendPaymentReceiveMailNotification {
.findById(tenantId)
.withGraphFetched('metadata');
- return formatSmsMessage(text, {
+ return {
CompanyName: organization.metadata.name,
CustomerName: payment.customer.displayName,
PaymentNumber: payment.payment_receive_no,
PaymentDate: payment.formattedPaymentDate,
PaymentAmount: payment.formattedAmount,
- });
+ };
};
/**
@@ -105,7 +107,7 @@ export class SendPaymentReceiveMailNotification {
paymentReceiveId: number,
messageDTO: SendInvoiceMailDTO
): Promise {
- const defaultMessageOpts = await this.getDefaultMailOpts(
+ const defaultMessageOpts = await this.getMailOptions(
tenantId,
paymentReceiveId
);
@@ -114,18 +116,10 @@ export class SendPaymentReceiveMailNotification {
...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)
+ .setSubject(parsedMessageOpts.subject)
.setTo(parsedMessageOpts.to)
- .setContent(body)
+ .setContent(parsedMessageOpts.body)
.send();
}
}
diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentReceivesApplication.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentReceivesApplication.ts
index 6092664e6..bf1e2da3f 100644
--- a/packages/server/src/services/Sales/PaymentReceives/PaymentReceivesApplication.ts
+++ b/packages/server/src/services/Sales/PaymentReceives/PaymentReceivesApplication.ts
@@ -205,10 +205,7 @@ export class PaymentReceivesApplication {
* @returns {Promise}
*/
public getPaymentDefaultMail(tenantId: number, paymentReceiveId: number) {
- return this.paymentMailNotify.getDefaultMailOpts(
- tenantId,
- paymentReceiveId
- );
+ return this.paymentMailNotify.getMailOptions(tenantId, paymentReceiveId);
}
/**
diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptApplication.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptApplication.ts
index 6fe03b2a1..0f790ec6f 100644
--- a/packages/server/src/services/Sales/Receipts/SaleReceiptApplication.ts
+++ b/packages/server/src/services/Sales/Receipts/SaleReceiptApplication.ts
@@ -191,12 +191,12 @@ export class SaleReceiptApplication {
/**
* Retrieves the default mail options of the given sale receipt.
- * @param {number} tenantId
- * @param {number} saleReceiptId
- * @returns
+ * @param {number} tenantId
+ * @param {number} saleReceiptId
+ * @returns
*/
public getSaleReceiptMail(tenantId: number, saleReceiptId: number) {
- return this.saleReceiptNotifyByMailService.getDefaultMailOpts(
+ return this.saleReceiptNotifyByMailService.getMailOptions(
tenantId,
saleReceiptId
);
diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptMailNotification.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptMailNotification.ts
index 0d93c2b65..20bfc4073 100644
--- a/packages/server/src/services/Sales/Receipts/SaleReceiptMailNotification.ts
+++ b/packages/server/src/services/Sales/Receipts/SaleReceiptMailNotification.ts
@@ -2,8 +2,6 @@ import * as R from 'ramda';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject, Service } from 'typedi';
import { Tenant } from '@/system/models';
-import { formatSmsMessage } from '@/utils';
-import { ServiceError } from '@/exceptions';
import Mail from '@/lib/Mail';
import { GetSaleReceipt } from './GetSaleReceipt';
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
@@ -11,8 +9,8 @@ import {
DEFAULT_RECEIPT_MAIL_CONTENT,
DEFAULT_RECEIPT_MAIL_SUBJECT,
} from './constants';
-import { ERRORS } from './constants';
import { SaleReceiptMailOpts } from '@/interfaces';
+import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
@Service()
export class SaleReceiptMailNotification {
@@ -25,6 +23,9 @@ export class SaleReceiptMailNotification {
@Inject()
private receiptPdfService: SaleReceiptsPdf;
+ @Inject()
+ private contactMailNotification: ContactMailNotification;
+
@Inject('agenda')
private agenda: any;
@@ -48,25 +49,28 @@ export class SaleReceiptMailNotification {
}
/**
- * Retrieves the default receipt mail options.
+ * Retrieves the mail options of the given sale receipt.
* @param {number} tenantId
- * @param {number} invoiceId
- * @returns {Promise}
+ * @param {number} saleReceiptId
+ * @returns
*/
- public getDefaultMailOpts = async (tenantId: number, invoiceId: number) => {
+ public async getMailOptions(tenantId: number, saleReceiptId: number) {
const { SaleReceipt } = this.tenancy.models(tenantId);
+
const saleReceipt = await SaleReceipt.query()
- .findById(invoiceId)
- .withGraphFetched('customer')
+ .findById(saleReceiptId)
.throwIfNotFound();
- return {
- attachInvoice: true,
- subject: DEFAULT_RECEIPT_MAIL_SUBJECT,
- body: DEFAULT_RECEIPT_MAIL_CONTENT,
- to: saleReceipt.customer.email,
- };
- };
+ const formattedData = await this.textFormatter(tenantId, saleReceiptId);
+
+ return this.contactMailNotification.getMailOptions(
+ tenantId,
+ saleReceipt.customerId,
+ DEFAULT_RECEIPT_MAIL_SUBJECT,
+ DEFAULT_RECEIPT_MAIL_CONTENT,
+ formattedData
+ );
+ }
/**
* Retrieves the formatted text of the given sale invoice.
@@ -77,9 +81,8 @@ export class SaleReceiptMailNotification {
*/
public textFormatter = async (
tenantId: number,
- receiptId: number,
- text: string
- ): Promise => {
+ receiptId: number
+ ): Promise> => {
const invoice = await this.getSaleReceiptService.getSaleReceipt(
tenantId,
receiptId
@@ -88,13 +91,13 @@ export class SaleReceiptMailNotification {
.findById(tenantId)
.withGraphFetched('metadata');
- return formatSmsMessage(text, {
+ return {
CompanyName: organization.metadata.name,
CustomerName: invoice.customer.displayName,
ReceiptNumber: invoice.receiptNumber,
ReceiptDate: invoice.formattedReceiptDate,
ReceiptAmount: invoice.formattedAmount,
- });
+ };
};
/**
@@ -109,7 +112,7 @@ export class SaleReceiptMailNotification {
saleReceiptId: number,
messageOpts: SaleReceiptMailOpts
) {
- const defaultMessageOpts = await this.getDefaultMailOpts(
+ const defaultMessageOpts = await this.getMailOptions(
tenantId,
saleReceiptId
);
@@ -118,14 +121,11 @@ export class SaleReceiptMailNotification {
...defaultMessageOpts,
...messageOpts,
};
- // 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, saleReceiptId);
- const body = await formatter(parsedMessageOpts.body);
- const subject = await formatter(parsedMessageOpts.subject);
- const attachments = [];
+
+ const mail = new Mail()
+ .setSubject(parsedMessageOpts.subject)
+ .setTo(parsedMessageOpts.to)
+ .setContent(parsedMessageOpts.body);
if (parsedMessageOpts.attachInvoice) {
// Retrieves document buffer of the invoice pdf document.
@@ -133,13 +133,10 @@ export class SaleReceiptMailNotification {
tenantId,
saleReceiptId
);
- attachments.push({ filename: 'invoice.pdf', content: receiptPdfBuffer });
+ mail.setAttachments([
+ { filename: 'invoice.pdf', content: receiptPdfBuffer },
+ ]);
}
- await new Mail()
- .setSubject(subject)
- .setTo(parsedMessageOpts.to)
- .setContent(body)
- .setAttachments(attachments)
- .send();
+ await mail.send();
}
}