mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
feat: wip send invoice mail payment template
This commit is contained in:
@@ -179,10 +179,21 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
'/:id/mail',
|
'/:id/mail',
|
||||||
[
|
[
|
||||||
...this.specificSaleInvoiceValidation,
|
...this.specificSaleInvoiceValidation,
|
||||||
body('subject').isString().optional(),
|
|
||||||
|
body('subject').isString().optional({ nullable: true }),
|
||||||
|
body('message').isString().optional({ nullable: true }),
|
||||||
|
|
||||||
body('from').isString().optional(),
|
body('from').isString().optional(),
|
||||||
body('to').isString().optional(),
|
|
||||||
body('body').isString().optional(),
|
body('to').isArray().exists(),
|
||||||
|
body('to.*').isString().isEmail().optional(),
|
||||||
|
|
||||||
|
body('cc').isArray().optional({ nullable: true }),
|
||||||
|
body('cc.*').isString().isEmail().optional(),
|
||||||
|
|
||||||
|
body('bcc').isArray().optional({ nullable: true }),
|
||||||
|
body('bcc.*').isString().isEmail().optional(),
|
||||||
|
|
||||||
body('attach_invoice').optional().isBoolean().toBoolean(),
|
body('attach_invoice').optional().isBoolean().toBoolean(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ export const SALE_INVOICE_DELETED = 'Sale invoice deleted';
|
|||||||
export const SALE_INVOICE_MAIL_DELIVERED = 'Sale invoice mail delivered';
|
export const SALE_INVOICE_MAIL_DELIVERED = 'Sale invoice mail delivered';
|
||||||
export const SALE_INVOICE_VIEWED = 'Sale invoice viewed';
|
export const SALE_INVOICE_VIEWED = 'Sale invoice viewed';
|
||||||
export const SALE_INVOICE_PDF_VIEWED = 'Sale invoice PDF viewed';
|
export const SALE_INVOICE_PDF_VIEWED = 'Sale invoice PDF viewed';
|
||||||
|
export const SALE_INVOICE_MAIL_SENT = 'Sale invoice mail sent';
|
||||||
|
export const SALE_INVOICE_MAIL_REMINDER_SENT =
|
||||||
|
'Sale invoice reminder mail sent';
|
||||||
|
|
||||||
export const SALE_ESTIMATE_CREATED = 'Sale estimate created';
|
export const SALE_ESTIMATE_CREATED = 'Sale estimate created';
|
||||||
export const SALE_ESTIMATE_EDITED = 'Sale estimate edited';
|
export const SALE_ESTIMATE_EDITED = 'Sale estimate edited';
|
||||||
|
|||||||
@@ -30,18 +30,14 @@ export interface AddressItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CommonMailOptions {
|
export interface CommonMailOptions {
|
||||||
toAddresses: AddressItem[];
|
from: Array<string>;
|
||||||
fromAddresses: AddressItem[];
|
|
||||||
from: string;
|
|
||||||
to: string | string[];
|
|
||||||
subject: string;
|
subject: string;
|
||||||
body: string;
|
message: string;
|
||||||
|
to: Array<string>;
|
||||||
|
cc?: Array<string>;
|
||||||
|
bcc?: Array<string>;
|
||||||
data?: Record<string, any>;
|
data?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CommonMailOptionsDTO {
|
export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> {
|
||||||
to?: string | string[];
|
|
||||||
from?: string;
|
|
||||||
subject?: string;
|
|
||||||
body?: string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ export enum SaleInvoiceAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SaleInvoiceMailOptions extends CommonMailOptions {
|
export interface SaleInvoiceMailOptions extends CommonMailOptions {
|
||||||
attachInvoice: boolean;
|
attachInvoice?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
|
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
|
||||||
@@ -251,6 +251,7 @@ export interface ISaleInvoiceMailSend {
|
|||||||
tenantId: number;
|
tenantId: number;
|
||||||
saleInvoiceId: number;
|
saleInvoiceId: number;
|
||||||
messageOptions: SendInvoiceMailDTO;
|
messageOptions: SendInvoiceMailDTO;
|
||||||
|
formattedMessageOptions: SaleInvoiceMailOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISaleInvoiceMailSent {
|
export interface ISaleInvoiceMailSent {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
SALE_INVOICE_CREATED,
|
SALE_INVOICE_CREATED,
|
||||||
SALE_INVOICE_DELETED,
|
SALE_INVOICE_DELETED,
|
||||||
SALE_INVOICE_EDITED,
|
SALE_INVOICE_EDITED,
|
||||||
|
SALE_INVOICE_MAIL_SENT,
|
||||||
SALE_INVOICE_PDF_VIEWED,
|
SALE_INVOICE_PDF_VIEWED,
|
||||||
SALE_INVOICE_VIEWED,
|
SALE_INVOICE_VIEWED,
|
||||||
} from '@/constants/event-tracker';
|
} from '@/constants/event-tracker';
|
||||||
@@ -43,6 +44,10 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
|
|||||||
events.saleInvoice.onPdfViewed,
|
events.saleInvoice.onPdfViewed,
|
||||||
this.handleTrackPdfViewedInvoiceEvent
|
this.handleTrackPdfViewedInvoiceEvent
|
||||||
);
|
);
|
||||||
|
bus.subscribe(
|
||||||
|
events.saleInvoice.onMailSent,
|
||||||
|
this.handleTrackMailSentInvoiceEvent
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrackInvoiceCreatedEvent = ({
|
private handleTrackInvoiceCreatedEvent = ({
|
||||||
@@ -90,4 +95,12 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
|
|||||||
properties: {},
|
properties: {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private handleTrackMailSentInvoiceEvent = ({ tenantId }) => {
|
||||||
|
this.posthog.trackEvent({
|
||||||
|
distinctId: `tenant-${tenantId}`,
|
||||||
|
event: SALE_INVOICE_MAIL_SENT,
|
||||||
|
properties: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
|
|||||||
import { MailTenancy } from '@/services/MailTenancy/MailTenancy';
|
import { MailTenancy } from '@/services/MailTenancy/MailTenancy';
|
||||||
import { formatSmsMessage } from '@/utils';
|
import { formatSmsMessage } from '@/utils';
|
||||||
import { Tenant } from '@/system/models';
|
import { Tenant } from '@/system/models';
|
||||||
|
import { castArray } from 'lodash';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ContactMailNotification {
|
export class ContactMailNotification {
|
||||||
@@ -14,76 +15,54 @@ export class ContactMailNotification {
|
|||||||
private tenancy: HasTenancyService;
|
private tenancy: HasTenancyService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the default message options.
|
* Gets the default mail address of the given contact.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} invoiceId -
|
* @param {number} invoiceId - Contact id.
|
||||||
* @param {string} subject -
|
* @returns {Promise<Pick<CommonMailOptions, 'to' | 'from'>>}
|
||||||
* @param {string} body -
|
|
||||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
|
||||||
*/
|
*/
|
||||||
public async getDefaultMailOptions(
|
public async getDefaultMailOptions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
contactId: number,
|
customerId: number
|
||||||
subject: string = '',
|
): Promise<Pick<CommonMailOptions, 'to' | 'from'>> {
|
||||||
body: string = ''
|
|
||||||
): Promise<CommonMailOptions> {
|
|
||||||
const { Customer } = this.tenancy.models(tenantId);
|
const { Customer } = this.tenancy.models(tenantId);
|
||||||
const contact = await Customer.query()
|
const customer = await Customer.query()
|
||||||
.findById(contactId)
|
.findById(customerId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const toAddresses = contact.contactAddresses;
|
const toAddresses = customer.contactAddresses;
|
||||||
const fromAddresses = await this.mailTenancy.senders(tenantId);
|
const fromAddresses = await this.mailTenancy.senders(tenantId);
|
||||||
|
|
||||||
const toAddress = toAddresses.find((a) => a.primary);
|
const toAddress = toAddresses.find((a) => a.primary);
|
||||||
const fromAddress = fromAddresses.find((a) => a.primary);
|
const fromAddress = fromAddresses.find((a) => a.primary);
|
||||||
|
|
||||||
const to = toAddress?.mail || '';
|
const to = toAddress?.mail ? castArray(toAddress?.mail) : [];
|
||||||
const from = fromAddress?.mail || '';
|
const from = fromAddress?.mail ? castArray(fromAddress?.mail) : [];
|
||||||
|
|
||||||
return {
|
return { to, from };
|
||||||
subject,
|
|
||||||
body,
|
|
||||||
to,
|
|
||||||
from,
|
|
||||||
fromAddresses,
|
|
||||||
toAddresses,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the mail options of the given contact.
|
* Retrieves the mail options of the given contact.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} invoiceId - Invoice id.
|
|
||||||
* @param {string} defaultSubject - Default subject text.
|
|
||||||
* @param {string} defaultBody - Default body text.
|
|
||||||
* @returns {Promise<CommonMailOptions>}
|
* @returns {Promise<CommonMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getMailOptions(
|
public async parseMailOptions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
contactId: number,
|
mailOptions: CommonMailOptions,
|
||||||
defaultSubject?: string,
|
formatterArgs?: Record<string, any>
|
||||||
defaultBody?: string,
|
|
||||||
formatterData?: Record<string, any>
|
|
||||||
): Promise<CommonMailOptions> {
|
): Promise<CommonMailOptions> {
|
||||||
const mailOpts = await this.getDefaultMailOptions(
|
|
||||||
tenantId,
|
|
||||||
contactId,
|
|
||||||
defaultSubject,
|
|
||||||
defaultBody
|
|
||||||
);
|
|
||||||
const commonFormatArgs = await this.getCommonFormatArgs(tenantId);
|
const commonFormatArgs = await this.getCommonFormatArgs(tenantId);
|
||||||
const formatArgs = {
|
const formatArgs = {
|
||||||
...commonFormatArgs,
|
...commonFormatArgs,
|
||||||
...formatterData,
|
...formatterArgs,
|
||||||
};
|
};
|
||||||
const subject = formatSmsMessage(mailOpts.subject, formatArgs);
|
const subjectFormatted = formatSmsMessage(mailOptions?.subject, formatArgs);
|
||||||
const body = formatSmsMessage(mailOpts.body, formatArgs);
|
const messageFormatted = formatSmsMessage(mailOptions?.message, formatArgs);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...mailOpts,
|
...mailOptions,
|
||||||
subject,
|
subject: subjectFormatted,
|
||||||
body,
|
message: messageFormatted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,46 @@
|
|||||||
import { isEmpty } from 'lodash';
|
import { castArray, isEmpty } from 'lodash';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import { CommonMailOptions, CommonMailOptionsDTO } from '@/interfaces';
|
import { CommonMailOptions } from '@/interfaces';
|
||||||
import { ERRORS } from './constants';
|
import { ERRORS } from './constants';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges the mail options with incoming options.
|
* Merges the mail options with incoming options.
|
||||||
* @param {Partial<SaleInvoiceMailOptions>} mailOptions
|
* @param {Partial<SaleInvoiceMailOptions>} mailOptions
|
||||||
* @param {Partial<SendInvoiceMailDTO>} overridedOptions
|
* @param {Partial<SendInvoiceMailDTO>} overridedOptions
|
||||||
* @throws {ServiceError}
|
|
||||||
*/
|
*/
|
||||||
export function parseAndValidateMailOptions(
|
export function parseMailOptions(
|
||||||
mailOptions: Partial<CommonMailOptions>,
|
mailOptions: CommonMailOptions,
|
||||||
overridedOptions: Partial<CommonMailOptionsDTO>
|
overridedOptions: Partial<CommonMailOptions>
|
||||||
) {
|
): CommonMailOptions {
|
||||||
const mergedMessageOptions = {
|
const mergedMessageOptions = {
|
||||||
...mailOptions,
|
...mailOptions,
|
||||||
...overridedOptions,
|
...overridedOptions,
|
||||||
};
|
};
|
||||||
if (isEmpty(mergedMessageOptions.from)) {
|
const parsedMessageOptions = {
|
||||||
|
...mergedMessageOptions,
|
||||||
|
from: mergedMessageOptions?.from
|
||||||
|
? castArray(mergedMessageOptions?.from)
|
||||||
|
: [],
|
||||||
|
to: mergedMessageOptions?.to ? castArray(mergedMessageOptions?.to) : [],
|
||||||
|
cc: mergedMessageOptions?.cc ? castArray(mergedMessageOptions?.cc) : [],
|
||||||
|
bcc: mergedMessageOptions?.bcc ? castArray(mergedMessageOptions?.bcc) : [],
|
||||||
|
};
|
||||||
|
return parsedMessageOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateRequiredMailOptions(
|
||||||
|
mailOptions: Partial<CommonMailOptions>
|
||||||
|
) {
|
||||||
|
if (isEmpty(mailOptions.from)) {
|
||||||
throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND);
|
throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND);
|
||||||
}
|
}
|
||||||
if (isEmpty(mergedMessageOptions.to)) {
|
if (isEmpty(mailOptions.to)) {
|
||||||
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
|
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
|
||||||
}
|
}
|
||||||
if (isEmpty(mergedMessageOptions.subject)) {
|
if (isEmpty(mailOptions.subject)) {
|
||||||
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
|
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
if (isEmpty(mergedMessageOptions.body)) {
|
if (isEmpty(mailOptions.message)) {
|
||||||
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
|
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
|
||||||
}
|
}
|
||||||
return mergedMessageOptions;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import { Inject, Service } from 'typedi';
|
||||||
|
import { GetPdfTemplate } from '@/services/PdfTemplate/GetPdfTemplate';
|
||||||
|
import { GetSaleInvoice } from './GetSaleInvoice';
|
||||||
|
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||||
|
import {
|
||||||
|
InvoicePaymentEmailProps,
|
||||||
|
renderInvoicePaymentEmail,
|
||||||
|
} from '@bigcapital/email-components';
|
||||||
|
import { GetInvoiceMailTemplateAttributesTransformer } from './GetInvoicePaymentMailAttributesTransformer';
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class GetInvoicePaymentMail {
|
||||||
|
@Inject()
|
||||||
|
private getSaleInvoiceService: GetSaleInvoice;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getBrandingTemplate: GetPdfTemplate;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private transformer: TransformerInjectable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the mail template attributes of the given invoice.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} invoiceId - Invoice id.
|
||||||
|
*/
|
||||||
|
public async getMailTemplateAttributes(tenantId: number, invoiceId: number) {
|
||||||
|
const invoice = await this.getSaleInvoiceService.getSaleInvoice(
|
||||||
|
tenantId,
|
||||||
|
invoiceId
|
||||||
|
);
|
||||||
|
const brandingTemplate = await this.getBrandingTemplate.getPdfTemplate(
|
||||||
|
tenantId,
|
||||||
|
invoice.pdfTemplateId
|
||||||
|
);
|
||||||
|
const mailTemplateAttributes = await this.transformer.transform(
|
||||||
|
tenantId,
|
||||||
|
invoice,
|
||||||
|
new GetInvoiceMailTemplateAttributesTransformer(),
|
||||||
|
{
|
||||||
|
invoice,
|
||||||
|
brandingTemplate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return mailTemplateAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the mail template html content.
|
||||||
|
* @param {number} tenantId - Tenant id.
|
||||||
|
* @param {number} invoiceId - Invoice id.
|
||||||
|
*/
|
||||||
|
public async getMailTemplate(
|
||||||
|
tenantId: number,
|
||||||
|
invoiceId: number,
|
||||||
|
overrideAttributes?: Partial<InvoicePaymentEmailProps>
|
||||||
|
): Promise<string> {
|
||||||
|
const attributes = await this.getMailTemplateAttributes(
|
||||||
|
tenantId,
|
||||||
|
invoiceId
|
||||||
|
);
|
||||||
|
const mergedAttributes = { ...attributes, ...overrideAttributes };
|
||||||
|
|
||||||
|
return renderInvoicePaymentEmail(mergedAttributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||||
|
|
||||||
|
export class GetInvoiceMailTemplateAttributesTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to item entry object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return [
|
||||||
|
'companyLogoUri',
|
||||||
|
'companyName',
|
||||||
|
|
||||||
|
'invoiceAmount',
|
||||||
|
|
||||||
|
'primaryColor',
|
||||||
|
|
||||||
|
'invoiceAmount',
|
||||||
|
'invoiceMessage',
|
||||||
|
|
||||||
|
'dueDate',
|
||||||
|
'dueDateLabel',
|
||||||
|
|
||||||
|
'invoiceNumber',
|
||||||
|
'invoiceNumberLabel',
|
||||||
|
|
||||||
|
'total',
|
||||||
|
'totalLabel',
|
||||||
|
|
||||||
|
'dueAmount',
|
||||||
|
'dueAmountLabel',
|
||||||
|
|
||||||
|
'viewInvoiceButtonLabel',
|
||||||
|
'viewInvoiceButtonUrl',
|
||||||
|
|
||||||
|
'items',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['*'];
|
||||||
|
};
|
||||||
|
|
||||||
|
public companyLogoUri(): string {
|
||||||
|
return this.options.brandingTemplate?.attributes?.companyLogoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public companyName(): string {
|
||||||
|
return this.context.organization.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public invoiceAmount(): string {
|
||||||
|
return this.options.invoice.totalFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public primaryColor(): string {
|
||||||
|
return this.options?.brandingTemplate?.attributes?.primaryColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public invoiceMessage(): string {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public dueDate(): string {
|
||||||
|
return this.options?.invoice?.dueDateFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dueDateLabel(): string {
|
||||||
|
return 'Due {dueDate}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public invoiceNumber(): string {
|
||||||
|
return this.options?.invoice?.invoiceNo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public invoiceNumberLabel(): string {
|
||||||
|
return 'Invoice # {invoiceNumber}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public total(): string {
|
||||||
|
return this.options.invoice?.totalFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public totalLabel(): string {
|
||||||
|
return 'Total';
|
||||||
|
}
|
||||||
|
|
||||||
|
public dueAmount(): string {
|
||||||
|
return this.options?.invoice.dueAmountFormatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dueAmountLabel(): string {
|
||||||
|
return 'Due Amount';
|
||||||
|
}
|
||||||
|
|
||||||
|
public viewInvoiceButtonLabel(): string {
|
||||||
|
return 'View Invoice';
|
||||||
|
}
|
||||||
|
|
||||||
|
public viewInvoiceButtonUrl(): string {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public items(): Array<any> {
|
||||||
|
return this.item(
|
||||||
|
this.options.invoice?.entries,
|
||||||
|
new GetInvoiceMailTemplateItemAttrsTransformer()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GetInvoiceMailTemplateItemAttrsTransformer extends Transformer {
|
||||||
|
/**
|
||||||
|
* Include these attributes to item entry object.
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
public includeAttributes = (): string[] => {
|
||||||
|
return ['quantity', 'label', 'rate'];
|
||||||
|
};
|
||||||
|
|
||||||
|
public excludeAttributes = (): string[] => {
|
||||||
|
return ['*'];
|
||||||
|
};
|
||||||
|
|
||||||
|
public quantity(entry): string {
|
||||||
|
return entry?.quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public label(entry): string {
|
||||||
|
console.log(entry);
|
||||||
|
return entry?.item?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public rate(entry): string {
|
||||||
|
return entry?.rateFormatted;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
DEFAULT_INVOICE_MAIL_CONTENT,
|
DEFAULT_INVOICE_MAIL_CONTENT,
|
||||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
|
import { GetInvoicePaymentMail } from './GetInvoicePaymentMail';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendSaleInvoiceMailCommon {
|
export class SendSaleInvoiceMailCommon {
|
||||||
@@ -19,6 +20,9 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private contactMailNotification: ContactMailNotification;
|
private contactMailNotification: ContactMailNotification;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getInvoicePaymentMail: GetInvoicePaymentMail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the mail options.
|
* Retrieves the mail options.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
@@ -27,11 +31,11 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
* @param {string} defaultBody - Subject body.
|
* @param {string} defaultBody - Subject body.
|
||||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getMailOption(
|
public async getInvoiceMailOptions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
invoiceId: number,
|
invoiceId: number,
|
||||||
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
|
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
defaultBody: string = DEFAULT_INVOICE_MAIL_CONTENT
|
defaultMessage: string = DEFAULT_INVOICE_MAIL_CONTENT
|
||||||
): Promise<SaleInvoiceMailOptions> {
|
): Promise<SaleInvoiceMailOptions> {
|
||||||
const { SaleInvoice } = this.tenancy.models(tenantId);
|
const { SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
@@ -39,21 +43,51 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
.findById(invoiceId)
|
.findById(invoiceId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const formatterData = await this.formatText(tenantId, invoiceId);
|
const contactMailDefaultOptions =
|
||||||
|
await this.contactMailNotification.getDefaultMailOptions(
|
||||||
const mailOptions = await this.contactMailNotification.getMailOptions(
|
tenantId,
|
||||||
tenantId,
|
saleInvoice.customerId
|
||||||
saleInvoice.customerId,
|
);
|
||||||
defaultSubject,
|
|
||||||
defaultBody,
|
|
||||||
formatterData
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
...mailOptions,
|
...contactMailDefaultOptions,
|
||||||
attachInvoice: true,
|
attachInvoice: true,
|
||||||
|
subject: defaultSubject,
|
||||||
|
message: defaultMessage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the given invoice mail options.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @param {number} invoiceId
|
||||||
|
* @param {SaleInvoiceMailOptions} mailOptions
|
||||||
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
|
*/
|
||||||
|
public async formatInvoiceMailOptions(
|
||||||
|
tenantId: number,
|
||||||
|
invoiceId: number,
|
||||||
|
mailOptions: SaleInvoiceMailOptions
|
||||||
|
): Promise<SaleInvoiceMailOptions> {
|
||||||
|
const formatterArgs = await this.getInvoiceFormatterArgs(
|
||||||
|
tenantId,
|
||||||
|
invoiceId
|
||||||
|
);
|
||||||
|
const parsedOptions = await this.contactMailNotification.parseMailOptions(
|
||||||
|
tenantId,
|
||||||
|
mailOptions,
|
||||||
|
formatterArgs
|
||||||
|
);
|
||||||
|
const message = await this.getInvoicePaymentMail.getMailTemplate(
|
||||||
|
tenantId,
|
||||||
|
invoiceId,
|
||||||
|
{
|
||||||
|
// # Invoice message
|
||||||
|
invoiceMessage: parsedOptions.message,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return { ...parsedOptions, message };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the formatted text of the given sale invoice.
|
* Retrieves the formatted text of the given sale invoice.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
@@ -61,7 +95,7 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
* @param {string} text - The given text.
|
* @param {string} text - The given text.
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
public formatText = async (
|
public getInvoiceFormatterArgs = async (
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
invoiceId: number
|
invoiceId: number
|
||||||
): Promise<Record<string, string | number>> => {
|
): Promise<Record<string, string | number>> => {
|
||||||
@@ -69,7 +103,6 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
CustomerName: invoice.customer.displayName,
|
CustomerName: invoice.customer.displayName,
|
||||||
InvoiceNumber: invoice.invoiceNo,
|
InvoiceNumber: invoice.invoiceNo,
|
||||||
|
|||||||
@@ -1,15 +1,23 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import Mail from '@/lib/Mail';
|
import Mail from '@/lib/Mail';
|
||||||
import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces';
|
import {
|
||||||
|
ISaleInvoiceMailSend,
|
||||||
|
SaleInvoiceMailOptions,
|
||||||
|
SendInvoiceMailDTO,
|
||||||
|
} from '@/interfaces';
|
||||||
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
||||||
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
|
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
|
||||||
import {
|
import {
|
||||||
DEFAULT_INVOICE_MAIL_CONTENT,
|
DEFAULT_INVOICE_MAIL_CONTENT,
|
||||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
import {
|
||||||
|
parseMailOptions,
|
||||||
|
validateRequiredMailOptions,
|
||||||
|
} from '@/services/MailNotification/utils';
|
||||||
import events from '@/subscribers/events';
|
import events from '@/subscribers/events';
|
||||||
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
|
||||||
|
import { ParsedNumberSearch } from 'libphonenumber-js';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendSaleInvoiceMail {
|
export class SendSaleInvoiceMail {
|
||||||
@@ -57,12 +65,17 @@ export class SendSaleInvoiceMail {
|
|||||||
* @param {number} saleInvoiceId
|
* @param {number} saleInvoiceId
|
||||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getMailOption(tenantId: number, saleInvoiceId: number) {
|
public async getMailOption(
|
||||||
return this.invoiceMail.getMailOption(
|
tenantId: number,
|
||||||
|
saleInvoiceId: number,
|
||||||
|
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
|
defaultMessage: string = DEFAULT_INVOICE_MAIL_CONTENT
|
||||||
|
): Promise<SaleInvoiceMailOptions> {
|
||||||
|
return this.invoiceMail.getInvoiceMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
defaultSubject,
|
||||||
DEFAULT_INVOICE_MAIL_CONTENT
|
defaultMessage
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,44 +91,58 @@ export class SendSaleInvoiceMail {
|
|||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageOptions: SendInvoiceMailDTO
|
messageOptions: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
const defaultMessageOpts = await this.getMailOption(
|
const defaultMessageOptions = await this.getMailOption(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId
|
saleInvoiceId
|
||||||
);
|
);
|
||||||
// Merge message opts with default options and validate the incoming options.
|
// Merges message options with default options and parses the options values.
|
||||||
const messageOpts = parseAndValidateMailOptions(
|
const parsedMessageOptions = parseMailOptions(
|
||||||
defaultMessageOpts,
|
defaultMessageOptions,
|
||||||
messageOptions
|
messageOptions
|
||||||
);
|
);
|
||||||
const mail = new Mail()
|
// Validates the required mail options.
|
||||||
.setSubject(messageOpts.subject)
|
validateRequiredMailOptions(parsedMessageOptions);
|
||||||
.setTo(messageOpts.to)
|
|
||||||
.setContent(messageOpts.body);
|
|
||||||
|
|
||||||
if (messageOpts.attachInvoice) {
|
const formattedMessageOptions =
|
||||||
// Retrieves document buffer of the invoice pdf document.
|
await this.invoiceMail.formatInvoiceMailOptions(
|
||||||
const invoicePdfBuffer = await this.invoicePdf.saleInvoicePdf(
|
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId
|
saleInvoiceId,
|
||||||
|
parsedMessageOptions
|
||||||
);
|
);
|
||||||
|
const mail = new Mail()
|
||||||
|
.setSubject(formattedMessageOptions.subject)
|
||||||
|
.setTo(formattedMessageOptions.to)
|
||||||
|
.setContent(formattedMessageOptions.message);
|
||||||
|
|
||||||
|
// Attach invoice document.
|
||||||
|
if (formattedMessageOptions.attachInvoice) {
|
||||||
|
// Retrieves document buffer of the invoice pdf document.
|
||||||
|
const [invoicePdfBuffer, invoiceFilename] =
|
||||||
|
await this.invoicePdf.saleInvoicePdf(tenantId, saleInvoiceId);
|
||||||
|
|
||||||
mail.setAttachments([
|
mail.setAttachments([
|
||||||
{ filename: 'invoice.pdf', content: invoicePdfBuffer },
|
{ filename: `${invoiceFilename}.pdf`, content: invoicePdfBuffer },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
// Triggers the event `onSaleInvoiceSend`.
|
|
||||||
await this.eventPublisher.emitAsync(events.saleInvoice.onMailSend, {
|
const eventPayload = {
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
messageOptions,
|
messageOptions,
|
||||||
} as ISaleInvoiceMailSend);
|
formattedMessageOptions,
|
||||||
|
} as ISaleInvoiceMailSend;
|
||||||
|
|
||||||
|
// Triggers the event `onSaleInvoiceSend`.
|
||||||
|
await this.eventPublisher.emitAsync(
|
||||||
|
events.saleInvoice.onMailSend,
|
||||||
|
eventPayload
|
||||||
|
);
|
||||||
await mail.send();
|
await mail.send();
|
||||||
|
|
||||||
// Triggers the event `onSaleInvoiceSend`.
|
// Triggers the event `onSaleInvoiceSend`.
|
||||||
await this.eventPublisher.emitAsync(events.saleInvoice.onMailSent, {
|
await this.eventPublisher.emitAsync(
|
||||||
tenantId,
|
events.saleInvoice.onMailSent,
|
||||||
saleInvoiceId,
|
eventPayload
|
||||||
messageOptions,
|
);
|
||||||
} as ISaleInvoiceMailSend);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -406,7 +406,7 @@ export const runningAmount = (amount: number) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatSmsMessage = (message, args) => {
|
export const formatSmsMessage = (message: string, args) => {
|
||||||
let formattedMessage = message;
|
let formattedMessage = message;
|
||||||
|
|
||||||
Object.keys(args).forEach((key) => {
|
Object.keys(args).forEach((key) => {
|
||||||
|
|||||||
@@ -29,7 +29,13 @@ export const useStripeIntegrationEditBoot = () => {
|
|||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StripeIntegrationEditBoot: React.FC = ({ children }) => {
|
interface StripeIntegrationEditBootProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StripeIntegrationEditBoot: React.FC<
|
||||||
|
StripeIntegrationEditBootProps
|
||||||
|
> = ({ children }) => {
|
||||||
const {
|
const {
|
||||||
payload: { stripePaymentMethodId },
|
payload: { stripePaymentMethodId },
|
||||||
} = useDrawerContext();
|
} = useDrawerContext();
|
||||||
|
|||||||
@@ -357,7 +357,6 @@ export function useSendSaleInvoiceMail(
|
|||||||
(value) => apiRequest.post(`sales/invoices/${value.id}/mail`, value.values),
|
(value) => apiRequest.post(`sales/invoices/${value.id}/mail`, value.values),
|
||||||
{
|
{
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
// Common invalidate queries.
|
|
||||||
commonInvalidateQueries(queryClient);
|
commonInvalidateQueries(queryClient);
|
||||||
},
|
},
|
||||||
...options,
|
...options,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"name": "@bigcapital/email-components",
|
"name": "@bigcapital/email-components",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
|||||||
Reference in New Issue
Block a user