mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
feat(server): contact mail notification service
This commit is contained in:
@@ -696,7 +696,10 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
invoiceId,
|
invoiceId,
|
||||||
invoiceMailDTO
|
invoiceMailDTO
|
||||||
);
|
);
|
||||||
return res.status(200).send({});
|
return res.status(200).send({
|
||||||
|
code: 200,
|
||||||
|
message: 'The sale invoice mail has been sent successfully.',
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
@@ -717,18 +720,18 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
const { id: invoiceId } = req.params;
|
const { id: invoiceId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.saleInvoiceApplication.getSaleInvoiceMailReminder(
|
const data = await this.saleInvoiceApplication.getSaleInvoiceMailReminder(
|
||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
);
|
);
|
||||||
return res.status(200).send({});
|
return res.status(200).send(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Sends mail invoice of the given sale invoice.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
@@ -749,7 +752,10 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
invoiceId,
|
invoiceId,
|
||||||
invoiceMailDTO
|
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) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ module.exports = {
|
|||||||
secure: !!parseInt(process.env.MAIL_SECURE, 10),
|
secure: !!parseInt(process.env.MAIL_SECURE, 10),
|
||||||
username: process.env.MAIL_USERNAME,
|
username: process.env.MAIL_USERNAME,
|
||||||
password: process.env.MAIL_PASSWORD,
|
password: process.env.MAIL_PASSWORD,
|
||||||
|
from: process.env.MAIL_FROM_ADDRESS,
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
|
import { ISystemUser, IAccount, ITaxTransaction, AddressItem } from '@/interfaces';
|
||||||
import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
|
import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
|
||||||
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||||
|
|
||||||
@@ -187,8 +187,18 @@ export enum SaleInvoiceAction {
|
|||||||
NotifyBySms = 'NotifyBySms',
|
NotifyBySms = 'NotifyBySms',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SaleInvoiceMailOptions {
|
||||||
|
toAddresses: AddressItem[];
|
||||||
|
fromAddresses: AddressItem[];
|
||||||
|
from: string;
|
||||||
|
to: string | string[];
|
||||||
|
subject: string;
|
||||||
|
body: string;
|
||||||
|
attachInvoice: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SendInvoiceMailDTO {
|
export interface SendInvoiceMailDTO {
|
||||||
to: string;
|
to: string | string[];
|
||||||
from: string;
|
from: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
body: string;
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export default class Customer extends mixin(TenantModel, [
|
|||||||
CustomViewBaseModel,
|
CustomViewBaseModel,
|
||||||
ModelSearchable,
|
ModelSearchable,
|
||||||
]) {
|
]) {
|
||||||
|
email: string;
|
||||||
|
displayName: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Query builder.
|
* Query builder.
|
||||||
*/
|
*/
|
||||||
@@ -76,6 +79,19 @@ export default class Customer extends mixin(TenantModel, [
|
|||||||
return 'debit';
|
return 'debit';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
get contactAddresses() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
mail: this.email,
|
||||||
|
label: this.displayName,
|
||||||
|
primary: true
|
||||||
|
},
|
||||||
|
].filter((c) => c.mail);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Model modifiers.
|
* Model modifiers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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<SaleInvoiceMailOptions>}
|
||||||
|
*/
|
||||||
|
public async getDefaultMailOptions(
|
||||||
|
tenantId: number,
|
||||||
|
contactId: number,
|
||||||
|
subject: string = '',
|
||||||
|
body: string = ''
|
||||||
|
): Promise<any> {
|
||||||
|
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<string, any>
|
||||||
|
): Promise<SaleInvoiceMailOptions> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
25
packages/server/src/services/MailTenancy/MailTenancy.ts
Normal file
25
packages/server/src/services/MailTenancy/MailTenancy.ts
Normal file
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -240,7 +240,7 @@ export class SaleEstimatesApplication {
|
|||||||
* @returns {}
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
public getSaleEstimateMail(tenantId: number, saleEstimateId: number) {
|
public getSaleEstimateMail(tenantId: number, saleEstimateId: number) {
|
||||||
return this.sendEstimateMailService.getDefaultMailOpts(
|
return this.sendEstimateMailService.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId
|
saleEstimateId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import * as R from 'ramda';
|
|
||||||
import Mail from '@/lib/Mail';
|
import Mail from '@/lib/Mail';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import {
|
import {
|
||||||
@@ -8,28 +7,31 @@ import {
|
|||||||
} from './constants';
|
} from './constants';
|
||||||
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
|
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
|
||||||
import { GetSaleEstimate } from './GetSaleEstimate';
|
import { GetSaleEstimate } from './GetSaleEstimate';
|
||||||
import { formatSmsMessage } from '@/utils';
|
|
||||||
import { SaleEstimateMailOptions } from '@/interfaces';
|
import { SaleEstimateMailOptions } from '@/interfaces';
|
||||||
|
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendSaleEstimateMail {
|
export class SendSaleEstimateMail {
|
||||||
@Inject()
|
@Inject()
|
||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
@Inject('agenda')
|
|
||||||
private agenda: any;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private estimatePdf: SaleEstimatesPdf;
|
private estimatePdf: SaleEstimatesPdf;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private getSaleEstimateService: GetSaleEstimate;
|
private getSaleEstimateService: GetSaleEstimate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private contactMailNotification: ContactMailNotification;
|
||||||
|
|
||||||
|
@Inject('agenda')
|
||||||
|
private agenda: any;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the reminder mail of the given sale estimate.
|
* Triggers the reminder mail of the given sale estimate.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId -
|
||||||
* @param {number} saleEstimateId
|
* @param {number} saleEstimateId -
|
||||||
* @param {SaleEstimateMailOptions} messageOptions
|
* @param {SaleEstimateMailOptions} messageOptions -
|
||||||
*/
|
*/
|
||||||
public async triggerMail(
|
public async triggerMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
@@ -50,51 +52,50 @@ export class SendSaleEstimateMail {
|
|||||||
* @param {number} estimateId
|
* @param {number} estimateId
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
*/
|
*/
|
||||||
public formatText = async (
|
public formatterData = async (tenantId: number, estimateId: number) => {
|
||||||
tenantId: number,
|
|
||||||
estimateId: number,
|
|
||||||
text: string
|
|
||||||
) => {
|
|
||||||
const estimate = await this.getSaleEstimateService.getEstimate(
|
const estimate = await this.getSaleEstimateService.getEstimate(
|
||||||
tenantId,
|
tenantId,
|
||||||
estimateId
|
estimateId
|
||||||
);
|
);
|
||||||
return formatSmsMessage(text, {
|
return {
|
||||||
CustomerName: estimate.customer.displayName,
|
CustomerName: estimate.customer.displayName,
|
||||||
EstimateNumber: estimate.estimateNumber,
|
EstimateNumber: estimate.estimateNumber,
|
||||||
EstimateDate: estimate.formattedEstimateDate,
|
EstimateDate: estimate.formattedEstimateDate,
|
||||||
EstimateAmount: estimate.formattedAmount,
|
EstimateAmount: estimate.formattedAmount,
|
||||||
EstimateExpirationDate: estimate.formattedExpirationDate,
|
EstimateExpirationDate: estimate.formattedExpirationDate,
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the default mail options.
|
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {number} saleEstimateId
|
|
||||||
* @returns {Promise<any>}
|
|
||||||
*/
|
|
||||||
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} tenantId
|
||||||
* @param {number} saleEstimateId
|
* @param {number} saleEstimateId
|
||||||
* @param {SaleEstimateMailOptions} messageOptions
|
* @param {SaleEstimateMailOptions} messageOptions
|
||||||
@@ -104,34 +105,31 @@ export class SendSaleEstimateMail {
|
|||||||
saleEstimateId: number,
|
saleEstimateId: number,
|
||||||
messageOptions: SaleEstimateMailOptions
|
messageOptions: SaleEstimateMailOptions
|
||||||
) {
|
) {
|
||||||
const defaultMessageOpts = await this.getDefaultMailOpts(
|
const localMessageOpts = await this.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId
|
saleEstimateId
|
||||||
);
|
);
|
||||||
const parsedMessageOpts = {
|
const messageOpts = {
|
||||||
...defaultMessageOpts,
|
...localMessageOpts,
|
||||||
...messageOptions,
|
...messageOptions,
|
||||||
};
|
};
|
||||||
const formatter = R.curry(this.formatText)(tenantId, saleEstimateId);
|
const mail = new Mail()
|
||||||
const subject = await formatter(parsedMessageOpts.subject);
|
.setSubject(messageOpts.subject)
|
||||||
const body = await formatter(parsedMessageOpts.body);
|
.setTo(messageOpts.to)
|
||||||
const attachments = [];
|
.setContent(messageOpts.body);
|
||||||
|
|
||||||
if (parsedMessageOpts.to) {
|
if (messageOpts.to) {
|
||||||
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
|
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId
|
saleEstimateId
|
||||||
);
|
);
|
||||||
attachments.push({
|
mail.setAttachments([
|
||||||
filename: 'estimate.pdf',
|
{
|
||||||
content: estimatePdfBuffer,
|
filename: messageOpts.data?.EstimateNumber || 'estimate.pdf',
|
||||||
});
|
content: estimatePdfBuffer,
|
||||||
|
},
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
await new Mail()
|
await mail.send();
|
||||||
.setSubject(subject)
|
|
||||||
.setTo(parsedMessageOpts.to)
|
|
||||||
.setContent(body)
|
|
||||||
.setAttachments(attachments)
|
|
||||||
.send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -300,10 +300,7 @@ export class SaleInvoiceApplication {
|
|||||||
* @returns {}
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
public getSaleInvoiceMailReminder(tenantId: number, saleInvoiceId: number) {
|
public getSaleInvoiceMailReminder(tenantId: number, saleInvoiceId: number) {
|
||||||
return this.getSaleInvoiceReminderService.getInvoiceMailReminder(
|
return this.sendInvoiceReminderService.getMailOpts(tenantId, saleInvoiceId);
|
||||||
tenantId,
|
|
||||||
saleInvoiceId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -350,9 +347,6 @@ export class SaleInvoiceApplication {
|
|||||||
* @returns {Promise<SendInvoiceMailDTO>}
|
* @returns {Promise<SendInvoiceMailDTO>}
|
||||||
*/
|
*/
|
||||||
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
|
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
|
||||||
return this.sendInvoiceReminderService.getDefaultMailOpts(
|
return this.sendSaleInvoiceMailService.getMailOpts(tenantId, saleInvoiceid);
|
||||||
tenantId,
|
|
||||||
saleInvoiceid
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<SaleInvoiceMailOptions> {
|
||||||
|
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<string>}
|
||||||
|
*/
|
||||||
|
public formatText = async (
|
||||||
|
tenantId: number,
|
||||||
|
invoiceId: number
|
||||||
|
): Promise<Record<string, string | number>> => {
|
||||||
|
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<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',
|
||||||
|
};
|
||||||
@@ -1,30 +1,21 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import * as R from 'ramda';
|
|
||||||
import { SendInvoiceMailDTO } from '@/interfaces';
|
|
||||||
import Mail from '@/lib/Mail';
|
import Mail from '@/lib/Mail';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import { SendInvoiceMailDTO } from '@/interfaces';
|
||||||
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
||||||
|
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
|
||||||
import {
|
import {
|
||||||
DEFAULT_INVOICE_MAIL_CONTENT,
|
DEFAULT_INVOICE_MAIL_CONTENT,
|
||||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
ERRORS,
|
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { formatSmsMessage } from '@/utils';
|
|
||||||
import { GetSaleInvoice } from './GetSaleInvoice';
|
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendSaleInvoiceMail {
|
export class SendSaleInvoiceMail {
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject()
|
|
||||||
private getSaleInvoiceService: GetSaleInvoice;
|
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private invoicePdf: SaleInvoicePdf;
|
private invoicePdf: SaleInvoicePdf;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private invoiceMail: SendSaleInvoiceMailCommon;
|
||||||
|
|
||||||
@Inject('agenda')
|
@Inject('agenda')
|
||||||
private agenda: any;
|
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} tenantId
|
||||||
* @param {number} invoiceId
|
* @param {number} saleInvoiceId
|
||||||
* @returns {Promise<SendInvoiceMailDTO>}
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
*/
|
*/
|
||||||
public getDefaultMailOpts = async (tenantId: number, invoiceId: number) => {
|
public async getMailOpts(tenantId: number, saleInvoiceId: number) {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
return this.invoiceMail.getMailOpts(
|
||||||
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<string>}
|
|
||||||
*/
|
|
||||||
public textFormatter = async (
|
|
||||||
tenantId: number,
|
|
||||||
invoiceId: number,
|
|
||||||
text: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const invoice = await this.getSaleInvoiceService.getSaleInvoice(
|
|
||||||
tenantId,
|
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.
|
* Triggers the mail invoice.
|
||||||
@@ -111,37 +65,30 @@ export class SendSaleInvoiceMail {
|
|||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageDTO: SendInvoiceMailDTO
|
messageDTO: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
const defaultMessageOpts = await this.getDefaultMailOpts(
|
const defaultMessageOpts = await this.getMailOpts(tenantId, saleInvoiceId);
|
||||||
tenantId,
|
|
||||||
saleInvoiceId
|
|
||||||
);
|
|
||||||
// Parsed message opts with default options.
|
// Parsed message opts with default options.
|
||||||
const parsedMessageOpts = {
|
const messageOpts = {
|
||||||
...defaultMessageOpts,
|
...defaultMessageOpts,
|
||||||
...messageDTO,
|
...messageDTO,
|
||||||
};
|
};
|
||||||
// In case there is no email address from the customer or from options, throw an error.
|
this.invoiceMail.validateMailNotification(messageOpts);
|
||||||
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 = [];
|
|
||||||
|
|
||||||
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.
|
// Retrieves document buffer of the invoice pdf document.
|
||||||
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
|
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId
|
saleInvoiceId
|
||||||
);
|
);
|
||||||
attachments.push({ filename: 'invoice.pdf', content: invoicePdfBuffer });
|
mail.setAttachments([
|
||||||
|
{ filename: 'invoice.pdf', content: invoicePdfBuffer },
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
await new Mail()
|
await mail.send();
|
||||||
.setSubject(subject)
|
|
||||||
.setTo(parsedMessageOpts.to)
|
|
||||||
.setContent(body)
|
|
||||||
.setAttachments(attachments)
|
|
||||||
.send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,15 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import * as R from 'ramda';
|
|
||||||
import { SendInvoiceMailDTO } from '@/interfaces';
|
import { SendInvoiceMailDTO } from '@/interfaces';
|
||||||
import Mail from '@/lib/Mail';
|
import Mail from '@/lib/Mail';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
||||||
|
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
|
||||||
import {
|
import {
|
||||||
DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
|
DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
|
||||||
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
|
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
|
||||||
ERRORS,
|
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { GetSaleInvoice } from './GetSaleInvoice';
|
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
import { formatSmsMessage } from '@/utils';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendInvoiceMailReminder {
|
export class SendInvoiceMailReminder {
|
||||||
@Inject()
|
|
||||||
private tenancy: HasTenancyService;
|
|
||||||
|
|
||||||
@Inject('agenda')
|
@Inject('agenda')
|
||||||
private agenda: any;
|
private agenda: any;
|
||||||
|
|
||||||
@@ -26,7 +17,7 @@ export class SendInvoiceMailReminder {
|
|||||||
private invoicePdf: SaleInvoicePdf;
|
private invoicePdf: SaleInvoicePdf;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private getSaleInvoiceService: GetSaleInvoice;
|
private invoiceCommonMail: SendSaleInvoiceMailCommon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the reminder mail of the given sale invoice.
|
* 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} tenantId
|
||||||
* @param {number} invoiceId
|
* @param {number} saleInvoiceId
|
||||||
* @returns {Promise<SendInvoiceMailDTO>}
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getDefaultMailOpts(tenantId: number, invoiceId: number) {
|
public async getMailOpts(tenantId: number, saleInvoiceId: number) {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
return this.invoiceCommonMail.getMailOpts(
|
||||||
|
|
||||||
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<string>}
|
|
||||||
*/
|
|
||||||
public formatText = async (
|
|
||||||
tenantId: number,
|
|
||||||
invoiceId: number,
|
|
||||||
text: string
|
|
||||||
): Promise<string> => {
|
|
||||||
const invoice = await this.getSaleInvoiceService.getSaleInvoice(
|
|
||||||
tenantId,
|
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.
|
* Triggers the mail invoice.
|
||||||
@@ -111,37 +64,27 @@ export class SendInvoiceMailReminder {
|
|||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageOptions: SendInvoiceMailDTO
|
messageOptions: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
const defaultMessageOpts = await this.getDefaultMailOpts(
|
const localMessageOpts = await this.getMailOpts(tenantId, saleInvoiceId);
|
||||||
tenantId,
|
|
||||||
saleInvoiceId
|
const messageOpts = {
|
||||||
);
|
...localMessageOpts,
|
||||||
const parsedMessageOptions = {
|
|
||||||
...defaultMessageOpts,
|
|
||||||
...messageOptions,
|
...messageOptions,
|
||||||
};
|
};
|
||||||
// In case there is no email address from the customer or from options, throw an error.
|
const mail = new Mail()
|
||||||
if (!parsedMessageOptions.to) {
|
.setSubject(messageOpts.subject)
|
||||||
throw new ServiceError(ERRORS.NO_INVOICE_CUSTOMER_EMAIL_ADDR);
|
.setTo(messageOpts.to)
|
||||||
}
|
.setContent(messageOpts.body);
|
||||||
const formatter = R.curry(this.formatText)(tenantId, saleInvoiceId);
|
|
||||||
const subject = await formatter(parsedMessageOptions.subject);
|
|
||||||
const body = await formatter(parsedMessageOptions.body);
|
|
||||||
const attachments = [];
|
|
||||||
|
|
||||||
if (parsedMessageOptions.attachInvoice) {
|
if (messageOpts.attachInvoice) {
|
||||||
// Retrieves document buffer of the invoice pdf document.
|
// Retrieves document buffer of the invoice pdf document.
|
||||||
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
|
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId
|
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();
|
await mail.send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ Invoice <strong>#{InvoiceNumber}</strong><br />
|
|||||||
Due Date : <strong>{InvoiceDueDate}</strong><br />
|
Due Date : <strong>{InvoiceDueDate}</strong><br />
|
||||||
Amount : <strong>{InvoiceAmount}</strong></br />
|
Amount : <strong>{InvoiceAmount}</strong></br />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<i>Regards</i><br />
|
||||||
|
<i>{CompanyName}</i>
|
||||||
|
</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT =
|
export const DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT =
|
||||||
@@ -18,6 +23,11 @@ export const DEFAULT_INVOICE_REMINDER_MAIL_CONTENT = `
|
|||||||
<p>Invoice <strong>#{InvoiceNumber}</strong><br />
|
<p>Invoice <strong>#{InvoiceNumber}</strong><br />
|
||||||
Due Date : <strong>{InvoiceDueDate}</strong><br />
|
Due Date : <strong>{InvoiceDueDate}</strong><br />
|
||||||
Amount : <strong>{InvoiceAmount}</strong></p>
|
Amount : <strong>{InvoiceAmount}</strong></p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<i>Regards</i><br />
|
||||||
|
<i>{CompanyName}</i>
|
||||||
|
</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ERRORS = {
|
export const ERRORS = {
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import * as R from 'ramda';
|
|
||||||
import { IPaymentReceiveMailOpts, SendInvoiceMailDTO } from '@/interfaces';
|
import { IPaymentReceiveMailOpts, SendInvoiceMailDTO } from '@/interfaces';
|
||||||
import Mail from '@/lib/Mail';
|
import Mail from '@/lib/Mail';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import {
|
import {
|
||||||
DEFAULT_PAYMENT_MAIL_CONTENT,
|
DEFAULT_PAYMENT_MAIL_CONTENT,
|
||||||
DEFAULT_PAYMENT_MAIL_SUBJECT,
|
DEFAULT_PAYMENT_MAIL_SUBJECT,
|
||||||
ERRORS,
|
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { formatSmsMessage } from '@/utils';
|
|
||||||
import { Tenant } from '@/system/models';
|
import { Tenant } from '@/system/models';
|
||||||
import { GetPaymentReceive } from './GetPaymentReceive';
|
import { GetPaymentReceive } from './GetPaymentReceive';
|
||||||
|
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendPaymentReceiveMailNotification {
|
export class SendPaymentReceiveMailNotification {
|
||||||
@@ -21,6 +18,9 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getPaymentService: GetPaymentReceive;
|
private getPaymentService: GetPaymentReceive;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private contactMailNotification: ContactMailNotification;
|
||||||
|
|
||||||
@Inject('agenda')
|
@Inject('agenda')
|
||||||
private agenda: any;
|
private agenda: any;
|
||||||
|
|
||||||
@@ -49,19 +49,22 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
* @param {number} invoiceId
|
* @param {number} invoiceId
|
||||||
* @returns {Promise<SendInvoiceMailDTO>}
|
* @returns {Promise<SendInvoiceMailDTO>}
|
||||||
*/
|
*/
|
||||||
public getDefaultMailOpts = async (tenantId: number, invoiceId: number) => {
|
public getMailOptions = async (tenantId: number, invoiceId: number) => {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const paymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query()
|
||||||
.findById(invoiceId)
|
.findById(invoiceId)
|
||||||
.withGraphFetched('customer')
|
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
return {
|
const formatterData = await this.textFormatter(tenantId, invoiceId);
|
||||||
attachInvoice: true,
|
|
||||||
subject: DEFAULT_PAYMENT_MAIL_SUBJECT,
|
return this.contactMailNotification.getMailOptions(
|
||||||
body: DEFAULT_PAYMENT_MAIL_CONTENT,
|
tenantId,
|
||||||
to: paymentReceive.customer.email,
|
paymentReceive.customerId,
|
||||||
};
|
DEFAULT_PAYMENT_MAIL_SUBJECT,
|
||||||
|
DEFAULT_PAYMENT_MAIL_CONTENT,
|
||||||
|
formatterData
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,9 +76,8 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
*/
|
*/
|
||||||
public textFormatter = async (
|
public textFormatter = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
invoiceId: number,
|
invoiceId: number
|
||||||
text: string
|
): Promise<Record<string, string>> => {
|
||||||
): Promise<string> => {
|
|
||||||
const payment = await this.getPaymentService.getPaymentReceive(
|
const payment = await this.getPaymentService.getPaymentReceive(
|
||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
@@ -84,13 +86,13 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
.findById(tenantId)
|
.findById(tenantId)
|
||||||
.withGraphFetched('metadata');
|
.withGraphFetched('metadata');
|
||||||
|
|
||||||
return formatSmsMessage(text, {
|
return {
|
||||||
CompanyName: organization.metadata.name,
|
CompanyName: organization.metadata.name,
|
||||||
CustomerName: payment.customer.displayName,
|
CustomerName: payment.customer.displayName,
|
||||||
PaymentNumber: payment.payment_receive_no,
|
PaymentNumber: payment.payment_receive_no,
|
||||||
PaymentDate: payment.formattedPaymentDate,
|
PaymentDate: payment.formattedPaymentDate,
|
||||||
PaymentAmount: payment.formattedAmount,
|
PaymentAmount: payment.formattedAmount,
|
||||||
});
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +107,7 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
paymentReceiveId: number,
|
paymentReceiveId: number,
|
||||||
messageDTO: SendInvoiceMailDTO
|
messageDTO: SendInvoiceMailDTO
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const defaultMessageOpts = await this.getDefaultMailOpts(
|
const defaultMessageOpts = await this.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId
|
paymentReceiveId
|
||||||
);
|
);
|
||||||
@@ -114,18 +116,10 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
...defaultMessageOpts,
|
...defaultMessageOpts,
|
||||||
...messageDTO,
|
...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()
|
await new Mail()
|
||||||
.setSubject(subject)
|
.setSubject(parsedMessageOpts.subject)
|
||||||
.setTo(parsedMessageOpts.to)
|
.setTo(parsedMessageOpts.to)
|
||||||
.setContent(body)
|
.setContent(parsedMessageOpts.body)
|
||||||
.send();
|
.send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -205,10 +205,7 @@ export class PaymentReceivesApplication {
|
|||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public getPaymentDefaultMail(tenantId: number, paymentReceiveId: number) {
|
public getPaymentDefaultMail(tenantId: number, paymentReceiveId: number) {
|
||||||
return this.paymentMailNotify.getDefaultMailOpts(
|
return this.paymentMailNotify.getMailOptions(tenantId, paymentReceiveId);
|
||||||
tenantId,
|
|
||||||
paymentReceiveId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ export class SaleReceiptApplication {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public getSaleReceiptMail(tenantId: number, saleReceiptId: number) {
|
public getSaleReceiptMail(tenantId: number, saleReceiptId: number) {
|
||||||
return this.saleReceiptNotifyByMailService.getDefaultMailOpts(
|
return this.saleReceiptNotifyByMailService.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId
|
saleReceiptId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import * as R from 'ramda';
|
|||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Tenant } from '@/system/models';
|
import { Tenant } from '@/system/models';
|
||||||
import { formatSmsMessage } from '@/utils';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import Mail from '@/lib/Mail';
|
import Mail from '@/lib/Mail';
|
||||||
import { GetSaleReceipt } from './GetSaleReceipt';
|
import { GetSaleReceipt } from './GetSaleReceipt';
|
||||||
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
|
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
|
||||||
@@ -11,8 +9,8 @@ import {
|
|||||||
DEFAULT_RECEIPT_MAIL_CONTENT,
|
DEFAULT_RECEIPT_MAIL_CONTENT,
|
||||||
DEFAULT_RECEIPT_MAIL_SUBJECT,
|
DEFAULT_RECEIPT_MAIL_SUBJECT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { ERRORS } from './constants';
|
|
||||||
import { SaleReceiptMailOpts } from '@/interfaces';
|
import { SaleReceiptMailOpts } from '@/interfaces';
|
||||||
|
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleReceiptMailNotification {
|
export class SaleReceiptMailNotification {
|
||||||
@@ -25,6 +23,9 @@ export class SaleReceiptMailNotification {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private receiptPdfService: SaleReceiptsPdf;
|
private receiptPdfService: SaleReceiptsPdf;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private contactMailNotification: ContactMailNotification;
|
||||||
|
|
||||||
@Inject('agenda')
|
@Inject('agenda')
|
||||||
private agenda: any;
|
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} tenantId
|
||||||
* @param {number} invoiceId
|
* @param {number} saleReceiptId
|
||||||
* @returns {Promise<SendInvoiceMailDTO>}
|
* @returns
|
||||||
*/
|
*/
|
||||||
public getDefaultMailOpts = async (tenantId: number, invoiceId: number) => {
|
public async getMailOptions(tenantId: number, saleReceiptId: number) {
|
||||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const saleReceipt = await SaleReceipt.query()
|
const saleReceipt = await SaleReceipt.query()
|
||||||
.findById(invoiceId)
|
.findById(saleReceiptId)
|
||||||
.withGraphFetched('customer')
|
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
return {
|
const formattedData = await this.textFormatter(tenantId, saleReceiptId);
|
||||||
attachInvoice: true,
|
|
||||||
subject: DEFAULT_RECEIPT_MAIL_SUBJECT,
|
return this.contactMailNotification.getMailOptions(
|
||||||
body: DEFAULT_RECEIPT_MAIL_CONTENT,
|
tenantId,
|
||||||
to: saleReceipt.customer.email,
|
saleReceipt.customerId,
|
||||||
};
|
DEFAULT_RECEIPT_MAIL_SUBJECT,
|
||||||
};
|
DEFAULT_RECEIPT_MAIL_CONTENT,
|
||||||
|
formattedData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the formatted text of the given sale invoice.
|
* Retrieves the formatted text of the given sale invoice.
|
||||||
@@ -77,9 +81,8 @@ export class SaleReceiptMailNotification {
|
|||||||
*/
|
*/
|
||||||
public textFormatter = async (
|
public textFormatter = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
receiptId: number,
|
receiptId: number
|
||||||
text: string
|
): Promise<Record<string, string>> => {
|
||||||
): Promise<string> => {
|
|
||||||
const invoice = await this.getSaleReceiptService.getSaleReceipt(
|
const invoice = await this.getSaleReceiptService.getSaleReceipt(
|
||||||
tenantId,
|
tenantId,
|
||||||
receiptId
|
receiptId
|
||||||
@@ -88,13 +91,13 @@ export class SaleReceiptMailNotification {
|
|||||||
.findById(tenantId)
|
.findById(tenantId)
|
||||||
.withGraphFetched('metadata');
|
.withGraphFetched('metadata');
|
||||||
|
|
||||||
return formatSmsMessage(text, {
|
return {
|
||||||
CompanyName: organization.metadata.name,
|
CompanyName: organization.metadata.name,
|
||||||
CustomerName: invoice.customer.displayName,
|
CustomerName: invoice.customer.displayName,
|
||||||
ReceiptNumber: invoice.receiptNumber,
|
ReceiptNumber: invoice.receiptNumber,
|
||||||
ReceiptDate: invoice.formattedReceiptDate,
|
ReceiptDate: invoice.formattedReceiptDate,
|
||||||
ReceiptAmount: invoice.formattedAmount,
|
ReceiptAmount: invoice.formattedAmount,
|
||||||
});
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,7 +112,7 @@ export class SaleReceiptMailNotification {
|
|||||||
saleReceiptId: number,
|
saleReceiptId: number,
|
||||||
messageOpts: SaleReceiptMailOpts
|
messageOpts: SaleReceiptMailOpts
|
||||||
) {
|
) {
|
||||||
const defaultMessageOpts = await this.getDefaultMailOpts(
|
const defaultMessageOpts = await this.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId
|
saleReceiptId
|
||||||
);
|
);
|
||||||
@@ -118,14 +121,11 @@ export class SaleReceiptMailNotification {
|
|||||||
...defaultMessageOpts,
|
...defaultMessageOpts,
|
||||||
...messageOpts,
|
...messageOpts,
|
||||||
};
|
};
|
||||||
// In case there is no email address from the customer or from options, throw an error.
|
|
||||||
if (!parsedMessageOpts.to) {
|
const mail = new Mail()
|
||||||
throw new ServiceError(ERRORS.NO_INVOICE_CUSTOMER_EMAIL_ADDR);
|
.setSubject(parsedMessageOpts.subject)
|
||||||
}
|
.setTo(parsedMessageOpts.to)
|
||||||
const formatter = R.curry(this.textFormatter)(tenantId, saleReceiptId);
|
.setContent(parsedMessageOpts.body);
|
||||||
const body = await formatter(parsedMessageOpts.body);
|
|
||||||
const subject = await formatter(parsedMessageOpts.subject);
|
|
||||||
const attachments = [];
|
|
||||||
|
|
||||||
if (parsedMessageOpts.attachInvoice) {
|
if (parsedMessageOpts.attachInvoice) {
|
||||||
// Retrieves document buffer of the invoice pdf document.
|
// Retrieves document buffer of the invoice pdf document.
|
||||||
@@ -133,13 +133,10 @@ export class SaleReceiptMailNotification {
|
|||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId
|
saleReceiptId
|
||||||
);
|
);
|
||||||
attachments.push({ filename: 'invoice.pdf', content: receiptPdfBuffer });
|
mail.setAttachments([
|
||||||
|
{ filename: 'invoice.pdf', content: receiptPdfBuffer },
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
await new Mail()
|
await mail.send();
|
||||||
.setSubject(subject)
|
|
||||||
.setTo(parsedMessageOpts.to)
|
|
||||||
.setContent(body)
|
|
||||||
.setAttachments(attachments)
|
|
||||||
.send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user