Merge branch 'feature/credit-memo' into develop

This commit is contained in:
a.bouhuolia
2021-12-08 15:43:49 +02:00
82 changed files with 3689 additions and 203 deletions

View File

@@ -26,6 +26,10 @@ import NotifyEstimateViaSMSDialog from '../containers/Dialogs/NotifyEstimateViaS
import NotifyPaymentReceiveViaSMSDialog from '../containers/Dialogs/NotifyPaymentReceiveViaSMSDialog';
import SMSMessageDialog from '../containers/Dialogs/SMSMessageDialog';
import TransactionsLockingDialog from '../containers/Dialogs/TransactionsLockingDialog';
import RefundCreditNoteDialog from '../containers/Dialogs/RefundCreditNoteDialog';
import RefundVendorCreditDialog from '../containers/Dialogs/RefundVendorCreditDialog';
import ReconcileCreditNoteDialog from '../containers/Dialogs/ReconcileCreditNoteDialog';
import ReconcileVendorCreditDialog from '../containers/Dialogs/ReconcileVendorCreditDialog';
/**
* Dialogs container.
@@ -60,6 +64,10 @@ export default function DialogsContainer() {
<BadDebtDialog dialogName={'write-off-bad-debt'} />
<SMSMessageDialog dialogName={'sms-message-form'} />
<TransactionsLockingDialog dialogName={'transactions-locking'} />
<RefundCreditNoteDialog dialogName={'refund-credit-note'} />
<RefundVendorCreditDialog dialogName={'refund-vendor-credit'} />
<ReconcileCreditNoteDialog dialogName={'reconcile-credit-note'} />
<ReconcileVendorCreditDialog dialogName={'reconcile-vendor-credit'} />
</div>
);
}

View File

@@ -9,6 +9,7 @@ import withAlertActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { useDeleteCreditNote } from 'hooks/query';
import { handleDeleteErrors } from '../../Sales/CreditNotes/CreditNotesLanding/utils';
import { compose } from 'utils';
/**
@@ -48,7 +49,9 @@ function CreditNoteDeleteAlert({
response: {
data: { errors },
},
}) => {},
}) => {
handleDeleteErrors(errors);
},
)
.finally(() => {
closeAlert(name);

View File

@@ -0,0 +1,68 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { useOpenCreditNote } from 'hooks/query';
import { AppToaster } from 'components';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Credit note opened alert.
*/
function CreditNoteOpenedAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { creditNoteId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: openCreditNoteMutate, isLoading } = useOpenCreditNote();
// Handle cancel opened credit note alert.
const handleAlertCancel = () => {
closeAlert(name);
};
// Handle confirm credit note opened.
const handleAlertConfirm = () => {
openCreditNoteMutate(creditNoteId)
.then(() => {
AppToaster.show({
message: intl.get('credit_note_opened.alert.success_message'),
intent: Intent.SUCCESS,
});
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'open'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleAlertCancel}
onConfirm={handleAlertConfirm}
loading={isLoading}
>
<p>
<T id={'credit_note_opened.are_sure_to_open_this_credit'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(CreditNoteOpenedAlert);

View File

@@ -0,0 +1,88 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { useDeleteReconcileCredit } from 'hooks/query';
import { handleDeleteErrors } from '../../Sales/CreditNotes/CreditNotesLanding/utils';
import { compose } from 'utils';
/**
* Reconcile credit note delete alert.
*/
function ReconcileCreditNoteDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { creditNoteId },
// #withAlertActions
closeAlert,
// #withDrawerActions
closeDrawer,
}) {
const { isLoading, mutateAsync: deleteReconcileCreditMutate } =
useDeleteReconcileCredit();
// handle cancel delete credit note alert.
const handleCancelDeleteAlert = () => {
closeAlert(name);
};
const handleConfirmVendorCreditDelete = () => {
deleteReconcileCreditMutate(creditNoteId)
.then(() => {
AppToaster.show({
message: intl.get('reconcile_credit_note.alert.success_message'),
intent: Intent.SUCCESS,
});
closeDrawer('credit-note-detail-drawer');
})
.catch(
({
response: {
data: { errors },
},
}) => {
// handleDeleteErrors(errors);
},
)
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelDeleteAlert}
onConfirm={handleConfirmVendorCreditDelete}
loading={isLoading}
>
<p>
<FormattedHTMLMessage
id={
'reconcile_credit_note.once_you_delete_this_reconcile_credit_note'
}
/>
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
withDrawerActions,
)(ReconcileCreditNoteDeleteAlert);

View File

@@ -0,0 +1,68 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useDeleteRefundCreditNote } from 'hooks/query';
import withAlertActions from 'containers/Alert/withAlertActions';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import { compose } from 'utils';
/**
* Refund credit transactions delete alert
*/
function RefundCreditNoteDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { creditNoteId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: deleteRefundCreditMutate, isLoading } =
useDeleteRefundCreditNote();
// Handle cancel delete.
const handleCancelAlert = () => {
closeAlert(name);
};
// Handle confirm delete .
const handleConfirmRefundCreditDelete = () => {
deleteRefundCreditMutate(creditNoteId)
.then(() => {
AppToaster.show({
message: intl.get('refund_credit_transactions.alert.delete_message'),
intent: Intent.SUCCESS,
});
closeAlert(name);
})
.catch(() => {});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelAlert}
onConfirm={handleConfirmRefundCreditDelete}
loading={isLoading}
>
<p>
<T
id={`refund_credit_transactions.once_your_delete_this_refund_credit_note`}
/>
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(RefundCreditNoteDeleteAlert);

View File

@@ -0,0 +1,70 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
import { Intent, Alert } from '@blueprintjs/core';
import { AppToaster } from 'components';
import { useDeleteRefundVendorCredit } from 'hooks/query';
import withAlertActions from 'containers/Alert/withAlertActions';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import { compose } from 'utils';
/**
* Refund Vendor transactions delete alert.
*/
function RefundVendorCreditDeleteAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { vendorCreditId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: deleteRefundVendorCreditMutate, isLoading } =
useDeleteRefundVendorCredit();
// Handle cancel delete.
const handleCancelAlert = () => {
closeAlert(name);
};
// Handle confirm delete .
const handleConfirmRefundVendorCreditDelete = () => {
deleteRefundVendorCreditMutate(vendorCreditId)
.then(() => {
AppToaster.show({
message: intl.get(
'refund_vendor_credit_transactions.alert.delete_message',
),
intent: Intent.SUCCESS,
});
closeAlert(name);
})
.catch(() => {});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon="trash"
intent={Intent.DANGER}
isOpen={isOpen}
onCancel={handleCancelAlert}
onConfirm={handleConfirmRefundVendorCreditDelete}
loading={isLoading}
>
<p>
<T
id={`refund_vendor_credit_transactions.once_your_delete_this_refund_vendor_credit`}
/>
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(RefundVendorCreditDeleteAlert);

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { FormattedMessage as T } from 'components';
import intl from 'react-intl-universal';
import { Intent, Alert } from '@blueprintjs/core';
import { useOpenVendorCredit } from 'hooks/query';
import { AppToaster } from 'components';
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
import withAlertActions from 'containers/Alert/withAlertActions';
import { compose } from 'utils';
/**
* Vendor credit opened alert.
*/
function VendorCreditOpenedAlert({
name,
// #withAlertStoreConnect
isOpen,
payload: { vendorCreditId },
// #withAlertActions
closeAlert,
}) {
const { mutateAsync: openVendorCreditMutate, isLoading } =
useOpenVendorCredit();
// Handle cancel opened credit note alert.
const handleAlertCancel = () => {
closeAlert(name);
};
// Handle confirm vendor credit as opened.
const handleAlertConfirm = () => {
openVendorCreditMutate(vendorCreditId)
.then(() => {
AppToaster.show({
message: intl.get('vendor_credit_opened.alert.success_message'),
intent: Intent.SUCCESS,
});
})
.catch((error) => {})
.finally(() => {
closeAlert(name);
});
};
return (
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'open'} />}
intent={Intent.WARNING}
isOpen={isOpen}
onCancel={handleAlertCancel}
onConfirm={handleAlertConfirm}
loading={isLoading}
>
<p>
<T id={'vendor_credit_opened.are_sure_to_open_this_credit'} />
</p>
</Alert>
);
}
export default compose(
withAlertStoreConnect(),
withAlertActions,
)(VendorCreditOpenedAlert);

View File

@@ -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>
);
}

View File

@@ -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,
}}
/>
);
}

View File

@@ -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.invoice_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);

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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: '95px' }}
type="submit"
loading={isSubmitting}
>
{<T id={'save'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(
ReconcileCreditNoteFormFloatingActions,
);

View File

@@ -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 };

View 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);

View 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>
);
}

View File

@@ -0,0 +1,18 @@
import React from 'react';
import { ReconcileVendorCreditFormProvider } from './ReconcileVendorCreditFormProvider';
import ReconcileVendorCreditForm from './ReconcileVendorCreditForm';
export default function ReconcileVendorCreditDialogContent({
// #ownProps
dialogName,
vendorCreditId,
}) {
return (
<ReconcileVendorCreditFormProvider
vendorCreditId={vendorCreditId}
dialogName={dialogName}
>
<ReconcileVendorCreditForm />
</ReconcileVendorCreditFormProvider>
);
}

View File

@@ -0,0 +1,72 @@
import React from 'react';
import intl from 'react-intl-universal';
import { MoneyFieldCell, DataTableEditable, FormatDateCell } from 'components';
import { compose, updateTableCell } from 'utils';
export default function ReconcileVendorCreditEntriesTable({
onUpdateData,
entries,
errors,
}) {
const columns = React.useMemo(
() => [
{
Header: intl.get('bill_date'),
accessor: 'formatted_bill_date',
Cell: FormatDateCell,
disableSortBy: true,
width: '120',
},
{
Header: intl.get('reconcile_vendor_credit.column.bill_number'),
accessor: 'bill_number',
disableSortBy: true,
width: '100',
},
{
Header: intl.get('amount'),
accessor: 'formatted_amount',
disableSortBy: true,
align: 'right',
width: '100',
},
{
Header: intl.get('reconcile_vendor_credit.column.remaining_amount'),
accessor: 'formatted_due_amount',
disableSortBy: true,
align: 'right',
width: '150',
},
{
Header: intl.get('reconcile_vendor_credit.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,
}}
/>
);
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { FormattedMessage as T } from 'components';
import { useReconcileVendorCreditContext } from './ReconcileVendorCreditFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
function ReconcileVendorCreditFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting } = useFormikContext();
const { dialogName } = useReconcileVendorCreditContext();
// 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: '95px' }}
type="submit"
loading={isSubmitting}
>
{<T id={'save'} />}
</Button>
</div>
</div>
);
}
export default compose(withDialogActions)(ReconcileVendorCreditFloatingActions);

