Skip to content

Commit

Permalink
feat: wip send invoice mail payment template
Browse files Browse the repository at this point in the history
  • Loading branch information
abouolia committed Oct 28, 2024
1 parent 0111b0e commit 12189f0
Show file tree
Hide file tree
Showing 15 changed files with 396 additions and 114 deletions.
17 changes: 14 additions & 3 deletions packages/server/src/api/controllers/Sales/SalesInvoices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,21 @@ export default class SaleInvoicesController extends BaseController {
'/:id/mail',
[
...this.specificSaleInvoiceValidation,
body('subject').isString().optional(),

body('subject').isString().optional({ nullable: true }),
body('message').isString().optional({ nullable: true }),

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(),
],
this.validationResult,
Expand Down
3 changes: 3 additions & 0 deletions packages/server/src/constants/event-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_VIEWED = 'Sale invoice 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_EDITED = 'Sale estimate edited';
Expand Down
16 changes: 6 additions & 10 deletions packages/server/src/interfaces/Mailable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,14 @@ export interface AddressItem {
}

export interface CommonMailOptions {
toAddresses: AddressItem[];
fromAddresses: AddressItem[];
from: string;
to: string | string[];
from: Array<string>;
subject: string;
body: string;
message: string;
to: Array<string>;
cc?: Array<string>;
bcc?: Array<string>;
data?: Record<string, any>;
}

export interface CommonMailOptionsDTO {
to?: string | string[];
from?: string;
subject?: string;
body?: string;
export interface CommonMailOptionsDTO extends Partial<CommonMailOptions> {
}
3 changes: 2 additions & 1 deletion packages/server/src/interfaces/SaleInvoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export enum SaleInvoiceAction {
}

export interface SaleInvoiceMailOptions extends CommonMailOptions {
attachInvoice: boolean;
attachInvoice?: boolean;
}

export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
Expand All @@ -251,6 +251,7 @@ export interface ISaleInvoiceMailSend {
tenantId: number;
saleInvoiceId: number;
messageOptions: SendInvoiceMailDTO;
formattedMessageOptions: SaleInvoiceMailOptions;
}

export interface ISaleInvoiceMailSent {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
SALE_INVOICE_CREATED,
SALE_INVOICE_DELETED,
SALE_INVOICE_EDITED,
SALE_INVOICE_MAIL_SENT,
SALE_INVOICE_PDF_VIEWED,
SALE_INVOICE_VIEWED,
} from '@/constants/event-tracker';
Expand Down Expand Up @@ -43,6 +44,10 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
events.saleInvoice.onPdfViewed,
this.handleTrackPdfViewedInvoiceEvent
);
bus.subscribe(
events.saleInvoice.onMailSent,
this.handleTrackMailSentInvoiceEvent
);
}

private handleTrackInvoiceCreatedEvent = ({
Expand Down Expand Up @@ -90,4 +95,12 @@ export class SaleInvoiceEventsTracker extends EventSubscriber {
properties: {},
});
};

private handleTrackMailSentInvoiceEvent = ({ tenantId }) => {
this.posthog.trackEvent({
distinctId: `tenant-${tenantId}`,
event: SALE_INVOICE_MAIL_SENT,
properties: {},
});
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
import { MailTenancy } from '@/services/MailTenancy/MailTenancy';
import { formatSmsMessage } from '@/utils';
import { Tenant } from '@/system/models';
import { castArray } from 'lodash';

@Service()
export class ContactMailNotification {
Expand All @@ -14,76 +15,54 @@ export class ContactMailNotification {
private tenancy: HasTenancyService;

/**
* Parses the default message options.
* @param {number} tenantId -
* @param {number} invoiceId -
* @param {string} subject -
* @param {string} body -
* @returns {Promise<SaleInvoiceMailOptions>}
* Gets the default mail address of the given contact.
* @param {number} tenantId - Tenant id.
* @param {number} invoiceId - Contact id.
* @returns {Promise<Pick<CommonMailOptions, 'to' | 'from'>>}
*/
public async getDefaultMailOptions(
tenantId: number,
contactId: number,
subject: string = '',
body: string = ''
): Promise<CommonMailOptions> {
customerId: number
): Promise<Pick<CommonMailOptions, 'to' | 'from'>> {
const { Customer } = this.tenancy.models(tenantId);
const contact = await Customer.query()
.findById(contactId)
const customer = await Customer.query()
.findById(customerId)
.throwIfNotFound();

const toAddresses = contact.contactAddresses;
const toAddresses = customer.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 || '';
const to = toAddress?.mail ? castArray(toAddress?.mail) : [];
const from = fromAddress?.mail ? castArray(fromAddress?.mail) : [];

return {
subject,
body,
to,
from,
fromAddresses,
toAddresses,
};
return { to, from };
}

/**
* Retrieves the mail options of the given contact.
* @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>}
*/
public async getMailOptions(
public async parseMailOptions(
tenantId: number,
contactId: number,
defaultSubject?: string,
defaultBody?: string,
formatterData?: Record<string, any>
mailOptions: CommonMailOptions,
formatterArgs?: Record<string, any>
): Promise<CommonMailOptions> {
const mailOpts = await this.getDefaultMailOptions(
tenantId,
contactId,
defaultSubject,
defaultBody
);
const commonFormatArgs = await this.getCommonFormatArgs(tenantId);
const formatArgs = {
...commonFormatArgs,
...formatterData,
...formatterArgs,
};
const subject = formatSmsMessage(mailOpts.subject, formatArgs);
const body = formatSmsMessage(mailOpts.body, formatArgs);
const subjectFormatted = formatSmsMessage(mailOptions?.subject, formatArgs);
const messageFormatted = formatSmsMessage(mailOptions?.message, formatArgs);

return {
...mailOpts,
subject,
body,
...mailOptions,
subject: subjectFormatted,
message: messageFormatted,
};
}

Expand Down
37 changes: 25 additions & 12 deletions packages/server/src/services/MailNotification/utils.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,46 @@
import { isEmpty } from 'lodash';
import { castArray, isEmpty } from 'lodash';
import { ServiceError } from '@/exceptions';
import { CommonMailOptions, CommonMailOptionsDTO } from '@/interfaces';
import { CommonMailOptions } from '@/interfaces';
import { ERRORS } from './constants';

/**
* Merges the mail options with incoming options.
* @param {Partial<SaleInvoiceMailOptions>} mailOptions
* @param {Partial<SendInvoiceMailDTO>} overridedOptions
* @throws {ServiceError}
*/
export function parseAndValidateMailOptions(
mailOptions: Partial<CommonMailOptions>,
overridedOptions: Partial<CommonMailOptionsDTO>
) {
export function parseMailOptions(
mailOptions: CommonMailOptions,
overridedOptions: Partial<CommonMailOptions>
): CommonMailOptions {
const mergedMessageOptions = {
...mailOptions,
...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);
}
if (isEmpty(mergedMessageOptions.to)) {
if (isEmpty(mailOptions.to)) {
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
}
if (isEmpty(mergedMessageOptions.subject)) {
if (isEmpty(mailOptions.subject)) {
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
}
if (isEmpty(mergedMessageOptions.body)) {
if (isEmpty(mailOptions.message)) {
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
}
return mergedMessageOptions;
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit 12189f0

Please sign in to comment.