mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
feat: send invoice receipt preview
This commit is contained in:
@@ -201,7 +201,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
this.handleServiceErrors
|
this.handleServiceErrors
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/:id/mail',
|
'/:id/mail/state',
|
||||||
[...this.specificSaleInvoiceValidation],
|
[...this.specificSaleInvoiceValidation],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getSaleInvoiceMail.bind(this)),
|
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 {Request} req
|
||||||
* @param {Response} res
|
* @param {Response} res
|
||||||
* @param {NextFunction} next
|
* @param {NextFunction} next
|
||||||
@@ -803,7 +803,7 @@ export default class SaleInvoicesController extends BaseController {
|
|||||||
const { id: invoiceId } = req.params;
|
const { id: invoiceId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await this.saleInvoiceApplication.getSaleInvoiceMail(
|
const data = await this.saleInvoiceApplication.getSaleInvoiceMailState(
|
||||||
tenantId,
|
tenantId,
|
||||||
invoiceId
|
invoiceId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -238,6 +238,30 @@ export interface SaleInvoiceMailOptions extends CommonMailOptions {
|
|||||||
formatArgs?: Record<string, any>;
|
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 {
|
export interface SendInvoiceMailDTO extends CommonMailOptionsDTO {
|
||||||
attachInvoice?: boolean;
|
attachInvoice?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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',
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
ISystemUser,
|
ISystemUser,
|
||||||
ITenantUser,
|
ITenantUser,
|
||||||
InvoiceNotificationType,
|
InvoiceNotificationType,
|
||||||
|
SaleInvoiceMailState,
|
||||||
SendInvoiceMailDTO,
|
SendInvoiceMailDTO,
|
||||||
} from '@/interfaces';
|
} from '@/interfaces';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
@@ -29,6 +30,8 @@ import { SendInvoiceMailReminder } from './SendSaleInvoiceMailReminder';
|
|||||||
import { SendSaleInvoiceMail } from './SendSaleInvoiceMail';
|
import { SendSaleInvoiceMail } from './SendSaleInvoiceMail';
|
||||||
import { GetSaleInvoiceMailReminder } from './GetSaleInvoiceMailReminder';
|
import { GetSaleInvoiceMailReminder } from './GetSaleInvoiceMailReminder';
|
||||||
import { GetSaleInvoiceState } from './GetSaleInvoiceState';
|
import { GetSaleInvoiceState } from './GetSaleInvoiceState';
|
||||||
|
import { GetSaleInvoiceBrandTemplate } from './GetSaleInvoiceBrandTemplate';
|
||||||
|
import { GetSaleInvoiceMailState } from './GetSaleInvoiceMailState';
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export class SaleInvoiceApplication {
|
export class SaleInvoiceApplication {
|
||||||
@@ -72,7 +75,7 @@ export class SaleInvoiceApplication {
|
|||||||
private sendSaleInvoiceMailService: SendSaleInvoiceMail;
|
private sendSaleInvoiceMailService: SendSaleInvoiceMail;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private getSaleInvoiceReminderService: GetSaleInvoiceMailReminder;
|
private getSaleInvoiceMailStateService: GetSaleInvoiceMailState;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
private getSaleInvoiceStateService: GetSaleInvoiceState;
|
private getSaleInvoiceStateService: GetSaleInvoiceState;
|
||||||
@@ -361,10 +364,10 @@ export class SaleInvoiceApplication {
|
|||||||
* Retrieves the default mail options of the given sale invoice.
|
* Retrieves the default mail options of the given sale invoice.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} saleInvoiceid
|
* @param {number} saleInvoiceid
|
||||||
* @returns {Promise<SendInvoiceMailDTO>}
|
* @returns {Promise<SaleInvoiceMailState>}
|
||||||
*/
|
*/
|
||||||
public getSaleInvoiceMail(tenantId: number, saleInvoiceid: number) {
|
public getSaleInvoiceMailState(tenantId: number, saleInvoiceid: number) {
|
||||||
return this.sendSaleInvoiceMailService.getMailOption(
|
return this.getSaleInvoiceMailStateService.getInvoiceMailState(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceid
|
saleInvoiceid
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +1,8 @@
|
|||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import Mail from '@/lib/Mail';
|
import Mail from '@/lib/Mail';
|
||||||
import {
|
import { ISaleInvoiceMailSend, SendInvoiceMailDTO } from '@/interfaces';
|
||||||
ISaleInvoiceMailSend,
|
|
||||||
SaleInvoiceMailOptions,
|
|
||||||
SendInvoiceMailDTO,
|
|
||||||
} from '@/interfaces';
|
|
||||||
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
import { SaleInvoicePdf } from './SaleInvoicePdf';
|
||||||
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
|
import { SendSaleInvoiceMailCommon } from './SendInvoiceInvoiceMailCommon';
|
||||||
import {
|
|
||||||
DEFAULT_INVOICE_MAIL_CONTENT,
|
|
||||||
DEFAULT_INVOICE_MAIL_SUBJECT,
|
|
||||||
} from './constants';
|
|
||||||
import {
|
import {
|
||||||
parseMailOptions,
|
parseMailOptions,
|
||||||
validateRequiredMailOptions,
|
validateRequiredMailOptions,
|
||||||
@@ -58,26 +50,6 @@ export class SendSaleInvoiceMail {
|
|||||||
} as ISaleInvoiceMailSend);
|
} 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.
|
* Triggers the mail invoice.
|
||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
@@ -90,7 +62,7 @@ export class SendSaleInvoiceMail {
|
|||||||
saleInvoiceId: number,
|
saleInvoiceId: number,
|
||||||
messageOptions: SendInvoiceMailDTO
|
messageOptions: SendInvoiceMailDTO
|
||||||
) {
|
) {
|
||||||
const defaultMessageOptions = await this.getMailOption(
|
const defaultMessageOptions = await this.invoiceMail.getInvoiceMailOptions(
|
||||||
tenantId,
|
tenantId,
|
||||||
saleInvoiceId
|
saleInvoiceId
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/P
|
|||||||
import TaxRateFormDialog from '@/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialog';
|
import TaxRateFormDialog from '@/containers/TaxRates/dialogs/TaxRateFormDialog/TaxRateFormDialog';
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
import InvoiceExchangeRateChangeDialog from '@/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog';
|
import InvoiceExchangeRateChangeDialog from '@/containers/Sales/Invoices/InvoiceForm/Dialogs/InvoiceExchangeRateChangeDialog';
|
||||||
import InvoiceMailDialog from '@/containers/Sales/Invoices/InvoiceMailDialog/InvoiceMailDialog';
|
|
||||||
import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog';
|
import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/EstimateMailDialog';
|
||||||
import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog';
|
import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog';
|
||||||
import PaymentMailDialog from '@/containers/Sales/PaymentsReceived/PaymentMailDialog/PaymentMailDialog';
|
import PaymentMailDialog from '@/containers/Sales/PaymentsReceived/PaymentMailDialog/PaymentMailDialog';
|
||||||
@@ -144,7 +143,6 @@ export default function DialogsContainer() {
|
|||||||
<InvoiceExchangeRateChangeDialog
|
<InvoiceExchangeRateChangeDialog
|
||||||
dialogName={DialogsName.InvoiceExchangeRateChangeNotice}
|
dialogName={DialogsName.InvoiceExchangeRateChangeNotice}
|
||||||
/>
|
/>
|
||||||
<InvoiceMailDialog dialogName={DialogsName.InvoiceMail} />
|
|
||||||
<EstimateMailDialog dialogName={DialogsName.EstimateMail} />
|
<EstimateMailDialog dialogName={DialogsName.EstimateMail} />
|
||||||
<ReceiptMailDialog dialogName={DialogsName.ReceiptMail} />
|
<ReceiptMailDialog dialogName={DialogsName.ReceiptMail} />
|
||||||
<PaymentMailDialog dialogName={DialogsName.PaymentMail} />
|
<PaymentMailDialog dialogName={DialogsName.PaymentMail} />
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import { Dialog, DialogSuspense } from '@/components';
|
|
||||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
|
||||||
import { compose } from '@/utils';
|
|
||||||
|
|
||||||
const InvoiceFormMailDeliverDialogContent = React.lazy(
|
|
||||||
() => import('./InvoiceFormMailDeliverDialogContent'),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoice mail dialog.
|
|
||||||
*/
|
|
||||||
function InvoiceFormMailDeliverDialog({
|
|
||||||
dialogName,
|
|
||||||
payload: { invoiceId = null },
|
|
||||||
isOpen,
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
name={dialogName}
|
|
||||||
title={'Invoice Mail'}
|
|
||||||
isOpen={isOpen}
|
|
||||||
canEscapeJeyClose={false}
|
|
||||||
isCloseButtonShown={false}
|
|
||||||
autoFocus={true}
|
|
||||||
style={{ width: 600 }}
|
|
||||||
>
|
|
||||||
<DialogSuspense>
|
|
||||||
<InvoiceFormMailDeliverDialogContent
|
|
||||||
dialogName={dialogName}
|
|
||||||
invoiceId={invoiceId}
|
|
||||||
/>
|
|
||||||
</DialogSuspense>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default compose(withDialogRedux())(InvoiceFormMailDeliverDialog);
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import { useHistory } from 'react-router-dom';
|
|
||||||
import InvoiceMailDialogContent from '../../../InvoiceMailDialog/InvoiceMailDialogContent';
|
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
|
||||||
|
|
||||||
interface InvoiceFormDeliverDialogContent {
|
|
||||||
invoiceId: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function InvoiceFormDeliverDialogContentRoot({
|
|
||||||
invoiceId,
|
|
||||||
|
|
||||||
// #withDialogActions
|
|
||||||
closeDialog,
|
|
||||||
}: InvoiceFormDeliverDialogContent) {
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
history.push('/invoices');
|
|
||||||
closeDialog(DialogsName.InvoiceFormMailDeliver);
|
|
||||||
};
|
|
||||||
const handleCancel = () => {
|
|
||||||
history.push('/invoices');
|
|
||||||
closeDialog(DialogsName.InvoiceFormMailDeliver);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InvoiceMailDialogContent
|
|
||||||
invoiceId={invoiceId}
|
|
||||||
onFormSubmit={handleSubmit}
|
|
||||||
onCancelClick={handleCancel}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default R.compose(withDialogActions)(
|
|
||||||
InvoiceFormDeliverDialogContentRoot,
|
|
||||||
);
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import InvoiceNumberDialog from '@/containers/Dialogs/InvoiceNumberDialog';
|
import InvoiceNumberDialog from '@/containers/Dialogs/InvoiceNumberDialog';
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
import InvoiceFormMailDeliverDialog from './Dialogs/InvoiceFormMailDeliverDialog/InvoiceFormMailDeliverDialog';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invoice form dialogs.
|
* Invoice form dialogs.
|
||||||
@@ -28,9 +27,6 @@ export default function InvoiceFormDialogs() {
|
|||||||
dialogName={DialogsName.InvoiceNumberSettings}
|
dialogName={DialogsName.InvoiceNumberSettings}
|
||||||
onConfirm={handleInvoiceNumberFormConfirm}
|
onConfirm={handleInvoiceNumberFormConfirm}
|
||||||
/>
|
/>
|
||||||
<InvoiceFormMailDeliverDialog
|
|
||||||
dialogName={DialogsName.InvoiceFormMailDeliver}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import { Dialog, DialogSuspense } from '@/components';
|
|
||||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
|
||||||
import { compose } from '@/utils';
|
|
||||||
|
|
||||||
const InvoiceMailDialogBody = React.lazy(
|
|
||||||
() => import('./InvoiceMailDialogBody'),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoice mail dialog.
|
|
||||||
*/
|
|
||||||
function InvoiceMailDialog({
|
|
||||||
dialogName,
|
|
||||||
payload: { invoiceId = null },
|
|
||||||
isOpen,
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<Dialog
|
|
||||||
name={dialogName}
|
|
||||||
title={'Invoice Mail'}
|
|
||||||
isOpen={isOpen}
|
|
||||||
canEscapeJeyClose={false}
|
|
||||||
isCloseButtonShown={false}
|
|
||||||
autoFocus={true}
|
|
||||||
style={{ width: 600 }}
|
|
||||||
>
|
|
||||||
<DialogSuspense>
|
|
||||||
<InvoiceMailDialogBody invoiceId={invoiceId} />
|
|
||||||
</DialogSuspense>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default compose(withDialogRedux())(InvoiceMailDialog);
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import * as R from 'ramda';
|
|
||||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
|
||||||
import InvoiceMailDialogContent, {
|
|
||||||
InvoiceMailDialogContentProps,
|
|
||||||
} from './InvoiceMailDialogContent';
|
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
|
||||||
|
|
||||||
export interface InvoiceMailDialogBodyProps
|
|
||||||
extends InvoiceMailDialogContentProps {}
|
|
||||||
|
|
||||||
function InvoiceMailDialogBodyRoot({
|
|
||||||
invoiceId,
|
|
||||||
onCancelClick,
|
|
||||||
onFormSubmit,
|
|
||||||
|
|
||||||
// #withDialogActions
|
|
||||||
closeDialog,
|
|
||||||
}: InvoiceMailDialogBodyProps) {
|
|
||||||
const handleCancelClick = () => {
|
|
||||||
closeDialog(DialogsName.InvoiceMail);
|
|
||||||
};
|
|
||||||
const handleSubmitClick = () => {
|
|
||||||
closeDialog(DialogsName.InvoiceMail);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InvoiceMailDialogContent
|
|
||||||
invoiceId={invoiceId}
|
|
||||||
onCancelClick={handleCancelClick}
|
|
||||||
onFormSubmit={handleSubmitClick}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default R.compose(withDialogActions)(InvoiceMailDialogBodyRoot);
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import React, { createContext } from 'react';
|
|
||||||
import { useSaleInvoiceDefaultOptions } from '@/hooks/query';
|
|
||||||
import { DialogContent } from '@/components';
|
|
||||||
|
|
||||||
interface InvoiceMailDialogBootValues {
|
|
||||||
invoiceId: number;
|
|
||||||
mailOptions: any;
|
|
||||||
redirectToInvoicesList: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const InvoiceMailDialagBoot = createContext<InvoiceMailDialogBootValues>();
|
|
||||||
|
|
||||||
interface InvoiceMailDialogBootProps {
|
|
||||||
invoiceId: number;
|
|
||||||
redirectToInvoicesList?: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoice mail dialog boot provider.
|
|
||||||
*/
|
|
||||||
function InvoiceMailDialogBoot({
|
|
||||||
invoiceId,
|
|
||||||
redirectToInvoicesList,
|
|
||||||
...props
|
|
||||||
}: InvoiceMailDialogBootProps) {
|
|
||||||
const { data: mailOptions, isLoading: isMailOptionsLoading } =
|
|
||||||
useSaleInvoiceDefaultOptions(invoiceId);
|
|
||||||
|
|
||||||
const provider = {
|
|
||||||
saleInvoiceId: invoiceId,
|
|
||||||
mailOptions,
|
|
||||||
isMailOptionsLoading,
|
|
||||||
redirectToInvoicesList,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DialogContent isLoading={isMailOptionsLoading}>
|
|
||||||
<InvoiceMailDialagBoot.Provider value={provider} {...props} />
|
|
||||||
</DialogContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const useInvoiceMailDialogBoot = () =>
|
|
||||||
React.useContext<InvoiceMailDialogBootValues>(InvoiceMailDialagBoot);
|
|
||||||
|
|
||||||
export { InvoiceMailDialogBoot, useInvoiceMailDialogBoot };
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { InvoiceMailDialogBoot } from './InvoiceMailDialogBoot';
|
|
||||||
import { InvoiceMailDialogForm } from './InvoiceMailDialogForm';
|
|
||||||
|
|
||||||
export interface InvoiceMailDialogContentProps {
|
|
||||||
invoiceId: number;
|
|
||||||
onFormSubmit?: () => void;
|
|
||||||
onCancelClick?: () => void;
|
|
||||||
}
|
|
||||||
export default function InvoiceMailDialogContent({
|
|
||||||
invoiceId,
|
|
||||||
onFormSubmit,
|
|
||||||
onCancelClick,
|
|
||||||
}: InvoiceMailDialogContentProps) {
|
|
||||||
return (
|
|
||||||
<InvoiceMailDialogBoot invoiceId={invoiceId}>
|
|
||||||
<InvoiceMailDialogForm
|
|
||||||
onFormSubmit={onFormSubmit}
|
|
||||||
onCancelClick={onCancelClick}
|
|
||||||
/>
|
|
||||||
</InvoiceMailDialogBoot>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import * as Yup from 'yup';
|
|
||||||
|
|
||||||
export const InvoiceMailFormSchema = Yup.object().shape({
|
|
||||||
from: Yup.array().required().min(1).max(5).label('From address'),
|
|
||||||
to: Yup.array().required().min(1).max(5).label('To address'),
|
|
||||||
subject: Yup.string().required().label('Mail subject'),
|
|
||||||
body: Yup.string().required().label('Mail body'),
|
|
||||||
});
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { Formik } from 'formik';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
import { useInvoiceMailDialogBoot } from './InvoiceMailDialogBoot';
|
|
||||||
import { AppToaster } from '@/components';
|
|
||||||
import { useSendSaleInvoiceMail } from '@/hooks/query';
|
|
||||||
import { InvoiceMailDialogFormContent } from './InvoiceMailDialogFormContent';
|
|
||||||
import { InvoiceMailFormSchema } from './InvoiceMailDialogForm.schema';
|
|
||||||
import {
|
|
||||||
MailNotificationFormValues,
|
|
||||||
initialMailNotificationValues,
|
|
||||||
transformMailFormToRequest,
|
|
||||||
transformMailFormToInitialValues,
|
|
||||||
} from '@/containers/SendMailNotification/utils';
|
|
||||||
|
|
||||||
const initialFormValues = {
|
|
||||||
...initialMailNotificationValues,
|
|
||||||
attachInvoice: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface InvoiceMailFormValues extends MailNotificationFormValues {
|
|
||||||
attachInvoice: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function InvoiceMailDialogForm({ onFormSubmit, onCancelClick }) {
|
|
||||||
const { mailOptions, saleInvoiceId } = useInvoiceMailDialogBoot();
|
|
||||||
const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail();
|
|
||||||
|
|
||||||
const initialValues = transformMailFormToInitialValues(
|
|
||||||
mailOptions,
|
|
||||||
initialFormValues,
|
|
||||||
);
|
|
||||||
// Handle the form submitting.
|
|
||||||
const handleSubmit = (values: InvoiceMailFormValues, { setSubmitting }) => {
|
|
||||||
const reqValues = transformMailFormToRequest(values);
|
|
||||||
|
|
||||||
setSubmitting(true);
|
|
||||||
sendInvoiceMail([saleInvoiceId, reqValues])
|
|
||||||
.then(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'The mail notification has been sent successfully.',
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
setSubmitting(false);
|
|
||||||
onFormSubmit && onFormSubmit(values);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: 'Something went wrong.',
|
|
||||||
intent: Intent.DANGER,
|
|
||||||
});
|
|
||||||
setSubmitting(false);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// Handle the close button click.
|
|
||||||
const handleClose = () => {
|
|
||||||
onCancelClick && onCancelClick();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Formik
|
|
||||||
initialValues={initialValues}
|
|
||||||
validationSchema={InvoiceMailFormSchema}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
>
|
|
||||||
<InvoiceMailDialogFormContent onClose={handleClose} />
|
|
||||||
</Formik>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// @ts-nocheck
|
|
||||||
import { Form, useFormikContext } from 'formik';
|
|
||||||
import { Button, Classes, Intent } from '@blueprintjs/core';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { FFormGroup, FSwitch } from '@/components';
|
|
||||||
import { MailNotificationForm } from '@/containers/SendMailNotification';
|
|
||||||
import { saveInvoke } from '@/utils';
|
|
||||||
import { useInvoiceMailDialogBoot } from './InvoiceMailDialogBoot';
|
|
||||||
|
|
||||||
interface SendMailNotificationFormProps {
|
|
||||||
onClose?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function InvoiceMailDialogFormContent({
|
|
||||||
onClose,
|
|
||||||
}: SendMailNotificationFormProps) {
|
|
||||||
const { isSubmitting } = useFormikContext();
|
|
||||||
const { mailOptions } = useInvoiceMailDialogBoot();
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
saveInvoke(onClose);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
<div className={Classes.DIALOG_BODY}>
|
|
||||||
<MailNotificationForm
|
|
||||||
fromAddresses={mailOptions.from_addresses}
|
|
||||||
toAddresses={mailOptions.to_addresses}
|
|
||||||
/>
|
|
||||||
<AttachFormGroup name={'attachInvoice'} inline>
|
|
||||||
<FSwitch name={'attachInvoice'} label={'Attach Invoice'} />
|
|
||||||
</AttachFormGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
|
||||||
<Button
|
|
||||||
disabled={isSubmitting}
|
|
||||||
onClick={handleClose}
|
|
||||||
style={{ minWidth: '65px' }}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
intent={Intent.PRIMARY}
|
|
||||||
loading={isSubmitting}
|
|
||||||
style={{ minWidth: '75px' }}
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Send
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const AttachFormGroup = styled(FFormGroup)`
|
|
||||||
background: #f8f9fb;
|
|
||||||
margin-top: 0.6rem;
|
|
||||||
padding: 4px 14px;
|
|
||||||
border-radius: 5px;
|
|
||||||
border: 1px solid #dcdcdd;
|
|
||||||
`;
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './InvoiceMailDialog';
|
|
||||||
export * from './InvoiceMailDialogContent';
|
|
||||||
@@ -1,33 +1,38 @@
|
|||||||
import { Box, Group, Stack } from '@/components';
|
|
||||||
import { InvoiceMailReceiptPreview } from '../InvoiceCustomize/InvoiceMailReceiptPreview';
|
|
||||||
import { css } from '@emotion/css';
|
|
||||||
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { x } from '@xstyled/emotion';
|
import { css } from '@emotion/css';
|
||||||
|
import { Box, } from '@/components';
|
||||||
|
import { InvoiceMailReceiptPreview } from '../InvoiceCustomize/InvoiceMailReceiptPreview';
|
||||||
|
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
|
||||||
import { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
|
import { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
|
||||||
import { useSendInvoiceMailMessage } from './_hooks';
|
import { useSendInvoiceMailMessage } from './_hooks';
|
||||||
|
|
||||||
export function InvoiceMailReceiptPreviewConneceted() {
|
export function InvoiceMailReceiptPreviewConneceted() {
|
||||||
const { invoice } = useInvoiceSendMailBoot();
|
|
||||||
const mailMessage = useSendInvoiceMailMessage();
|
const mailMessage = useSendInvoiceMailMessage();
|
||||||
|
const { invoiceMailState } = useInvoiceSendMailBoot();
|
||||||
|
|
||||||
const items = useMemo(
|
const items = useMemo(
|
||||||
() =>
|
() =>
|
||||||
invoice.entries.map((entry: any) => ({
|
invoiceMailState?.entries?.map((entry: any) => ({
|
||||||
quantity: entry.quantity,
|
quantity: entry.quantity,
|
||||||
total: entry.rate_formatted,
|
total: entry.totalFormatted,
|
||||||
label: entry.item.name,
|
label: entry.name,
|
||||||
})),
|
})),
|
||||||
[invoice.entries],
|
[invoiceMailState?.entries],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InvoiceSendMailPreviewWithHeader>
|
<InvoiceSendMailPreviewWithHeader>
|
||||||
<Box px={4} pt={8} pb={16}>
|
<Box px={4} pt={8} pb={16}>
|
||||||
<InvoiceMailReceiptPreview
|
<InvoiceMailReceiptPreview
|
||||||
total={invoice.total_formatted}
|
companyName={invoiceMailState?.companyName}
|
||||||
dueDate={invoice.due_date_formatted}
|
// companyLogoUri={invoiceMailState?.companyLogoUri}
|
||||||
invoiceNumber={invoice.invoice_no}
|
|
||||||
|
primaryColor={invoiceMailState?.primaryColor}
|
||||||
|
total={invoiceMailState?.totalFormatted}
|
||||||
|
dueDate={invoiceMailState?.dueDateFormatted}
|
||||||
|
dueAmount={invoiceMailState?.dueAmountFormatted}
|
||||||
|
|
||||||
|
invoiceNumber={invoiceMailState?.invoiceNo}
|
||||||
items={items}
|
items={items}
|
||||||
message={mailMessage}
|
message={mailMessage}
|
||||||
className={css`
|
className={css`
|
||||||
|
|||||||
@@ -3,18 +3,15 @@ import React, { createContext, useContext } from 'react';
|
|||||||
import { Spinner } from '@blueprintjs/core';
|
import { Spinner } from '@blueprintjs/core';
|
||||||
import {
|
import {
|
||||||
GetSaleInvoiceDefaultOptionsResponse,
|
GetSaleInvoiceDefaultOptionsResponse,
|
||||||
useInvoice,
|
useSaleInvoiceMailState,
|
||||||
useSaleInvoiceDefaultOptions,
|
|
||||||
} from '@/hooks/query';
|
} from '@/hooks/query';
|
||||||
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
import { useDrawerContext } from '@/components/Drawer/DrawerProvider';
|
||||||
|
|
||||||
interface InvoiceSendMailBootValues {
|
interface InvoiceSendMailBootValues {
|
||||||
invoice: any;
|
|
||||||
invoiceId: number;
|
invoiceId: number;
|
||||||
isInvoiceLoading: boolean;
|
|
||||||
|
|
||||||
invoiceMailOptions: GetSaleInvoiceDefaultOptionsResponse | undefined;
|
invoiceMailState: GetSaleInvoiceDefaultOptionsResponse | undefined;
|
||||||
isInvoiceMailOptionsLoading: boolean;
|
isInvoiceMailState: boolean;
|
||||||
}
|
}
|
||||||
interface InvoiceSendMailBootProps {
|
interface InvoiceSendMailBootProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -28,25 +25,21 @@ export const InvoiceSendMailBoot = ({ children }: InvoiceSendMailBootProps) => {
|
|||||||
payload: { invoiceId },
|
payload: { invoiceId },
|
||||||
} = useDrawerContext();
|
} = useDrawerContext();
|
||||||
|
|
||||||
// Invoice details.
|
|
||||||
const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
|
|
||||||
enabled: !!invoiceId,
|
|
||||||
});
|
|
||||||
// Invoice mail options.
|
// Invoice mail options.
|
||||||
const { data: invoiceMailOptions, isLoading: isInvoiceMailOptionsLoading } =
|
const { data: invoiceMailState, isLoading: isInvoiceMailState } =
|
||||||
useSaleInvoiceDefaultOptions(invoiceId);
|
useSaleInvoiceMailState(invoiceId);
|
||||||
|
|
||||||
const isLoading = isInvoiceLoading || isInvoiceMailOptionsLoading;
|
const isLoading = isInvoiceMailState;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return <Spinner size={20} />;
|
return <Spinner size={20} />;
|
||||||
}
|
}
|
||||||
const value = {
|
const value = {
|
||||||
invoice,
|
|
||||||
isInvoiceLoading,
|
|
||||||
invoiceId,
|
invoiceId,
|
||||||
invoiceMailOptions,
|
|
||||||
isInvoiceMailOptionsLoading,
|
// # Invoice mail options
|
||||||
|
isInvoiceMailState,
|
||||||
|
invoiceMailState,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -25,13 +25,14 @@ interface InvoiceSendMailFormProps {
|
|||||||
|
|
||||||
export function InvoiceSendMailForm({ children }: InvoiceSendMailFormProps) {
|
export function InvoiceSendMailForm({ children }: InvoiceSendMailFormProps) {
|
||||||
const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail();
|
const { mutateAsync: sendInvoiceMail } = useSendSaleInvoiceMail();
|
||||||
const { invoiceId, invoiceMailOptions } = useInvoiceSendMailBoot();
|
const { invoiceId, invoiceMailState } = useInvoiceSendMailBoot();
|
||||||
|
|
||||||
const { name } = useDrawerContext();
|
const { name } = useDrawerContext();
|
||||||
const { closeDrawer } = useDrawerActions();
|
const { closeDrawer } = useDrawerActions();
|
||||||
|
|
||||||
const _initialValues: InvoiceSendMailFormValues = {
|
const _initialValues: InvoiceSendMailFormValues = {
|
||||||
...initialValues,
|
...initialValues,
|
||||||
...transformToForm(invoiceMailOptions, initialValues),
|
...transformToForm(invoiceMailState, initialValues),
|
||||||
};
|
};
|
||||||
const handleSubmit = (
|
const handleSubmit = (
|
||||||
values: InvoiceSendMailFormValues,
|
values: InvoiceSendMailFormValues,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { useDrawerActions } from '@/hooks/state';
|
|||||||
interface ElementCustomizeHeaderProps {
|
interface ElementCustomizeHeaderProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
closeButton?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function InvoiceSendMailHeader({
|
export function InvoiceSendMailHeader({
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
import { x } from '@xstyled/emotion';
|
import { x } from '@xstyled/emotion';
|
||||||
import { Box, Group, Stack } from '@/components';
|
import { Box, Group, Stack } from '@/components';
|
||||||
import React from 'react';
|
import { useSendInvoiceMailForm, useSendInvoiceMailSubject } from './_hooks';
|
||||||
import { useSendInvoiceMailSubject } from './_hooks';
|
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
|
||||||
|
|
||||||
export function InvoiceSendMailHeaderPreview() {
|
export function InvoiceSendMailHeaderPreview() {
|
||||||
const mailSubject = useSendInvoiceMailSubject();
|
const mailSubject = useSendInvoiceMailSubject();
|
||||||
|
const { invoiceMailState } = useInvoiceSendMailBoot();
|
||||||
|
const toAddresses = useMailHeaderToAddresses();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
@@ -43,12 +46,12 @@ export function InvoiceSendMailHeaderPreview() {
|
|||||||
<Group spacing={2}>
|
<Group spacing={2}>
|
||||||
<Box fontWeight={600}>Ahmed </Box>
|
<Box fontWeight={600}>Ahmed </Box>
|
||||||
<Box color={'#738091'}>
|
<Box color={'#738091'}>
|
||||||
<messaging-service@post.xero.com>
|
<messaging-service@post.bigcapital.app>
|
||||||
</Box>
|
</Box>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Box fontSize={'sm'} color={'#738091'}>
|
<Box fontSize={'sm'} color={'#738091'}>
|
||||||
Reply to: Ahmed <a.m.bouhuolia@gmail.com>
|
Reply to: {invoiceMailState?.companyName} {toAddresses};
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -65,8 +68,15 @@ export function InvoiceSendMailPreviewWithHeader({
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<InvoiceSendMailHeaderPreview />
|
<InvoiceSendMailHeaderPreview />
|
||||||
|
|
||||||
<Box>{children}</Box>
|
<Box>{children}</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useMailHeaderToAddresses = () => {
|
||||||
|
const {
|
||||||
|
values: { to },
|
||||||
|
} = useSendInvoiceMailForm();
|
||||||
|
|
||||||
|
return useMemo(() => to?.map((email) => '<' + email + '>').join(' '), [to]);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
import { Tab, Tabs } from '@blueprintjs/core';
|
import { lazy, Suspense } from 'react';
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
|
import { Tab, Tabs } from '@blueprintjs/core';
|
||||||
import { Stack } from '@/components';
|
import { Stack } from '@/components';
|
||||||
import { InvoiceMailReceiptPreviewConneceted } from './InvoiceMailReceiptPreviewConnected.';
|
|
||||||
import { InvoiceSendPdfPreviewConnected } from './InvoiceSendPdfPreviewConnected';
|
const InvoiceMailReceiptPreviewConneceted = lazy(() =>
|
||||||
|
import('./InvoiceMailReceiptPreviewConnected.').then((module) => ({
|
||||||
|
default: module.InvoiceMailReceiptPreviewConneceted,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const InvoiceSendPdfPreviewConnected = lazy(() =>
|
||||||
|
import('./InvoiceSendPdfPreviewConnected').then((module) => ({
|
||||||
|
default: module.InvoiceSendPdfPreviewConnected,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
export function InvoiceSendMailPreview() {
|
export function InvoiceSendMailPreview() {
|
||||||
return (
|
return (
|
||||||
@@ -39,12 +49,20 @@ export function InvoiceSendMailPreview() {
|
|||||||
<Tab
|
<Tab
|
||||||
id={'payment-page'}
|
id={'payment-page'}
|
||||||
title={'Payment page'}
|
title={'Payment page'}
|
||||||
panel={<InvoiceMailReceiptPreviewConneceted />}
|
panel={
|
||||||
|
<Suspense>
|
||||||
|
<InvoiceMailReceiptPreviewConneceted />
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
id="pdf-document"
|
id="pdf-document"
|
||||||
title={'PDF document'}
|
title={'PDF document'}
|
||||||
panel={<InvoiceSendPdfPreviewConnected />}
|
panel={
|
||||||
|
<Suspense>
|
||||||
|
<InvoiceSendPdfPreviewConnected />
|
||||||
|
</Suspense>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
import { Box } from '@/components';
|
import { Box } from '@/components';
|
||||||
import { InvoicePaperTemplate } from '../InvoiceCustomize/InvoicePaperTemplate';
|
import { InvoicePaperTemplate } from '../InvoiceCustomize/InvoicePaperTemplate';
|
||||||
import { css } from '@emotion/css';
|
|
||||||
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
|
|
||||||
import { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
|
import { InvoiceSendMailPreviewWithHeader } from './InvoiceSendMailHeaderPreview';
|
||||||
|
|
||||||
export function InvoiceSendPdfPreviewConnected() {
|
export function InvoiceSendPdfPreviewConnected() {
|
||||||
const { invoice } = useInvoiceSendMailBoot();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InvoiceSendMailPreviewWithHeader>
|
<InvoiceSendMailPreviewWithHeader>
|
||||||
<Box px={4} py={6}>
|
<Box px={4} py={6}>
|
||||||
<InvoicePaperTemplate
|
<InvoicePaperTemplate
|
||||||
dueDate={invoice.due_date_formatted}
|
|
||||||
dateIssue={invoice.invoice_date_formatted}
|
|
||||||
invoiceNumber={invoice.invoice_no}
|
|
||||||
total={invoice.total_formatted}
|
|
||||||
subtotal={invoice.subtotal}
|
|
||||||
discount={''}
|
|
||||||
paymentMade={''}
|
|
||||||
balanceDue={invoice.due_amount_Formatted}
|
|
||||||
statement={invoice.statement}
|
|
||||||
className={css`
|
className={css`
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
`}
|
`}
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import { chain, defaultTo, mapKeys, snakeCase, startCase } from 'lodash';
|
|||||||
import { InvoiceSendMailFormValues } from './_types';
|
import { InvoiceSendMailFormValues } from './_types';
|
||||||
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
|
import { useInvoiceSendMailBoot } from './InvoiceSendMailContentBoot';
|
||||||
|
|
||||||
|
export const useSendInvoiceMailForm = () => {
|
||||||
|
return useFormikContext<InvoiceSendMailFormValues>();
|
||||||
|
};
|
||||||
|
|
||||||
export const useInvoiceMailItems = () => {
|
export const useInvoiceMailItems = () => {
|
||||||
const { values } = useFormikContext<InvoiceSendMailFormValues>();
|
const { values } = useFormikContext<InvoiceSendMailFormValues>();
|
||||||
const cc = values?.cc || [];
|
const cc = values?.cc || [];
|
||||||
@@ -21,13 +25,13 @@ export const useInvoiceMailItems = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const useSendInvoiceMailFormatArgs = (): Record<string, string> => {
|
export const useSendInvoiceMailFormatArgs = (): Record<string, string> => {
|
||||||
const { invoiceMailOptions } = useInvoiceSendMailBoot();
|
const { invoiceMailState } = useInvoiceSendMailBoot();
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return mapKeys(invoiceMailOptions?.formatArgs, (_, key) =>
|
return mapKeys(invoiceMailState?.formatArgs, (_, key) =>
|
||||||
startCase(snakeCase(key).replace('_', ' ')),
|
startCase(snakeCase(key).replace('_', ' ')),
|
||||||
);
|
);
|
||||||
}, [invoiceMailOptions]);
|
}, [invoiceMailState]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useSendInvoiceMailSubject = (): string => {
|
export const useSendInvoiceMailSubject = (): string => {
|
||||||
|
|||||||
@@ -320,6 +320,8 @@ export function useInvoicePaymentTransactions(invoiceId, props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// # Send sale invoice mail.
|
||||||
|
// ------------------------------
|
||||||
export interface SendSaleInvoiceMailValues {
|
export interface SendSaleInvoiceMailValues {
|
||||||
id: number;
|
id: number;
|
||||||
values: {
|
values: {
|
||||||
@@ -366,16 +368,49 @@ export function useSendSaleInvoiceMail(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// # Get sale invoice default options.
|
||||||
|
// --------------------------------------
|
||||||
export interface GetSaleInvoiceDefaultOptionsResponse {
|
export interface GetSaleInvoiceDefaultOptionsResponse {
|
||||||
to: Array<string>;
|
companyName: string;
|
||||||
from: Array<String>;
|
|
||||||
subject: string;
|
dueDate: string;
|
||||||
message: string;
|
dueDateFormatted: string;
|
||||||
attachInvoice: boolean;
|
|
||||||
|
dueAmount: number;
|
||||||
|
dueAmountFormatted: string;
|
||||||
|
|
||||||
|
entries: Array<{
|
||||||
|
quantity: number;
|
||||||
|
quantityFormatted: string;
|
||||||
|
rate: number;
|
||||||
|
rateFormatted: string;
|
||||||
|
total: number;
|
||||||
|
totalFormatted: string;
|
||||||
|
}>;
|
||||||
formatArgs: Record<string, string>;
|
formatArgs: Record<string, string>;
|
||||||
|
|
||||||
|
from: string[];
|
||||||
|
to: string[];
|
||||||
|
|
||||||
|
invoiceDate: string;
|
||||||
|
invoiceDateFormatted: string;
|
||||||
|
|
||||||
|
invoiceNo: string;
|
||||||
|
|
||||||
|
message: string;
|
||||||
|
subject: string;
|
||||||
|
|
||||||
|
subtotal: number;
|
||||||
|
subtotalFormatted: string;
|
||||||
|
|
||||||
|
total: number;
|
||||||
|
totalFormatted: string;
|
||||||
|
|
||||||
|
attachInvoice: boolean;
|
||||||
|
primaryColor: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSaleInvoiceDefaultOptions(
|
export function useSaleInvoiceMailState(
|
||||||
invoiceId: number,
|
invoiceId: number,
|
||||||
options?: UseQueryOptions<GetSaleInvoiceDefaultOptionsResponse>,
|
options?: UseQueryOptions<GetSaleInvoiceDefaultOptionsResponse>,
|
||||||
): UseQueryResult<GetSaleInvoiceDefaultOptionsResponse> {
|
): UseQueryResult<GetSaleInvoiceDefaultOptionsResponse> {
|
||||||
@@ -385,12 +420,14 @@ export function useSaleInvoiceDefaultOptions(
|
|||||||
[t.SALE_INVOICE_DEFAULT_OPTIONS, invoiceId],
|
[t.SALE_INVOICE_DEFAULT_OPTIONS, invoiceId],
|
||||||
() =>
|
() =>
|
||||||
apiRequest
|
apiRequest
|
||||||
.get(`/sales/invoices/${invoiceId}/mail`)
|
.get(`/sales/invoices/${invoiceId}/mail/state`)
|
||||||
.then((res) => transformToCamelCase(res.data?.data)),
|
.then((res) => transformToCamelCase(res.data?.data)),
|
||||||
options,
|
options,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// # Get sale invoice state.
|
||||||
|
// -------------------------------------
|
||||||
export interface GetSaleInvoiceStateResponse {
|
export interface GetSaleInvoiceStateResponse {
|
||||||
defaultTemplateId: number;
|
defaultTemplateId: number;
|
||||||
}
|
}
|
||||||
@@ -409,3 +446,68 @@ export function useGetSaleInvoiceState(
|
|||||||
{ ...options },
|
{ ...options },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// # Get sale invoice branding template.
|
||||||
|
// --------------------------------------
|
||||||
|
export interface GetSaleInvoiceBrandingTemplateResponse {
|
||||||
|
id: number;
|
||||||
|
default: number;
|
||||||
|
predefined: number;
|
||||||
|
resource: string;
|
||||||
|
resourceFormatted: string;
|
||||||
|
templateName: string;
|
||||||
|
updatedAt: string;
|
||||||
|
createdAt: string;
|
||||||
|
createdAtFormatted: string;
|
||||||
|
attributes: {
|
||||||
|
billedToLabel?: string;
|
||||||
|
companyLogoKey?: string | null;
|
||||||
|
companyLogoUri?: string;
|
||||||
|
dateIssueLabel?: string;
|
||||||
|
discountLabel?: string;
|
||||||
|
dueAmountLabel?: string;
|
||||||
|
dueDateLabel?: string;
|
||||||
|
invoiceNumberLabel?: string;
|
||||||
|
itemDescriptionLabel?: string;
|
||||||
|
itemNameLabel?: string;
|
||||||
|
itemRateLabel?: string;
|
||||||
|
itemTotalLabel?: string;
|
||||||
|
paymentMadeLabel?: string;
|
||||||
|
primaryColor?: string;
|
||||||
|
secondaryColor?: string;
|
||||||
|
showCompanyAddress?: boolean;
|
||||||
|
showCompanyLogo?: boolean;
|
||||||
|
showCustomerAddress?: boolean;
|
||||||
|
showDateIssue?: boolean;
|
||||||
|
showDiscount?: boolean;
|
||||||
|
showDueAmount?: boolean;
|
||||||
|
showDueDate?: boolean;
|
||||||
|
showInvoiceNumber?: boolean;
|
||||||
|
showPaymentMade?: boolean;
|
||||||
|
showStatement?: boolean;
|
||||||
|
showSubtotal?: boolean;
|
||||||
|
showTaxes?: boolean;
|
||||||
|
showTermsConditions?: boolean;
|
||||||
|
showTotal?: boolean;
|
||||||
|
statementLabel?: string;
|
||||||
|
subtotalLabel?: string;
|
||||||
|
termsConditionsLabel?: string;
|
||||||
|
totalLabel?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGetSaleInvoiceBrandingTemplate(
|
||||||
|
invoiceId: number,
|
||||||
|
options?: UseQueryOptions<GetSaleInvoiceBrandingTemplateResponse, Error>,
|
||||||
|
): UseQueryResult<GetSaleInvoiceBrandingTemplateResponse, Error> {
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useQuery<GetSaleInvoiceBrandingTemplateResponse, Error>(
|
||||||
|
['SALE_INVOICE_BRANDING_TEMPLATE', invoiceId],
|
||||||
|
() =>
|
||||||
|
apiRequest
|
||||||
|
.get(`/sales/invoices/${invoiceId}/template`)
|
||||||
|
.then((res) => transformToCamelCase(res.data?.data)),
|
||||||
|
{ ...options },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user