View File

@@ -0,0 +1,98 @@
import React from 'react';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import '../../../style/pages/ReconcileVendorCredit/ReconcileVendorCreditForm.scss';
import { AppToaster } from 'components';
import { CreateReconcileVendorCreditFormSchema } from './ReconcileVendorCreditForm.schema';
import { useReconcileVendorCreditContext } from './ReconcileVendorCreditFormProvider';
import ReconcileVendorCreditFormContent from './ReconcileVendorCreditFormContent';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, transformToForm } from 'utils';
// Default form initial values.
const defaultInitialValues = {
entries: [
{
bill_id: '',
amount: '',
},
],
};
/**
* Reconcile vendor credit form.
*/
function ReconcileVendorCreditForm({
// #withDialogActions
closeDialog,
}) {
const {
dialogName,
reconcileVendorCredits,
createReconcileVendorCreditMutate,
vendorCredit,
} = useReconcileVendorCreditContext();
// Initial form values.
const initialValues = {
entries: reconcileVendorCredits.map((entry) => ({
...entry,
bill_id: entry.id,
amount: '',
})),
};
// Handle form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
setSubmitting(false);
// Filters the entries.
const entries = values.entries
.filter((entry) => entry.bill_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_vendor_credit.dialog.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);
};
createReconcileVendorCreditMutate([vendorCredit.id, form])
.then(onSuccess)
.catch(onError);
};
return (
<Formik
validationSchema={CreateReconcileVendorCreditFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={ReconcileVendorCreditFormContent}
/>
);
}
export default compose(withDialogActions)(ReconcileVendorCreditForm);

View File

@@ -0,0 +1,12 @@
import * as Yup from 'yup';
const Schema = Yup.object().shape({
entries: Yup.array().of(
Yup.object().shape({
bill_id: Yup.number().required(),
amount: Yup.number().nullable(),
}),
),
});
export const CreateReconcileVendorCreditFormSchema = Schema;

View File

@@ -0,0 +1,26 @@
import React from 'react';
import { Form } from 'formik';
import { Choose } from 'components';
import { EmptyStatuCallout } from './utils';
import ReconcileVendorCreditFormFields from './ReconcileVendorCreditFormFields';
import ReconcileVendorCreditFloatingActions from './ReconcileVendorCreditFloatingActions';
import { useReconcileVendorCreditContext } from './ReconcileVendorCreditFormProvider';
export default function ReconcileVendorCreditFormContent() {
const { isEmptyStatus } = useReconcileVendorCreditContext();
return (
<Choose>
<Choose.When condition={isEmptyStatus}>
<EmptyStatuCallout />
</Choose.When>
<Choose.Otherwise>
<Form>
<ReconcileVendorCreditFormFields />
<ReconcileVendorCreditFloatingActions />
</Form>
</Choose.Otherwise>
</Choose>
);
}

View File

@@ -0,0 +1,56 @@
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 ReconcileVendorCreditEntriesTable from './ReconcileVendorCreditEntriesTable';
import { useReconcileVendorCreditContext } from './ReconcileVendorCreditFormProvider';
import { formattedAmount } from 'utils';
export default function ReconcileVendorCreditFormFields() {
const { vendorCredit } = useReconcileVendorCreditContext();
const { values } = useFormikContext();
// Calculate the total amount.
const totalAmount = React.useMemo(
() => getEntriesTotal(values.entries),
[values.entries],
);
return (
<div className={Classes.DIALOG_BODY}>
<FastField name={'entries'}>
{({
form: { setFieldValue, values },
field: { value },
meta: { error, touched },
}) => (
<ReconcileVendorCreditEntriesTable
entries={value}
errors={error}
onUpdateData={(newEntries) => {
setFieldValue('entries', newEntries);
}}
/>
)}
</FastField>
<div className="footer">
<TotalLines className="total_lines">
<TotalLine
title={
<T id={'reconcile_vendor_credit.dialog.total_amount_to_credit'} />
}
value={formattedAmount(totalAmount, vendorCredit.currency_code)}
/>
<TotalLine
title={
<T id={'reconcile_vendor_credit.dialog.remaining_credits'} />
}
value={vendorCredit.formatted_credits_remaining}
/>
</TotalLines>
</div>
</div>
);
}

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { DialogContent } from 'components';
import {
useVendorCredit,
useReconcileVendorCredit,
useCreateReconcileVendorCredit,
} from 'hooks/query';
import { isEmpty } from 'lodash';
const ReconcileVendorCreditFormContext = React.createContext();
/**
* Reconcile vendor credit provider.
*/
function ReconcileVendorCreditFormProvider({
vendorCreditId,
dialogName,
...props
}) {
// Handle fetch reconcile
const {
isLoading: isReconcileVendorCreditLoading,
data: reconcileVendorCredits,
} = useReconcileVendorCredit(vendorCreditId, {
enabled: !!vendorCreditId,
});
// Handle fetch vendor credit details.
const { data: vendorCredit, isLoading: isVendorCreditLoading } =
useVendorCredit(vendorCreditId, {
enabled: !!vendorCreditId,
});
// Create reconcile vendor credit mutations.
const { mutateAsync: createReconcileVendorCreditMutate } =
useCreateReconcileVendorCredit();
// Detarmines the datatable empty status.
const isEmptyStatus = isEmpty(reconcileVendorCredits);
// provider.
const provider = {
dialogName,
reconcileVendorCredits,
createReconcileVendorCreditMutate,
isEmptyStatus,
vendorCredit,
};
return (
<DialogContent
isLoading={isVendorCreditLoading || isReconcileVendorCreditLoading}
name={'reconcile-vendor-credit'}
>
<ReconcileVendorCreditFormContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useReconcileVendorCreditContext = () =>
React.useContext(ReconcileVendorCreditFormContext);
export { ReconcileVendorCreditFormProvider, useReconcileVendorCreditContext };

View 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 ReconcileVendorCreditDialogContent = React.lazy(() =>
import('./ReconcileVendorCreditDialogContent'),
);
/**
* Reconcile vendor credit dialog.
*/
function ReconcileVendorCreditDialog({
dialogName,
payload: { vendorCreditId },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={<T id={'reconcile_vendor_credit.dialog.label'} />}
canEscapeKeyClose={true}
isOpen={isOpen}
className="dialog--reconcile-vendor-credit-form"
>
<DialogSuspense>
<ReconcileVendorCreditDialogContent
vendorCreditId={vendorCreditId}
dialogName={dialogName}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(ReconcileVendorCreditDialog);

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { Callout, Intent, Classes } from '@blueprintjs/core';
import { AppToaster, T } from 'components';
export const transformErrors = (errors, { setErrors }) => {};
export function EmptyStatuCallout() {
return (
<div className={Classes.DIALOG_BODY}>
<Callout intent={Intent.PRIMARY}>
<p>
<T id={'reconcile_vendor_credit.alert.there_is_no_open_bills'} />
</p>
</Callout>
</div>
);
}

View File

@@ -0,0 +1,23 @@
import React from 'react';
import 'style/pages/RefundCreditNote/RefundCreditNote.scss';
import { RefundCreditNoteFormProvider } from './RefundCreditNoteFormProvider';
import RefundCreditNoteForm from './RefundCreditNoteForm';
/**
* Refund credit note dialog content.
*/
export default function RefundCreditNoteDialogContent({
// #ownProps
dialogName,
creditNoteId,
}) {
return (
<RefundCreditNoteFormProvider
creditNoteId={creditNoteId}
dialogName={dialogName}
>
<RefundCreditNoteForm />
</RefundCreditNoteFormProvider>
);
}

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from 'components';
import { useRefundCreditNoteContext } from './RefundCreditNoteFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Refund credit note floating actions.
*/
function RefundCreditNoteFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting, values, errors } = useFormikContext();
// refund credit note dialog context.
const { dialogName } = useRefundCreditNoteContext();
// Handle close button click.
const handleCancelBtnClick = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '85px' }}
type="submit"
text={<T id={'refund'} />}
/>
</div>
</div>
);
}
export default compose(withDialogActions)(RefundCreditNoteFloatingActions);

View File

@@ -0,0 +1,77 @@
import React from 'react';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import moment from 'moment';
import { omit, defaultTo } from 'lodash';
import { AppToaster } from 'components';
import { useRefundCreditNoteContext } from './RefundCreditNoteFormProvider';
import { CreateRefundCreditNoteFormSchema } from './RefundCreditNoteForm.schema';
import RefundCreditNoteFormContent from './RefundCreditNoteFormContent';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, transactionNumber } from 'utils';
const defaultInitialValues = {
from_account_id: '',
date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '',
description: '',
amount: '',
};
/**
* Refund credit note form.
*/
function RefundCreditNoteForm({
// #withDialogActions
closeDialog,
}) {
const { dialogName, creditNote, createRefundCreditNoteMutate } =
useRefundCreditNoteContext();
// Initial form values
const initialValues = {
...defaultInitialValues,
...creditNote,
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setFieldError }) => {
const form = {
...omit(values, ['currency_code', 'credits_remaining']),
};
// Handle request response success.
const onSaved = (response) => {
AppToaster.show({
message: intl.get('refund_credit_note.dialog.success_message'),
intent: Intent.SUCCESS,
});
closeDialog(dialogName);
};
// Handle request response errors.
const onError = ({
response: {
data: { errors },
},
}) => {
setSubmitting(false);
};
createRefundCreditNoteMutate([creditNote.id, form])
.then(onSaved)
.catch(onError);
};
return (
<Formik
validationSchema={CreateRefundCreditNoteFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={RefundCreditNoteFormContent}
/>
);
}
export default compose(withDialogActions)(RefundCreditNoteForm);

View File

@@ -0,0 +1,12 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from 'common/dataTypes';
const Schema = Yup.object().shape({
date: Yup.date().required().label(intl.get('date')),
amount: Yup.number().required(),
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
from_account_id: Yup.number().required().label(intl.get('deposit_account_')),
description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
});
export const CreateRefundCreditNoteFormSchema = Schema;

View File

@@ -0,0 +1,16 @@
import React from 'react';
import { Form } from 'formik';
import RefundCreditNoteFormFields from './RefundCreditNoteFormFields';
import RefundCreditNoteFloatingActions from './RefundCreditNoteFloatingActions';
/**
* Refund credit note form content.
*/
export default function RefundCreditNoteFormContent() {
return (
<Form>
<RefundCreditNoteFormFields />
<RefundCreditNoteFloatingActions />
</Form>
);
}

View File

