feat: payment received mail receipt

This commit is contained in:
Ahmed Bouhuolia
2024-11-25 11:51:13 +02:00
parent 459bf4cd55
commit df41de7239
12 changed files with 221 additions and 181 deletions

View File

@@ -3,7 +3,6 @@ import HasTenancyService from '@/services/Tenancy/TenancyService';
import { GetPaymentReceivedMailStateTransformer } from './GetPaymentReceivedMailStateTransformer'; import { GetPaymentReceivedMailStateTransformer } from './GetPaymentReceivedMailStateTransformer';
import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable';
import { Inject, Service } from 'typedi'; import { Inject, Service } from 'typedi';
import { ContactMailNotification } from '@/services/MailNotification/ContactMailNotification';
import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification'; import { SendPaymentReceiveMailNotification } from './PaymentReceivedMailNotification';
@Service() @Service()

View File

@@ -82,14 +82,6 @@ export class GetPaymentReceivedMailStateTransformer extends PaymentReceiveTransf
return this.formatDate(payment.paymentDate); return this.formatDate(payment.paymentDate);
}; };
/**
* Retrieves the formatted payment amount.
* @returns {string}
*/
protected paymentAmountFormatted = (payment) => {
return this.formatMoney(payment.paymentAmount);
};
/** /**
* Retrieves the payment amount. * Retrieves the payment amount.
* @param payment * @param payment
@@ -175,7 +167,7 @@ export class GetPaymentReceivedEntryMailState extends PaymentReceivedEntryTransf
}; };
/** /**
* * Retrieves the paid amount.
* @param entry * @param entry
* @returns {string} * @returns {string}
*/ */
@@ -184,7 +176,7 @@ export class GetPaymentReceivedEntryMailState extends PaymentReceivedEntryTransf
}; };
/** /**
* * Retrieves the invoice number.
* @param entry * @param entry
* @returns {string} * @returns {string}
*/ */

View File

