mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 23:00:34 +00:00
feat: add reconcile credit note.
This commit is contained in:
@@ -28,6 +28,7 @@ import SMSMessageDialog from '../containers/Dialogs/SMSMessageDialog';
|
|||||||
import TransactionsLockingDialog from '../containers/Dialogs/TransactionsLockingDialog';
|
import TransactionsLockingDialog from '../containers/Dialogs/TransactionsLockingDialog';
|
||||||
import RefundCreditNoteDialog from '../containers/Dialogs/RefundCreditNoteDialog';
|
import RefundCreditNoteDialog from '../containers/Dialogs/RefundCreditNoteDialog';
|
||||||
import RefundVendorCreditDialog from '../containers/Dialogs/RefundVendorCreditDialog';
|
import RefundVendorCreditDialog from '../containers/Dialogs/RefundVendorCreditDialog';
|
||||||
|
import ReconcileCreditNoteDialog from '../containers/Dialogs/ReconcileCreditNoteDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialogs container.
|
* Dialogs container.
|
||||||
@@ -64,6 +65,7 @@ export default function DialogsContainer() {
|
|||||||
<TransactionsLockingDialog dialogName={'transactions-locking'} />
|
<TransactionsLockingDialog dialogName={'transactions-locking'} />
|
||||||
<RefundCreditNoteDialog dialogName={'refund-credit-note'} />
|
<RefundCreditNoteDialog dialogName={'refund-credit-note'} />
|
||||||
<RefundVendorCreditDialog dialogName={'refund-vendor-credit'} />
|
<RefundVendorCreditDialog dialogName={'refund-vendor-credit'} />
|
||||||
|
<ReconcileCreditNoteDialog dialogName={'reconcile-credit-note'} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ReconcileCreditNoteFormProvider } from './ReconcileCreditNoteFormProvider';
|
||||||
|
import ReconcileCreditNoteForm from './ReconcileCreditNoteForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile credit note dialog content.
|
||||||
|
*/
|
||||||
|
export default function ReconcileCreditNoteDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
creditNoteId,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ReconcileCreditNoteFormProvider
|
||||||
|
creditNoteId={creditNoteId}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<ReconcileCreditNoteForm />
|
||||||
|
</ReconcileCreditNoteFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { MoneyFieldCell, DataTableEditable, FormatDateCell } from 'components';
|
||||||
|
import { compose, updateTableCell } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile credit note entries table.
|
||||||
|
*/
|
||||||
|
export default function ReconcileCreditNoteEntriesTable({
|
||||||
|
onUpdateData,
|
||||||
|
entries,
|
||||||
|
errors,
|
||||||
|
}) {
|
||||||
|
const columns = React.useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: intl.get('invoice_date'),
|
||||||
|
accessor: 'formatted_invoice_date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
width: '120',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: intl.get('invoice_no'),
|
||||||
|
accessor: 'invoice_no',
|
||||||
|
disableSortBy: true,
|
||||||
|
width: '100',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: intl.get('amount'),
|
||||||
|
accessor: 'formatted_amount',
|
||||||
|
disableSortBy: true,
|
||||||
|
align: 'right',
|
||||||
|
width: '100',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: intl.get('reconcile_credit_note.column.remaining_amount'),
|
||||||
|
accessor: 'formatted_due_amount',
|
||||||
|
disableSortBy: true,
|
||||||
|
align: 'right',
|
||||||
|
width: '150',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: intl.get('reconcile_credit_note.column.amount_to_credit'),
|
||||||
|
accessor: 'amount',
|
||||||
|
Cell: MoneyFieldCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
width: '150',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle update data.
|
||||||
|
const handleUpdateData = React.useCallback(
|
||||||
|
(rowIndex, columnId, value) => {
|
||||||
|
const newRows = compose(updateTableCell(rowIndex, columnId, value))(
|
||||||
|
entries,
|
||||||
|
);
|
||||||
|
onUpdateData(newRows);
|
||||||
|
},
|
||||||
|
[onUpdateData, entries],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTableEditable
|
||||||
|
columns={columns}
|
||||||
|
data={entries}
|
||||||
|
payload={{
|
||||||
|
errors: errors || [],
|
||||||
|
updateData: handleUpdateData,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import '../../../style/pages/ReconcileCreditNote/ReconcileCreditNoteForm.scss';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { CreateReconcileCreditNoteFormSchema } from './ReconcileCreditNoteForm.schema';
|
||||||
|
import { useReconcileCreditNoteContext } from './ReconcileCreditNoteFormProvider';
|
||||||
|
import ReconcileCreditNoteFormContent from './ReconcileCreditNoteFormContent';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose, transformToForm } from 'utils';
|
||||||
|
import { transformErrors } from './utils';
|
||||||
|
|
||||||
|
// Default form initial values.
|
||||||
|
const defaultInitialValues = {
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
invoice_id: '',
|
||||||
|
amount: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile credit note form.
|
||||||
|
*/
|
||||||
|
function ReconcileCreditNoteForm({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
const {
|
||||||
|
dialogName,
|
||||||
|
creditNoteId,
|
||||||
|
reconcileCreditNotes,
|
||||||
|
createReconcileCreditNoteMutate,
|
||||||
|
} = useReconcileCreditNoteContext();
|
||||||
|
|
||||||
|
// Initial form values.
|
||||||
|
const initialValues = {
|
||||||
|
entries: reconcileCreditNotes.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
invoice_id: entry.id,
|
||||||
|
amount: '',
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
|
||||||
|
// Filters the entries.
|
||||||
|
const entries = values.entries
|
||||||
|
.filter((entry) => entry.id && entry.amount)
|
||||||
|
.map((entry) => transformToForm(entry, defaultInitialValues.entries[0]));
|
||||||
|
|
||||||
|
const form = {
|
||||||
|
...values,
|
||||||
|
entries: entries,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle the request success.
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('reconcile_credit_note.success_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle the request error.
|
||||||
|
const onError = ({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
if (errors) {
|
||||||
|
transformErrors(errors, { setErrors });
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
createReconcileCreditNoteMutate([creditNoteId, form])
|
||||||
|
.then(onSuccess)
|
||||||
|
.catch(onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateReconcileCreditNoteFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
component={ReconcileCreditNoteFormContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(ReconcileCreditNoteForm);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
entries: Yup.array().of(
|
||||||
|
Yup.object().shape({
|
||||||
|
invoice_id: Yup.number().required(),
|
||||||
|
amount: Yup.number().nullable(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateReconcileCreditNoteFormSchema = Schema;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
import { Choose } from 'components';
|
||||||
|
|
||||||
|
import ReconcileCreditNoteFormFields from './ReconcileCreditNoteFormFields';
|
||||||
|
import ReconcileCreditNoteFormFloatingActions from './ReconcileCreditNoteFormFloatingActions';
|
||||||
|
import { EmptyStatuCallout } from './utils';
|
||||||
|
import { useReconcileCreditNoteContext } from './ReconcileCreditNoteFormProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile credit note form content.
|
||||||
|
*/
|
||||||
|
export default function ReconcileCreditNoteFormContent() {
|
||||||
|
const { isEmptyStatus } = useReconcileCreditNoteContext();
|
||||||
|
return (
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={isEmptyStatus}>
|
||||||
|
<EmptyStatuCallout />
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.Otherwise>
|
||||||
|
<Form>
|
||||||
|
<ReconcileCreditNoteFormFields />
|
||||||
|
<ReconcileCreditNoteFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
</Choose.Otherwise>
|
||||||
|
</Choose>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FastField, useFormikContext } from 'formik';
|
||||||
|
import { Classes } from '@blueprintjs/core';
|
||||||
|
import { T, TotalLines, TotalLine } from 'components';
|
||||||
|
import { getEntriesTotal } from 'containers/Entries/utils';
|
||||||
|
import ReconcileCreditNoteEntriesTable from './ReconcileCreditNoteEntriesTable';
|
||||||
|
import { useReconcileCreditNoteContext } from './ReconcileCreditNoteFormProvider';
|
||||||
|
import { formattedAmount } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile credit note form fields.
|
||||||
|
*/
|
||||||
|
export default function ReconcileCreditNoteFormFields() {
|
||||||
|
const {
|
||||||
|
creditNote: { formatted_credits_remaining, currency_code },
|
||||||
|
} = useReconcileCreditNoteContext();
|
||||||
|
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
// Calculate the total amount.
|
||||||
|
const totalAmount = React.useMemo(
|
||||||
|
() => getEntriesTotal(values.entries),
|
||||||
|
[values.entries],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
{/*------------ Reconcile credit entries table -----------*/}
|
||||||
|
<FastField name={'entries'}>
|
||||||
|
{({
|
||||||
|
form: { setFieldValue, values },
|
||||||
|
field: { value },
|
||||||
|
meta: { error, touched },
|
||||||
|
}) => (
|
||||||
|
<ReconcileCreditNoteEntriesTable
|
||||||
|
entries={value}
|
||||||
|
errors={error}
|
||||||
|
onUpdateData={(newEntries) => {
|
||||||
|
setFieldValue('entries', newEntries);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FastField>
|
||||||
|
<div className="footer">
|
||||||
|
<TotalLines className="total_lines">
|
||||||
|
<TotalLine
|
||||||
|
title={
|
||||||
|
<T id={'reconcile_credit_note.dialog.total_amount_to_credit'} />
|
||||||
|
}
|
||||||
|
value={formattedAmount(totalAmount, currency_code)}
|
||||||
|
/>
|
||||||
|
<TotalLine
|
||||||
|
title={<T id={'reconcile_credit_note.dialog.remaining_credits'}/>}
|
||||||
|
value={formatted_credits_remaining}
|
||||||
|
/>
|
||||||
|
</TotalLines>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
|
||||||
|
import { useReconcileCreditNoteContext } from './ReconcileCreditNoteFormProvider';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile credit note floating actions.
|
||||||
|
*/
|
||||||
|
function ReconcileCreditNoteFormFloatingActions({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
const { dialogName } = useReconcileCreditNoteContext();
|
||||||
|
|
||||||
|
// Handle cancel button click.
|
||||||
|
const handleCancelBtnClick = (event) => {
|
||||||
|
closeDialog(dialogName);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button onClick={handleCancelBtnClick} style={{ minWidth: '85px' }}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
style={{ minWidth: '85px' }}
|
||||||
|
type="submit"
|
||||||
|
loading={isSubmitting}
|
||||||
|
>
|
||||||
|
{<T id={'save'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default compose(withDialogActions)(
|
||||||
|
ReconcileCreditNoteFormFloatingActions,
|
||||||
|
);
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
import {
|
||||||
|
useCreditNote,
|
||||||
|
useReconcileCreditNote,
|
||||||
|
useCreateReconcileCreditNote,
|
||||||
|
} from 'hooks/query';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
|
||||||
|
const ReconcileCreditNoteDialogContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile credit note provider.
|
||||||
|
*/
|
||||||
|
function ReconcileCreditNoteFormProvider({
|
||||||
|
creditNoteId,
|
||||||
|
dialogName,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
// Handle fetch reconcile credit note details.
|
||||||
|
const { isLoading: isReconcileCreditLoading, data: reconcileCreditNotes } =
|
||||||
|
useReconcileCreditNote(creditNoteId, {
|
||||||
|
enabled: !!creditNoteId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle fetch vendor credit details.
|
||||||
|
const { data: creditNote, isLoading: isCreditNoteLoading } = useCreditNote(
|
||||||
|
creditNoteId,
|
||||||
|
{
|
||||||
|
enabled: !!creditNoteId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create reconcile credit note mutations.
|
||||||
|
const { mutateAsync: createReconcileCreditNoteMutate } =
|
||||||
|
useCreateReconcileCreditNote();
|
||||||
|
|
||||||
|
// Detarmines the datatable empty status.
|
||||||
|
const isEmptyStatus = isEmpty(reconcileCreditNotes);
|
||||||
|
|
||||||
|
// provider payload.
|
||||||
|
const provider = {
|
||||||
|
dialogName,
|
||||||
|
reconcileCreditNotes,
|
||||||
|
createReconcileCreditNoteMutate,
|
||||||
|
isEmptyStatus,
|
||||||
|
creditNote,
|
||||||
|
creditNoteId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent
|
||||||
|
isLoading={isReconcileCreditLoading || isCreditNoteLoading}
|
||||||
|
name={'reconcile-credit-note'}
|
||||||
|
>
|
||||||
|
<ReconcileCreditNoteDialogContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useReconcileCreditNoteContext = () =>
|
||||||
|
React.useContext(ReconcileCreditNoteDialogContext);
|
||||||
|
|
||||||
|
export { ReconcileCreditNoteFormProvider, useReconcileCreditNoteContext };
|
||||||
36
src/containers/Dialogs/ReconcileCreditNoteDialog/index.js
Normal file
36
src/containers/Dialogs/ReconcileCreditNoteDialog/index.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage as T, Dialog, DialogSuspense } from 'components';
|
||||||
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const ReconcileCreditNoteDialogContent = React.lazy(() =>
|
||||||
|
import('./ReconcileCreditNoteDialogContent'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reconcile credit note dialog.
|
||||||
|
*/
|
||||||
|
function ReconcileCreditNoteDialog({
|
||||||
|
dialogName,
|
||||||
|
payload: { creditNoteId },
|
||||||
|
isOpen,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'reconcile_credit_note.label'} />}
|
||||||
|
canEscapeKeyClose={true}
|
||||||
|
isOpen={isOpen}
|
||||||
|
className="dialog--reconcile-credit-form"
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<ReconcileCreditNoteDialogContent
|
||||||
|
creditNoteId={creditNoteId}
|
||||||
|
dialogName={dialogName}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogRedux())(ReconcileCreditNoteDialog);
|
||||||
35
src/containers/Dialogs/ReconcileCreditNoteDialog/utils.js
Normal file
35
src/containers/Dialogs/ReconcileCreditNoteDialog/utils.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Callout, Intent, Classes } from '@blueprintjs/core';
|
||||||
|
|
||||||
|
import { AppToaster, T } from 'components';
|
||||||
|
|
||||||
|
export const transformErrors = (errors, { setErrors }) => {
|
||||||
|
if (errors.some((e) => e.type === 'INVOICES_HAS_NO_REMAINING_AMOUNT')) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'INVOICES_HAS_NO_REMAINING_AMOUNT',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
errors.find((error) => error.type === 'CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT')
|
||||||
|
) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: 'CREDIT_NOTE_HAS_NO_REMAINING_AMOUNT',
|
||||||
|
intent: Intent.DANGER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function EmptyStatuCallout() {
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<Callout intent={Intent.PRIMARY}>
|
||||||
|
<p>
|
||||||
|
<T id={'reconcile_credit_note.alert.there_is_no_open_sale_invoices'} />
|
||||||
|
</p>
|
||||||
|
</Callout>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ export default function VendorCreditNoteFloatingActions() {
|
|||||||
|
|
||||||
// Credit note form context.
|
// Credit note form context.
|
||||||
const { setSubmitPayload, vendorCredit } = useVendorCreditNoteFormContext();
|
const { setSubmitPayload, vendorCredit } = useVendorCreditNoteFormContext();
|
||||||
|
|
||||||
// Handle submit as open button click.
|
// Handle submit as open button click.
|
||||||
const handleSubmitOpenBtnClick = (event) => {
|
const handleSubmitOpenBtnClick = (event) => {
|
||||||
setSubmitPayload({ redirect: true, open: true });
|
setSubmitPayload({ redirect: true, open: true });
|
||||||
@@ -70,7 +71,6 @@ export default function VendorCreditNoteFloatingActions() {
|
|||||||
const handleClearBtnClick = (event) => {
|
const handleClearBtnClick = (event) => {
|
||||||
resetForm();
|
resetForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
{/* ----------- Save And Open ----------- */}
|
{/* ----------- Save And Open ----------- */}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import { Button, Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { sumBy, omit, isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useHistory } from 'react-router-dom';
|
|||||||
import { Formik, Form } from 'formik';
|
import { Formik, Form } from 'formik';
|
||||||
import { Intent } from '@blueprintjs/core';
|
import { Intent } from '@blueprintjs/core';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { sumBy, omit, isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -105,6 +105,11 @@ function CreditNotesDataTable({
|
|||||||
openAlert('credit-note-open', { creditNoteId: id });
|
openAlert('credit-note-open', { creditNoteId: id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle reconcile credit note.
|
||||||
|
const handleReconcileCreditNote = ({ id }) => {
|
||||||
|
openDialog('reconcile-credit-note', { creditNoteId: id });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardContentTable>
|
<DashboardContentTable>
|
||||||
<DataTable
|
<DataTable
|
||||||
@@ -132,6 +137,7 @@ function CreditNotesDataTable({
|
|||||||
onEdit: hanldeEditCreditNote,
|
onEdit: hanldeEditCreditNote,
|
||||||
onRefund: handleRefundCreditNote,
|
onRefund: handleRefundCreditNote,
|
||||||
onOpen: handleOpenCreditNote,
|
onOpen: handleOpenCreditNote,
|
||||||
|
onReconcile: handleReconcileCreditNote,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DashboardContentTable>
|
</DashboardContentTable>
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
import { safeCallback } from 'utils';
|
import { safeCallback } from 'utils';
|
||||||
|
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
payload: { onEdit, onDelete, onRefund, onOpen, onViewDetails },
|
payload: { onEdit, onDelete, onRefund, onOpen, onReconcile, onViewDetails },
|
||||||
row: { original },
|
row: { original },
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -44,7 +44,12 @@ export function ActionsMenu({
|
|||||||
onClick={safeCallback(onOpen, original)}
|
onClick={safeCallback(onOpen, original)}
|
||||||
/>
|
/>
|
||||||
</If>
|
</If>
|
||||||
|
<MenuItem
|
||||||
|
text={'Reconcile Credit Note With Invoice'}
|
||||||
|
// icon={<Icon icon="quick-payment-16" />}
|
||||||
|
// text={intl.get('credit_note.action.refund_credit_note')}
|
||||||
|
onClick={safeCallback(onReconcile, original)}
|
||||||
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={intl.get('credit_note.action.delete_credit_note')}
|
text={intl.get('credit_note.action.delete_credit_note')}
|
||||||
intent={Intent.DANGER}
|
intent={Intent.DANGER}
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ const commonInvalidateQueries = (queryClient) => {
|
|||||||
// Invalidate refund credit
|
// Invalidate refund credit
|
||||||
queryClient.invalidateQueries(t.REFUND_CREDIT_NOTE);
|
queryClient.invalidateQueries(t.REFUND_CREDIT_NOTE);
|
||||||
|
|
||||||
|
// Invalidate reconcile.
|
||||||
|
queryClient.invalidateQueries(t.RECONCILE_CREDIT_NOTES);
|
||||||
|
|
||||||
// Invalidate financial reports.
|
// Invalidate financial reports.
|
||||||
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
|
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
|
||||||
};
|
};
|
||||||
@@ -227,3 +230,88 @@ export function useOpenCreditNote(props) {
|
|||||||
...props,
|
...props,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve reconcile credit note of the given id.
|
||||||
|
* @param {number} id
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function useReconcileCreditNote(id, props, requestProps) {
|
||||||
|
return useRequestQuery(
|
||||||
|
[t.RECONCILE_CREDIT_NOTE, id],
|
||||||
|
{
|
||||||
|
method: 'get',
|
||||||
|
url: `sales/credit_notes/${id}/apply-to-invoices`,
|
||||||
|
...requestProps,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: (res) => res.data.data,
|
||||||
|
defaultData: [],
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Reconcile credit note.
|
||||||
|
*/
|
||||||
|
export function useCreateReconcileCreditNote(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
([id, values]) =>
|
||||||
|
apiRequest.post(`sales/credit_notes/${id}/apply-to-invoices`, values),
|
||||||
|
{
|
||||||
|
onSuccess: (res, [id, values]) => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
|
||||||
|
// Invalidate credit note query.
|
||||||
|
queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve reconcile credit notes.
|
||||||
|
*/
|
||||||
|
export function useReconcileCreditNotes(id, props, requestProps) {
|
||||||
|
return useRequestQuery(
|
||||||
|
[t.RECONCILE_CREDIT_NOTES, id],
|
||||||
|
{
|
||||||
|
method: 'get',
|
||||||
|
url: `sales/credit_notes/${id}/applied-invoices`,
|
||||||
|
...requestProps,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
select: (res) => res.data.data,
|
||||||
|
defaultData: {},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the given reconcile credit note.
|
||||||
|
*/
|
||||||
|
export function useDeleteReconcileCredit(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
(id) => apiRequest.delete(`sales/credit_notes/applied-to-invoices/${id}`),
|
||||||
|
{
|
||||||
|
onSuccess: (res, id) => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
|
||||||
|
// Invalidate vendor credit query.
|
||||||
|
queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,12 +112,16 @@ const CREDIT_NOTES = {
|
|||||||
CREDIT_NOTE: 'CREDIT_NOTE',
|
CREDIT_NOTE: 'CREDIT_NOTE',
|
||||||
CREDIT_NOTES: 'CREDIT_NOTES',
|
CREDIT_NOTES: 'CREDIT_NOTES',
|
||||||
REFUND_CREDIT_NOTE: 'REFUND_CREDIT_NOTE',
|
REFUND_CREDIT_NOTE: 'REFUND_CREDIT_NOTE',
|
||||||
|
RECONCILE_CREDIT_NOTE: 'RECONCILE_CREDIT_NOTE',
|
||||||
|
RECONCILE_CREDIT_NOTES: 'RECONCILE_CREDIT_NOTES',
|
||||||
};
|
};
|
||||||
|
|
||||||
const VENDOR_CREDIT_NOTES = {
|
const VENDOR_CREDIT_NOTES = {
|
||||||
VENDOR_CREDITS: 'VENDOR_CREDITS',
|
VENDOR_CREDITS: 'VENDOR_CREDITS',
|
||||||
VENDOR_CREDIT: 'VENDOR_CREDIT',
|
VENDOR_CREDIT: 'VENDOR_CREDIT',
|
||||||
REFUND_VENDOR_CREDIT:'REFUND_VENDOR_CREDIT'
|
REFUND_VENDOR_CREDIT: 'REFUND_VENDOR_CREDIT',
|
||||||
|
RECONCILE_VENDOR_CREDIT: 'RECONCILE_VENDOR_CREDIT',
|
||||||
|
RECONCILE_VENDOR_CREDITS: 'RECONCILE_VENDOR_CREDITS',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SETTING = {
|
const SETTING = {
|
||||||
|
|||||||
Reference in New Issue
Block a user