@@ -0,0 +1,166 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FastField, ErrorMessage } from 'formik';
import {
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { DateInput } from '@blueprintjs/datetime';
import {
Icon,
FieldRequiredHint,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FormattedMessage as T,
} from 'components';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
compose,
} from 'utils';
import { useAutofocus } from 'hooks';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { useRefundCreditNoteContext } from './RefundCreditNoteFormProvider';
import withSettings from 'containers/Settings/withSettings';
/**
* Refund credit note form fields.
*/
function RefundCreditNoteFormFields() {
const { accounts } = useRefundCreditNoteContext();
const amountFieldRef = useAutofocus();
return (
<div className={Classes.DIALOG_BODY}>
{/* ------------- Refund date ------------- */}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.refund_date'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
inline={true}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
{/* ------------- Amount ------------- */}
<FastField name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.amount'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--amount', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
inline={true}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
intent={inputIntent({ error, touched })}
inputRef={(ref) => (amountFieldRef.current = ref)}
/>
</ControlGroup>
</FormGroup>
)}
</FastField>
{/* ------------ Reference No. ------------ */}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
className={classNames('form-group--reference', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference" />}
inline={true}
>
<InputGroup
intent={inputIntent({ error, touched })}
minimal={true}
{...field}
/>
</FormGroup>
)}
</FastField>
{/* ------------ Form account ------------ */}
<FastField name={'from_account_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.form_account'} />}
className={classNames(
'form-group--from_account_id',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'from_account_id'} />}
inline={true}
>
<AccountsSuggestField
selectedAccountId={value}
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('from_account_id', id)
}
inputProps={{
placeholder: intl.get('select_account'),
}}
filterByTypes={[
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.FIXED_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
{/* --------- Statement --------- */}
<FastField name={'description'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_credit_note.dialog.description'} />}
className={'form-group--description'}
inline={true}
>
<TextArea growVertically={true} {...field} />
</FormGroup>
)}
</FastField>
</div>
);
}
export default RefundCreditNoteFormFields;

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { DialogContent } from 'components';
import { pick } from 'lodash';
import {
useAccounts,
useCreditNote,
useCreateRefundCreditNote,
} from 'hooks/query';
const RefundCreditNoteContext = React.createContext();
/**
* Refund credit note form provider.
*/
function RefundCreditNoteFormProvider({ creditNoteId, dialogName, ...props }) {
// Handle fetch accounts data.
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
// Handle fetch credit note data.
const { data: creditNote, isLoading: isCreditNoteLoading } = useCreditNote(
creditNoteId,
{
enabled: !!creditNoteId,
},
);
// Create and edit credit note mutations.
const { mutateAsync: createRefundCreditNoteMutate } =
useCreateRefundCreditNote();
// State provider.
const provider = {
creditNote: {
...pick(creditNote, ['id', 'credits_remaining', 'currency_code']),
amount: creditNote.credits_remaining,
},
accounts,
dialogName,
createRefundCreditNoteMutate,
};
return (
<DialogContent isLoading={isAccountsLoading || isCreditNoteLoading}>
<RefundCreditNoteContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useRefundCreditNoteContext = () =>
React.useContext(RefundCreditNoteContext);
export { RefundCreditNoteFormProvider, useRefundCreditNoteContext };

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'redux';
const RefundCreditNoteDialogContent = React.lazy(() =>
import('./RefundCreditNoteDialogContent'),
);
/**
* Refund credit note dialog.
*/
function RefundCreditNoteDialog({
dialogName,
payload: { creditNoteId },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={<T id={'refund_credit_note.dialog.label'} />}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
className={'dialog--refund-credit-note'}
>
<DialogSuspense>
<RefundCreditNoteDialogContent
dialogName={dialogName}
creditNoteId={creditNoteId}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(RefundCreditNoteDialog);

View File

@@ -0,0 +1,21 @@
import React from 'react';
import 'style/pages/RefundVendorCredit/RefundVendorCredit.scss';
import { RefundVendorCreditFormProvider } from './RefundVendorCreditFormProvider';
import RefundVendorCreditForm from './RefundVendorCreditForm';
export default function RefundVendorCreditDialogContent({
// #ownProps
dialogName,
vendorCreditId,
}) {
return (
<RefundVendorCreditFormProvider
vendorCreditId={vendorCreditId}
dialogName={dialogName}
>
<RefundVendorCreditForm />
</RefundVendorCreditFormProvider>
);
}

View File

@@ -0,0 +1,45 @@
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from 'components';
import { useRefundVendorCreditContext } from './RefundVendorCreditFormProvider';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
/**
* Refund vendor flaoting actions.
*/
function RefundVendorCreditFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting } = useFormikContext();
// refund vendor credit dialog context.
const { dialogName } = useRefundVendorCreditContext();
// Handle close button click.
const handleCancelBtnClick = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
<T id={'cancel'} />
</Button>
<Button
intent={Intent.PRIMARY}
loading={isSubmitting}
style={{ minWidth: '85px' }}
type="submit"
text="Refund"
/>
</div>
</div>
);
}
export default compose(withDialogActions)(RefundVendorCreditFloatingActions);

View File

@@ -0,0 +1,79 @@
import React from 'react';
import { Formik } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import moment from 'moment';
import { omit, defaultTo } from 'lodash';
import { AppToaster } from 'components';
import { useRefundVendorCreditContext } from './RefundVendorCreditFormProvider';
import { CreateVendorRefundCreditFormSchema } from './RefundVendorCreditForm.schema';
import RefundVendorCreditFormContent from './RefundVendorCreditFormContent';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, transactionNumber } from 'utils';
const defaultInitialValues = {
deposit_account_id: '',
date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '',
description: '',
amount: '',
};
/**
* Refund Vendor credit form.
*/
function RefundVendorCreditForm({
// #withDialogActions
closeDialog,
}) {
const { vendorCredit, dialogName, createRefundVendorCreditMutate } =
useRefundVendorCreditContext();
// Initial form values
const initialValues = {
...defaultInitialValues,
...vendorCredit,
};
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setFieldError }) => {
const form = {
...omit(values, ['currency_code', 'credits_remaining']),
};
// Handle request response success.
const onSaved = (response) => {
AppToaster.show({
message: intl.get('refund_vendor_credit.dialog.success_message'),
intent: Intent.SUCCESS,
});
closeDialog(dialogName);
};
// Handle request response errors.
const onError = ({
response: {
data: { errors },
},
}) => {
setSubmitting(false);
};
createRefundVendorCreditMutate([vendorCredit.id, form])
.then(onSaved)
.catch(onError);
};
return (
<Formik
validationSchema={CreateVendorRefundCreditFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={RefundVendorCreditFormContent}
/>
);
}
export default compose(withDialogActions)(RefundVendorCreditForm);

View File

@@ -0,0 +1,14 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
import { DATATYPES_LENGTH } from 'common/dataTypes';
const Schema = Yup.object().shape({
date: Yup.date().required().label(intl.get('date')),
amount: Yup.number().required(),
reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING).nullable(),
deposit_account_id: Yup.number()
.required()
.label(intl.get('deposit_account_')),
description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
});
export const CreateVendorRefundCreditFormSchema = Schema;

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { Form } from 'formik';
import RefundVendorCreditFormFields from './RefundVendorCreditFormFields';
import RefundVendorCreditFloatingActions from './RefundVendorCreditFloatingActions';
export default function RefundVendorCreditFormContent() {
return (
<Form>
<RefundVendorCreditFormFields />
<RefundVendorCreditFloatingActions />
</Form>
);
}

View File

