mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-23 16:19:49 +00:00
feat: send invoices and reminder notifications to the customers
This commit is contained in:
@@ -157,10 +157,11 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
'/:id/mail-reminder',
|
'/:id/mail-reminder',
|
||||||
[
|
[
|
||||||
...this.specificSaleInvoiceValidation,
|
...this.specificSaleInvoiceValidation,
|
||||||
body('from').isString().exists(),
|
body('subject').isString().optional(),
|
||||||
body('to').isString().exists(),
|
body('from').isString().optional(),
|
||||||
body('body').isString().exists(),
|
body('to').isString().optional(),
|
||||||
body('attach_invoice').exists().isBoolean().toBoolean(),
|
body('body').isString().optional(),
|
||||||
|
body('attach_invoice').optional().isBoolean().toBoolean(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.sendSaleInvoiceMailReminder.bind(this)),
|
asyncMiddleware(this.sendSaleInvoiceMailReminder.bind(this)),
|
||||||
@@ -170,6 +171,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
'/:id/mail',
|
'/:id/mail',
|
||||||
[
|
[
|
||||||
...this.specificSaleInvoiceValidation,
|
...this.specificSaleInvoiceValidation,
|
||||||
|
body('subject').isString().optional(),
|
||||||
body('from').isString().optional(),
|
body('from').isString().optional(),
|
||||||
body('to').isString().optional(),
|
body('to').isString().optional(),
|
||||||
body('body').isString().optional(),
|
body('body').isString().optional(),
|
||||||
@@ -677,7 +679,9 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
) {
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: invoiceId } = req.params;
|
const { id: invoiceId } = req.params;
|
||||||
const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req);
|
const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req, {
|
||||||
|
includeOptionals: false,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.saleInvoiceApplication.sendSaleInvoiceMail(
|
await this.saleInvoiceApplication.sendSaleInvoiceMail(
|
||||||
@@ -692,7 +696,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Retreivers the sale invoice reminder options.
|
||||||
* @param {Request} req
|
* @param {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
@@ -729,8 +733,9 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
) {
|
) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: invoiceId } = req.params;
|
const { id: invoiceId } = req.params;
|
||||||
const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req);
|
const invoiceMailDTO: SendInvoiceMailDTO = this.matchedBodyData(req, {
|
||||||
|
includeOptionals: false,
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
await this.saleInvoiceApplication.sendSaleInvoiceMailReminder(
|
await this.saleInvoiceApplication.sendSaleInvoiceMailReminder(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ export class GetSaleInvoice {
|
|||||||
*/
|
*/
|
||||||
public async getSaleInvoice(
|
public async getSaleInvoice(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleInvoiceId: number,
|
saleInvoiceId: number
|
||||||
authorizedUser: ISystemUser
|
|
||||||
): Promise<ISaleInvoice> {
|
): Promise<ISaleInvoice> {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { GetSaleInvoice } from './GetSaleInvoice';
|
||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { Tenant } from '@/system/models';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class SaleInvoiceMailFormatter {
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getSaleInvoiceService: GetSaleInvoice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
invoiceId
|
||||||
|
);
|
||||||
|
const organization = await Tenant.query()
|
||||||
|
.findById(tenantId)
|
||||||
|
.withGraphFetched('metadata');
|
||||||
|
|
||||||
|
return text
|
||||||
|
.replace('{CompanyName}', organization.metadata.name)
|
||||||
|
.replace('{CustomerName}', invoice.customer.displayName)
|
||||||
|
.replace('{InvoiceNumber}', invoice.invoiceNo)
|
||||||
|
.replace('{InvoiceDueAmount}', invoice.dueAmountFormatted)
|
||||||
|
.replace('{InvoiceDueDate}', invoice.dueDateFormatted)
|
||||||
|
.replace('{InvoiceDate}', invoice.invoiceDateFormatted)
|
||||||
|
.replace('{InvoiceAmount}', invoice.totalFormatted);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
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 { ISaleInvoice } from '@/interfaces';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleInvoicePdf {
|
export class SaleInvoicePdf {
|
||||||
@@ -11,16 +12,34 @@ export class SaleInvoicePdf {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private templateInjectable: TemplateInjectable;
|
private templateInjectable: TemplateInjectable;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private validators: CommandSaleInvoiceValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve sale invoice pdf content.
|
* Retrieve sale invoice pdf content.
|
||||||
* @param {number} tenantId - Tenant Id.
|
* @param {number} tenantId - Tenant Id.
|
||||||
* @param {ISaleInvoice} saleInvoice -
|
* @param {ISaleInvoice} saleInvoice -
|
||||||
* @returns {Promise<Buffer>}
|
* @returns {Promise<Buffer>}
|
||||||
*/
|
*/
|
||||||
async saleInvoicePdf(
|
public async saleInvoicePdf(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleInvoice: ISaleInvoice
|
invoiceId: number
|
||||||
): Promise<Buffer> {
|
): Promise<Buffer> {
|
||||||
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
|
const saleInvoice = await SaleInvoice.query()
|
||||||
|
.findById(invoiceId)
|
||||||
|
.withGraphFetched('entries.item')
|
||||||
|
.withGraphFetched('entries.tax')
|
||||||
|
.withGraphFetched('customer')
|
||||||
|
.withGraphFetched('taxes.taxRate');
|
||||||
|
|
||||||
|
// Validates the given sale invoice existance.
|
||||||
|
this.validators.validateInvoiceExistance(saleInvoice);
|
||||||
|
|
||||||
const htmlContent = await this.templateInjectable.render(
|
const htmlContent = await this.templateInjectable.render(
|
||||||
tenantId,
|
tenantId,
|
||||||
'modules/invoice-regular',
|
'modules/invoice-regular',
|
||||||
|
|||||||
@@ -249,13 +249,13 @@ export class SaleInvoiceApplication {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Retrieves the pdf buffer of the given sale invoice.
|
||||||
* @param {number} tenantId ]
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param saleInvoice
|
* @param {number} saleInvoice
|
||||||
* @returns
|
* @returns {Promise<Buffer>}
|
||||||
*/
|
*/
|
||||||
public saleInvoicePdf(tenantId: number, saleInvoice) {
|
public saleInvoicePdf(tenantId: number, saleInvoiceId: number) {
|
||||||
return this.pdfSaleInvoiceService.saleInvoicePdf(tenantId, saleInvoice);
|
return this.pdfSaleInvoiceService.saleInvoicePdf(tenantId, saleInvoiceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -336,7 +336,7 @@ export class SaleInvoiceApplication {
|
|||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageDTO: SendInvoiceMailDTO
|
messageDTO: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
return this.sendSaleInvoiceMailService.sendMail(
|
return this.sendSaleInvoiceMailService.triggerMail(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
messageDTO
|
messageDTO
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { ISaleInvoiceNotifyPayload, SendInvoiceMailDTO } from '@/interfaces';
|
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 HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import events from '@/subscribers/events';
|
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { SaleInvoiceMailFormatter } from './SaleInvoiceMailFormatter';
|
||||||
|
import {
|
||||||
|
DEFAULT_INVOICE_MAIL_CONTENT,
|
||||||
|
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
|
ERRORS,
|
||||||
|
} from './constants';
|
||||||
|
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendSaleInvoiceMail {
|
export class SendSaleInvoiceMail {
|
||||||
@@ -13,13 +21,22 @@ export class SendSaleInvoiceMail {
|
|||||||
@Inject('agenda')
|
@Inject('agenda')
|
||||||
private agenda: any;
|
private agenda: any;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private invoicePdf: SaleInvoicePdf;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private invoiceFormatter: SaleInvoiceMailFormatter;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private commandInvoiceValidator: CommandSaleInvoiceValidators;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the invoice mail of the given sale invoice.
|
* Sends the invoice mail of the given sale invoice.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleInvoiceId
|
* @param {number} saleInvoiceId
|
||||||
* @param {SendInvoiceMailDTO} messageDTO
|
* @param {SendInvoiceMailDTO} messageDTO
|
||||||
*/
|
*/
|
||||||
public async sendMail(
|
public async triggerMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageDTO: SendInvoiceMailDTO
|
messageDTO: SendInvoiceMailDTO
|
||||||
@@ -39,7 +56,7 @@ export class SendSaleInvoiceMail {
|
|||||||
* @param {SendInvoiceMailDTO} messageDTO
|
* @param {SendInvoiceMailDTO} messageDTO
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async triggerMail(
|
public async sendMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageDTO: SendInvoiceMailDTO
|
messageDTO: SendInvoiceMailDTO
|
||||||
@@ -50,17 +67,45 @@ export class SendSaleInvoiceMail {
|
|||||||
.findById(saleInvoiceId)
|
.findById(saleInvoiceId)
|
||||||
.withGraphFetched('customer');
|
.withGraphFetched('customer');
|
||||||
|
|
||||||
const toEmail = messageDTO.to || saleInvoice.customer.email;
|
this.commandInvoiceValidator.validateInvoiceExistance(saleInvoice);
|
||||||
const subject = messageDTO.subject || saleInvoice.invoiceNo;
|
|
||||||
|
|
||||||
if (!toEmail) {
|
// Parsed message opts with default options.
|
||||||
return null;
|
const parsedMessageOpts = {
|
||||||
|
attachInvoice: true,
|
||||||
|
subject: DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
|
body: DEFAULT_INVOICE_MAIL_CONTENT,
|
||||||
|
to: saleInvoice.customer.email,
|
||||||
|
...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.invoiceFormatter.formatText)(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId
|
||||||
|
);
|
||||||
|
const toEmail = parsedMessageOpts.to;
|
||||||
|
const subject = await formatter(parsedMessageOpts.subject);
|
||||||
|
const body = await formatter(parsedMessageOpts.body);
|
||||||
|
const attachments = [];
|
||||||
|
|
||||||
|
if (parsedMessageOpts.attachInvoice) {
|
||||||
|
// Retrieves document buffer of the invoice pdf document.
|
||||||
|
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId
|
||||||
|
);
|
||||||
|
attachments.push({
|
||||||
|
filename: 'invoice.pdf',
|
||||||
|
content: invoicePdfBuffer,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const mail = new Mail()
|
const mail = new Mail()
|
||||||
.setSubject(subject)
|
.setSubject(subject)
|
||||||
.setView('mail/UserInvite.html')
|
|
||||||
.setTo(toEmail)
|
.setTo(toEmail)
|
||||||
.setData({});
|
.setContent(body)
|
||||||
|
.setAttachments(attachments);
|
||||||
|
|
||||||
await mail.send();
|
await mail.send();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export class SendSaleInvoiceMailJob {
|
|||||||
constructor(agenda) {
|
constructor(agenda) {
|
||||||
agenda.define(
|
agenda.define(
|
||||||
'sale-invoice-mail-send',
|
'sale-invoice-mail-send',
|
||||||
{ priority: 'high', concurrency: 1 },
|
{ priority: 'high', concurrency: 2 },
|
||||||
this.handler
|
this.handler
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ export class SendSaleInvoiceMailJob {
|
|||||||
const sendInvoiceMail = Container.get(SendSaleInvoiceMail);
|
const sendInvoiceMail = Container.get(SendSaleInvoiceMail);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendInvoiceMail.triggerMail(tenantId, saleInvoiceId, messageDTO);
|
await sendInvoiceMail.sendMail(tenantId, saleInvoiceId, messageDTO);
|
||||||
done();
|
done();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
|
import * as R from 'ramda';
|
||||||
|
import { assign } from 'lodash';
|
||||||
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 HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { CommandSaleInvoiceValidators } from './CommandSaleInvoiceValidators';
|
||||||
|
import { SaleInvoiceMailFormatter } from './SaleInvoiceMailFormatter';
|
||||||
|
import {
|
||||||
|
DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
|
||||||
|
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
|
||||||
|
ERRORS,
|
||||||
|
} from './constants';
|
||||||
|
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendInvoiceMailReminder {
|
export class SendInvoiceMailReminder {
|
||||||
@@ -11,6 +22,15 @@ export class SendInvoiceMailReminder {
|
|||||||
@Inject('agenda')
|
@Inject('agenda')
|
||||||
private agenda: any;
|
private agenda: any;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private commandInvoiceValidator: CommandSaleInvoiceValidators;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private invoiceFormatter: SaleInvoiceMailFormatter;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private invoicePdf: SaleInvoicePdf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the reminder mail of the given sale invoice.
|
* Triggers the reminder mail of the given sale invoice.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -19,12 +39,12 @@ export class SendInvoiceMailReminder {
|
|||||||
public async triggerMail(
|
public async triggerMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageDTO: SendInvoiceMailDTO
|
messageOptions: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
const payload = {
|
const payload = {
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
messageDTO,
|
messageOptions,
|
||||||
};
|
};
|
||||||
await this.agenda.now('sale-invoice-reminder-mail-send', payload);
|
await this.agenda.now('sale-invoice-reminder-mail-send', payload);
|
||||||
}
|
}
|
||||||
@@ -33,13 +53,13 @@ export class SendInvoiceMailReminder {
|
|||||||
* Triggers the mail invoice.
|
* Triggers the mail invoice.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleInvoiceId
|
* @param {number} saleInvoiceId
|
||||||
* @param {SendInvoiceMailDTO} messageDTO
|
* @param {SendInvoiceMailDTO} messageOptions
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async sendMail(
|
public async sendMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageDTO: SendInvoiceMailDTO
|
messageOptions: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -47,17 +67,45 @@ export class SendInvoiceMailReminder {
|
|||||||
.findById(saleInvoiceId)
|
.findById(saleInvoiceId)
|
||||||
.withGraphFetched('customer');
|
.withGraphFetched('customer');
|
||||||
|
|
||||||
const toEmail = messageDTO.to || saleInvoice.customer.email;
|
// Validates the invoice existance.
|
||||||
const subject = messageDTO.subject || saleInvoice.invoiceNo;
|
this.commandInvoiceValidator.validateInvoiceExistance(saleInvoice);
|
||||||
|
|
||||||
if (!toEmail) {
|
const parsedMessageOptions = {
|
||||||
return null;
|
attachInvoice: true,
|
||||||
|
subject: DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
|
||||||
|
body: DEFAULT_INVOICE_REMINDER_MAIL_CONTENT,
|
||||||
|
to: saleInvoice.customer.email,
|
||||||
|
...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.invoiceFormatter.formatText)(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId
|
||||||
|
);
|
||||||
|
const toEmail = parsedMessageOptions.to;
|
||||||
|
const subject = await formatter(parsedMessageOptions.subject);
|
||||||
|
const body = await formatter(parsedMessageOptions.body);
|
||||||
|
const attachments = [];
|
||||||
|
|
||||||
|
if (parsedMessageOptions.attachInvoice) {
|
||||||
|
// Retrieves document buffer of the invoice pdf document.
|
||||||
|
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId
|
||||||
|
);
|
||||||
|
attachments.push({
|
||||||
|
filename: 'invoice.pdf',
|
||||||
|
content: invoicePdfBuffer,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
const mail = new Mail()
|
const mail = new Mail()
|
||||||
.setSubject(subject)
|
.setSubject(subject)
|
||||||
.setView('mail/UserInvite.html')
|
|
||||||
.setTo(toEmail)
|
.setTo(toEmail)
|
||||||
.setData({});
|
.setContent(body)
|
||||||
|
.setAttachments(attachments);
|
||||||
|
|
||||||
await mail.send();
|
await mail.send();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export class SendSaleInvoiceReminderMailJob {
|
|||||||
* Triggers sending invoice mail.
|
* Triggers sending invoice mail.
|
||||||
*/
|
*/
|
||||||
private handler = async (job, done: Function) => {
|
private handler = async (job, done: Function) => {
|
||||||
const { tenantId, saleInvoiceId, messageDTO } = job.attrs.data;
|
const { tenantId, saleInvoiceId, messageOptions } = job.attrs.data;
|
||||||
const sendInvoiceMail = Container.get(SendInvoiceMailReminder);
|
const sendInvoiceMail = Container.get(SendInvoiceMailReminder);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendInvoiceMail.sendMail(tenantId, saleInvoiceId, messageDTO);
|
await sendInvoiceMail.sendMail(tenantId, saleInvoiceId, messageOptions);
|
||||||
done();
|
done();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -1,3 +1,25 @@
|
|||||||
|
export const DEFAULT_INVOICE_MAIL_SUBJECT =
|
||||||
|
'Invoice {InvoiceNumber} from {CompanyName}';
|
||||||
|
export const DEFAULT_INVOICE_MAIL_CONTENT = `
|
||||||
|
<p>Dear {CustomerName}</p>
|
||||||
|
<p>Thank you for your business, You can view or print your invoice from attachements.</p>
|
||||||
|
<p>
|
||||||
|
Invoice <strong>#{InvoiceNumber}</strong><br />
|
||||||
|
Due Date : <strong>{InvoiceDueDate}</strong><br />
|
||||||
|
Amount : <strong>{InvoiceAmount}</strong></br />
|
||||||
|
</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT =
|
||||||
|
'Invoice {InvoiceNumber} reminder from {CompanyName}';
|
||||||
|
export const DEFAULT_INVOICE_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 = {
|
||||||
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
INVOICE_NUMBER_NOT_UNIQUE: 'INVOICE_NUMBER_NOT_UNIQUE',
|
||||||
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
SALE_INVOICE_NOT_FOUND: 'SALE_INVOICE_NOT_FOUND',
|
||||||
@@ -16,6 +38,7 @@ export const ERRORS = {
|
|||||||
PAYMENT_ACCOUNT_CURRENCY_INVALID: 'PAYMENT_ACCOUNT_CURRENCY_INVALID',
|
PAYMENT_ACCOUNT_CURRENCY_INVALID: 'PAYMENT_ACCOUNT_CURRENCY_INVALID',
|
||||||
SALE_INVOICE_ALREADY_WRITTEN_OFF: 'SALE_INVOICE_ALREADY_WRITTEN_OFF',
|
SALE_INVOICE_ALREADY_WRITTEN_OFF: 'SALE_INVOICE_ALREADY_WRITTEN_OFF',
|
||||||
SALE_INVOICE_NOT_WRITTEN_OFF: 'SALE_INVOICE_NOT_WRITTEN_OFF',
|
SALE_INVOICE_NOT_WRITTEN_OFF: 'SALE_INVOICE_NOT_WRITTEN_OFF',
|
||||||
|
NO_INVOICE_CUSTOMER_EMAIL_ADDR: 'NO_INVOICE_CUSTOMER_EMAIL_ADDR',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_VIEW_COLUMNS = [];
|
export const DEFAULT_VIEW_COLUMNS = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user