mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +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;
|
const { id: receiptId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.saleReceiptsApplication.getSaleReceiptMail(
|
const data = await this.saleReceiptsApplication.getSaleReceiptMailState(
|
||||||
tenantId,
|
tenantId,
|
||||||
receiptId
|
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 { SaleReceiptNotifyBySms } from './SaleReceiptNotifyBySms';
|
||||||
import { SaleReceiptMailNotification } from './SaleReceiptMailNotification';
|
import { SaleReceiptMailNotification } from './SaleReceiptMailNotification';
|
||||||
import { GetSaleReceiptState } from './GetSaleReceiptState';
|
import { GetSaleReceiptState } from './GetSaleReceiptState';
|
||||||
|
import { GetSaleReceiptMailState } from './GetSaleReceiptMailState';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleReceiptApplication {
|
export class SaleReceiptApplication {
|
||||||
@@ -51,6 +52,9 @@ export class SaleReceiptApplication {
|
|||||||
@Inject()
|
@Inject()
|
||||||
private getSaleReceiptStateService: GetSaleReceiptState;
|
private getSaleReceiptStateService: GetSaleReceiptState;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
private getSaleReceiptMailStateService: GetSaleReceiptMailState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new sale receipt with associated entries.
|
* Creates a new sale receipt with associated entries.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -234,4 +238,20 @@ export class SaleReceiptApplication {
|
|||||||
public getSaleReceiptState(tenantId: number): Promise<ISaleReceiptState> {
|
public getSaleReceiptState(tenantId: number): Promise<ISaleReceiptState> {
|
||||||
return this.getSaleReceiptStateService.getSaleReceiptState(tenantId);
|
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
|
||||||
subtotal: string;
|
subtotal: string;
|
||||||
subtotalLabel?: string;
|
subtotalLabel?: string;
|
||||||
|
|
||||||
// # View receipt button
|
|
||||||
showViewReceiptButton?: boolean;
|
|
||||||
viewReceiptButtonLabel?: string;
|
|
||||||
viewReceiptButtonOnClick?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ReceiptSendMailReceipt({
|
export function ReceiptSendMailReceipt({
|
||||||
@@ -55,11 +50,6 @@ export function ReceiptSendMailReceipt({
|
|||||||
subtotal,
|
subtotal,
|
||||||
subtotalLabel = 'Subtotal',
|
subtotalLabel = 'Subtotal',
|
||||||
|
|
||||||
// # View receipt button
|
|
||||||
showViewReceiptButton,
|
|
||||||
viewReceiptButtonLabel,
|
|
||||||
viewReceiptButtonOnClick,
|
|
||||||
|
|
||||||
...rest
|
...rest
|
||||||
}: ReceiptSendMailReceiptProps) {
|
}: ReceiptSendMailReceiptProps) {
|
||||||
return (
|
return (
|
||||||
@@ -80,62 +70,57 @@ export function ReceiptSendMailReceipt({
|
|||||||
{receiptNumberLabel} {receiptNumber}
|
{receiptNumberLabel} {receiptNumber}
|
||||||
</x.span>
|
</x.span>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
{showViewReceiptButton && (
|
<x.p m={0} whiteSpace={'pre-line'} color="#252A31">
|
||||||
<SendMailReceipt.PrimaryButton
|
{message}
|
||||||
primaryColor={'#000'}
|
</x.p>
|
||||||
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>
|
|
||||||
))}
|
|
||||||
|
|
||||||
|
<Stack spacing={0}>
|
||||||
|
{items?.map((item, key) => (
|
||||||
<Group
|
<Group
|
||||||
|
key={key}
|
||||||
h={'40px'}
|
h={'40px'}
|
||||||
position={'apart'}
|
position={'apart'}
|
||||||
borderBottomStyle="solid"
|
borderBottomStyle="solid"
|
||||||
borderBottomWidth={'1px'}
|
borderBottomWidth={'1px'}
|
||||||
borderBottomColor={'#000'}
|
borderBottomColor={'#D9D9D9'}
|
||||||
|
borderTopStyle="solid"
|
||||||
|
borderTopColor={'#D9D9D9'}
|
||||||
|
borderTopWidth={'1px'}
|
||||||
>
|
>
|
||||||
<x.span fontWeight={500}>{subtotalLabel}</x.span>
|
<x.span>{item.label}</x.span>
|
||||||
<x.span fontWeight={600} fontSize={15}>
|
<x.span>
|
||||||
{subtotal}
|
{item.quantity} x {item.total}
|
||||||
</x.span>
|
</x.span>
|
||||||
</Group>
|
</Group>
|
||||||
|
))}
|
||||||
|
|
||||||
<Group
|
<Group
|
||||||
h={'40px'}
|
h={'40px'}
|
||||||
position={'apart'}
|
position={'apart'}
|
||||||
borderBottomStyle="solid"
|
borderBottomStyle="solid"
|
||||||
borderBottomWidth={'1px'}
|
borderBottomWidth={'1px'}
|
||||||
borderColor={'#000'}
|
borderBottomColor={'#000'}
|
||||||
>
|
>
|
||||||
<x.span fontWeight={500}>{totalLabel}</x.span>
|
<x.span fontWeight={500}>{subtotalLabel}</x.span>
|
||||||
<x.span fontWeight={600} fontSize={15}>
|
<x.span fontWeight={600} fontSize={15}>
|
||||||
{total}
|
{subtotal}
|
||||||
</x.span>
|
</x.span>
|
||||||
</Group>
|
</Group>
|
||||||
</Stack>
|
|
||||||
|
<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>
|
</Stack>
|
||||||
</SendMailReceipt>
|
</SendMailReceipt>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ComponentType } from 'react';
|
import { ComponentType, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
ReceiptSendMailReceipt,
|
ReceiptSendMailReceipt,
|
||||||
ReceiptSendMailReceiptProps,
|
ReceiptSendMailReceiptProps,
|
||||||
@@ -19,26 +19,25 @@ export const withReceiptMailReceiptPreviewProps = <
|
|||||||
const message = useSendReceiptMailMessage();
|
const message = useSendReceiptMailMessage();
|
||||||
const { receiptMailState } = useReceiptSendMailBoot();
|
const { receiptMailState } = useReceiptSendMailBoot();
|
||||||
|
|
||||||
// const items = useMemo(
|
const items = useMemo(
|
||||||
// () =>
|
() =>
|
||||||
// invoiceMailState?.entries?.map((entry: any) => ({
|
receiptMailState?.entries?.map((entry: any) => ({
|
||||||
// quantity: entry.quantity,
|
quantity: entry.quantity,
|
||||||
// total: entry.totalFormatted,
|
total: entry.totalFormatted,
|
||||||
// label: entry.name,
|
label: entry.name,
|
||||||
// })),
|
})),
|
||||||
// [invoiceMailState?.entries],
|
[receiptMailState?.entries],
|
||||||
// );
|
);
|
||||||
|
|
||||||
const mailReceiptPreviewProps = {
|
const mailReceiptPreviewProps = {
|
||||||
...defaultReceiptMailProps,
|
...defaultReceiptMailProps,
|
||||||
// companyName: receiptMailState?.companyName,
|
companyName: receiptMailState?.companyName,
|
||||||
// companyLogoUri: receiptMailState?.companyLogoUri,
|
companyLogoUri: receiptMailState?.companyLogoUri,
|
||||||
// primaryColor: receiptMailState?.primaryColor,
|
primaryColor: receiptMailState?.primaryColor,
|
||||||
// total: receiptMailState?.totalFormatted,
|
total: receiptMailState?.totalFormatted,
|
||||||
// dueDate: receiptMailState?.dueDateFormatted,
|
subtotal: receiptMailState?.subtotalFormatted,
|
||||||
// dueAmount: invoiceMailState?.dueAmountFormatted,
|
receiptNumber: receiptMailState?.receiptNumber,
|
||||||
// invoiceNumber: invoiceMailState?.invoiceNo,
|
items,
|
||||||
// items,
|
|
||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
return <WrappedComponent {...mailReceiptPreviewProps} {...props} />;
|
return <WrappedComponent {...mailReceiptPreviewProps} {...props} />;
|
||||||
|
|||||||
@@ -239,13 +239,46 @@ export function useSendSaleReceiptMail(props) {
|
|||||||
|
|
||||||
export interface GetSaleReceiptMailStateResponse {
|
export interface GetSaleReceiptMailStateResponse {
|
||||||
attachReceipt: boolean;
|
attachReceipt: boolean;
|
||||||
|
|
||||||
|
closedAtDate: string;
|
||||||
|
closedAtDateFormatted: string;
|
||||||
|
|
||||||
|
companyName: string;
|
||||||
|
customerName: string;
|
||||||
|
|
||||||
formatArgs: Record<string, any>;
|
formatArgs: Record<string, any>;
|
||||||
|
|
||||||
from: string[];
|
from: string[];
|
||||||
fromOptions: Array<{ mail: string; label: string; primary: boolean; }>
|
fromOptions: Array<{ mail: string; label: string; primary: boolean; }>;
|
||||||
message: string;
|
message: string;
|
||||||
|
|
||||||
|
receiptDate: string;
|
||||||
|
receiptDateFormatted: string;
|
||||||
|
|
||||||
subject: string;
|
subject: string;
|
||||||
|
|
||||||
|
subtotal: number;
|
||||||
|
subtotalFormatted: string;
|
||||||
|
|
||||||
to: string[];
|
to: string[];
|
||||||
toOptions: Array<{ mail: string; label: string; primary: boolean; }>;
|
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(
|
export function useSaleReceiptMailState(
|
||||||
|
|||||||
Reference in New Issue
Block a user