@@ -0,0 +1,167 @@
import React from 'react';
import intl from 'react-intl-universal';
import { FastField, ErrorMessage } from 'formik';
import {
Classes,
FormGroup,
InputGroup,
TextArea,
Position,
ControlGroup,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import { DateInput } from '@blueprintjs/datetime';
import {
Icon,
FieldRequiredHint,
AccountsSuggestField,
InputPrependText,
MoneyInputGroup,
FormattedMessage as T,
} from 'components';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
compose,
} from 'utils';
import { useAutofocus } from 'hooks';
import { ACCOUNT_TYPE } from 'common/accountTypes';
import { useRefundVendorCreditContext } from './RefundVendorCreditFormProvider';
import withSettings from 'containers/Settings/withSettings';
/**
* Refund Vendor credit form fields.
*/
function RefundVendorCreditFormFields() {
const { accounts } = useRefundVendorCreditContext();
const amountFieldRef = useAutofocus();
return (
<div className={Classes.DIALOG_BODY}>
{/* ------------- Refund date ------------- */}
<FastField name={'refund_date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_vendor_credit.dialog.refund_date'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="refund_date" />}
inline={true}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
value={tansformDateValue(value)}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('refund_date', formattedDate);
})}
popoverProps={{ position: Position.BOTTOM, minimal: true }}
inputProps={{
leftIcon: <Icon icon={'date-range'} />,
}}
/>
</FormGroup>
)}
</FastField>
{/* ------------- Amount ------------- */}
<FastField name={'amount'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'refund_vendor_credit.dialog.amount'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--amount', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="amount" />}
inline={true}
>
<ControlGroup>
<InputPrependText text={values.currency_code} />
<MoneyInputGroup
value={value}
minimal={true}
onChange={(amount) => {
setFieldValue('amount', amount);
}}
intent={inputIntent({ error, touched })}
inputRef={(ref) => (amountFieldRef.current = ref)}
/>
</ControlGroup>
</FormGroup>
)}
</FastField>
{/* ------------ Reference No. ------------ */}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
className={classNames('form-group--reference', CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference" />}
inline={true}
>
<InputGroup
intent={inputIntent({ error, touched })}
minimal={true}
{...field}
/>
</FormGroup>
)}
</FastField>
{/* ------------ Form account ------------ */}
<FastField name={'deposit_account_id'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_vendor_credit.dialog.deposit_to_account'} />}
className={classNames(
'form-group--deposit_account_id',
'form-group--select-list',
CLASSES.FILL,
)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'deposit_account_id'} />}
inline={true}
>
<AccountsSuggestField
selectedAccountId={value}
accounts={accounts}
onAccountSelected={({ id }) =>
form.setFieldValue('deposit_account_id', id)
}
inputProps={{
placeholder: intl.get('select_account'),
}}
filterByTypes={[
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.FIXED_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
{/* --------- Statement --------- */}
<FastField name={'description'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'refund_vendor_credit.dialog.description'} />}
className={'form-group--description'}
inline={true}
>
<TextArea growVertically={true} {...field} />
</FormGroup>
)}
</FastField>
</div>
);
}
export default RefundVendorCreditFormFields;

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { DialogContent } from 'components';
import { pick } from 'lodash';
import {
useAccounts,
useVendorCredit,
useCreateRefundVendorCredit,
} from 'hooks/query';
const RefundVendorCreditContext = React.createContext();
function RefundVendorCreditFormProvider({
vendorCreditId,
dialogName,
...props
}) {
// Handle fetch accounts data.
const { data: accounts, isLoading: isAccountsLoading } = useAccounts();
// Handle fetch vendor credit details.
const { data: vendorCredit, isLoading: isVendorCreditLoading } =
useVendorCredit(vendorCreditId, {
enabled: !!vendorCreditId,
});
// Create refund vendor credit mutations.
const { mutateAsync: createRefundVendorCreditMutate } =
useCreateRefundVendorCredit();
// State provider.
const provider = {
vendorCredit: {
...pick(vendorCredit, ['id', 'credits_remaining', 'currency_code']),
amount: vendorCredit.credits_remaining,
},
accounts,
dialogName,
createRefundVendorCreditMutate,
};
return (
<DialogContent isLoading={isAccountsLoading || isVendorCreditLoading}>
<RefundVendorCreditContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useRefundVendorCreditContext = () =>
React.useContext(RefundVendorCreditContext);
export { RefundVendorCreditFormProvider, useRefundVendorCreditContext };

View File

@@ -0,0 +1,38 @@
import React from 'react';
import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'redux';
const RefundVendorCreditDialogContent = React.lazy(() =>
import('./RefundVendorCreditDialogContent'),
);
/**
* Refund vendor credit dialog.
*/
function RefundVendorCreditDialog({
dialogName,
payload: { vendorCreditId },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={<T id={'refund_vendor_credit.dialog.label'} />}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
className={'dialog--refund-vendor-credit'}
>
<DialogSuspense>
<RefundVendorCreditDialogContent
dialogName={dialogName}
vendorCreditId={vendorCreditId}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(RefundVendorCreditDialog);

View File

@@ -4,6 +4,8 @@ import intl from 'react-intl-universal';
import { DrawerMainTabs } from 'components';
import CreditNoteDetailPanel from './CreditNoteDetailPanel';
import RefundCreditNoteTransactionsTable from './RefundCreditNoteTransactions/RefundCreditNoteTransactionsTable';
import ReconcileCreditNoteTransactionsTable from './ReconcileCreditNoteTransactions/ReconcileCreditNoteTransactionsTable';
import clsx from 'classnames';
import CreditNoteDetailCls from '../../../style/components/Drawers/CreditNoteDetails.module.scss';
@@ -20,6 +22,16 @@ export default function CreditNoteDetail() {
id={'details'}
panel={<CreditNoteDetailPanel />}
/>
<Tab
title={intl.get('credit_note.drawer.label_refund_transactions')}
id={'refund_transactions'}
panel={<RefundCreditNoteTransactionsTable />}
/>
<Tab
title={intl.get('credit_note.drawer.label_reconcile_transactions')}
id={'reconcile_transactions'}
panel={<ReconcileCreditNoteTransactionsTable />}
/>
</DrawerMainTabs>
</div>
);

View File

@@ -15,7 +15,13 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T, MoreMenuItems, Can } from 'components';
import {
Icon,
FormattedMessage as T,
If,
MoreMenuItems,
Can,
} from 'components';
import { compose } from 'utils';
@@ -32,7 +38,7 @@ function CreditNoteDetailActionsBar({
// #withDrawerActions
closeDrawer,
}) {
const { creditNoteId } = useCreditNoteDetailDrawerContext();
const { creditNoteId, creditNote } = useCreditNoteDetailDrawerContext();
const history = useHistory();
@@ -42,6 +48,10 @@ function CreditNoteDetailActionsBar({
closeDrawer('credit-note-detail-drawer');
};
const handleRefundCreditNote = () => {
openDialog('refund-credit-note', { creditNoteId });
};
// Handle delete credit note.
const handleDeleteCreditNote = () => {
openAlert('credit-note-delete', { creditNoteId });
@@ -57,6 +67,15 @@ function CreditNoteDetailActionsBar({
onClick={handleEditCreditNote}
/>
<NavbarDivider />
<If condition={!creditNote.is_closed && !creditNote.is_draft}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="quick-payment-16" iconSize={16} />}
text={<T id={'refund'} />}
onClick={handleRefundCreditNote}
/>
<NavbarDivider />
</If>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}

View File

@@ -1,6 +1,10 @@
import React from 'react';
import intl from 'react-intl-universal';
import { useCreditNote } from 'hooks/query';
import {
useCreditNote,
useRefundCreditNote,
useReconcileCreditNotes,
} from 'hooks/query';
import { DrawerHeaderContent, DrawerLoading } from 'components';
const CreditNoteDetailDrawerContext = React.createContext();
@@ -17,13 +21,42 @@ function CreditNoteDetailDrawerProvider({ creditNoteId, ...props }) {
},
);
// Handle fetch refund credit note.
const {
data: refundCreditNote,
isFetching: isRefundCreditNoteFetching,
isLoading: isRefundCreditNoteLoading,
} = useRefundCreditNote(creditNoteId, {
enabled: !!creditNoteId,
});
// Handle fetch refund credit note.
const {
data: reconcileCreditNotes,
isFetching: isReconcileCreditNoteFetching,
isLoading: isReconcileCreditNoteLoading,
} = useReconcileCreditNotes(creditNoteId, {
enabled: !!creditNoteId,
});
const provider = {
creditNote,
refundCreditNote,
reconcileCreditNotes,
isRefundCreditNoteLoading,
isRefundCreditNoteFetching,
creditNoteId,
};
return (
<DrawerLoading loading={isCreditNoteLoading}>
<DrawerLoading
loading={
isCreditNoteLoading ||
isRefundCreditNoteLoading ||
isReconcileCreditNoteLoading
}
>
<DrawerHeaderContent
name="credit-note-detail-drawer"
title={intl.get('credit_note.drawer_credit_note_detail')}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { DataTable, Card } from 'components';
import '../../../../style/pages/RefundCreditNote/List.scss';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { useCreditNoteDetailDrawerContext } from '../CreditNoteDetailDrawerProvider';
import {
useReconcileCreditTransactionsTableColumns,
ActionsMenu,
} from './components';
import { compose } from 'utils';
/**
* Reconcile credit transactions table.
*/
function RefundCreditNoteTransactionsTable({
// #withAlertsActions
openAlert,
}) {
const { reconcileCreditNotes } = useCreditNoteDetailDrawerContext();
const columns = useReconcileCreditTransactionsTableColumns();
// Handle delete reconile credit.
const handleDeleteReconcileCreditNote = ({ id }) => {
openAlert('reconcile-credit-delete', { creditNoteId: id });
};
return (
<Card>
<DataTable
columns={columns}
data={reconcileCreditNotes}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteReconcileCreditNote,
}}
className={'datatable--refund-transactions'}
/>
</Card>
);
}
export default compose(withAlertsActions)(RefundCreditNoteTransactionsTable);

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { FormatDateCell, Icon } from 'components';
import { safeCallback } from 'utils';
/**
* Actions menu.
*/
export function ActionsMenu({ payload: { onDelete }, row: { original } }) {
return (
<Menu>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Menu>
);
}
export function useReconcileCreditTransactionsTableColumns() {
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: 'formatted_credit_note_date',
Cell: FormatDateCell,
width: 100,
className: 'date',
},
{
Header: intl.get('invoice_no'),
accessor: 'invoice_number',
width: 100,
className: 'invoice_number',
},
{
Header: intl.get('amount'),
accessor: 'formtted_amount',
width: 100,
className: 'amount',
align: 'right',
},
],
[],
);
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { DataTable, Card } from 'components';
import '../../../../style/pages/RefundCreditNote/List.scss';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { useCreditNoteDetailDrawerContext } from '../CreditNoteDetailDrawerProvider';
import {
useRefundCreditTransactionsTableColumns,
ActionsMenu,
} from './components';
import { compose } from 'utils';
/**
* Refund credit note transactions table.
*/
function RefundCreditNoteTransactionsTable({
// #withAlertsActions
openAlert,
}) {
const { refundCreditNote } = useCreditNoteDetailDrawerContext();
const columns = useRefundCreditTransactionsTableColumns();
// Handle delete refund credit.
const handleDeleteRefundCreditNote = ({ id }) => {
openAlert('refund-credit-delete', { creditNoteId: id });
};
return (
<Card>
<DataTable
columns={columns}
data={refundCreditNote}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteRefundCreditNote,
}}
className={'datatable--refund-transactions'}
/>
</Card>
);
}
export default compose(withAlertsActions)(RefundCreditNoteTransactionsTable);

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { FormatDateCell, Icon } from 'components';
import { safeCallback } from 'utils';
/**
* Actions menu.
*/
export function ActionsMenu({ payload: { onDelete }, row: { original } }) {
return (
<Menu>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Menu>
);
}
export function useRefundCreditTransactionsTableColumns() {
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: 'date',
Cell: FormatDateCell,
width: 100,
className: 'date',
},
{
Header: intl.get('refund_credit_transactions.column.amount_refunded'),
accessor: 'amount',
width: 100,
className: 'amount',
align: 'right',
},
{
Header: intl.get(
'refund_credit_transactions.column.withdrawal_account',
),
accessor: ({ from_account }) => from_account.name,
width: 100,
className: 'from_account',
},
{
id: 'reference_no',
Header: intl.get('reference_no'),
accessor: 'reference_no',
width: 100,
className: 'reference_no',
textOverview: true,
},
],
[],
);
}

View File

@@ -0,0 +1,46 @@
import React from 'react';
import { DataTable, Card } from 'components';
import 'style/pages/RefundVendorCredit/List.scss';
import withAlertsActions from 'containers/Alert/withAlertActions';
import { useVendorCreditDetailDrawerContext } from '../VendorCreditDetailDrawerProvider';
import {
useRefundCreditTransactionsTableColumns,
ActionsMenu,
} from './components';
import { compose } from 'utils';
/**
* Refund vendor transactions table.
*/
function RefundVendorCreditTransactionsTable({
// #withAlertsActions
openAlert,
}) {
const { refundVendorCredit } = useVendorCreditDetailDrawerContext();
const columns = useRefundCreditTransactionsTableColumns();
// Handle delete refund vendor credit.
const handleDeleteRefundVendorCredit = ({ id }) => {
openAlert('refund-vendor-delete', { vendorCreditId: id });
};
return (
<Card>
<DataTable
columns={columns}
data={refundVendorCredit}
ContextMenu={ActionsMenu}
payload={{
onDelete: handleDeleteRefundVendorCredit,
}}
className={'datatable--refund-transactions'}
/>
</Card>
);
}
export default compose(withAlertsActions)(RefundVendorCreditTransactionsTable);

View File

