mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
feat: send sale estimate mail notification
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
|||||||
ISaleEstimate,
|
ISaleEstimate,
|
||||||
ISaleEstimateDTO,
|
ISaleEstimateDTO,
|
||||||
ISalesEstimatesFilter,
|
ISalesEstimatesFilter,
|
||||||
|
SaleEstimateMailOptions,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { EditSaleEstimate } from './EditSaleEstimate';
|
import { EditSaleEstimate } from './EditSaleEstimate';
|
||||||
import { DeleteSaleEstimate } from './DeleteSaleEstimate';
|
import { DeleteSaleEstimate } from './DeleteSaleEstimate';
|
||||||
@@ -202,25 +203,33 @@ export class SaleEstimatesApplication {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Retrieve the PDF content of the given sale estimate.
|
||||||
* @param {number} tenantId
|
|
||||||
* @param {} saleEstimate
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public getSaleEstimatePdf(tenantId: number, saleEstimate) {
|
|
||||||
return this.saleEstimatesPdfService.getSaleEstimatePdf(
|
|
||||||
tenantId,
|
|
||||||
saleEstimate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleEstimateId
|
* @param {number} saleEstimateId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public sendSaleEstimateMail(tenantId: number, saleEstimateId: number) {
|
public getSaleEstimatePdf(tenantId: number, saleEstimateId: number) {
|
||||||
return this.sendEstimateMailService.sendMail(tenantId, saleEstimateId);
|
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 { Inject, Service } from 'typedi';
|
||||||
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
|
import { ChromiumlyTenancy } from '@/services/ChromiumlyTenancy/ChromiumlyTenancy';
|
||||||
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
|
import { TemplateInjectable } from '@/services/TemplateInjectable/TemplateInjectable';
|
||||||
|
import { GetSaleEstimate } from './GetSaleEstimate';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleEstimatesPdf {
|
export class SaleEstimatesPdf {
|
||||||
@@ -10,11 +11,19 @@ export class SaleEstimatesPdf {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private templateInjectable: TemplateInjectable;
|
private templateInjectable: TemplateInjectable;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getSaleEstimate: GetSaleEstimate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve sale invoice pdf content.
|
* 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(
|
const htmlContent = await this.templateInjectable.render(
|
||||||
tenantId,
|
tenantId,
|
||||||
'modules/estimate-regular',
|
'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()
|
@Service()
|
||||||
export class SendSaleEstimateMail {
|
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 = {
|
export const ERRORS = {
|
||||||
SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND',
|
SALE_ESTIMATE_NOT_FOUND: 'SALE_ESTIMATE_NOT_FOUND',
|
||||||
@@ -8,7 +17,7 @@ export const ERRORS = {
|
|||||||
CUSTOMER_HAS_SALES_ESTIMATES: 'CUSTOMER_HAS_SALES_ESTIMATES',
|
CUSTOMER_HAS_SALES_ESTIMATES: 'CUSTOMER_HAS_SALES_ESTIMATES',
|
||||||
SALE_ESTIMATE_NO_IS_REQUIRED: 'SALE_ESTIMATE_NO_IS_REQUIRED',
|
SALE_ESTIMATE_NO_IS_REQUIRED: 'SALE_ESTIMATE_NO_IS_REQUIRED',
|
||||||
SALE_ESTIMATE_ALREADY_DELIVERED: 'SALE_ESTIMATE_ALREADY_DELIVERED',
|
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 = [];
|
export const DEFAULT_VIEW_COLUMNS = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user