mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 06:10:31 +00:00
feat: receipt mail preview
This commit is contained in:
@@ -524,7 +524,7 @@ export default class SalesReceiptsController extends BaseController {
|
||||
const { id: receiptId } = req.params;
|
||||
|
||||
try {
|
||||
const data = await this.saleReceiptsApplication.getSaleReceiptMail(
|
||||
const data = await this.saleReceiptsApplication.getSaleReceiptMailState(
|
||||
tenantId,
|
||||
receiptId
|
||||
);
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { Inject, Service } from 'typedi';
|
||||
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
|
||||
import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
import { SaleReceiptMailNotification } from './SaleReceiptMailNotification';
|
||||
import { GetSaleReceiptMailStateTransformer } from './GetSaleReceiptMailStateTransformer';
|
||||
|
||||
@Service()
|
||||
export class GetSaleReceiptMailState {
|
||||
@Inject()
|
||||
private tenancy: HasTenancyService;
|
||||
|
||||
@Inject()
|
||||
private transformer: TransformerInjectable;
|
||||
|
||||
@Inject()
|
||||
private receiptMail: SaleReceiptMailNotification;
|
||||
|
||||
/**
|
||||
* Retrieves the sale receipt mail state of the given sale receipt.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
*/
|
||||
public async getMailState(tenantId: number, saleReceiptId: number) {
|
||||
const { SaleReceipt } = this.tenancy.models(tenantId);
|
||||
|
||||
const saleReceipt = await SaleReceipt.query()
|
||||
.findById(saleReceiptId)
|
||||
.withGraphFetched('entries.item')
|
||||
.withGraphFetched('customer')
|
||||
.throwIfNotFound();
|
||||
|
||||
const mailOptions = await this.receiptMail.getMailOptions(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
return this.transformer.transform(
|
||||
tenantId,
|
||||
saleReceipt,
|
||||
new GetSaleReceiptMailStateTransformer(),
|
||||
{
|
||||
mailOptions,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
import { Transformer } from '@/lib/Transformer/Transformer';
|
||||
import { ItemEntryTransformer } from '../Invoices/ItemEntryTransformer';
|
||||
|
||||
export class GetSaleReceiptMailStateTransformer extends Transformer {
|
||||
/**
|
||||
* Exclude these attributes from user object.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public excludeAttributes = (): string[] => {
|
||||
return ['*'];
|
||||
};
|
||||
|
||||
/**
|
||||
* Included attributes.
|
||||
* @returns {Array}
|
||||
*/
|
||||
public includeAttributes = (): string[] => {
|
||||
return [
|
||||
'companyName',
|
||||
'companyLogoUri',
|
||||
'primaryColor',
|
||||
'customerName',
|
||||
'total',
|
||||
'totalFormatted',
|
||||
'subtotal',
|
||||
'subtotalFormatted',
|
||||
'receiptDate',
|
||||
'receiptDateFormatted',
|
||||
'closedAtDate',
|
||||
'closedAtDateFormatted',
|
||||
'receiptNumber',
|
||||
'entries',
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the customer name of the invoice.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected customerName = (receipt) => {
|
||||
return receipt.customer.displayName;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the company name.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected companyName = () => {
|
||||
return this.context.organization.name;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the company logo uri.
|
||||
* @returns {string | null}
|
||||
*/
|
||||
protected companyLogoUri = (receipt) => {
|
||||
return receipt.pdfTemplate?.companyLogoUri;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the primary color.
|
||||
* @returns {string}
|
||||
*/
|
||||
protected primaryColor = (receipt) => {
|
||||
return receipt.pdfTemplate?.attributes?.primaryColor;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param receipt
|
||||
* @returns
|
||||
*/
|
||||
protected total = (receipt) => {
|
||||
return receipt.amount;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param receipt
|
||||
* @returns
|
||||
*/
|
||||
protected totalFormatted = (receipt) => {
|
||||
return this.formatMoney(receipt.amount, {
|
||||
currencyCode: receipt.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param receipt
|
||||
* @returns
|
||||
*/
|
||||
protected subtotal = (receipt) => {
|
||||
return receipt.amount;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param receipt
|
||||
* @returns
|
||||
*/
|
||||
protected subtotalFormatted = (receipt) => {
|
||||
return this.formatMoney(receipt.amount, {
|
||||
currencyCode: receipt.currencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param receipt
|
||||
* @returns
|
||||
*/
|
||||
protected receiptDate = (receipt): string => {
|
||||
return receipt.receiptDate;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {ISaleReceipt} invoice
|
||||
* @returns {string}
|
||||
*/
|
||||
protected receiptDateFormatted = (receipt): string => {
|
||||
return this.formatDate(receipt.receiptDate);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param receipt
|
||||
* @returns
|
||||
*/
|
||||
protected closedAtDate = (receipt): string => {
|
||||
return receipt.closedAt;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve formatted estimate closed at date.
|
||||
* @param {ISaleReceipt} invoice
|
||||
* @returns {String}
|
||||
*/
|
||||
protected closedAtDateFormatted = (receipt): string => {
|
||||
return this.formatDate(receipt.closedAt);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param invoice
|
||||
* @returns
|
||||
*/
|
||||
protected entries = (receipt) => {
|
||||
return this.item(
|
||||
receipt.entries,
|
||||
new GetSaleReceiptEntryMailStateTransformer(),
|
||||
{
|
||||
currencyCode: receipt.currencyCode,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Merges the mail options with the invoice object.
|
||||
*/
|
||||
public transform = (object: any) => {
|
||||
return {
|
||||
...this.options.mailOptions,
|
||||
...object,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
class GetSaleReceiptEntryMailStateTransformer 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',
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import { SaleReceiptsPdf } from './SaleReceiptsPdfService';
|
||||
import { SaleReceiptNotifyBySms } from './SaleReceiptNotifyBySms';
|
||||
import { SaleReceiptMailNotification } from './SaleReceiptMailNotification';
|
||||
import { GetSaleReceiptState } from './GetSaleReceiptState';
|
||||
import { GetSaleReceiptMailState } from './GetSaleReceiptMailState';
|
||||
|
||||
@Service()
|
||||
export class SaleReceiptApplication {
|
||||
@@ -51,6 +52,9 @@ export class SaleReceiptApplication {
|
||||
@Inject()
|
||||
private getSaleReceiptStateService: GetSaleReceiptState;
|
||||
|
||||
@Inject()
|
||||
private getSaleReceiptMailStateService: GetSaleReceiptMailState;
|
||||
|
||||
/**
|
||||
* Creates a new sale receipt with associated entries.
|
||||
* @param {number} tenantId
|
||||
@@ -234,4 +238,20 @@ export class SaleReceiptApplication {
|
||||
public getSaleReceiptState(tenantId: number): Promise<ISaleReceiptState> {
|
||||
return this.getSaleReceiptStateService.getSaleReceiptState(tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the mail state of the given sale receipt.
|
||||
* @param {number} tenantId
|
||||
* @param {number} saleReceiptId
|
||||
* @returns
|
||||
*/
|
||||
public getSaleReceiptMailState(
|
||||
tenantId: number,
|
||||
saleReceiptId: number
|
||||
): Promise<ISaleReceiptState> {
|
||||
return this.getSaleReceiptMailStateService.getMailState(
|
||||
tenantId,
|
||||
saleReceiptId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,6 @@ export interface ReceiptSendMailReceiptProps extends SendMailReceiptProps {
|
||||
// # Subtotal
|
||||
subtotal: string;
|
||||
subtotalLabel?: string;
|
||||
|
||||
// # View receipt button
|
||||
showViewReceiptButton?: boolean;
|
||||
viewReceiptButtonLabel?: string;
|
||||
viewReceiptButtonOnClick?: () => void;
|
||||
}
|
||||
|
||||
export function ReceiptSendMailReceipt({
|
||||
@@ -55,11 +50,6 @@ export function ReceiptSendMailReceipt({
|
||||
subtotal,
|
||||
subtotalLabel = 'Subtotal',
|
||||
|
||||
// # View receipt button
|
||||
showViewReceiptButton,
|
||||
viewReceiptButtonLabel,
|
||||
viewReceiptButtonOnClick,
|
||||
|
||||
...rest
|
||||
}: ReceiptSendMailReceiptProps) {
|
||||
return (
|
||||
@@ -80,62 +70,57 @@ export function ReceiptSendMailReceipt({
|
||||
{receiptNumberLabel} {receiptNumber}
|
||||
</x.span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
{showViewReceiptButton && (
|
||||
<SendMailReceipt.PrimaryButton
|
||||
primaryColor={'#000'}
|
||||
onClick={viewReceiptButtonOnClick}
|
||||
>
|
||||
{viewReceiptButtonLabel}
|
||||
</SendMailReceipt.PrimaryButton>
|
||||
)}
|
||||
|
||||
<Stack spacing={0}>
|
||||
{items?.map((item, key) => (
|
||||
<Group
|
||||
key={key}
|
||||
h={'40px'}
|
||||
position={'apart'}
|
||||
borderBottomStyle="solid"
|
||||
borderBottomWidth={'1px'}
|
||||
borderBottomColor={'#D9D9D9'}
|
||||
borderTopStyle="solid"
|
||||
borderTopColor={'#D9D9D9'}
|
||||
borderTopWidth={'1px'}
|
||||
>
|
||||
<x.span>{item.label}</x.span>
|
||||
<x.span>
|
||||
{item.quantity} x {item.total}
|
||||
</x.span>
|
||||
</Group>
|
||||
))}
|
||||
<x.p m={0} whiteSpace={'pre-line'} color="#252A31">
|
||||
{message}
|
||||
</x.p>
|
||||
|
||||
<Stack spacing={0}>
|
||||
{items?.map((item, key) => (
|
||||
<Group
|
||||
key={key}
|
||||
h={'40px'}
|
||||
position={'apart'}
|
||||
borderBottomStyle="solid"
|
||||
borderBottomWidth={'1px'}
|
||||
borderBottomColor={'#000'}
|
||||
borderBottomColor={'#D9D9D9'}
|
||||
borderTopStyle="solid"
|
||||
borderTopColor={'#D9D9D9'}
|
||||
borderTopWidth={'1px'}
|
||||
>
|
||||
<x.span fontWeight={500}>{subtotalLabel}</x.span>
|
||||
<x.span fontWeight={600} fontSize={15}>
|
||||
{subtotal}
|
||||
<x.span>{item.label}</x.span>
|
||||
<x.span>
|
||||
{item.quantity} x {item.total}
|
||||
</x.span>
|
||||
</Group>
|
||||
))}
|
||||
|
||||
<Group
|
||||
h={'40px'}
|
||||
position={'apart'}
|
||||
borderBottomStyle="solid"
|
||||
borderBottomWidth={'1px'}
|
||||
borderColor={'#000'}
|
||||
>
|
||||
<x.span fontWeight={500}>{totalLabel}</x.span>
|
||||
<x.span fontWeight={600} fontSize={15}>
|
||||
{total}
|
||||
</x.span>
|
||||
</Group>
|
||||
</Stack>
|
||||
<Group
|
||||
h={'40px'}
|
||||
position={'apart'}
|
||||
borderBottomStyle="solid"
|
||||
borderBottomWidth={'1px'}
|
||||
borderBottomColor={'#000'}
|
||||
>
|
||||
<x.span fontWeight={500}>{subtotalLabel}</x.span>
|
||||
<x.span fontWeight={600} fontSize={15}>
|
||||
{subtotal}
|
||||
</x.span>
|
||||
</Group>
|
||||
|
||||
<Group
|
||||
h={'40px'}
|
||||
position={'apart'}
|
||||
borderBottomStyle="solid"
|
||||
borderBottomWidth={'1px'}
|
||||
borderColor={'#000'}
|
||||
>
|
||||
<x.span fontWeight={500}>{totalLabel}</x.span>
|
||||
<x.span fontWeight={600} fontSize={15}>
|
||||
{total}
|
||||
</x.span>
|
||||
</Group>
|
||||
</Stack>
|
||||
</SendMailReceipt>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ComponentType } from 'react';
|
||||
import { ComponentType, useMemo } from 'react';
|
||||
import {
|
||||
ReceiptSendMailReceipt,
|
||||
ReceiptSendMailReceiptProps,
|
||||
@@ -19,26 +19,25 @@ export const withReceiptMailReceiptPreviewProps = <
|
||||
const message = useSendReceiptMailMessage();
|
||||
const { receiptMailState } = useReceiptSendMailBoot();
|
||||
|
||||
// const items = useMemo(
|
||||
// () =>
|
||||
// invoiceMailState?.entries?.map((entry: any) => ({
|
||||
// quantity: entry.quantity,
|
||||
// total: entry.totalFormatted,
|
||||
// label: entry.name,
|
||||
// })),
|
||||
// [invoiceMailState?.entries],
|
||||
// );
|
||||
const items = useMemo(
|
||||
() =>
|
||||
receiptMailState?.entries?.map((entry: any) => ({
|
||||
quantity: entry.quantity,
|
||||
total: entry.totalFormatted,
|
||||
label: entry.name,
|
||||
})),
|
||||
[receiptMailState?.entries],
|
||||
);
|
||||
|
||||
const mailReceiptPreviewProps = {
|
||||
...defaultReceiptMailProps,
|
||||
// companyName: receiptMailState?.companyName,
|
||||
// companyLogoUri: receiptMailState?.companyLogoUri,
|
||||
// primaryColor: receiptMailState?.primaryColor,
|
||||
// total: receiptMailState?.totalFormatted,
|
||||
// dueDate: receiptMailState?.dueDateFormatted,
|
||||
// dueAmount: invoiceMailState?.dueAmountFormatted,
|
||||
// invoiceNumber: invoiceMailState?.invoiceNo,
|
||||
// items,
|
||||
companyName: receiptMailState?.companyName,
|
||||
companyLogoUri: receiptMailState?.companyLogoUri,
|
||||
primaryColor: receiptMailState?.primaryColor,
|
||||
total: receiptMailState?.totalFormatted,
|
||||
subtotal: receiptMailState?.subtotalFormatted,
|
||||
receiptNumber: receiptMailState?.receiptNumber,
|
||||
items,
|
||||
message,
|
||||
};
|
||||
return <WrappedComponent {...mailReceiptPreviewProps} {...props} />;
|
||||
|
||||
@@ -239,13 +239,46 @@ export function useSendSaleReceiptMail(props) {
|
||||
|
||||
export interface GetSaleReceiptMailStateResponse {
|
||||
attachReceipt: boolean;
|
||||
|
||||
closedAtDate: string;
|
||||
closedAtDateFormatted: string;
|
||||
|
||||
companyName: string;
|
||||
customerName: string;
|
||||
|
||||
formatArgs: Record<string, any>;
|
||||
|
||||
from: string[];
|
||||
fromOptions: Array<{ mail: string; label: string; primary: boolean; }>
|
||||
fromOptions: Array<{ mail: string; label: string; primary: boolean; }>;
|
||||
message: string;
|
||||
|
||||
receiptDate: string;
|
||||
receiptDateFormatted: string;
|
||||
|
||||
subject: string;
|
||||
|
||||
subtotal: number;
|
||||
subtotalFormatted: string;
|
||||
|
||||
to: string[];
|
||||
toOptions: Array<{ mail: string; label: string; primary: boolean; }>;
|
||||
|
||||
total: number;
|
||||
totalFormatted: string;
|
||||
|
||||
companyLogoUri?: string | null;
|
||||
primaryColor?: string | null;
|
||||
|
||||
entries: Array<{
|
||||
name: string;
|
||||
quantity: number;
|
||||
quantityFormatted: string;
|
||||
rate: number;
|
||||
rateFormatted: string;
|
||||
total: number;
|
||||
totalFormatted: string
|
||||
}>,
|
||||
receiptNumber: string;
|
||||
}
|
||||
|
||||
export function useSaleReceiptMailState(
|
||||
|
||||
Reference in New Issue
Block a user