@@ -0,0 +1,57 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { FormatDateCell, Icon } from 'components';
import { safeCallback } from 'utils';
/**
* Actions menu.
*/
export function ActionsMenu({ payload: { onDelete }, row: { original } }) {
return (
<Menu>
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_transaction')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Menu>
);
}
export function useRefundCreditTransactionsTableColumns() {
return React.useMemo(
() => [
{
Header: intl.get('date'),
accessor: 'date',
Cell: FormatDateCell,
width: 100,
className: 'date',
},
{
Header: intl.get('refund_vendor_credit.column.amount'),
accessor: 'amount',
width: 100,
className: 'amount',
align: 'right',
},
{
Header: intl.get('refund_vendor_credit.column.withdrawal_account'),
accessor: ({ from_account }) => from_account.name,
width: 100,
className: 'from_account',
},
{
id: 'reference_no',
Header: intl.get('reference_no'),
accessor: 'reference_no',
width: 100,
className: 'reference_no',
textOverview: true,
},
],
[],
);
}

View File

@@ -4,6 +4,7 @@ import intl from 'react-intl-universal';
import { DrawerMainTabs } from 'components';
import VendorCreditDetailPanel from './VendorCreditDetailPanel';
import RefundVendorCreditTransactionsTable from './RefundVendorCreditTransactions/RefundVendorCreditTransactionsTable';
import clsx from 'classnames';
import VendorCreditDetailCls from '../../../style/components/Drawers/VendorCreditDetail.module.scss';
@@ -20,6 +21,11 @@ export default function VendorCreditDetail() {
id={'details'}
panel={<VendorCreditDetailPanel />}
/>
<Tab
title={intl.get('credit_note.drawer.label_refund_transactions')}
id={'refund_transactions'}
panel={<RefundVendorCreditTransactionsTable />}
/>
</DrawerMainTabs>
</div>
);

View File

