mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
feat: mail notifications of sales transactions
This commit is contained in:
@@ -4,9 +4,8 @@ import { body, check, param, query, ValidationChain } from 'express-validator';
|
|||||||
import {
|
import {
|
||||||
AbilitySubject,
|
AbilitySubject,
|
||||||
IPaymentReceiveDTO,
|
IPaymentReceiveDTO,
|
||||||
IPaymentReceiveMailOpts,
|
|
||||||
// IPaymentReceiveMailOpts,
|
|
||||||
PaymentReceiveAction,
|
PaymentReceiveAction,
|
||||||
|
PaymentReceiveMailOptsDTO,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
@@ -541,9 +540,12 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
) => {
|
) => {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
const paymentMailDTO: IPaymentReceiveMailOpts = this.matchedBodyData(req, {
|
const paymentMailDTO: PaymentReceiveMailOptsDTO = this.matchedBodyData(
|
||||||
includeOptionals: false,
|
req,
|
||||||
});
|
{
|
||||||
|
includeOptionals: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await this.paymentReceiveApplication.notifyPaymentByMail(
|
await this.paymentReceiveApplication.notifyPaymentByMail(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -574,7 +576,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
const { id: paymentReceiveId } = req.params;
|
const { id: paymentReceiveId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.paymentReceiveApplication.getPaymentDefaultMail(
|
const data = await this.paymentReceiveApplication.getPaymentMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId
|
paymentReceiveId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
AbilitySubject,
|
AbilitySubject,
|
||||||
ISaleEstimateDTO,
|
ISaleEstimateDTO,
|
||||||
SaleEstimateAction,
|
SaleEstimateAction,
|
||||||
SaleEstimateMailOptions,
|
SaleEstimateMailOptionsDTO,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import BaseController from '@/api/controllers/BaseController';
|
import BaseController from '@/api/controllers/BaseController';
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
@@ -513,10 +513,12 @@ export default class SalesEstimatesController extends BaseController {
|
|||||||
) => {
|
) => {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: invoiceId } = req.params;
|
const { id: invoiceId } = req.params;
|
||||||
const saleEstimateDTO: SaleEstimateMailOptions = this.matchedBodyData(req, {
|
const saleEstimateDTO: SaleEstimateMailOptionsDTO = this.matchedBodyData(
|
||||||
includeOptionals: false,
|
req,
|
||||||
});
|
{
|
||||||
|
includeOptionals: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
try {
|
try {
|
||||||
await this.saleEstimatesApplication.sendSaleEstimateMail(
|
await this.saleEstimatesApplication.sendSaleEstimateMail(
|
||||||
tenantId,
|
tenantId,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { body, check, param, query } from 'express-validator';
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import { ISaleReceiptDTO, SaleReceiptMailOpts } from '@/interfaces/SaleReceipt';
|
import { ISaleReceiptDTO, SaleReceiptMailOpts, SaleReceiptMailOptsDTO } from '@/interfaces/SaleReceipt';
|
||||||
import { ServiceError } from '@/exceptions';
|
import { ServiceError } from '@/exceptions';
|
||||||
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
|
||||||
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
import CheckPolicies from '@/api/middleware/CheckPolicies';
|
||||||
@@ -54,7 +54,7 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
body('from').isString().optional(),
|
body('from').isString().optional(),
|
||||||
body('to').isString().optional(),
|
body('to').isString().optional(),
|
||||||
body('body').isString().optional(),
|
body('body').isString().optional(),
|
||||||
body('attach_invoice').optional().isBoolean().toBoolean(),
|
body('attach_receipt').optional().isBoolean().toBoolean(),
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.sendSaleReceiptMail.bind(this)),
|
asyncMiddleware(this.sendSaleReceiptMail.bind(this)),
|
||||||
@@ -439,7 +439,7 @@ export default class SalesReceiptsController extends BaseController {
|
|||||||
) => {
|
) => {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { id: receiptId } = req.params;
|
const { id: receiptId } = req.params;
|
||||||
const receiptMailDTO: SaleReceiptMailOpts = this.matchedBodyData(req, {
|
const receiptMailDTO: SaleReceiptMailOptsDTO = this.matchedBodyData(req, {
|
||||||
includeOptionals: false,
|
includeOptionals: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
|
export type IMailAttachment = MailAttachmentPath | MailAttachmentContent;
|
||||||
|
|
||||||
|
export interface MailAttachmentPath {
|
||||||
|
filename: string;
|
||||||
|
path: string;
|
||||||
|
cid: string;
|
||||||
|
}
|
||||||
|
export interface MailAttachmentContent {
|
||||||
|
filename: string;
|
||||||
|
content: Buffer;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IMailable {
|
export interface IMailable {
|
||||||
constructor(
|
constructor(view: string, data?: { [key: string]: string | number });
|
||||||
view: string,
|
|
||||||
data?: { [key: string]: string | number },
|
|
||||||
);
|
|
||||||
send(): Promise<any>;
|
send(): Promise<any>;
|
||||||
build(): void;
|
build(): void;
|
||||||
setData(data: { [key: string]: string | number }): IMailable;
|
setData(data: { [key: string]: string | number }): IMailable;
|
||||||
@@ -14,3 +22,26 @@ export interface IMailable {
|
|||||||
render(data?: { [key: string]: string | number }): string;
|
render(data?: { [key: string]: string | number }): string;
|
||||||
getViewContent(): string;
|
getViewContent(): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AddressItem {
|
||||||
|
label: string;
|
||||||
|
mail: string;
|
||||||
|
primary?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommonMailOptions {
|
||||||
|
toAddresses: AddressItem[];
|
||||||
|
fromAddresses: AddressItem[];
|
||||||
|
from: string;
|
||||||
|
to: string | string[];
|
||||||
|
subject: string;
|
||||||
|
body: string;
|
||||||
|
data?: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommonMailOptionsDTO {
|
||||||
|
to?: string | string[];
|
||||||
|
from?: string;
|
||||||
|
subject?: string;
|
||||||
|
body?: string;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { ISystemUser } from '@/interfaces';
|
import {
|
||||||
|
CommonMailOptions,
|
||||||
|
CommonMailOptionsDTO,
|
||||||
|
ISystemUser,
|
||||||
|
} from '@/interfaces';
|
||||||
import { ILedgerEntry } from './Ledger';
|
import { ILedgerEntry } from './Ledger';
|
||||||
import { ISaleInvoice } from './SaleInvoice';
|
import { ISaleInvoice } from './SaleInvoice';
|
||||||
|
|
||||||
@@ -19,7 +23,7 @@ export interface IPaymentReceive {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
localAmount?: number;
|
localAmount?: number;
|
||||||
branchId?: number
|
branchId?: number;
|
||||||
}
|
}
|
||||||
export interface IPaymentReceiveCreateDTO {
|
export interface IPaymentReceiveCreateDTO {
|
||||||
customerId: number;
|
customerId: number;
|
||||||
@@ -166,6 +170,6 @@ export type IPaymentReceiveGLCommonEntry = Pick<
|
|||||||
| 'branchId'
|
| 'branchId'
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export interface IPaymentReceiveMailOpts {
|
export interface PaymentReceiveMailOpts extends CommonMailOptions {}
|
||||||
|
|
||||||
}
|
export interface PaymentReceiveMailOptsDTO extends CommonMailOptionsDTO {}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||||
import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
|
import { IDynamicListFilterDTO } from '@/interfaces/DynamicFilter';
|
||||||
|
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
|
||||||
|
|
||||||
export interface ISaleEstimate {
|
export interface ISaleEstimate {
|
||||||
id?: number;
|
id?: number;
|
||||||
@@ -125,10 +126,10 @@ export interface ISaleEstimateApprovedEvent {
|
|||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaleEstimateMailOptions {
|
export interface SaleEstimateMailOptions extends CommonMailOptions {
|
||||||
to: string;
|
attachEstimate?: boolean;
|
||||||
from: string;
|
}
|
||||||
subject: string;
|
|
||||||
body: string;
|
export interface SaleEstimateMailOptionsDTO extends CommonMailOptionsDTO {
|
||||||
attachInvoice?: boolean;
|
attachEstimate?: boolean;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { ISystemUser, IAccount, ITaxTransaction, AddressItem } from '@/interfaces';
|
import { ISystemUser, IAccount, ITaxTransaction } from '@/interfaces';
|
||||||
|
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
|
||||||
import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
|
import { IDynamicListFilter } from '@/interfaces/DynamicFilter';
|
||||||
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
import { IItemEntry, IItemEntryDTO } from './ItemEntry';
|
||||||
|
|
||||||
@@ -187,21 +188,11 @@ export enum SaleInvoiceAction {
|
|||||||
NotifyBySms = 'NotifyBySms',
|
NotifyBySms = 'NotifyBySms',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaleInvoiceMailOptions {
|
export interface SaleInvoiceMailOptions extends CommonMailOptions {
|
||||||
toAddresses: AddressItem[];
|
|
||||||
fromAddresses: AddressItem[];
|
|
||||||
from: string;
|
|
||||||
to: string | string[];
|
|
||||||
subject: string;
|
|
||||||
body: string;
|
|
||||||
attachInvoice: boolean;
|
attachInvoice: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SendInvoiceMailDTO {
|
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
|
||||||
to: string | string[];
|
|
||||||
from: string;
|
|
||||||
subject: string;
|
|
||||||
body: string;
|
|
||||||
attachInvoice?: boolean;
|
attachInvoice?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { IItemEntry } from './ItemEntry';
|
import { IItemEntry } from './ItemEntry';
|
||||||
|
import { CommonMailOptions, CommonMailOptionsDTO } from './Mailable';
|
||||||
|
|
||||||
export interface ISaleReceipt {
|
export interface ISaleReceipt {
|
||||||
id?: number;
|
id?: number;
|
||||||
@@ -135,6 +136,10 @@ export interface ISaleReceiptDeletingPayload {
|
|||||||
trx: Knex.Transaction;
|
trx: Knex.Transaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SaleReceiptMailOpts {
|
export interface SaleReceiptMailOpts extends CommonMailOptions {
|
||||||
|
attachReceipt: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SaleReceiptMailOptsDTO extends CommonMailOptionsDTO {
|
||||||
|
attachReceipt?: boolean;
|
||||||
}
|
}
|
||||||
@@ -2,18 +2,13 @@ import fs from 'fs';
|
|||||||
import Mustache from 'mustache';
|
import Mustache from 'mustache';
|
||||||
import { Container } from 'typedi';
|
import { Container } from 'typedi';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { IMailable } from '@/interfaces';
|
import { IMailAttachment } from '@/interfaces';
|
||||||
|
|
||||||
interface IMailAttachment {
|
|
||||||
filename: string;
|
|
||||||
path: string;
|
|
||||||
cid: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Mail {
|
export default class Mail {
|
||||||
view: string;
|
view: string;
|
||||||
subject: string;
|
subject: string = '';
|
||||||
to: string;
|
content: string = '';
|
||||||
|
to: string | string[];
|
||||||
from: string = `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`;
|
from: string = `${process.env.MAIL_FROM_NAME} ${process.env.MAIL_FROM_ADDRESS}`;
|
||||||
data: { [key: string]: string | number };
|
data: { [key: string]: string | number };
|
||||||
attachments: IMailAttachment[];
|
attachments: IMailAttachment[];
|
||||||
@@ -21,16 +16,24 @@ export default class Mail {
|
|||||||
/**
|
/**
|
||||||
* Mail options.
|
* Mail options.
|
||||||
*/
|
*/
|
||||||
private get mailOptions() {
|
public get mailOptions() {
|
||||||
return {
|
return {
|
||||||
to: this.to,
|
to: this.to,
|
||||||
from: this.from,
|
from: this.from,
|
||||||
subject: this.subject,
|
subject: this.subject,
|
||||||
html: this.render(this.data),
|
html: this.html,
|
||||||
attachments: this.attachments,
|
attachments: this.attachments,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the html content of the mail.
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public get html() {
|
||||||
|
return this.view ? Mail.render(this.view, this.data) : this.content;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the given mail to the target address.
|
* Sends the given mail to the target address.
|
||||||
*/
|
*/
|
||||||
@@ -52,7 +55,7 @@ export default class Mail {
|
|||||||
* Set send mail to address.
|
* Set send mail to address.
|
||||||
* @param {string} to -
|
* @param {string} to -
|
||||||
*/
|
*/
|
||||||
setTo(to: string) {
|
setTo(to: string | string[]) {
|
||||||
this.to = to;
|
this.to = to;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@@ -62,11 +65,16 @@ export default class Mail {
|
|||||||
* @param {string} from
|
* @param {string} from
|
||||||
* @return {}
|
* @return {}
|
||||||
*/
|
*/
|
||||||
private setFrom(from: string) {
|
setFrom(from: string) {
|
||||||
this.from = from;
|
this.from = from;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set attachments to the mail.
|
||||||
|
* @param {IMailAttachment[]} attachments
|
||||||
|
* @returns {Mail}
|
||||||
|
*/
|
||||||
setAttachments(attachments: IMailAttachment[]) {
|
setAttachments(attachments: IMailAttachment[]) {
|
||||||
this.attachments = attachments;
|
this.attachments = attachments;
|
||||||
return this;
|
return this;
|
||||||
@@ -95,21 +103,26 @@ export default class Mail {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setContent(content: string) {
|
||||||
|
this.content = content;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the view template with the given data.
|
* Renders the view template with the given data.
|
||||||
* @param {object} data
|
* @param {object} data
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
render(data): string {
|
static render(view: string, data: Record<string, any>): string {
|
||||||
const viewContent = this.getViewContent();
|
const viewContent = Mail.getViewContent(view);
|
||||||
return Mustache.render(viewContent, data);
|
return Mustache.render(viewContent, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve view content from the view directory.
|
* Retrieve view content from the view directory.
|
||||||
*/
|
*/
|
||||||
private getViewContent(): string {
|
static getViewContent(view: string): string {
|
||||||
const filePath = path.join(global.__views_dir, `/${this.view}`);
|
const filePath = path.join(global.__views_dir, `/${view}`);
|
||||||
return fs.readFileSync(filePath, 'utf8');
|
return fs.readFileSync(filePath, 'utf8');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import * as R from 'ramda';
|
import { CommonMailOptions } from '@/interfaces';
|
||||||
import { SaleInvoiceMailOptions } from '@/interfaces';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
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';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class ContactMailNotification {
|
export class ContactMailNotification {
|
||||||
@@ -15,8 +15,10 @@ export class ContactMailNotification {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the default message options.
|
* Parses the default message options.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId -
|
||||||
* @param {number} invoiceId
|
* @param {number} invoiceId -
|
||||||
|
* @param {string} subject -
|
||||||
|
* @param {string} body -
|
||||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getDefaultMailOptions(
|
public async getDefaultMailOptions(
|
||||||
@@ -24,9 +26,11 @@ export class ContactMailNotification {
|
|||||||
contactId: number,
|
contactId: number,
|
||||||
subject: string = '',
|
subject: string = '',
|
||||||
body: string = ''
|
body: string = ''
|
||||||
): Promise<any> {
|
): Promise<CommonMailOptions> {
|
||||||
const { Contact, Customer } = this.tenancy.models(tenantId);
|
const { Customer } = this.tenancy.models(tenantId);
|
||||||
const contact = await Customer.query().findById(contactId).throwIfNotFound();
|
const contact = await Customer.query()
|
||||||
|
.findById(contactId)
|
||||||
|
.throwIfNotFound();
|
||||||
|
|
||||||
const toAddresses = contact.contactAddresses;
|
const toAddresses = contact.contactAddresses;
|
||||||
const fromAddresses = await this.mailTenancy.senders(tenantId);
|
const fromAddresses = await this.mailTenancy.senders(tenantId);
|
||||||
@@ -48,10 +52,12 @@ export class ContactMailNotification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the mail options.
|
* Retrieves the mail options of the given contact.
|
||||||
* @param {number}
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} invoiceId
|
* @param {number} invoiceId - Invoice id.
|
||||||
* @returns {}
|
* @param {string} defaultSubject - Default subject text.
|
||||||
|
* @param {string} defaultBody - Default body text.
|
||||||
|
* @returns {Promise<CommonMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getMailOptions(
|
public async getMailOptions(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
@@ -59,15 +65,20 @@ export class ContactMailNotification {
|
|||||||
defaultSubject?: string,
|
defaultSubject?: string,
|
||||||
defaultBody?: string,
|
defaultBody?: string,
|
||||||
formatterData?: Record<string, any>
|
formatterData?: Record<string, any>
|
||||||
): Promise<SaleInvoiceMailOptions> {
|
): Promise<CommonMailOptions> {
|
||||||
const mailOpts = await this.getDefaultMailOptions(
|
const mailOpts = await this.getDefaultMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
contactId,
|
contactId,
|
||||||
defaultSubject,
|
defaultSubject,
|
||||||
defaultBody
|
defaultBody
|
||||||
);
|
);
|
||||||
const subject = formatSmsMessage(mailOpts.subject, formatterData);
|
const commonFormatArgs = await this.getCommonFormatArgs(tenantId);
|
||||||
const body = formatSmsMessage(mailOpts.body, formatterData);
|
const formatArgs = {
|
||||||
|
...commonFormatArgs,
|
||||||
|
...formatterData,
|
||||||
|
};
|
||||||
|
const subject = formatSmsMessage(mailOpts.subject, formatArgs);
|
||||||
|
const body = formatSmsMessage(mailOpts.body, formatArgs);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...mailOpts,
|
...mailOpts,
|
||||||
@@ -75,4 +86,21 @@ export class ContactMailNotification {
|
|||||||
body,
|
body,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the common format args.
|
||||||
|
* @param {number} tenantId
|
||||||
|
* @returns {Promise<Record<string, string>>}
|
||||||
|
*/
|
||||||
|
public async getCommonFormatArgs(
|
||||||
|
tenantId: number
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
const organization = await Tenant.query()
|
||||||
|
.findById(tenantId)
|
||||||
|
.withGraphFetched('metadata');
|
||||||
|
|
||||||
|
return {
|
||||||
|
CompanyName: organization.metadata.name,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export const ERRORS = {
|
||||||
|
MAIL_FROM_NOT_FOUND: 'Mail from address not found',
|
||||||
|
MAIL_TO_NOT_FOUND: 'Mail to address not found',
|
||||||
|
MAIL_SUBJECT_NOT_FOUND: 'Mail subject not found',
|
||||||
|
MAIL_BODY_NOT_FOUND: 'Mail body not found',
|
||||||
|
};
|
||||||
33
packages/server/src/services/MailNotification/utils.ts
Normal file
33
packages/server/src/services/MailNotification/utils.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { ServiceError } from '@/exceptions';
|
||||||
|
import { CommonMailOptions, CommonMailOptionsDTO } 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>
|
||||||
|
) {
|
||||||
|
const mergedMessageOptions = {
|
||||||
|
...mailOptions,
|
||||||
|
...overridedOptions,
|
||||||
|
};
|
||||||
|
if (isEmpty(mergedMessageOptions.from)) {
|
||||||
|
throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND);
|
||||||
|
}
|
||||||
|
if (isEmpty(mergedMessageOptions.to)) {
|
||||||
|
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
|
||||||
|
}
|
||||||
|
if (isEmpty(mergedMessageOptions.subject)) {
|
||||||
|
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
|
||||||
|
}
|
||||||
|
if (isEmpty(mergedMessageOptions.body)) {
|
||||||
|
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
|
||||||
|
}
|
||||||
|
return mergedMessageOptions;
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
ISaleEstimateDTO,
|
ISaleEstimateDTO,
|
||||||
ISalesEstimatesFilter,
|
ISalesEstimatesFilter,
|
||||||
SaleEstimateMailOptions,
|
SaleEstimateMailOptions,
|
||||||
|
SaleEstimateMailOptionsDTO,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { EditSaleEstimate } from './EditSaleEstimate';
|
import { EditSaleEstimate } from './EditSaleEstimate';
|
||||||
import { DeleteSaleEstimate } from './DeleteSaleEstimate';
|
import { DeleteSaleEstimate } from './DeleteSaleEstimate';
|
||||||
@@ -224,8 +225,8 @@ export class SaleEstimatesApplication {
|
|||||||
public sendSaleEstimateMail(
|
public sendSaleEstimateMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleEstimateId: number,
|
saleEstimateId: number,
|
||||||
saleEstimateMailOpts: SaleEstimateMailOptions
|
saleEstimateMailOpts: SaleEstimateMailOptionsDTO
|
||||||
) {
|
): Promise<void> {
|
||||||
return this.sendEstimateMailService.triggerMail(
|
return this.sendEstimateMailService.triggerMail(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId,
|
saleEstimateId,
|
||||||
@@ -237,9 +238,12 @@ export class SaleEstimatesApplication {
|
|||||||
* Retrieves the default mail options of the given sale estimate.
|
* Retrieves the default mail options of the given sale estimate.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleEstimateId
|
* @param {number} saleEstimateId
|
||||||
* @returns {}
|
* @returns {Promise<SaleEstimateMailOptions>}
|
||||||
*/
|
*/
|
||||||
public getSaleEstimateMail(tenantId: number, saleEstimateId: number) {
|
public getSaleEstimateMail(
|
||||||
|
tenantId: number,
|
||||||
|
saleEstimateId: number
|
||||||
|
): Promise<SaleEstimateMailOptions> {
|
||||||
return this.sendEstimateMailService.getMailOptions(
|
return this.sendEstimateMailService.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId
|
saleEstimateId
|
||||||
|
|||||||
@@ -7,8 +7,12 @@ import {
|
|||||||
} from './constants';
|
} from './constants';
|
||||||
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
|
import { SaleEstimatesPdf } from './SaleEstimatesPdf';
|
||||||
import { GetSaleEstimate } from './GetSaleEstimate';
|
import { GetSaleEstimate } from './GetSaleEstimate';
|
||||||
import { SaleEstimateMailOptions } from '@/interfaces';
|
import {
|
||||||
|
SaleEstimateMailOptions,
|
||||||
|
SaleEstimateMailOptionsDTO,
|
||||||
|
} from '@/interfaces';
|
||||||
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||||
|
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendSaleEstimateMail {
|
export class SendSaleEstimateMail {
|
||||||
@@ -31,13 +35,14 @@ export class SendSaleEstimateMail {
|
|||||||
* Triggers the reminder mail of the given sale estimate.
|
* Triggers the reminder mail of the given sale estimate.
|
||||||
* @param {number} tenantId -
|
* @param {number} tenantId -
|
||||||
* @param {number} saleEstimateId -
|
* @param {number} saleEstimateId -
|
||||||
* @param {SaleEstimateMailOptions} messageOptions -
|
* @param {SaleEstimateMailOptionsDTO} messageOptions -
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async triggerMail(
|
public async triggerMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleEstimateId: number,
|
saleEstimateId: number,
|
||||||
messageOptions: SaleEstimateMailOptions
|
messageOptions: SaleEstimateMailOptionsDTO
|
||||||
) {
|
): Promise<void> {
|
||||||
const payload = {
|
const payload = {
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId,
|
saleEstimateId,
|
||||||
@@ -48,9 +53,9 @@ export class SendSaleEstimateMail {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Formates the text of the mail.
|
* Formates the text of the mail.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} estimateId
|
* @param {number} estimateId - Estimate id.
|
||||||
* @param {string} text
|
* @returns {Promise<Record<string, any>>}
|
||||||
*/
|
*/
|
||||||
public formatterData = async (tenantId: number, estimateId: number) => {
|
public formatterData = async (tenantId: number, estimateId: number) => {
|
||||||
const estimate = await this.getSaleEstimateService.getEstimate(
|
const estimate = await this.getSaleEstimateService.getEstimate(
|
||||||
@@ -70,9 +75,12 @@ export class SendSaleEstimateMail {
|
|||||||
* Retrieves the mail options.
|
* Retrieves the mail options.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleEstimateId
|
* @param {number} saleEstimateId
|
||||||
* @returns
|
* @returns {Promise<SaleEstimateMailOptions>}
|
||||||
*/
|
*/
|
||||||
public getMailOptions = async (tenantId: number, saleEstimateId: number) => {
|
public getMailOptions = async (
|
||||||
|
tenantId: number,
|
||||||
|
saleEstimateId: number
|
||||||
|
): Promise<SaleEstimateMailOptions> => {
|
||||||
const { SaleEstimate } = this.tenancy.models(tenantId);
|
const { SaleEstimate } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const saleEstimate = await SaleEstimate.query()
|
const saleEstimate = await SaleEstimate.query()
|
||||||
@@ -91,6 +99,7 @@ export class SendSaleEstimateMail {
|
|||||||
return {
|
return {
|
||||||
...mailOptions,
|
...mailOptions,
|
||||||
data: formatterData,
|
data: formatterData,
|
||||||
|
attachEstimate: true
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -99,26 +108,28 @@ export class SendSaleEstimateMail {
|
|||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleEstimateId
|
* @param {number} saleEstimateId
|
||||||
* @param {SaleEstimateMailOptions} messageOptions
|
* @param {SaleEstimateMailOptions} messageOptions
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async sendMail(
|
public async sendMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleEstimateId: number,
|
saleEstimateId: number,
|
||||||
messageOptions: SaleEstimateMailOptions
|
messageOptions: SaleEstimateMailOptionsDTO
|
||||||
) {
|
): Promise<void> {
|
||||||
const localMessageOpts = await this.getMailOptions(
|
const localMessageOpts = await this.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId
|
saleEstimateId
|
||||||
);
|
);
|
||||||
const messageOpts = {
|
// Overrides and validates the given mail options.
|
||||||
...localMessageOpts,
|
const messageOpts = parseAndValidateMailOptions(
|
||||||
...messageOptions,
|
localMessageOpts,
|
||||||
};
|
messageOptions
|
||||||
|
);
|
||||||
const mail = new Mail()
|
const mail = new Mail()
|
||||||
.setSubject(messageOpts.subject)
|
.setSubject(messageOpts.subject)
|
||||||
.setTo(messageOpts.to)
|
.setTo(messageOpts.to)
|
||||||
.setContent(messageOpts.body);
|
.setContent(messageOpts.body);
|
||||||
|
|
||||||
if (messageOpts.to) {
|
if (messageOpts.attachEstimate) {
|
||||||
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
|
const estimatePdfBuffer = await this.estimatePdf.getSaleEstimatePdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleEstimateId
|
saleEstimateId
|
||||||
|
|||||||
@@ -300,7 +300,10 @@ export class SaleInvoiceApplication {
|
|||||||
* @returns {}
|
* @returns {}
|
||||||
*/
|
*/
|
||||||
public getSaleInvoiceMailReminder(tenantId: number, saleInvoiceId: number) {
|
public getSaleInvoiceMailReminder(tenantId: number, saleInvoiceId: number) {
|
||||||
return this.sendInvoiceReminderService.getMailOpts(tenantId, saleInvoiceId);
|
return this.sendInvoiceReminderService.getMailOption(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -347,6 +350,9 @@ export class SaleInvoiceApplication {
|
|||||||
* @returns {Promise<SendInvoiceMailDTO>}
|
* @returns {Promise<SendInvoiceMailDTO>}
|
||||||
*/
|
*/
|
||||||
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
|
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
|
||||||
return this.sendSaleInvoiceMailService.getMailOpts(tenantId, saleInvoiceid);
|
return this.sendSaleInvoiceMailService.getMailOption(
|
||||||
|
tenantId,
|
||||||
|
saleInvoiceid
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { SaleInvoiceMailOptions } from '@/interfaces';
|
import { SaleInvoiceMailOptions } from '@/interfaces';
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
|
import { GetSaleInvoice } from './GetSaleInvoice';
|
||||||
|
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||||
import {
|
import {
|
||||||
DEFAULT_INVOICE_MAIL_CONTENT,
|
DEFAULT_INVOICE_MAIL_CONTENT,
|
||||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { GetSaleInvoice } from './GetSaleInvoice';
|
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
import { ServiceError } from '@/exceptions';
|
|
||||||
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendSaleInvoiceMailCommon {
|
export class SendSaleInvoiceMailCommon {
|
||||||
@@ -28,9 +25,9 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
* @param {number} invoiceId - Invoice id.
|
* @param {number} invoiceId - Invoice id.
|
||||||
* @param {string} defaultSubject - Subject text.
|
* @param {string} defaultSubject - Subject text.
|
||||||
* @param {string} defaultBody - Subject body.
|
* @param {string} defaultBody - Subject body.
|
||||||
* @returns {}
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getMailOpts(
|
public async getMailOption(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
invoiceId: number,
|
invoiceId: number,
|
||||||
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
|
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
@@ -44,13 +41,17 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
|
|
||||||
const formatterData = await this.formatText(tenantId, invoiceId);
|
const formatterData = await this.formatText(tenantId, invoiceId);
|
||||||
|
|
||||||
return this.contactMailNotification.getMailOptions(
|
const mailOptions = await this.contactMailNotification.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoice.customerId,
|
saleInvoice.customerId,
|
||||||
defaultSubject,
|
defaultSubject,
|
||||||
defaultBody,
|
defaultBody,
|
||||||
formatterData
|
formatterData
|
||||||
);
|
);
|
||||||
|
return {
|
||||||
|
...mailOptions,
|
||||||
|
attachInvoice: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,12 +69,8 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
);
|
);
|
||||||
const organization = await Tenant.query()
|
|
||||||
.findById(tenantId)
|
|
||||||
.withGraphFetched('metadata');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
CompanyName: organization.metadata.name,
|
|
||||||
CustomerName: invoice.customer.displayName,
|
CustomerName: invoice.customer.displayName,
|
||||||
InvoiceNumber: invoice.invoiceNo,
|
InvoiceNumber: invoice.invoiceNo,
|
||||||
InvoiceDueAmount: invoice.dueAmountFormatted,
|
InvoiceDueAmount: invoice.dueAmountFormatted,
|
||||||
@@ -83,33 +80,4 @@ export class SendSaleInvoiceMailCommon {
|
|||||||
OverdueDays: invoice.overdueDays,
|
OverdueDays: invoice.overdueDays,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the mail notification options before sending it.
|
|
||||||
* @param {Partial<SaleInvoiceMailOptions>} mailNotificationOpts
|
|
||||||
* @throws {ServiceError}
|
|
||||||
*/
|
|
||||||
public validateMailNotification(
|
|
||||||
mailNotificationOpts: Partial<SaleInvoiceMailOptions>
|
|
||||||
) {
|
|
||||||
if (isEmpty(mailNotificationOpts.from)) {
|
|
||||||
throw new ServiceError(ERRORS.MAIL_FROM_NOT_FOUND);
|
|
||||||
}
|
|
||||||
if (isEmpty(mailNotificationOpts.to)) {
|
|
||||||
throw new ServiceError(ERRORS.MAIL_TO_NOT_FOUND);
|
|
||||||
}
|
|
||||||
if (isEmpty(mailNotificationOpts.subject)) {
|
|
||||||
throw new ServiceError(ERRORS.MAIL_SUBJECT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
if (isEmpty(mailNotificationOpts.body)) {
|
|
||||||
throw new ServiceError(ERRORS.MAIL_BODY_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ERRORS = {
|
|
||||||
MAIL_FROM_NOT_FOUND: 'Mail from address not found',
|
|
||||||
MAIL_TO_NOT_FOUND: 'Mail to address not found',
|
|
||||||
MAIL_SUBJECT_NOT_FOUND: 'Mail subject not found',
|
|
||||||
MAIL_BODY_NOT_FOUND: 'Mail body not found',
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -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 { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendSaleInvoiceMail {
|
export class SendSaleInvoiceMail {
|
||||||
@@ -44,8 +45,8 @@ export class SendSaleInvoiceMail {
|
|||||||
* @param {number} saleInvoiceId
|
* @param {number} saleInvoiceId
|
||||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getMailOpts(tenantId: number, saleInvoiceId: number) {
|
public async getMailOption(tenantId: number, saleInvoiceId: number) {
|
||||||
return this.invoiceMail.getMailOpts(
|
return this.invoiceMail.getMailOption(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
DEFAULT_INVOICE_MAIL_SUBJECT,
|
||||||
@@ -65,15 +66,15 @@ export class SendSaleInvoiceMail {
|
|||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageDTO: SendInvoiceMailDTO
|
messageDTO: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
const defaultMessageOpts = await this.getMailOpts(tenantId, saleInvoiceId);
|
const defaultMessageOpts = await this.getMailOption(
|
||||||
|
tenantId,
|
||||||
// Parsed message opts with default options.
|
saleInvoiceId
|
||||||
const messageOpts = {
|
);
|
||||||
...defaultMessageOpts,
|
// Merge message opts with default options and validate the incoming options.
|
||||||
...messageDTO,
|
const messageOpts = parseAndValidateMailOptions(
|
||||||
};
|
defaultMessageOpts,
|
||||||
this.invoiceMail.validateMailNotification(messageOpts);
|
messageDTO
|
||||||
|
);
|
||||||
const mail = new Mail()
|
const mail = new Mail()
|
||||||
.setSubject(messageOpts.subject)
|
.setSubject(messageOpts.subject)
|
||||||
.setTo(messageOpts.to)
|
.setTo(messageOpts.to)
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ export class SendInvoiceMailReminder {
|
|||||||
* @param {number} saleInvoiceId
|
* @param {number} saleInvoiceId
|
||||||
* @returns {Promise<SaleInvoiceMailOptions>}
|
* @returns {Promise<SaleInvoiceMailOptions>}
|
||||||
*/
|
*/
|
||||||
public async getMailOpts(tenantId: number, saleInvoiceId: number) {
|
public async getMailOption(tenantId: number, saleInvoiceId: number) {
|
||||||
return this.invoiceCommonMail.getMailOpts(
|
return this.invoiceCommonMail.getMailOption(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId,
|
saleInvoiceId,
|
||||||
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
|
DEFAULT_INVOICE_REMINDER_MAIL_SUBJECT,
|
||||||
@@ -64,7 +64,7 @@ export class SendInvoiceMailReminder {
|
|||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageOptions: SendInvoiceMailDTO
|
messageOptions: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
const localMessageOpts = await this.getMailOpts(tenantId, saleInvoiceId);
|
const localMessageOpts = await this.getMailOption(tenantId, saleInvoiceId);
|
||||||
|
|
||||||
const messageOpts = {
|
const messageOpts = {
|
||||||
...localMessageOpts,
|
...localMessageOpts,
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { IPaymentReceiveMailOpts, SendInvoiceMailDTO } from '@/interfaces';
|
import {
|
||||||
|
PaymentReceiveMailOpts,
|
||||||
|
PaymentReceiveMailOptsDTO,
|
||||||
|
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 {
|
import {
|
||||||
DEFAULT_PAYMENT_MAIL_CONTENT,
|
DEFAULT_PAYMENT_MAIL_CONTENT,
|
||||||
DEFAULT_PAYMENT_MAIL_SUBJECT,
|
DEFAULT_PAYMENT_MAIL_SUBJECT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
import { GetPaymentReceive } from './GetPaymentReceive';
|
import { GetPaymentReceive } from './GetPaymentReceive';
|
||||||
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||||
|
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SendPaymentReceiveMailNotification {
|
export class SendPaymentReceiveMailNotification {
|
||||||
@@ -28,13 +32,14 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
* Sends the mail of the given payment receive.
|
* Sends the mail of the given payment receive.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} paymentReceiveId
|
* @param {number} paymentReceiveId
|
||||||
* @param {SendInvoiceMailDTO} messageDTO
|
* @param {PaymentReceiveMailOptsDTO} messageDTO
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async triggerMail(
|
public async triggerMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveId: number,
|
paymentReceiveId: number,
|
||||||
messageDTO: IPaymentReceiveMailOpts
|
messageDTO: PaymentReceiveMailOptsDTO
|
||||||
) {
|
): Promise<void> {
|
||||||
const payload = {
|
const payload = {
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
@@ -45,18 +50,21 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the default payment mail options.
|
* Retrieves the default payment mail options.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} invoiceId
|
* @param {number} paymentReceiveId - Payment receive id.
|
||||||
* @returns {Promise<SendInvoiceMailDTO>}
|
* @returns {Promise<PaymentReceiveMailOpts>}
|
||||||
*/
|
*/
|
||||||
public getMailOptions = async (tenantId: number, invoiceId: number) => {
|
public getMailOptions = async (
|
||||||
|
tenantId: number,
|
||||||
|
paymentId: number
|
||||||
|
): Promise<PaymentReceiveMailOpts> => {
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
const { PaymentReceive } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const paymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query()
|
||||||
.findById(invoiceId)
|
.findById(paymentId)
|
||||||
.throwIfNotFound();
|
.throwIfNotFound();
|
||||||
|
|
||||||
const formatterData = await this.textFormatter(tenantId, invoiceId);
|
const formatterData = await this.textFormatter(tenantId, paymentId);
|
||||||
|
|
||||||
return this.contactMailNotification.getMailOptions(
|
return this.contactMailNotification.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -82,12 +90,7 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
);
|
);
|
||||||
const organization = await Tenant.query()
|
|
||||||
.findById(tenantId)
|
|
||||||
.withGraphFetched('metadata');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
CompanyName: organization.metadata.name,
|
|
||||||
CustomerName: payment.customer.displayName,
|
CustomerName: payment.customer.displayName,
|
||||||
PaymentNumber: payment.payment_receive_no,
|
PaymentNumber: payment.payment_receive_no,
|
||||||
PaymentDate: payment.formattedPaymentDate,
|
PaymentDate: payment.formattedPaymentDate,
|
||||||
@@ -112,10 +115,10 @@ export class SendPaymentReceiveMailNotification {
|
|||||||
paymentReceiveId
|
paymentReceiveId
|
||||||
);
|
);
|
||||||
// Parsed message opts with default options.
|
// Parsed message opts with default options.
|
||||||
const parsedMessageOpts = {
|
const parsedMessageOpts = parseAndValidateMailOptions(
|
||||||
...defaultMessageOpts,
|
defaultMessageOpts,
|
||||||
...messageDTO,
|
messageDTO
|
||||||
};
|
);
|
||||||
await new Mail()
|
await new Mail()
|
||||||
.setSubject(parsedMessageOpts.subject)
|
.setSubject(parsedMessageOpts.subject)
|
||||||
.setTo(parsedMessageOpts.to)
|
.setTo(parsedMessageOpts.to)
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import {
|
|||||||
IPaymentReceive,
|
IPaymentReceive,
|
||||||
IPaymentReceiveCreateDTO,
|
IPaymentReceiveCreateDTO,
|
||||||
IPaymentReceiveEditDTO,
|
IPaymentReceiveEditDTO,
|
||||||
IPaymentReceiveMailOpts,
|
|
||||||
IPaymentReceiveSmsDetails,
|
IPaymentReceiveSmsDetails,
|
||||||
IPaymentReceivesFilter,
|
IPaymentReceivesFilter,
|
||||||
ISystemUser,
|
ISystemUser,
|
||||||
|
PaymentReceiveMailOptsDTO,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { CreatePaymentReceive } from './CreatePaymentReceive';
|
import { CreatePaymentReceive } from './CreatePaymentReceive';
|
||||||
@@ -189,8 +189,8 @@ export class PaymentReceivesApplication {
|
|||||||
public notifyPaymentByMail(
|
public notifyPaymentByMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
paymentReceiveId: number,
|
paymentReceiveId: number,
|
||||||
messageOpts: IPaymentReceiveMailOpts
|
messageOpts: PaymentReceiveMailOptsDTO
|
||||||
) {
|
): Promise<void> {
|
||||||
return this.paymentMailNotify.triggerMail(
|
return this.paymentMailNotify.triggerMail(
|
||||||
tenantId,
|
tenantId,
|
||||||
paymentReceiveId,
|
paymentReceiveId,
|
||||||
@@ -204,7 +204,7 @@ export class PaymentReceivesApplication {
|
|||||||
* @param {number} paymentReceiveId
|
* @param {number} paymentReceiveId
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public getPaymentDefaultMail(tenantId: number, paymentReceiveId: number) {
|
public getPaymentMailOptions(tenantId: number, paymentReceiveId: number) {
|
||||||
return this.paymentMailNotify.getMailOptions(tenantId, paymentReceiveId);
|
return this.paymentMailNotify.getMailOptions(tenantId, paymentReceiveId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
ISaleReceipt,
|
ISaleReceipt,
|
||||||
ISalesReceiptsFilter,
|
ISalesReceiptsFilter,
|
||||||
SaleReceiptMailOpts,
|
SaleReceiptMailOpts,
|
||||||
|
SaleReceiptMailOptsDTO,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { EditSaleReceipt } from './EditSaleReceipt';
|
import { EditSaleReceipt } from './EditSaleReceipt';
|
||||||
import { GetSaleReceipt } from './GetSaleReceipt';
|
import { GetSaleReceipt } from './GetSaleReceipt';
|
||||||
@@ -176,12 +177,13 @@ export class SaleReceiptApplication {
|
|||||||
* Sends the receipt mail of the given sale receipt.
|
* Sends the receipt mail of the given sale receipt.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleReceiptId
|
* @param {number} saleReceiptId
|
||||||
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public sendSaleReceiptMail(
|
public sendSaleReceiptMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleReceiptId: number,
|
saleReceiptId: number,
|
||||||
messageOpts: SaleReceiptMailOpts
|
messageOpts: SaleReceiptMailOptsDTO
|
||||||
) {
|
): Promise<void> {
|
||||||
return this.saleReceiptNotifyByMailService.triggerMail(
|
return this.saleReceiptNotifyByMailService.triggerMail(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId,
|
saleReceiptId,
|
||||||
@@ -193,9 +195,12 @@ export class SaleReceiptApplication {
|
|||||||
* Retrieves the default mail options of the given sale receipt.
|
* Retrieves the default mail options of the given sale receipt.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleReceiptId
|
* @param {number} saleReceiptId
|
||||||
* @returns
|
* @returns {Promise<SaleReceiptMailOpts>}
|
||||||
*/
|
*/
|
||||||
public getSaleReceiptMail(tenantId: number, saleReceiptId: number) {
|
public getSaleReceiptMail(
|
||||||
|
tenantId: number,
|
||||||
|
saleReceiptId: number
|
||||||
|
): Promise<SaleReceiptMailOpts> {
|
||||||
return this.saleReceiptNotifyByMailService.getMailOptions(
|
return this.saleReceiptNotifyByMailService.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId
|
saleReceiptId
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import * as R from 'ramda';
|
|
||||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import { Tenant } from '@/system/models';
|
|
||||||
import Mail from '@/lib/Mail';
|
import Mail from '@/lib/Mail';
|
||||||
import { GetSaleReceipt } from './GetSaleReceipt';
|
import { GetSaleReceipt } from './GetSaleReceipt';
|
||||||
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
|
import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
|
||||||
@@ -9,8 +7,9 @@ import {
|
|||||||
DEFAULT_RECEIPT_MAIL_CONTENT,
|
DEFAULT_RECEIPT_MAIL_CONTENT,
|
||||||
DEFAULT_RECEIPT_MAIL_SUBJECT,
|
DEFAULT_RECEIPT_MAIL_SUBJECT,
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { SaleReceiptMailOpts } from '@/interfaces';
|
import { SaleReceiptMailOpts, SaleReceiptMailOptsDTO } from '@/interfaces';
|
||||||
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
|
||||||
|
import { parseAndValidateMailOptions } from '@/services/MailNotification/utils';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleReceiptMailNotification {
|
export class SaleReceiptMailNotification {
|
||||||
@@ -32,13 +31,13 @@ export class SaleReceiptMailNotification {
|
|||||||
/**
|
/**
|
||||||
* Sends the receipt mail of the given sale receipt.
|
* Sends the receipt mail of the given sale receipt.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleInvoiceId
|
* @param {number} saleReceiptId
|
||||||
* @param {SendInvoiceMailDTO} messageDTO
|
* @param {SaleReceiptMailOptsDTO} messageDTO
|
||||||
*/
|
*/
|
||||||
public async triggerMail(
|
public async triggerMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleReceiptId: number,
|
saleReceiptId: number,
|
||||||
messageOpts: SaleReceiptMailOpts
|
messageOpts: SaleReceiptMailOptsDTO
|
||||||
) {
|
) {
|
||||||
const payload = {
|
const payload = {
|
||||||
tenantId,
|
tenantId,
|
||||||
@@ -52,9 +51,12 @@ export class SaleReceiptMailNotification {
|
|||||||
* Retrieves the mail options of the given sale receipt.
|
* Retrieves the mail options of the given sale receipt.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleReceiptId
|
* @param {number} saleReceiptId
|
||||||
* @returns
|
* @returns {Promise<SaleReceiptMailOptsDTO>}
|
||||||
*/
|
*/
|
||||||
public async getMailOptions(tenantId: number, saleReceiptId: number) {
|
public async getMailOptions(
|
||||||
|
tenantId: number,
|
||||||
|
saleReceiptId: number
|
||||||
|
): Promise<SaleReceiptMailOpts> {
|
||||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const saleReceipt = await SaleReceipt.query()
|
const saleReceipt = await SaleReceipt.query()
|
||||||
@@ -63,17 +65,21 @@ export class SaleReceiptMailNotification {
|
|||||||
|
|
||||||
const formattedData = await this.textFormatter(tenantId, saleReceiptId);
|
const formattedData = await this.textFormatter(tenantId, saleReceiptId);
|
||||||
|
|
||||||
return this.contactMailNotification.getMailOptions(
|
const mailOpts = await this.contactMailNotification.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceipt.customerId,
|
saleReceipt.customerId,
|
||||||
DEFAULT_RECEIPT_MAIL_SUBJECT,
|
DEFAULT_RECEIPT_MAIL_SUBJECT,
|
||||||
DEFAULT_RECEIPT_MAIL_CONTENT,
|
DEFAULT_RECEIPT_MAIL_CONTENT,
|
||||||
formattedData
|
formattedData
|
||||||
);
|
);
|
||||||
|
return {
|
||||||
|
...mailOpts,
|
||||||
|
attachReceipt: true,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the formatted text of the given sale invoice.
|
* Retrieves the formatted text of the given sale receipt.
|
||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} receiptId - Sale receipt id.
|
* @param {number} receiptId - Sale receipt id.
|
||||||
* @param {string} text - The given text.
|
* @param {string} text - The given text.
|
||||||
@@ -83,58 +89,52 @@ export class SaleReceiptMailNotification {
|
|||||||
tenantId: number,
|
tenantId: number,
|
||||||
receiptId: number
|
receiptId: number
|
||||||
): Promise<Record<string, string>> => {
|
): Promise<Record<string, string>> => {
|
||||||
const invoice = await this.getSaleReceiptService.getSaleReceipt(
|
const receipt = await this.getSaleReceiptService.getSaleReceipt(
|
||||||
tenantId,
|
tenantId,
|
||||||
receiptId
|
receiptId
|
||||||
);
|
);
|
||||||
const organization = await Tenant.query()
|
|
||||||
.findById(tenantId)
|
|
||||||
.withGraphFetched('metadata');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
CompanyName: organization.metadata.name,
|
CustomerName: receipt.customer.displayName,
|
||||||
CustomerName: invoice.customer.displayName,
|
ReceiptNumber: receipt.receiptNumber,
|
||||||
ReceiptNumber: invoice.receiptNumber,
|
ReceiptDate: receipt.formattedReceiptDate,
|
||||||
ReceiptDate: invoice.formattedReceiptDate,
|
ReceiptAmount: receipt.formattedAmount,
|
||||||
ReceiptAmount: invoice.formattedAmount,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the mail invoice.
|
* Triggers the mail notification of the given sale receipt.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {number} saleInvoiceId
|
* @param {number} saleReceiptId - Sale receipt id.
|
||||||
* @param {SendInvoiceMailDTO} messageDTO
|
* @param {SaleReceiptMailOpts} messageDTO - Overrided message options.
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
public async sendMail(
|
public async sendMail(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
saleReceiptId: number,
|
saleReceiptId: number,
|
||||||
messageOpts: SaleReceiptMailOpts
|
messageOpts: SaleReceiptMailOptsDTO
|
||||||
) {
|
) {
|
||||||
const defaultMessageOpts = await this.getMailOptions(
|
const defaultMessageOpts = await this.getMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId
|
saleReceiptId
|
||||||
);
|
);
|
||||||
// Parsed message opts with default options.
|
// Merges message opts with default options.
|
||||||
const parsedMessageOpts = {
|
const parsedMessageOpts = parseAndValidateMailOptions(
|
||||||
...defaultMessageOpts,
|
defaultMessageOpts,
|
||||||
...messageOpts,
|
messageOpts
|
||||||
};
|
);
|
||||||
|
|
||||||
const mail = new Mail()
|
const mail = new Mail()
|
||||||
.setSubject(parsedMessageOpts.subject)
|
.setSubject(parsedMessageOpts.subject)
|
||||||
.setTo(parsedMessageOpts.to)
|
.setTo(parsedMessageOpts.to)
|
||||||
.setContent(parsedMessageOpts.body);
|
.setContent(parsedMessageOpts.body);
|
||||||
|
|
||||||
if (parsedMessageOpts.attachInvoice) {
|
if (parsedMessageOpts.attachReceipt) {
|
||||||
// Retrieves document buffer of the invoice pdf document.
|
// Retrieves document buffer of the receipt pdf document.
|
||||||
const receiptPdfBuffer = await this.receiptPdfService.saleReceiptPdf(
|
const receiptPdfBuffer = await this.receiptPdfService.saleReceiptPdf(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleReceiptId
|
saleReceiptId
|
||||||
);
|
);
|
||||||
mail.setAttachments([
|
mail.setAttachments([
|
||||||
{ filename: 'invoice.pdf', content: receiptPdfBuffer },
|
{ filename: 'receipt.pdf', content: receiptPdfBuffer },
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await mail.send();
|
await mail.send();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const DEFAULT_RECEIPT_MAIL_SUBJECT =
|
export const DEFAULT_RECEIPT_MAIL_SUBJECT =
|
||||||
'Invoice {InvoiceNumber} from {CompanyName}';
|
'Receipt {ReceiptNumber} from {CompanyName}';
|
||||||
export const DEFAULT_RECEIPT_MAIL_CONTENT = `
|
export const DEFAULT_RECEIPT_MAIL_CONTENT = `
|
||||||
<p>Dear {CustomerName}</p>
|
<p>Dear {CustomerName}</p>
|
||||||
<p>Thank you for your business, You can view or print your receipt from attachements.</p>
|
<p>Thank you for your business, You can view or print your receipt from attachements.</p>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ function PaymentMailDialogBoot({
|
|||||||
const provider = {
|
const provider = {
|
||||||
mailOptions,
|
mailOptions,
|
||||||
isMailOptionsLoading,
|
isMailOptionsLoading,
|
||||||
|
paymentReceiveId
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function PaymentMailDialogFormRoot({
|
|||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
const { mailOptions, paymentId } = usePaymentMailDialogBoot();
|
const { mailOptions, paymentReceiveId } = usePaymentMailDialogBoot();
|
||||||
const { mutateAsync: sendPaymentMail } = useSendPaymentReceiveMail();
|
const { mutateAsync: sendPaymentMail } = useSendPaymentReceiveMail();
|
||||||
|
|
||||||
const initialValues = transformMailFormToInitialValues(
|
const initialValues = transformMailFormToInitialValues(
|
||||||
@@ -43,7 +43,7 @@ export function PaymentMailDialogFormRoot({
|
|||||||
const reqValues = transformMailFormToRequest(values);
|
const reqValues = transformMailFormToRequest(values);
|
||||||
|
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
sendPaymentMail([paymentId, reqValues])
|
sendPaymentMail([paymentReceiveId, reqValues])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'The mail notification has been sent successfully.',
|
message: 'The mail notification has been sent successfully.',
|
||||||
|
|||||||
Reference in New Issue
Block a user