feat: send invoice receipt preview

This commit is contained in:
Ahmed Bouhuolia
2024-10-31 12:40:48 +02:00
parent 470bfd32f7
commit dbbaa387bd
27 changed files with 383 additions and 476 deletions

View File

@@ -179,7 +179,7 @@ export default class SaleInvoicesController extends BaseController {
'/:id/mail',
[
...this.specificSaleInvoiceValidation,
body('subject').isString().optional({ nullable: true }),
body('message').isString().optional({ nullable: true }),
@@ -201,7 +201,7 @@ export default class SaleInvoicesController extends BaseController {
this.handleServiceErrors
);
router.get(
'/:id/mail',
'/:id/mail/state',
[...this.specificSaleInvoiceValidation],
this.validationResult,
asyncMiddleware(this.getSaleInvoiceMail.bind(this)),
@@ -789,7 +789,7 @@ export default class SaleInvoicesController extends BaseController {
}
/**
* Retrieves the default mail options of the given sale invoice.
* Retrieves the mail state of the given sale invoice.
* @param {Request} req
* @param {Response} res
* @param {NextFunction} next
@@ -803,7 +803,7 @@ export default class SaleInvoicesController extends BaseController {
const { id: invoiceId } = req.params;
try {
const data = await this.saleInvoiceApplication.getSaleInvoiceMail(
const data = await this.saleInvoiceApplication.getSaleInvoiceMailState(
tenantId,
invoiceId
);

View File

@@ -238,6 +238,30 @@ export interface SaleInvoiceMailOptions extends CommonMailOptions {
formatArgs?: Record<string, any>;
}
export interface SaleInvoiceMailState extends SaleInvoiceMailOptions {
invoiceNo: string;
invoiceDate: string;
invoiceDateFormatted: string;
dueDate: string;
dueDateFormatted: string;
total: number;
totalFormatted: string;
subtotal: number;
subtotalFormatted: number;
companyName: string;
companyLogoUri: string;
customerName: string;
// # Invoice entries
entries?: Array<{ label: string; total: string; quantity: string | number }>;
}
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
attachInvoice?: boolean;
}

View File

@@ -0,0 +1,54 @@
import { SaleInvoiceMailOptions, SaleInvoiceMailState } from '@/interfaces';
import HasTenancyService from '@/services/Tenancy/TenancyService';
import { Inject } from 'typedi';
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { GetSaleInvoiceMailStateTransformer } from './GetSaleInvoiceMailStateTransformer';
export class GetSaleInvoiceMailState {
@Inject()
private tenancy: HasTenancyService;
@Inject()
private invoiceMail: SendSaleInvoiceMailCommon;
@Inject()
private transformer: TransformerInjectable;
/**
* Retrieves the invoice mail state of the given sale invoice.
* Invoice mail state includes the mail options, branding attributes and the invoice details.
*
* @param {number} tenantId
* @param {number} saleInvoiceId
* @returns {Promise<SaleInvoiceMailState>}
*/
async getInvoiceMailState(
tenantId: number,
saleInvoiceId: number
): Promise<SaleInvoiceMailState> {
const { SaleInvoice } = this.tenancy.models(tenantId);
const saleInvoice = await SaleInvoice.query()
.findById(saleInvoiceId)
.withGraphFetched('customer')
.withGraphFetched('entries.item')
.withGraphFetched('pdfTemplate')
.throwIfNotFound();
const mailOptions = await this.invoiceMail.getInvoiceMailOptions(
tenantId,
saleInvoiceId
);
// Transforms the sale invoice mail state.
const transformed = await this.transformer.transform(
tenantId,
saleInvoice,
new GetSaleInvoiceMailStateTransformer(),
{
mailOptions,
}
);
return transformed;
}
}

View File

@@ -0,0 +1,104 @@
import { Transformer } from '@/lib/Transformer/Transformer';
import { SaleInvoiceTransformer } from './SaleInvoiceTransformer';
import { ItemEntryTransformer } from './ItemEntryTransformer';
export class GetSaleInvoiceMailStateTransformer extends SaleInvoiceTransformer {
/**
* Exclude these attributes from user object.
* @returns {Array}
*/
public excludeAttributes = (): string[] => {
return ['*'];
};
public includeAttributes = (): string[] => {
return [
'invoiceDate',
'invoiceDateFormatted',
'dueDate',
'dueDateFormatted',
'dueAmount',
'dueAmountFormatted',
'total',
'totalFormatted',
'subtotal',
'subtotalFormatted',
'invoiceNo',
'entries',
'companyName',
'companyLogoUri',
'primaryColor',
];
};
protected companyName = () => {
return this.context.organization.name;
};
protected companyLogoUri = (invoice) => {
return invoice.pdfTemplate?.attributes?.companyLogoUri;
};
protected primaryColor = (invoice) => {
return invoice.pdfTemplate?.attributes?.primaryColor;
};
/**
*
* @param invoice
* @returns
*/
protected entries = (invoice) => {
return this.item(
invoice.entries,
new GetSaleInvoiceMailStateEntryTransformer(),
{
currencyCode: invoice.currencyCode,
}
);
};
/**
* Merges the mail options with the invoice object.
*/
public transform = (object: any) => {
return {
...this.options.mailOptions,
...object,
};
};
}
class GetSaleInvoiceMailStateEntryTransformer extends ItemEntryTransformer {
/**
* Exclude these attributes from user object.
* @returns {Array}
*/
public excludeAttributes = (): string[] => {
return ['*'];
};
public name = (entry) => {
return entry.item.name;
};
public includeAttributes = (): string[] => {
return [
'name',
'quantity',
'quantityFormatted',
'rate',
'rateFormatted',
'total',
'totalFormatted',
];
};
}

View File

@@ -11,6 +11,7 @@ import {
ISystemUser,
ITenantUser,
InvoiceNotificationType,
SaleInvoiceMailState,
SendInvoiceMailDTO,
} from '@/interfaces';
import { Inject, Service } from 'typedi';
@@ -29,6 +30,8 @@ import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder';
import { SendSaleInvoiceMail } from './SendSaleInvoiceMail';
import { GetSaleInvoiceMailReminder } from './GetSaleInvoiceMailReminder';
import { GetSaleInvoiceState } from './GetSaleInvoiceState';
import { GetSaleInvoiceBrandTemplate } from './GetSaleInvoiceBrandTemplate';
import { GetSaleInvoiceMailState } from './GetSaleInvoiceMailState';
@Service()
export class SaleInvoiceApplication {
@@ -72,7 +75,7 @@ export class SaleInvoiceApplication {
private sendSaleInvoiceMailService: SendSaleInvoiceMail;
@Inject()
private getSaleInvoiceReminderService: GetSaleInvoiceMailReminder;
private getSaleInvoiceMailStateService: GetSaleInvoiceMailState;
@Inject()
private getSaleInvoiceStateService: GetSaleInvoiceState;
@@ -361,10 +364,10 @@ export class SaleInvoiceApplication {
* Retrieves the default mail options of the given sale invoice.
* @param {number} tenantId
* @param {number} saleInvoiceid
* @returns {Promise<SendInvoiceMailDTO>}
* @returns {Promise<SaleInvoiceMailState>}
*/
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
return this.sendSaleInvoiceMailService.getMailOption(
public getSaleInvoiceMailState(tenantId: number, saleInvoiceid: number) {
return this.getSaleInvoiceMailStateService.getInvoiceMailState(
tenantId,
saleInvoiceid
);

View File

@@ -1,16 +1,8 @@
import { Inject, Service } from 'typedi';
import Mail from '@/lib/Mail';
import {
ISaleInvoiceMailSend,
SaleInvoiceMailOptions,
SendInvoiceMailDTO,
} from '@/interfaces';
import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces';
import { SaleInvoicePdf } from './SaleInvoicePdf';
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
import {
DEFAULT_INVOICE_MAIL_CONTENT,
DEFAULT_INVOICE_MAIL_SUBJECT,
} from './constants';
import {
parseMailOptions,
validateRequiredMailOptions,
@@ -58,26 +50,6 @@ export class SendSaleInvoiceMail {
} as ISaleInvoiceMailSend);
}
/**
* Retrieves the mail options of the given sale invoice.
* @param {number} tenantId
* @param {number} saleInvoiceId
* @returns {Promise<SaleInvoiceMailOptions>}
*/
public async getMailOption(
tenantId: number,
saleInvoiceId: number,
defaultSubject: string = DEFAULT_INVOICE_MAIL_SUBJECT,
defaultMessage: string = DEFAULT_INVOICE_MAIL_CONTENT
): Promise<SaleInvoiceMailOptions> {
return this.invoiceMail.getInvoiceMailOptions(
tenantId,
saleInvoiceId,
defaultSubject,
defaultMessage
);
}
/**
* Triggers the mail invoice.
* @param {number} tenantId
@@ -90,7 +62,7 @@ export class SendSaleInvoiceMail {
saleInvoiceId: number,
messageOptions: SendInvoiceMailDTO
) {
const defaultMessageOptions = await this.getMailOption(
const defaultMessageOptions = await this.invoiceMail.getInvoiceMailOptions(
tenantId,
saleInvoiceId
);