@@ -15,7 +15,7 @@ import withDialogActions from 'containers/Dialog/withDialogActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import { Icon, FormattedMessage as T, Can } from 'components';
import { If, Icon, FormattedMessage as T, Can } from 'components';
import { compose } from 'utils';
@@ -32,7 +32,7 @@ function VendorCreditDetailActionsBar({
// #withDrawerActions
closeDrawer,
}) {
const { vendorCreditId } = useVendorCreditDetailDrawerContext();
const { vendorCreditId, vendorCredit } = useVendorCreditDetailDrawerContext();
const history = useHistory();
@@ -47,6 +47,10 @@ function VendorCreditDetailActionsBar({
openAlert('vendor-credit-delete', { vendorCreditId });
};
const handleRefundVendorCredit = () => {
openDialog('refund-vendor-credit', { vendorCreditId });
};
return (
<DashboardActionsBar>
<NavbarGroup>
@@ -57,6 +61,15 @@ function VendorCreditDetailActionsBar({
onClick={handleEditVendorCredit}
/>
<NavbarDivider />
<If condition={!vendorCredit.is_closed && !vendorCredit.is_draft}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="quick-payment-16" iconSize={16} />}
text={<T id={'refund'} />}
onClick={handleRefundVendorCredit}
/>
<NavbarDivider />
</If>
<Button
className={Classes.MINIMAL}
icon={<Icon icon={'trash-16'} iconSize={16} />}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import intl from 'react-intl-universal';
import { useVendorCredit } from 'hooks/query';
import { useVendorCredit, useRefundVendorCredit } from 'hooks/query';
import { DrawerHeaderContent, DrawerLoading } from 'components';
const VendorCreditDetailDrawerContext = React.createContext();
@@ -15,13 +15,27 @@ function VendorCreditDetailDrawerProvider({ vendorCreditId, ...props }) {
enabled: !!vendorCreditId,
});
// Handle fetch refund credit note.
const {
data: refundVendorCredit,
isFetching: isRefundVendorCreditFetching,
isLoading: isRefundVendorCreditLoading,
} = useRefundVendorCredit(vendorCreditId, {
enabled: !!vendorCreditId,
});
const provider = {
vendorCredit,
refundVendorCredit,
isRefundVendorCreditLoading,
isRefundVendorCreditFetching,
vendorCreditId,
};
return (
<DrawerLoading loading={isVendorCreditLoading}>
<DrawerLoading
loading={isVendorCreditLoading || isRefundVendorCreditLoading}
>
<DrawerHeaderContent
name="vendor-credit-detail-drawer"
title={intl.get('vendor_credit.drawer_vendor_credit_detail')}

View File

@@ -26,35 +26,40 @@ export default function VendorCreditNoteFloatingActions() {
const { resetForm, submitForm, isSubmitting } = useFormikContext();
// Credit note form context.
const { setSubmitPayload, isNewMode } = useVendorCreditNoteFormContext();
const { setSubmitPayload, vendorCredit } = useVendorCreditNoteFormContext();
// Handle submit, save and anothe new button click.
const handleSubmitAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: true, resetForm: true });
// Handle submit as open button click.
const handleSubmitOpenBtnClick = (event) => {
setSubmitPayload({ redirect: true, open: true });
submitForm();
};
// Handle submit as save & continue editing button click.
const handleSubmitSaveContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: true });
// Handle submit, open and anothe new button click.
const handleSubmitOpenAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, open: true, resetForm: true });
submitForm();
};
// Handle submit as open & continue editing button click.
const handleSubmitOpenContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, open: true });
submitForm();
};
// Handle submit as draft button click.
const handleSubmitDraftBtnClick = (event) => {
setSubmitPayload({ redirect: true, status: false });
setSubmitPayload({ redirect: true, open: false });
submitForm();
};
// handle submit as draft & new button click.
const handleSubmitDraftAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: false, resetForm: true });
setSubmitPayload({ redirect: false, open: false, resetForm: true });
submitForm();
};
// Handle submit as draft & continue editing button click.
const handleSubmitDraftContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: false });
setSubmitPayload({ redirect: false, open: false });
submitForm();
};
@@ -63,89 +68,113 @@ export default function VendorCreditNoteFloatingActions() {
history.goBack();
};
// Handle submit button click.
const handleSubmitBtnClick = (event) => {
setSubmitPayload({ redirect: true });
submitForm();
};
const handleClearBtnClick = (event) => {
resetForm();
};
return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
{/* ----------- Save ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitBtnClick}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitSaveContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
{/* ----------- Save And Open ----------- */}
<If condition={!vendorCredit || !vendorCredit?.is_open}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
onClick={handleSubmitOpenBtnClick}
text={<T id={'save_open'} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'open_and_new'} />}
onClick={handleSubmitOpenAndNewBtnClick}
/>
<MenuItem
text={<T id={'open_continue_editing'} />}
onClick={handleSubmitOpenContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
</Popover>
</ButtonGroup>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={vendorCredit && vendorCredit?.is_open}>
<ButtonGroup>
<Button
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitOpenBtnClick}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitOpenAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
text={vendorCredit ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { useHistory } from 'react-router-dom';
import { Formik, Form } from 'formik';
import { Button, Intent } from '@blueprintjs/core';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { sumBy, omit, isEmpty } from 'lodash';
import { isEmpty } from 'lodash';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import {
@@ -95,6 +95,7 @@ function VendorCreditNoteForm({
}
const form = {
...transformFormValuesToRequest(values),
open: submitPayload.open,
};
// Handle the request success.
const onSuccess = (response) => {

View File

@@ -15,6 +15,7 @@ const getSchema = Yup.object().shape({
.min(1)
.max(DATATYPES_LENGTH.TEXT)
.label(intl.get('note')),
open: Yup.boolean(),
entries: Yup.array().of(
Yup.object().shape({
quantity: Yup.number()

View File

@@ -33,6 +33,7 @@ export const defaultVendorsCreditNote = {
vendor_id: '',
vendor_credit_number: '',
vendor_credit_no_manually: false,
open: '',
vendor_credit_date: moment(new Date()).format('YYYY-MM-DD'),
// reference_no: '',
note: '',
@@ -93,6 +94,7 @@ export const transformFormValuesToRequest = (values) => {
return {
...values,
entries: transformEntriesToSubmit(entries),
open: false,
};
};
@@ -119,7 +121,7 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
/**
* Syncs invoice no. settings with form.
*/
export const useObserveVendorCreditNoSettings = (prefix, nextNumber) => {
export const useObserveVendorCreditNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
React.useEffect(() => {

View File

@@ -13,6 +13,7 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withVendorsCreditNotesActions from './withVendorsCreditNotesActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from '../../../Settings/withSettings';
import { useVendorsCreditNoteTableColumns, ActionsMenu } from './components';
@@ -33,6 +34,9 @@ function VendorsCreditNoteDataTable({
// #withDrawerActions
openDrawer,
// #withDialogAction
openDialog,
// #withSettings
creditNoteTableSize,
}) {
@@ -92,6 +96,20 @@ function VendorsCreditNoteDataTable({
});
};
const handleRefundCreditVendor = ({ id }) => {
openDialog('refund-vendor-credit', { vendorCreditId: id });
};
// Handle cancel/confirm vendor credit open.
const handleOpenCreditNote = ({ id }) => {
openAlert('vendor-credit-open', { vendorCreditId: id });
};
// Handle reconcile credit note.
const handleReconcileVendorCredit = ({ id }) => {
openDialog('reconcile-vendor-credit', { vendorCreditId: id });
};
return (
<DashboardContentTable>
<DataTable
@@ -118,6 +136,9 @@ function VendorsCreditNoteDataTable({
onViewDetails: handleViewDetailVendorCredit,
onDelete: handleDeleteVendorCreditNote,
onEdit: hanldeEditVendorCreditNote,
onRefund: handleRefundCreditVendor,
onOpen: handleOpenCreditNote,
onReconcile: handleReconcileVendorCredit,
}}
/>
</DashboardContentTable>
@@ -129,6 +150,7 @@ export default compose(
withVendorsCreditNotesActions,
withAlertsActions,
withDrawerActions,
withDialogActions,
withSettings(({ vendorsCreditNoteSetting }) => ({
creditNoteTableSize: vendorsCreditNoteSetting?.tableSize,
})),

View File

@@ -1,12 +1,5 @@
import React from 'react';
import {
Intent,
Tag,
Menu,
MenuItem,
MenuDivider,
ProgressBar,
} from '@blueprintjs/core';
import { Intent, Tag, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import clsx from 'classnames';
@@ -14,18 +7,17 @@ import { CLASSES } from '../../../../common/classes';
import {
FormatDateCell,
FormattedMessage as T,
AppToaster,
Choose,
If,
Icon,
} from 'components';
import { formattedAmount, safeCallback, calculateStatus } from 'utils';
import { safeCallback } from 'utils';
/**
* Actions menu.
*/
export function ActionsMenu({
payload: { onEdit, onDelete, onViewDetails },
payload: { onEdit, onDelete, onOpen, onRefund, onReconcile, onViewDetails },
row: { original },
}) {
return (
@@ -41,6 +33,26 @@ export function ActionsMenu({
text={intl.get('vendor_credits.action.edit_vendor_credit')}
onClick={safeCallback(onEdit, original)}
/>
<If condition={!original.is_closed && !original.is_draft}>
<MenuItem
icon={<Icon icon="quick-payment-16" />}
text={intl.get('vendor_credits.action.refund_vendor_credit')}
onClick={safeCallback(onRefund, original)}
/>
</If>
<If condition={original.is_draft}>
<MenuItem
icon={<Icon icon={'check'} iconSize={18} />}
text={intl.get('mark_as_opened')}
onClick={safeCallback(onOpen, original)}
/>
</If>
<MenuItem
text={'Reconcile Credit Note With bills'}
// icon={<Icon icon="quick-payment-16" />}
// text={intl.get('credit_note.action.refund_credit_note')}
onClick={safeCallback(onReconcile, original)}
/>
<MenuItem
text={intl.get('vendor_credits.action.delete_vendor_credit')}
intent={Intent.DANGER}
@@ -51,6 +63,35 @@ export function ActionsMenu({
);
}
/**
* Status accessor.
*/
export function StatusAccessor(creditNote) {
return (
<div>
<Choose>
<Choose.When condition={creditNote.is_open}>
<Tag minimal={true} intent={Intent.WARNING}>
<T id={'open'} />
</Tag>
</Choose.When>
<Choose.When condition={creditNote.is_closed}>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'closed'} />
</Tag>
</Choose.When>
<Choose.When condition={creditNote.is_draft}>
<Tag minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.When>
</Choose>
</div>
);
}
/**
* Retrieve vendors credit note table columns.
*/
@@ -98,8 +139,8 @@ export function useVendorsCreditNoteTableColumns() {
{
id: 'status',
Header: intl.get('status'),
// accessor:
width: 120, // 160
accessor: StatusAccessor,
width: 160,
className: 'status',
clickable: true,
},

View File

@@ -4,6 +4,14 @@ const VendorCreditDeleteAlert = React.lazy(() =>
import('../../Alerts/VendorCeditNotes/VendorCreditDeleteAlert'),
);
const RefundVendorCreditDeleteAlert = React.lazy(() =>
import('../../Alerts/VendorCeditNotes/RefundVendorCreditDeleteAlert'),
);
const OpenVendorCreditAlert = React.lazy(() =>
import('../../Alerts/VendorCeditNotes/VendorCreditOpenedAlert'),
);
/**
* Vendor Credit notes alerts.
*/
@@ -12,4 +20,12 @@ export default [
name: 'vendor-credit-delete',
component: VendorCreditDeleteAlert,
},
{
name: 'vendor-credit-open',
component: OpenVendorCreditAlert,
},
{
name: 'refund-vendor-delete',
component: RefundVendorCreditDeleteAlert,
},
];

View File

@@ -26,35 +26,40 @@ export default function CreditNoteFloatingActions() {
const { resetForm, submitForm, isSubmitting } = useFormikContext();
// Credit note form context.
const { setSubmitPayload, isNewMode } = useCreditNoteFormContext();
const { setSubmitPayload, creditNote } = useCreditNoteFormContext();
// Handle submit, save and anothe new button click.
const handleSubmitAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: true, resetForm: true });
// Handle submit as open button click.
const handleSubmitOpenBtnClick = (event) => {
setSubmitPayload({ redirect: true, open: true });
submitForm();
};
// Handle submit as save & continue editing button click.
const handleSubmitSaveContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: true });
// Handle submit, open and anothe new button click.
const handleSubmitOpenAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, open: true, resetForm: true });
submitForm();
};
// Handle submit as open & continue editing button click.
const handleSubmitOpenContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, open: true });
submitForm();
};
// Handle submit as draft button click.
const handleSubmitDraftBtnClick = (event) => {
setSubmitPayload({ redirect: true, status: false });
setSubmitPayload({ redirect: true, open: false });
submitForm();
};
// handle submit as draft & new button click.
const handleSubmitDraftAndNewBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: false, resetForm: true });
setSubmitPayload({ redirect: false, open: false, resetForm: true });
submitForm();
};
// Handle submit as draft & continue editing button click.
const handleSubmitDraftContinueEditingBtnClick = (event) => {
setSubmitPayload({ redirect: false, status: false });
setSubmitPayload({ redirect: false, open: false });
submitForm();
};
@@ -63,89 +68,114 @@ export default function CreditNoteFloatingActions() {
history.goBack();
};
// Handle submit button click.
const handleSubmitBtnClick = (event) => {
setSubmitPayload({ redirect: true });
submitForm();
};
const handleClearBtnClick = (event) => {
resetForm();
};
return (
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
{/* ----------- Save ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitBtnClick}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitSaveContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
{/* ----------- Save And Open ----------- */}
<If condition={!creditNote || !creditNote?.is_open}>
<ButtonGroup>
<Button
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
onClick={handleSubmitOpenBtnClick}
text={<T id={'save_open'} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'open_and_new'} />}
onClick={handleSubmitOpenAndNewBtnClick}
/>
<MenuItem
text={<T id={'open_continue_editing'} />}
onClick={handleSubmitOpenContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
className={'ml1'}
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
</Popover>
</ButtonGroup>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitDraftAndNewBtnClick}
/>
<MenuItem
text={<T id={'save_continue_editing'} />}
onClick={handleSubmitDraftContinueEditingBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={creditNote && creditNote?.is_open}>
<ButtonGroup>
<Button
loading={isSubmitting}
intent={Intent.PRIMARY}
onClick={handleSubmitOpenBtnClick}
text={<T id={'save'} />}
/>
<Popover
content={
<Menu>
<MenuItem
text={<T id={'save_and_new'} />}
onClick={handleSubmitOpenAndNewBtnClick}
/>
</Menu>
}
minimal={true}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
rightIcon={<Icon icon="arrow-drop-up-16" iconSize={20} />}
/>
</Popover>
</ButtonGroup>
</If>
{/* ----------- Clear & Reset----------- */}
<Button
className={'ml1'}
disabled={isSubmitting}
onClick={handleClearBtnClick}
text={isNewMode ? <T id={'reset'} /> : <T id={'clear'} />}
text={creditNote ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button

View File

@@ -3,7 +3,7 @@ import { useHistory } from 'react-router-dom';
import { Formik, Form } from 'formik';
import { Intent } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import { sumBy, omit, isEmpty } from 'lodash';
import { isEmpty } from 'lodash';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
import {
@@ -97,6 +97,7 @@ function CreditNoteForm({
}
const form = {
...transformFormValuesToRequest(values),
open: submitPayload.open,
};
// Handle the request success.
const onSuccess = (response) => {

View File

@@ -16,6 +16,7 @@ const getSchema = () =>
.min(1)
.max(DATATYPES_LENGTH.TEXT)
.label(intl.get('note')),
open: Yup.boolean(),
terms_conditions: Yup.string()
.trim()
.min(1)

View File

@@ -35,6 +35,7 @@ export const defaultCreditNote = {
credit_note_date: moment(new Date()).format('YYYY-MM-DD'),
credit_note_number: '',
credit_note_no_manually: false,
open: '',
// reference_no: '',
note: '',
terms_conditions: '',
@@ -82,19 +83,20 @@ export const transformEntriesToSubmit = (entries) => {
/**
* Filters the givne non-zero entries.
*/
export const filterNonZeroEntries = (entries) => {
export const filterNonZeroEntries = (entries) => {
return entries.filter((item) => item.item_id && item.quantity);
};
/**
* Transformes form values to request body.
*/
export const transformFormValuesToRequest = (values) => {
export const transformFormValuesToRequest = (values) => {
const entries = filterNonZeroEntries(values.entries);
return {
...values,
entries: transformEntriesToSubmit(entries),
open: false,
};
};
@@ -121,7 +123,7 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
/**
* Syncs invoice no. settings with form.
*/
export const useObserveCreditNoSettings = (prefix, nextNumber) => {
export const useObserveCreditNoSettings = (prefix, nextNumber) => {
const { setFieldValue } = useFormikContext();
React.useEffect(() => {

View File

@@ -4,6 +4,18 @@ const CreditNoteDeleteAlert = React.lazy(() =>
import('../../Alerts/CreditNotes/CreditNoteDeleteAlert'),
);
const RefundCreditNoteDeleteAlert = React.lazy(() =>
import('../../Alerts/CreditNotes/RefundCreditNoteDeleteAlert'),
);
const OpenCreditNoteAlert = React.lazy(() =>
import('../../Alerts/CreditNotes/CreditNoteOpenedAlert'),
);
const ReconcileCreditDeleteAlert = React.lazy(() =>
import('../../Alerts/CreditNotes/ReconcileCreditNoteDeleteAlert'),
);
/**
* Credit notes alerts.
*/
@@ -12,4 +24,16 @@ export default [
name: 'credit-note-delete',
component: CreditNoteDeleteAlert,
},
{
name: 'credit-note-open',
component: OpenCreditNoteAlert,
},
{
name: 'refund-credit-delete',
component: RefundCreditNoteDeleteAlert,
},
{
name: 'reconcile-credit-delete',
component: ReconcileCreditDeleteAlert,
},
];

View File

@@ -13,6 +13,7 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withCreditNotesActions from './withCreditNotesActions';
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withSettings from '../../../Settings/withSettings';
import { useCreditNoteTableColumns, ActionsMenu } from './components';
@@ -33,6 +34,9 @@ function CreditNotesDataTable({
// #withDrawerActions
openDrawer,
// #withDialogAction
openDialog,
// #withSettings
creditNoteTableSize,
}) {
@@ -92,6 +96,20 @@ function CreditNotesDataTable({
});
};
const handleRefundCreditNote = ({ id }) => {
openDialog('refund-credit-note', { creditNoteId: id });
};
// Handle cancel/confirm crdit note open.
const handleOpenCreditNote = ({ id }) => {
openAlert('credit-note-open', { creditNoteId: id });
};
// Handle reconcile credit note.
const handleReconcileCreditNote = ({ id }) => {
openDialog('reconcile-credit-note', { creditNoteId: id });
};
return (
<DashboardContentTable>
<DataTable
@@ -117,6 +135,9 @@ function CreditNotesDataTable({
onViewDetails: handleViewDetailCreditNote,
onDelete: handleDeleteCreditNote,
onEdit: hanldeEditCreditNote,
onRefund: handleRefundCreditNote,
onOpen: handleOpenCreditNote,
onReconcile: handleReconcileCreditNote,
}}
/>
</DashboardContentTable>
@@ -128,6 +149,7 @@ export default compose(
withCreditNotesActions,
withDrawerActions,
withAlertsActions,
withDialogActions,
withSettings(({ creditNoteSettings }) => ({
creditNoteTableSize: creditNoteSettings?.tableSize,
})),

View File

@@ -1,12 +1,5 @@
import React from 'react';
import {
Intent,
Tag,
Menu,
MenuItem,
MenuDivider,
ProgressBar,
} from '@blueprintjs/core';
import { Intent, Tag, Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import clsx from 'classnames';
@@ -14,15 +7,14 @@ import { CLASSES } from '../../../../common/classes';
import {
FormatDateCell,
FormattedMessage as T,
AppToaster,
Choose,
If,
Icon,
} from 'components';
import { formattedAmount, safeCallback, calculateStatus } from 'utils';
import { safeCallback } from 'utils';
export function ActionsMenu({
payload: { onEdit, onDelete, onViewDetails },
payload: { onEdit, onDelete, onRefund, onOpen, onReconcile, onViewDetails },
row: { original },
}) {
return (
@@ -38,6 +30,26 @@ export function ActionsMenu({
text={intl.get('credit_note.action.edit_credit_note')}
onClick={safeCallback(onEdit, original)}
/>
<If condition={!original.is_closed && !original.is_draft}>
<MenuItem
icon={<Icon icon="quick-payment-16" />}
text={intl.get('credit_note.action.refund_credit_note')}
onClick={safeCallback(onRefund, original)}
/>
</If>
<If condition={original.is_draft}>
<MenuItem
icon={<Icon icon={'check'} iconSize={18} />}
text={intl.get('mark_as_opened')}
onClick={safeCallback(onOpen, original)}
/>
</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
text={intl.get('credit_note.action.delete_credit_note')}
intent={Intent.DANGER}
@@ -48,6 +60,35 @@ export function ActionsMenu({
);
}
/**
* Status accessor.
*/
export function StatusAccessor(creditNote) {
return (
<div>
<Choose>
<Choose.When condition={creditNote.is_open}>
<Tag minimal={true} intent={Intent.WARNING}>
<T id={'open'} />
</Tag>
</Choose.When>
<Choose.When condition={creditNote.is_closed}>
<Tag minimal={true} intent={Intent.SUCCESS}>
<T id={'closed'} />
</Tag>
</Choose.When>
<Choose.When condition={creditNote.is_draft}>
<Tag minimal={true}>
<T id={'draft'} />
</Tag>
</Choose.When>
</Choose>
</div>
);
}
/**
* Retrieve credit note table columns.
*/
@@ -95,8 +136,8 @@ export function useCreditNoteTableColumns() {
{
id: 'status',
Header: intl.get('status'),
// accessor:
width: 120, // 160
accessor: StatusAccessor,
width: 160, // 160
className: 'status',
clickable: true,
},

View File

@@ -0,0 +1,29 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Intent } from '@blueprintjs/core';
import { AppToaster } from 'components';
export const handleDeleteErrors = (errors) => {
if (
errors.find((error) => error.type === 'CREDIT_NOTE_HAS_APPLIED_INVOICES')
) {
AppToaster.show({
message: intl.get(
'credit_note.error.you_couldn_t_delete_credit_note_that_has_associated_invoice',
),
intent: Intent.DANGER,
});
}
if (
errors.find(
(error) => error.type === 'CREDIT_NOTE_HAS_REFUNDS_TRANSACTIONS',
)
) {
AppToaster.show({
message: intl.get(
'credit_note.error.you_couldn_t_delete_credit_note_that_has_associated_refund',
),
intent: Intent.DANGER,
});
}
};

View File

@@ -99,6 +99,18 @@ export const handleDeleteErrors = (errors) => {
intent: Intent.DANGER,
});
}
if (
errors.find(
(error) => error.type === 'SALE_INVOICE_HAS_APPLIED_TO_CREDIT_NOTES',
)
) {
AppToaster.show({
message: intl.get(
'invoices.error.you_couldn_t_delete_sale_invoice_that_has_reconciled',
),
intent: Intent.DANGER,
});
}
};
export function ActionsMenu({

View File

@@ -24,6 +24,13 @@ const commonInvalidateQueries = (queryClient) => {
// Invalidate settings.
queryClient.invalidateQueries([t.SETTING, t.SETTING_CREDIT_NOTES]);
// Invalidate refund credit
queryClient.invalidateQueries(t.REFUND_CREDIT_NOTE);
// Invalidate reconcile.
queryClient.invalidateQueries(t.RECONCILE_CREDIT_NOTE);
queryClient.invalidateQueries(t.RECONCILE_CREDIT_NOTES);
// Invalidate financial reports.
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
};
@@ -143,3 +150,169 @@ export function useRefreshCreditNotes() {
},
};
}
/**
* Create Round creidt note
*/
export function useCreateRefundCreditNote(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
([id, values]) =>
apiRequest.post(`sales/credit_notes/${id}/refund`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
// Invalidate credit note query.
queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
},
...props,
},
);
}
/**
* Delete the given refund credit note.
*/
export function useDeleteRefundCreditNote(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(id) => apiRequest.delete(`sales/credit_notes/refunds/${id}`),
{
onSuccess: (res, id) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
// Invalidate vendor credit query.
queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
},
...props,
},
);
}
/**
* Retrieve refund credit note detail of the given id.
* @param {number} id
*
*/
export function useRefundCreditNote(id, props, requestProps) {
return useRequestQuery(
[t.REFUND_CREDIT_NOTE, id],
{ method: 'get', url: `sales/credit_notes/${id}/refund`, ...requestProps },
{
select: (res) => res.data.data,
defaultData: {},
...props,
},
);
}
/**
* Mark the given credit note as opened.
*/
export function useOpenCreditNote(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation((id) => apiRequest.post(`sales/credit_notes/${id}/open`), {
onSuccess: (res, id) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
// Invalidate specific
queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
},
...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,
},
);
}

View File

@@ -111,11 +111,17 @@ const ROLES = {
const CREDIT_NOTES = {
CREDIT_NOTE: 'CREDIT_NOTE',
CREDIT_NOTES: 'CREDIT_NOTES',
REFUND_CREDIT_NOTE: 'REFUND_CREDIT_NOTE',
RECONCILE_CREDIT_NOTE: 'RECONCILE_CREDIT_NOTE',
RECONCILE_CREDIT_NOTES: 'RECONCILE_CREDIT_NOTES',
};
const VENDOR_CREDIT_NOTES = {
VENDOR_CREDITS: 'VENDOR_CREDITS',
VENDOR_CREDIT: 'VENDOR_CREDIT',
REFUND_VENDOR_CREDIT: 'REFUND_VENDOR_CREDIT',
RECONCILE_VENDOR_CREDIT: 'RECONCILE_VENDOR_CREDIT',
RECONCILE_VENDOR_CREDITS: 'RECONCILE_VENDOR_CREDITS',
};
const SETTING = {

View File

@@ -24,6 +24,13 @@ const commonInvalidateQueries = (queryClient) => {
// Invalidate settings.
queryClient.invalidateQueries([t.SETTING, t.SETTING_VENDOR_CREDITS]);
// Invalidate refund vendor credit
queryClient.invalidateQueries(t.REFUND_VENDOR_CREDIT);
// Invalidate reconcile vendor credit.
queryClient.invalidateQueries(t.RECONCILE_VENDOR_CREDIT);
queryClient.invalidateQueries(t.RECONCILE_VENDOR_CREDITS);
// Invalidate financial reports.
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
};
@@ -150,3 +157,175 @@ export function useRefreshVendorCredits() {
},
};
}
/**
* Create Round vendor creidt
*/
export function useCreateRefundVendorCredit(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
([id, values]) =>
apiRequest.post(`purchases/vendor-credit/${id}/refund`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
// Invalidate credit note query.
queryClient.invalidateQueries([t.VENDOR_CREDIT, id]);
},
...props,
},
);
}
/**
* Delete the given refund vendor credit.
*/
export function useDeleteRefundVendorCredit(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(id) => apiRequest.delete(`purchases/vendor-credit/refunds/${id}`),
{
onSuccess: (res, id) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
// Invalidate vendor credit query.
queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
},
...props,
},
);
}
/**
* Retrieve refund credit note detail of the given id.
* @param {number} id
*
*/
export function useRefundVendorCredit(id, props, requestProps) {
return useRequestQuery(
[t.REFUND_VENDOR_CREDIT, id],
{
method: 'get',
url: `purchases/vendor-credit/${id}/refund`,
...requestProps,
},
{
select: (res) => res.data.data,
defaultData: {},
...props,
},
);
}
/**
* Mark the given vendor credit as opened.
*/
export function useOpenVendorCredit(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(id) => apiRequest.post(`purchases/vendor-credit/${id}/open`),
{
onSuccess: (res, id) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
// Invalidate specific.
queryClient.invalidateQueries([t.VENDOR_CREDIT, id]);
},
...props,
},
);
}
/**
* Create Reconcile vendor credit.
*/
export function useCreateReconcileVendorCredit(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
([id, values]) =>
apiRequest.post(`purchases/vendor-credit/${id}/apply-to-bills`, values),
{
onSuccess: (res, [id, values]) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
// Invalidate credit note query.
queryClient.invalidateQueries([t.VENDOR_CREDIT, id]);
},
...props,
},
);
}
/**
* Retrieve reconcile vendor credit of the given id.
* @param {number} id
*
*/
export function useReconcileVendorCredit(id, props, requestProps) {
return useRequestQuery(
[t.RECONCILE_VENDOR_CREDIT, id],
{
method: 'get',
url: `purchases/vendor-credit/${id}/apply-to-bills`,
...requestProps,
},
{
select: (res) => res.data.data,
defaultData: [],
...props,
},
);
}
/**
* Retrieve reconcile credit notes.
*/
export function useReconcileVendorCredits(id, props, requestProps) {
return useRequestQuery(
[t.RECONCILE_VENDOR_CREDITS, id],
{
method: 'get',
url: `purchases/vendor-credit/${id}/applied-bills`,
...requestProps,
},
{
select: (res) => res.data.data,
defaultData: {},
...props,
},
);
}
/**
* Delete the given reconcile vendor credit.
*/
export function useDeleteReconcileVendorCredit(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation(
(id) => apiRequest.delete(`purchases/vendor-credit/applied-to-bills/${id}`),
{
onSuccess: (res, id) => {
// Common invalidate queries.
commonInvalidateQueries(queryClient);
// Invalidate vendor credit query.
queryClient.invalidateQueries([t.VENDOR_CREDIT, id]);
},
...props,
},
);
}

View File

@@ -1493,6 +1493,7 @@
"credit_note.label.new_credit_note": "New Credit Note",
"credit_note.label.edit_credit_note": "Edit Credit Note",
"credit_note.action.edit_credit_note": "Edit Credit note",
"credit_note.action.refund_credit_note": "Refund Credit note",
"credit_note.action.delete_credit_note": "Delete Credit note",
"credit_note.column.credit_note_no": "Credit Note #",
"credit_note.column.credit_date": "Credit Date",
@@ -1512,6 +1513,7 @@
"vendor_credits.column.vendor_credit_no": "Vendor Credit #",
"vendor_credits.action.new_vendor_credit": "New Vendor Credit",
"vendor_credits.action.edit_vendor_credit": "Edit Vendot Credit",
"vendor_credits.action.refund_vendor_credit": "Refund Vendot Credit",
"vendor_credits.action.delete_vendor_credit": "Delete Vendot Credit",
"vendor_credits.success_message": "The vendor credit has been created successfully",
"vendor_credits.edit_success_message": "The vendor credit has been edited successfully.",
@@ -1525,18 +1527,65 @@
"vendor_credit.auto_increment.auto": "Your vendor credit numbers are set on auto-increment mode. Are you sure changing this setting?",
"vendor_credit.auto_increment.manually": "Your vendor credit numbers are set on manual mode. Are you sure chaning this settings?",
"setting_your_auto_generated_vendor_credit_number": "Setting your auto-generated vendor credit number",
"credit_note.drawer_credit_note_detail":"Credit Note details",
"credit_note.drawer.label_credit_note_no":"Credit Note #",
"credit_note.drawer.label_credit_note_date":"Credit Date",
"credit_note.drawer.label_create_at":"Create at",
"credit_note.drawer_credit_note_detail": "Credit Note details",
"credit_note.drawer.label_credit_note_no": "Credit Note #",
"credit_note.drawer.label_credit_note_date": "Credit Date",
"credit_note.drawer.label_create_at": "Create at",
"credit_note.drawer.label_total": "TOTAL",
"credit_note.drawer.label_subtotal": "Subtotal",
"vendor_credit.drawer_vendor_credit_detail":"Vendor Credit details",
"vendor_credit.drawer.label_vendor_credit_no":"Vendor Credit #",
"vendor_credit.drawer.label_vendor_credit_date":"Vendor Credit Date",
"vendor_credit.drawer.label_create_at":"Create at",
"credit_note.drawer.label_refund_transactions": "Refund transactions",
"credit_note.drawer.label_reconcile_transactions": "Reconcile transactions",
"vendor_credit.drawer_vendor_credit_detail": "Vendor Credit details",
"vendor_credit.drawer.label_vendor_credit_no": "Vendor Credit #",
"vendor_credit.drawer.label_vendor_credit_date": "Vendor Credit Date",
"vendor_credit.drawer.label_create_at": "Create at",
"vendor_credit.drawer.label_total": "TOTAL",
"vendor_credit.drawer.label_subtotal": "Subtotal",
"landed_cost.dialog.label_select_transaction":"Select transaction",
"landed_cost.dialog.label_select_transaction_entry":"Select transaction entry"
"landed_cost.dialog.label_select_transaction": "Select transaction",
"landed_cost.dialog.label_select_transaction_entry": "Select transaction entry",
"refund_credit_note.dialog.label": "Refund Credit Note",
"refund_credit_note.dialog.success_message": "The customer credit note refund has been created successfully.",
"refund_credit_note.dialog.refund_date": "Refund date",
"refund_credit_note.dialog.amount": "Amount",
"refund_credit_note.dialog.description": "Description",
"refund_credit_note.dialog.form_account": "Form account",
"refund_vendor_credit.dialog.label": "Refund Vendor Credit",
"refund_vendor_credit.dialog.success_message": "The vendor credit refund has been created successfully.",
"refund_vendor_credit.dialog.refund_date": "Refund date",
"refund_vendor_credit.dialog.amount": "Amount",
"refund_vendor_credit.dialog.description": "Description",
"refund_vendor_credit.dialog.deposit_to_account": "Deposit to account",
"refund_credit_transactions.column.amount_refunded": "Amount refunded",
"refund_credit_transactions.column.withdrawal_account": "Withdrawal account",
"refund_credit_transactions.alert.delete_message": "The credit note refund has been deleted successfully.",
"refund_credit_transactions.once_your_delete_this_refund_credit_note": "Once your delete this refund credit note, you won't be able to restore it later, Are your sure you want to delete this transaction?",
"refund_vendor_credit.column.amount": "Amount refunded",
"refund_vendor_credit.column.withdrawal_account": "Withdrawal account",
"refund_vendor_credit_transactions.alert.delete_message": "The vendor credit refund has been deleted successfully.",
"refund_vendor_credit_transactions.once_your_delete_this_refund_vendor_credit": "Once your delete this refund vendor credit note, you won't be able to restore it later, Are your sure you want to delete this transaction?",
"refund": "Refund",
"credit_note_opened.alert.success_message": "The credit note has been opened successfully",
"credit_note_opened.are_sure_to_open_this_credit": "Are you sure you want to open this credit note?",
"vendor_credit_opened.alert.success_message": "The vendor credit has been opened successfully",
"vendor_credit_opened.are_sure_to_open_this_credit": "Are you sure you want to open this vendor credit?",
"reconcile_credit_note.label": "Reconcile Credit Note With Invoices",
"reconcile_credit_note.dialog.total_amount_to_credit": "Total amount to credit",
"reconcile_credit_note.dialog.remaining_credits": "Remaining credits",
"reconcile_credit_note.column.remaining_amount": "Remaining amount",
"reconcile_credit_note.column.amount_to_credit": "Amount to credit",
"reconcile_credit_note.success_message": "The credit note has been applied the given invoices successfully.",
"reconcile_credit_note.alert.there_is_no_open_sale_invoices": "There is no open sale invoices associated to credit note customer.",
"reconcile_credit_note.alert.success_message": "The applied credit to invoices has been deleted successfully.",
"reconcile_credit_note.once_you_delete_this_reconcile_credit_note": "Once you delete this reconcile credit note, you won't be able to restore it later. Are you sure you want to delete this reconcile credit note?",
"credit_note.error.you_couldn_t_delete_credit_note_that_has_associated_refund": "You couldn't delete credit note that has associated refund transactions.",
"credit_note.error.you_couldn_t_delete_credit_note_that_has_associated_invoice": "You couldn't delete credit note that has associated invoice reconcile transactions.",
"invoices.error.you_couldn_t_delete_sale_invoice_that_has_reconciled": "You couldn't delete sale invoice that has reconciled with credit note transaction.",
"reconcile_vendor_credit.dialog.label": "Reconcile Credit Note with Bills",
"reconcile_vendor_credit.dialog.success_message": "The vendor credit has been applied to the given bills successfully",
"reconcile_vendor_credit.alert.there_is_no_open_bills":"There is no open bills associated to credit note vendor.",
"reconcile_vendor_credit.dialog.total_amount_to_credit":"Total amount to credit",
"reconcile_vendor_credit.dialog.remaining_credits":"Remaining amount",
"reconcile_vendor_credit.column.bill_number":"Bill #",
"reconcile_vendor_credit.column.remaining_amount":"Remaining amount",
"reconcile_vendor_credit.column.amount_to_credit":"Amount to credit"
}

View File

@@ -0,0 +1,71 @@
.dialog--reconcile-credit-form {
width: 800px;
.bp3-dialog-body {
.footer {
display: flex;
margin-top: 40px;
.total_lines {
margin-left: auto;
&_line {
border-bottom: none;
.title {
font-weight: 600;
}
.amount,
.title {
padding: 8px 0px;
width: 165px;
}
.amount {
text-align: right;
}
}
}
}
}
.bigcapital-datatable {
.table {
border: 1px solid #d1dee2;
min-width: auto;
.tbody,
.tbody-inner {
height: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.tbody {
.tr .td {
padding: 0.4rem;
margin-left: -1px;
border-left: 1px solid #ececec;
}
.bp3-form-group {
margin-bottom: 0;
&:not(.bp3-intent-danger) .bp3-input {
border: 1px solid #d0dfe2;
&:focus {
box-shadow: 0 0 0 1px #116cd0;
border-color: #116cd0;
}
}
}
}
}
}
.bp3-callout {
font-size: 14px;
}
.bp3-dialog-footer {
padding-top: 10px;
}
}

View File

@@ -0,0 +1,72 @@
.dialog--reconcile-vendor-credit-form {
width: 800px;
.bp3-dialog-body {
.footer {
display: flex;
margin-top: 40px;
.total_lines {
margin-left: auto;
&_line {
border-bottom: none;
.title {
font-weight: 600;
}
.amount,
.title {
padding: 8px 0px;
width: 165px;
}
.amount {
text-align: right;
}
}
}
}
}
.bigcapital-datatable {
.table {
border: 1px solid #d1dee2;
min-width: auto;
.tbody,
.tbody-inner {
height: auto;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.tbody {
.tr .td {
padding: 0.4rem;
margin-left: -1px;
border-left: 1px solid #ececec;
}
.bp3-form-group {
margin-bottom: 0;
&:not(.bp3-intent-danger) .bp3-input {
border: 1px solid #d0dfe2;
&:focus {
box-shadow: 0 0 0 1px #116cd0;
border-color: #116cd0;
}
}
}
}
}
}
.bp3-callout {
font-size: 14px;
}
.bp3-dialog-footer {
padding-top: 10px;
}
}

View File

@@ -0,0 +1,27 @@
.datatable--refund-transactions {
padding: 12px;
.table {
.tbody,
.thead {
.tr .th {
padding: 8px 8px;
background-color: #fff;
font-size: 14px;
border-bottom: 1px solid #000;
border-top: 1px solid #000;
}
}
.tbody {
.tr .td {
border-bottom: 0;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
&.credit,
&.debit {
font-weight: 600;
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
.dialog--refund-credit-note {
.bp3-dialog-body {
.bp3-form-group {
label.bp3-label {
min-width: 140px;
font-size: 13px;
}
.bp3-form-content {
width: 250px;
}
}
.form-group {
&--description {
.bp3-form-content {
textarea {
width: 100%;
min-width: 100%;
font-size: 14px;
}
}
}
}
}
.bp3-dialog-footer {
padding-top: 10px;
}
}

View File

@@ -0,0 +1,27 @@
.datatable--refund-transactions {
padding: 12px;
.table {
.tbody,
.thead {
.tr .th {
padding: 8px 8px;
background-color: #fff;
font-size: 14px;
border-bottom: 1px solid #000;
border-top: 1px solid #000;
}
}
.tbody {
.tr .td {
border-bottom: 0;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
&.credit,
&.debit {
font-weight: 600;
}
}
}
}
}

View File

@@ -0,0 +1,28 @@
.dialog--refund-vendor-credit {
.bp3-dialog-body {
.bp3-form-group {
label.bp3-label {
min-width: 140px;
font-size: 13px;
}
.bp3-form-content {
width: 250px;
}
}
.form-group {
&--description {
.bp3-form-content {
textarea {
width: 100%;
min-width: 100%;
font-size: 14px;
}
}
}
}
}
.bp3-dialog-footer {
padding-top: 10px;
}
}