mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
feat: send sale estimate mail notification
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
ISaleEstimate,
|
||||
ISaleEstimateDTO,
|
||||
ISalesEstimatesFilter,
|
||||
SaleEstimateMailOptions,
|
||||
} from '@/interfaces';
|
||||
import { EditSaleEstimate } from './EditSaleEstimate';
|
||||
import { DeleteSaleEstimate } from './DeleteSaleEstimate';
|
||||
@@ -202,25 +203,33 @@ export class SaleEstimatesApplication {
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} tenantId
|
||||
* @param {} saleEstimate
|
||||
* @returns
|
||||
*/
|
||||
public getSaleEstimatePdf(tenantId: number, saleEstimate) {
|
||||
return this.saleEstimatesPdfService.getSaleEstimatePdf(
|
||||
tenantId,
|
||||
saleEstimate
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Retrieve the PDF content of the given sale estimate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @returns
|
||||
*/
|
||||
public sendSaleEstimateMail(tenantId: number, saleEstimateId: number) {
|
||||
return this.sendEstimateMailService.sendMail(tenantId, saleEstimateId);
|
||||
public getSaleEstimatePdf(tenantId: number, saleEstimateId: number) {
|
||||
return this.saleEstimatesPdfService.getSaleEstimatePdf(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the reminder mail of the given sale estimate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public sendSaleEstimateMail(
|
||||
tenantId: number,
|
||||
saleEstimateId: number,
|
||||
saleEstimateMailOpts: SaleEstimateMailOptions
|
||||
) {
|
||||
return this.sendEstimateMailService.triggerMail(
|
||||
tenantId,
|
||||
saleEstimateId,
|
||||
saleEstimateMailOpts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
|
||||
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
|
||||
import { GetSaleEstimate } from './GetSaleEstimate';
|
||||
|
||||
@Service()
|
||||
export class SaleEstimatesPdf {
|
||||
@@ -10,11 +11,19 @@ export class SaleEstimatesPdf {
|
||||
@Inject()
|
||||
private templateInjectable: TemplateInjectable;
|
||||
|
||||
@Inject()
|
||||
private getSaleEstimate: GetSaleEstimate;
|
||||
|
||||
/**
|
||||
* Retrieve sale invoice pdf content.
|
||||
* @param {} saleInvoice -
|
||||
* @param {number} tenantId -
|
||||
* @param {ISaleInvoice} saleInvoice -
|
||||
*/
|
||||
async getSaleEstimatePdf(tenantId: number, saleEstimate) {
|
||||
public async getSaleEstimatePdf(tenantId: number, saleEstimateId: number) {
|
||||
const saleEstimate = await this.getSaleEstimate.getEstimate(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
const htmlContent = await this.templateInjectable.render(
|
||||
tenantId,
|
||||
'modules/estimate-regular',
|
||||
|
||||
@@ -1,6 +1,139 @@
|
||||
import { Service } from "typedi";
|
||||
import { Inject, Service } from 'typedi';
|
||||
import * as R from 'ramda';
|
||||
import Mail from '@/lib/Mail';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import {
|
||||
DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT,
|
||||
DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT,
|
||||
} from './constants';
|
||||
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
|
||||
import { GetSaleEstimate } from './GetSaleEstimate';
|
||||
import { formatSmsMessage } from '@/utils';
|
||||
import { SaleEstimateMailOptions } from '@/interfaces';
|
||||
|
||||
@Service()
|
||||
export class SendSaleEstimateMail {
|
||||
sendMail(tenantId: number, saleEstimateId: number) {}
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject('agenda')
|
||||
private agenda: any;
|
||||
|
||||
@Inject()
|
||||
private estimatePdf: SaleEstimatesPdf;
|
||||
|
||||
@Inject()
|
||||
private getSaleEstimateService: GetSaleEstimate;
|
||||
|
||||
/**
|
||||
* Triggers the reminder mail of the given sale estimate.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @param messageOptions
|
||||
*/
|
||||
public async triggerMail(
|
||||
tenantId: number,
|
||||
saleEstimateId: number,
|
||||
messageOptions: any
|
||||
) {
|
||||
const payload = {
|
||||
tenantId,
|
||||
saleEstimateId,
|
||||
messageOptions,
|
||||
};
|
||||
await this.agenda.now('sale-estimate-mail-send', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formates the text of the mail.
|
||||
* @param {number} tenantId
|
||||
* @param {number} estimateId
|
||||
* @param {string} text
|
||||
*/
|
||||
public formatText = async (
|
||||
tenantId: number,
|
||||
estimateId: number,
|
||||
text: string
|
||||
) => {
|
||||
const estimate = await this.getSaleEstimateService.getEstimate(
|
||||
tenantId,
|
||||
estimateId
|
||||
);
|
||||
return formatSmsMessage(text, {
|
||||
CustomerName: estimate.customer.displayName,
|
||||
EstimateNumber: estimate.estimateNo,
|
||||
EstimateDate: estimate.estimateDateFormatted,
|
||||
EstimateAmount: estimate.totalFormatted,
|
||||
EstimateDueDate: estimate.dueDateFormatted,
|
||||
EstimateDueAmount: estimate.dueAmountFormatted,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleEstimateId
|
||||
* @param {SaleEstimateMailOptions} messageOptions
|
||||
*/
|
||||
public async sendMail(
|
||||
tenantId: number,
|
||||
saleEstimateId: number,
|
||||
messageOptions: SaleEstimateMailOptions
|
||||
) {
|
||||
const defaultMessageOpts = await this.getDefaultMailOpts(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
const parsedMessageOpts = {
|
||||
...defaultMessageOpts,
|
||||
...messageOptions,
|
||||
};
|
||||
const formatter = R.curry(this.formatText)(tenantId, saleEstimateId);
|
||||
const toEmail = parsedMessageOpts.to;
|
||||
const subject = await formatter(parsedMessageOpts.subject);
|
||||
const body = await formatter(parsedMessageOpts.body);
|
||||
const attachments = [];
|
||||
|
||||
if (parsedMessageOpts.to) {
|
||||
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
|
||||
tenantId,
|
||||
saleEstimateId
|
||||
);
|
||||
attachments.push({
|
||||
filename: 'estimate.pdf',
|
||||
content: estimatePdfBuffer,
|
||||
});
|
||||
}
|
||||
await new Mail()
|
||||
.setSubject(subject)
|
||||
.setTo(toEmail)
|
||||
.setContent(body)
|
||||
.setAttachments(attachments)
|
||||
.send();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import Container, { Service } from 'typedi';
|
||||
import { SendSaleEstimateMail } from './SendSaleEstimateMail';
|
||||
|
||||
@Service()
|
||||
export class SendSaleEstimateMailJob {
|
||||
/**
|
||||
* Constructor method.
|
||||
*/
|
||||
constructor(agenda) {
|
||||
agenda.define(
|
||||
'sale-estimate-mail-send',
|
||||
{ priority: 'high', concurrency: 2 },
|
||||
this.handler
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers sending invoice mail.
|
||||
*/
|
||||
private handler = async (job, done: Function) => {
|
||||
const { tenantId, saleEstimateId, messageOptions } = job.attrs.data;
|
||||
const sendSaleEstimateMail = Container.get(SendSaleEstimateMail);
|
||||
|
||||
try {
|
||||
await sendSaleEstimateMail.sendMail(
|
||||
tenantId,
|
||||
saleEstimateId,
|
||||
messageOptions
|
||||
);
|
||||
done();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
done(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,12 @@
|
||||
export const DEFAULT_ESTIMATE_REMINDER_MAIL_SUBJECT =
|
||||
'Invoice {InvoiceNumber} reminder from {CompanyName}';
|
||||
export const DEFAULT_ESTIMATE_REMINDER_MAIL_CONTENT = `
|
||||
<p>Dear {CustomerName}</p>
|
||||
<p>You might have missed the payment date and the invoice is now overdue by {OverdueDays} days.</p>
|
||||
<p>Invoice <strong>#{InvoiceNumber}</strong><br />
|
||||
Due Date : <strong>{InvoiceDueDate}</strong><br />
|
||||
Amount : <strong>{InvoiceAmount}</strong></p>
|
||||
`;
|
||||
|
||||
export const ERRORS = {
|
||||
SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND',
|
||||
@@ -8,7 +17,7 @@ export const ERRORS = {
|
||||
CUSTOMER_HAS_SALES_ESTIMATES: 'CUSTOMER_HAS_SALES_ESTIMATES',
|
||||
SALE_ESTIMATE_NO_IS_REQUIRED: 'SALE_ESTIMATE_NO_IS_REQUIRED',
|
||||
SALE_ESTIMATE_ALREADY_DELIVERED: 'SALE_ESTIMATE_ALREADY_DELIVERED',
|
||||
SALE_ESTIMATE_ALREADY_APPROVED: 'SALE_ESTIMATE_ALREADY_APPROVED'
|
||||
SALE_ESTIMATE_ALREADY_APPROVED: 'SALE_ESTIMATE_ALREADY_APPROVED',
|
||||
};
|
||||
|
||||
export const DEFAULT_VIEW_COLUMNS = [];
|
||||
|
||||
Reference in New Issue
Block a user