@@ -8,7 +8,7 @@ export class GetSaleReceiptState {
private tenancy: HasTenancyService; private tenancy: HasTenancyService;
/** /**
* Retireves the sale receipt state. * Retrieves the sale receipt state.
* @param {Number} tenantId - * @param {Number} tenantId -
* @return {Promise<ISaleReceiptState>} * @return {Promise<ISaleReceiptState>}
*/ */

View File

@@ -1,12 +1,20 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { ComponentType, useMemo } from 'react'; import { ComponentType } from 'react';
import { EstimateSendMailReceipt, EstimateSendMailReceiptProps } from './EstimateSendMailReceipt'; import {
EstimateSendMailReceipt,
EstimateSendMailReceiptProps,
} from './EstimateSendMailReceipt';
import { EstimateSendMailPreviewHeader } from './EstimateSendMailPreviewHeader'; import { EstimateSendMailPreviewHeader } from './EstimateSendMailPreviewHeader';
import { withEstimateMailReceiptPreviewProps } from './withEstimateMailReceiptPreviewProps';
import { Stack } from '@/components'; import { Stack } from '@/components';
import { useEstimateSendMailBoot } from './EstimateSendMailBoot';
import { useSendEstimateMailMessage } from './hooks';
import { defaultEstimateMailReceiptProps } from './_constants';
const estimatePreviewCss = css`
margin: 0 auto;
border-radius: 5px !important;
transform: scale(0.9);
transform-origin: top;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.05) !important;
`;
export const EstimateSendMailReceiptPreview = () => { export const EstimateSendMailReceiptPreview = () => {
return ( return (
@@ -14,57 +22,12 @@ export const EstimateSendMailReceiptPreview = () => {
<EstimateSendMailPreviewHeader /> <EstimateSendMailPreviewHeader />
<Stack px={4} py={6}> <Stack px={4} py={6}>
<EstimateSendMailReceiptConnected <EstimateSendMailReceiptConnected className={estimatePreviewCss} />
className={css`
margin: 0 auto;
border-radius: 5px !important;
transform: scale(0.9);
transform-origin: top;
boxshadow: 0 10px 15px rgba(0, 0, 0, 0.05) !important;
`}
/>
</Stack> </Stack>
</Stack> </Stack>
); );
}; };
/**
* Injects props from estimate mail state into the `EstimateSendMailReceipt` component.
*/
const withEstimateMailReceiptPreviewProps = <
P extends EstimateSendMailReceiptProps,
>(
WrappedComponent: ComponentType<P & EstimateSendMailReceiptProps>,
) => {
return function WithInvoiceMailReceiptPreviewProps(props: P) {
const { estimateMailState } = useEstimateSendMailBoot();
const message = useSendEstimateMailMessage();
const items = useMemo(
() =>
estimateMailState?.entries?.map((entry: any) => ({
quantity: entry.quantity,
total: entry.totalFormatted,
label: entry.name,
})),
[estimateMailState?.entries],
);
const mailReceiptPreviewProps = {
...defaultEstimateMailReceiptProps,
companyName: estimateMailState?.companyName,
companyLogoUri: estimateMailState?.companyLogoUri,
primaryColor: estimateMailState?.primaryColor,
total: estimateMailState?.totalFormatted,
expirationDate: estimateMailState?.expirationDateFormatted,
estimateNumber: estimateMailState?.estimateNumber,
estimateDate: estimateMailState?.estimateDateFormatted,
items,
message
};
return <WrappedComponent {...mailReceiptPreviewProps} {...props} />;
};
};
const EstimateSendMailReceiptConnected = withEstimateMailReceiptPreviewProps( const EstimateSendMailReceiptConnected = withEstimateMailReceiptPreviewProps(
EstimateSendMailReceipt EstimateSendMailReceipt,
); ) as ComponentType<Partial<EstimateSendMailReceiptProps>>;

View File

@@ -0,0 +1,43 @@
import { ComponentType, useMemo } from 'react';
import { defaultEstimateMailReceiptProps } from './_constants';
import { useEstimateSendMailBoot } from './EstimateSendMailBoot';
import { useSendEstimateMailMessage } from './hooks';
import { EstimateSendMailReceiptProps } from './EstimateSendMailReceipt';
/**
* Injects props from estimate mail state into the `EstimateSendMailReceipt` component.
*/
export const withEstimateMailReceiptPreviewProps = <
P extends EstimateSendMailReceiptProps,
>(
WrappedComponent: ComponentType<P & EstimateSendMailReceiptProps>,
) => {
return function WithInvoiceMailReceiptPreviewProps(props: P) {
const { estimateMailState } = useEstimateSendMailBoot();
const message = useSendEstimateMailMessage();
const items = useMemo(
() =>
estimateMailState?.entries?.map((entry: any) => ({
quantity: entry.quantity,
total: entry.totalFormatted,
label: entry.name,
})),
[estimateMailState?.entries],
);
const mailReceiptPreviewProps = {
...defaultEstimateMailReceiptProps,
companyName: estimateMailState?.companyName,
companyLogoUri: estimateMailState?.companyLogoUri,
primaryColor: estimateMailState?.primaryColor,
total: estimateMailState?.totalFormatted,
expirationDate: estimateMailState?.expirationDateFormatted,
estimateNumber: estimateMailState?.estimateNumber,
estimateDate: estimateMailState?.estimateDateFormatted,
items,
message,
};
return <WrappedComponent {...mailReceiptPreviewProps} {...props} />;
};
};

View File

@@ -1,4 +1,3 @@
// @ts-nocheck
import { Button, Intent } from '@blueprintjs/core'; import { Button, Intent } from '@blueprintjs/core';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import { FCheckbox, FFormGroup, FInputGroup, Group, Stack } from '@/components'; import { FCheckbox, FFormGroup, FInputGroup, Group, Stack } from '@/components';

View File

@@ -1,14 +1,20 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { ComponentType, useMemo } from 'react'; import { ComponentType } from 'react';
import { import {
PaymentReceivedMailReceipt, PaymentReceivedMailReceipt,
PaymentReceivedMailReceiptProps, PaymentReceivedMailReceiptProps,
} from './PaymentReceivedMailReceipt'; } from './PaymentReceivedMailReceipt';
import { PaymentReceivedMailPreviewHeader } from './PaymentReceivedMailPreviewHeader'; import { PaymentReceivedMailPreviewHeader } from './PaymentReceivedMailPreviewHeader';
import { withPaymentReceivedMailReceiptPreviewProps } from './withPaymentReceivedMailReceiptPreviewProps';
import { Stack } from '@/components'; import { Stack } from '@/components';
import { useSendPaymentReceivedtMailMessage } from './_hooks';
import { usePaymentReceivedSendMailBoot } from './PaymentReceivedMailBoot'; const mailReceiptCss = css`
import { defaultPaymentReceiptMailProps } from './_constants'; margin: 0 auto;
border-radius: 5px !important;
transform: scale(0.9);
transform-origin: top;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.05) !important;
`;
export function PaymentReceivedMailPreviewReceipt() { export function PaymentReceivedMailPreviewReceipt() {
return ( return (
@@ -17,54 +23,14 @@ export function PaymentReceivedMailPreviewReceipt() {
<Stack px={4} py={6}> <Stack px={4} py={6}>
<PaymentReceivedMailReceiptPreviewConnected <PaymentReceivedMailReceiptPreviewConnected
className={css` className={mailReceiptCss}
margin: 0 auto;
border-radius: 5px !important;
transform: scale(0.9);
transform-origin: top;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.05) !important;
`}
/> />
</Stack> </Stack>
</Stack> </Stack>
); );
} }
/**
* Injects props from invoice mail state into the InvoiceMailReceiptPreview component.
*/
const withPaymentReceivedMailReceiptPreviewProps = <
P extends PaymentReceivedMailReceiptProps,
>(
WrappedComponent: ComponentType<P & PaymentReceivedMailReceiptProps>,
) => {
return function WithInvoiceMailReceiptPreviewProps(props: P) {
const message = useSendPaymentReceivedtMailMessage();
const { paymentReceivedMailState } = usePaymentReceivedSendMailBoot();
const items = useMemo(
() =>
paymentReceivedMailState?.entries?.map((entry: any) => ({
total: entry.paidAmount,
label: entry.invoiceNumber,
})),
[paymentReceivedMailState?.entries],
);
const mailPaymentReceivedPreviewProps = {
...defaultPaymentReceiptMailProps,
companyName: paymentReceivedMailState?.companyName,
companyLogoUri: paymentReceivedMailState?.companyLogoUri,
primaryColor: paymentReceivedMailState?.primaryColor,
total: paymentReceivedMailState?.totalFormatted,
subtotal: paymentReceivedMailState?.subtotalFormatted,
paymentNumber: paymentReceivedMailState?.paymentNumber,
items,
message,
};
return <WrappedComponent {...mailPaymentReceivedPreviewProps} {...props} />;
};
};
export const PaymentReceivedMailReceiptPreviewConnected = export const PaymentReceivedMailReceiptPreviewConnected =
withPaymentReceivedMailReceiptPreviewProps(PaymentReceivedMailReceipt); withPaymentReceivedMailReceiptPreviewProps(
PaymentReceivedMailReceipt,
) as ComponentType<Partial<PaymentReceivedMailReceiptProps>>;

View File

@@ -0,0 +1,41 @@
import { ComponentType, useMemo } from 'react';
import { PaymentReceivedMailReceiptProps } from './PaymentReceivedMailReceipt';
import { useSendPaymentReceivedtMailMessage } from './_hooks';
import { usePaymentReceivedSendMailBoot } from './PaymentReceivedMailBoot';
import { defaultPaymentReceiptMailProps } from './_constants';
/**
* Injects props from invoice mail state into the InvoiceMailReceiptPreview component.
*/
export const withPaymentReceivedMailReceiptPreviewProps = <
P extends PaymentReceivedMailReceiptProps,
>(
WrappedComponent: ComponentType<P & PaymentReceivedMailReceiptProps>,
) => {
return function WithInvoiceMailReceiptPreviewProps(props: P) {
const message = useSendPaymentReceivedtMailMessage();
const { paymentReceivedMailState } = usePaymentReceivedSendMailBoot();
const items = useMemo(
() =>
paymentReceivedMailState?.entries?.map((entry: any) => ({
total: entry.paidAmount,
label: entry.invoiceNumber,
})),
[paymentReceivedMailState?.entries],
);
const mailPaymentReceivedPreviewProps = {
...defaultPaymentReceiptMailProps,
companyName: paymentReceivedMailState?.companyName,
companyLogoUri: paymentReceivedMailState?.companyLogoUri,
primaryColor: paymentReceivedMailState?.primaryColor,
total: paymentReceivedMailState?.totalFormatted,
subtotal: paymentReceivedMailState?.subtotalFormatted,
paymentNumber: paymentReceivedMailState?.paymentNumber,
items,
message,
};
return <WrappedComponent {...mailPaymentReceivedPreviewProps} {...props} />;
};
};

View File

@@ -1,14 +1,15 @@
import { ComponentType } from 'react';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { Stack } from '@/components'; import { Stack } from '@/components';
import { ReceiptSendMailPreviewHeader } from './ReceiptSendMailPreviewHeader'; import { ReceiptSendMailPreviewHeader } from './ReceiptSendMailPreviewHeader';
import { import { ReceiptMailPreviewConnected } from './withReceiptMailReceiptPreviewProps';
ReceiptSendMailReceipt,
ReceiptSendMailReceiptProps, const receiptPreviewCss = css`
} from './ReceiptSendMailReceipt'; margin: 0 auto;
import { defaultReceiptMailProps } from './_constants'; border-radius: 5px !important;
import { useReceiptSendMailBoot } from './ReceiptSendMailBoot'; transform: scale(0.9);
import { useSendReceiptMailMessage } from './_hooks'; transform-origin: top;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.05) !important;
`;
export function ReceiptSendMailPreview() { export function ReceiptSendMailPreview() {
return ( return (
@@ -16,58 +17,8 @@ export function ReceiptSendMailPreview() {
<ReceiptSendMailPreviewHeader /> <ReceiptSendMailPreviewHeader />
<Stack px={4} py={6}> <Stack px={4} py={6}>
<ReceiptMailPreviewConnected <ReceiptMailPreviewConnected className={receiptPreviewCss} />
className={css`
margin: 0 auto;
border-radius: 5px !important;
transform: scale(0.9);
transform-origin: top;
box-shadow: 0 10px 15px rgba(0, 0, 0, 0.05) !important;
`}
/>
</Stack> </Stack>
</Stack> </Stack>
); );
} }
/**
* Injects props from receipt mail state into the `ReceiptMailPreviewConnected` component.
*/
const withReceiptMailReceiptPreviewProps = <
P extends ReceiptSendMailReceiptProps,
>(
WrappedComponent: ComponentType<P & ReceiptSendMailReceiptProps>,
) => {
return function WithInvoiceMailReceiptPreviewProps(props: P) {
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 mailReceiptPreviewProps = {
...defaultReceiptMailProps,
// companyName: receiptMailState?.companyName,
// companyLogoUri: receiptMailState?.companyLogoUri,
// primaryColor: receiptMailState?.primaryColor,
// total: receiptMailState?.totalFormatted,
// dueDate: receiptMailState?.dueDateFormatted,
// dueAmount: invoiceMailState?.dueAmountFormatted,
// invoiceNumber: invoiceMailState?.invoiceNo,
// items,
message,
};
return <WrappedComponent {...mailReceiptPreviewProps} {...props} />;
};
};
export const ReceiptMailPreviewConnected = withReceiptMailReceiptPreviewProps(
ReceiptSendMailReceipt,
);

View File

@@ -0,0 +1,50 @@
import { ComponentType } from 'react';
import {
ReceiptSendMailReceipt,
ReceiptSendMailReceiptProps,
} from './ReceiptSendMailReceipt';
import { useSendReceiptMailMessage } from './_hooks';
import { useReceiptSendMailBoot } from './ReceiptSendMailBoot';
import { defaultReceiptMailProps } from './_constants';
/**
* Injects props from receipt mail state into the `ReceiptMailPreviewConnected` component.
*/
export const withReceiptMailReceiptPreviewProps = <
P extends ReceiptSendMailReceiptProps,
>(
WrappedComponent: ComponentType<P & ReceiptSendMailReceiptProps>,
) => {
return function WithInvoiceMailReceiptPreviewProps(props: P) {
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 mailReceiptPreviewProps = {
...defaultReceiptMailProps,
// companyName: receiptMailState?.companyName,
// companyLogoUri: receiptMailState?.companyLogoUri,
// primaryColor: receiptMailState?.primaryColor,
// total: receiptMailState?.totalFormatted,
// dueDate: receiptMailState?.dueDateFormatted,
// dueAmount: invoiceMailState?.dueAmountFormatted,
// invoiceNumber: invoiceMailState?.invoiceNo,
// items,
message,
};
return <WrappedComponent {...mailReceiptPreviewProps} {...props} />;
};
};
export const ReceiptMailPreviewConnected = withReceiptMailReceiptPreviewProps(
ReceiptSendMailReceipt,
) as ComponentType<Partial<ReceiptSendMailReceiptProps>>;

View File

@@ -5,6 +5,8 @@ import {
UseQueryOptions, UseQueryOptions,
UseQueryResult, UseQueryResult,
useQuery, useQuery,
UseMutationOptions,
UseMutationResult,
} from 'react-query'; } from 'react-query';
import useApiRequest from '../useRequest'; import useApiRequest from '../useRequest';
import { useRequestQuery } from '../useQueryRequest'; import { useRequestQuery } from '../useQueryRequest';
@@ -244,11 +246,45 @@ export function usePdfPaymentReceive(paymentReceiveId) {
return useRequestPdf({ url: `sales/payment_receives/${paymentReceiveId}` }); return useRequestPdf({ url: `sales/payment_receives/${paymentReceiveId}` });
} }
export function useSendPaymentReceiveMail(props?: any) { interface SendPaymentReceiveMailValues {
to: string[] | string;
cc?: string[] | string,
bcc?: string[] | string,
subject: string;
message: string;
from?: string[] | string;
attachPdf?: boolean;
}
interface SendPaymentReceiveMailResponse {
success: boolean;
message?: string;
}
type SendPaymentReceiveMailMutation = UseMutationResult<
SendPaymentReceiveMailResponse,
Error,
[number, SendPaymentReceiveMailValues],
unknown
>;
export function useSendPaymentReceiveMail(
props?: Partial<
UseMutationOptions<
SendPaymentReceiveMailResponse,
Error,
[number, SendPaymentReceiveMailValues]
>
>,
): SendPaymentReceiveMailMutation {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const apiRequest = useApiRequest(); const apiRequest = useApiRequest();
return useMutation( return useMutation<
SendPaymentReceiveMailResponse,
Error,
[number, SendPaymentReceiveMailValues]
>(
([id, values]) => ([id, values]) =>
apiRequest.post(`sales/payment_receives/${id}/mail`, values), apiRequest.post(`sales/payment_receives/${id}/mail`, values),
{ {
@@ -263,15 +299,16 @@ export function useSendPaymentReceiveMail(props?: any) {
export interface GetPaymentReceivedMailStateResponse { export interface GetPaymentReceivedMailStateResponse {
companyName: string; companyName: string;
companyLogoUri?: string;
primaryColor?: string;
customerName: string; customerName: string;
entries: Array<{ paymentAmountFormatted: string }>; entries: Array<{ invoiceNumber: string; paidAmount: string }>;
from: Array<string>; from: Array<string>;
fromOptions: Array<{ mail: string; label: string; primary: boolean }>; fromOptions: Array<{ mail: string; label: string; primary: boolean }>;
paymentAmountFormatted: string;
paymentDate: string; paymentDate: string;
paymentDateFormatted: string; paymentDateFormatted: string;
@@ -285,8 +322,7 @@ export interface GetPaymentReceivedMailStateResponse {
subtotalFormatted: string; subtotalFormatted: string;
paymentNumber: string; paymentNumber: string;
companyLogoUri?: string; formatArgs: Record<string, any>;
primaryColor?: string;
} }
export function usePaymentReceivedMailState( export function usePaymentReceivedMailState(

View File

@@ -24,7 +24,7 @@ EmailTemplate.CompanyLogo = ({ src }: { src: string }) => {
const containerStyle: CSSProperties = { const containerStyle: CSSProperties = {
backgroundColor: '#fff', backgroundColor: '#fff',
width: '100%', width: '100%',
maxWidth: '450px', maxWidth: '400px',
padding: '30px 20px', padding: '30px 20px',
color: '#000', color: '#000',
}; };