diff --git a/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDialogContent.js b/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDialogContent.js
new file mode 100644
index 000000000..b9dcecc61
--- /dev/null
+++ b/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDialogContent.js
@@ -0,0 +1,102 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { useSaveSettings } from 'hooks/query';
+
+import { VendorCreditNumberDilaogProvider } from './VendorCreditNumberDilaogProvider';
+import ReferenceNumberForm from 'containers/JournalNumber/ReferenceNumberForm';
+
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import withSettings from 'containers/Settings/withSettings';
+import withSettingsActions from 'containers/Settings/withSettingsActions';
+import { compose } from 'utils';
+import {
+ transformFormToSettings,
+ transformSettingsToForm,
+} from 'containers/JournalNumber/utils';
+
+/**
+ * Vendor credit number dialog
+ */
+function VendorCreditNumberDialogContent({
+ // #ownProps
+ initialValues,
+ onConfirm,
+
+ // #withSettings
+ nextNumber,
+ numberPrefix,
+ autoIncrement,
+
+ // #withDialogActions
+ closeDialog,
+}) {
+ const { mutateAsync: saveSettings } = useSaveSettings();
+ const [referenceFormValues, setReferenceFormValues] = React.useState(null);
+
+ // Handle the submit form.
+ const handleSubmitForm = (values, { setSubmitting }) => {
+ // Handle the form success.
+ const handleSuccess = () => {
+ setSubmitting(false);
+ closeDialog('vendor-credit-form');
+ onConfirm(values);
+ };
+ // Handle the form errors.
+ const handleErrors = () => {
+ setSubmitting(false);
+ };
+ if (values.incrementMode === 'manual-transaction') {
+ handleSuccess();
+ return;
+ }
+ // Transformes the form values to settings to save it.
+ const options = transformFormToSettings(values, 'vendor_credit');
+
+ // Save the settings.
+ saveSettings({ options }).then(handleSuccess).catch(handleErrors);
+ };
+
+ // Handle the dialog close.
+ const handleClose = () => {
+ closeDialog('vendor-credit-form');
+ };
+ // Handle form change.
+ const handleChange = (values) => {
+ setReferenceFormValues(values);
+ };
+
+ // Description.
+ const description =
+ referenceFormValues?.incrementMode === 'auto'
+ ? intl.get('vendor_credit.auto_increment.auto')
+ : intl.get('vendor_credit.auto_increment.manually');
+
+ return (
+
+
+
+ );
+}
+
+export default compose(
+ withDialogActions,
+ withSettingsActions,
+ withSettings(({ vendorsCreditNoteSetting }) => ({
+ autoIncrement: vendorsCreditNoteSetting?.autoIncrement,
+ nextNumber: vendorsCreditNoteSetting?.nextNumber,
+ numberPrefix: vendorsCreditNoteSetting?.numberPrefix,
+ })),
+)(VendorCreditNumberDialogContent);
diff --git a/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDilaogProvider.js b/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDilaogProvider.js
new file mode 100644
index 000000000..0d285b6fc
--- /dev/null
+++ b/src/containers/Dialogs/VendorCreditNumberDialog/VendorCreditNumberDilaogProvider.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import { DialogContent } from 'components';
+import { useSettingsVendorCredits } from 'hooks/query';
+
+const VendorCreditNumberDialogContext = React.createContext();
+
+/**
+ * Vendor credit number dialog provider
+ */
+function VendorCreditNumberDilaogProvider({ query, ...props }) {
+ const { isLoading: isSettingsLoading } = useSettingsVendorCredits();
+
+ // Provider payload.
+ const provider = {
+ isSettingsLoading,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useVendorCreditNumberDialogContext = () =>
+ React.useContext(VendorCreditNumberDialogContext);
+
+export { VendorCreditNumberDilaogProvider, useVendorCreditNumberDialogContext };
diff --git a/src/containers/Dialogs/VendorCreditNumberDialog/index.js b/src/containers/Dialogs/VendorCreditNumberDialog/index.js
new file mode 100644
index 000000000..e92fdf702
--- /dev/null
+++ b/src/containers/Dialogs/VendorCreditNumberDialog/index.js
@@ -0,0 +1,40 @@
+import React from 'react';
+import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+import { compose, saveInvoke } from 'utils';
+
+const VendorCreditNumberDialogContent = React.lazy(() =>
+ import('./VendorCreditNumberDialogContent'),
+);
+
+/**
+ * Vendor Credit number dialog.
+ */
+function VendorCreditNumberDialog({
+ dialogName,
+ payload: { initialFormValues },
+ isOpen,
+ onConfirm,
+}) {
+ const handleConfirm = (values) => {
+ saveInvoke(onConfirm, values);
+ };
+
+ return (
+
}
+ name={dialogName}
+ autoFocus={true}
+ canEscapeKeyClose={true}
+ isOpen={isOpen}
+ >
+
+
+
+
+ );
+}
+export default compose(withDialogRedux())(VendorCreditNumberDialog);
diff --git a/src/containers/Drawers/BillDrawer/BillDrawerContent.js b/src/containers/Drawers/BillDrawer/BillDrawerContent.js
index c5458fdc5..e2df6d6c4 100644
--- a/src/containers/Drawers/BillDrawer/BillDrawerContent.js
+++ b/src/containers/Drawers/BillDrawer/BillDrawerContent.js
@@ -1,7 +1,6 @@
import React from 'react';
import { DrawerBody } from 'components';
-import 'style/components/Drawers/ViewDetail/ViewDetail.scss';
import { BillDrawerProvider } from './BillDrawerProvider';
import BillDrawerDetails from './BillDrawerDetails';
diff --git a/src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js b/src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js
index c06848a30..5830a0166 100644
--- a/src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js
+++ b/src/containers/Drawers/BillDrawer/LocatedLandedCostTable.js
@@ -5,6 +5,8 @@ import { Button, Classes, NavbarGroup } from '@blueprintjs/core';
import { useLocatedLandedCostColumns, ActionsMenu } from './components';
import { useBillDrawerContext } from './BillDrawerProvider';
+import '../../../style/pages/AllocateLandedCost/List.scss';
+
import withAlertsActions from 'containers/Alert/withAlertActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withDrawerActions from 'containers/Drawer/withDrawerActions';
diff --git a/src/containers/Drawers/BillDrawer/components.js b/src/containers/Drawers/BillDrawer/components.js
index 2a4d29f93..184a120f0 100644
--- a/src/containers/Drawers/BillDrawer/components.js
+++ b/src/containers/Drawers/BillDrawer/components.js
@@ -1,5 +1,6 @@
import React from 'react';
import intl from 'react-intl-universal';
+import styled from 'styled-components';
import { Intent, MenuItem, Menu } from '@blueprintjs/core';
import { safeCallback } from 'utils';
import { Icon } from 'components';
@@ -24,7 +25,7 @@ export function ActionsMenu({ row: { original }, payload: { onDelete } }) {
*/
export function FromTransactionCell({
row: { original },
- payload: { onFromTranscationClick }
+ payload: { onFromTranscationClick },
}) {
// Handle the link click
const handleAnchorClick = () => {
@@ -38,6 +39,18 @@ export function FromTransactionCell({
);
}
+/**
+ * Name accessor.
+ */
+export const NameAccessor = (row) => {
+ return (
+
+ {row.name}
+ {row.description}
+
+ );
+};
+
/**
* Retrieve bill located landed cost table columns.
*/
@@ -46,7 +59,7 @@ export function useLocatedLandedCostColumns() {
() => [
{
Header: intl.get('name'),
- accessor: 'description',
+ accessor: NameAccessor,
width: 150,
className: 'name',
},
@@ -65,7 +78,7 @@ export function useLocatedLandedCostColumns() {
},
{
Header: intl.get('allocation_method'),
- accessor: 'allocation_method_formatted',
+ accessor: 'allocation_method',
width: 100,
className: 'allocation-method',
},
@@ -73,3 +86,12 @@ export function useLocatedLandedCostColumns() {
[],
);
}
+
+const LabelName = styled.div``;
+
+const LabelDescription = styled.div`
+ font-size: 12px;
+ margin-top: 6px;
+ display: block;
+ opacity: 0.75;
+`;
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetail.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetail.js
new file mode 100644
index 000000000..74f7e760b
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetail.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Tab } from '@blueprintjs/core';
+import intl from 'react-intl-universal';
+import { DrawerMainTabs } from 'components';
+
+import CreditNoteDetailPanel from './CreditNoteDetailPanel';
+import clsx from 'classnames';
+
+import CreditNoteDetailCls from '../../../style/components/Drawers/CreditNoteDetails.module.scss';
+
+/**
+ * Credit Note view detail.
+ */
+export default function CreditNoteDetail() {
+ return (
+
+
+ }
+ />
+
+
+ );
+}
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js
new file mode 100644
index 000000000..a7b92ae21
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailActionsBar.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+
+import {
+ Button,
+ NavbarGroup,
+ Classes,
+ NavbarDivider,
+ Intent,
+} from '@blueprintjs/core';
+import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
+import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
+
+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 { compose } from 'utils';
+
+/**
+ * Credit note detail actions bar.
+ */
+function CreditNoteDetailActionsBar({
+ // #withDialogActions
+ openDialog,
+
+ // #withAlertsActions
+ openAlert,
+
+ // #withDrawerActions
+ closeDrawer,
+}) {
+ const { creditNoteId } = useCreditNoteDetailDrawerContext();
+
+ const history = useHistory();
+
+ // Handle edit credit note.
+ const handleEditCreditNote = () => {
+ history.push(`/credit-notes/${creditNoteId}/edit`);
+ closeDrawer('credit-note-detail-drawer');
+ };
+
+ // Handle delete credit note.
+ const handleDeleteCreditNote = () => {
+ openAlert('credit-note-delete', { creditNoteId });
+ };
+
+ return (
+
+
+ }
+ text={}
+ onClick={handleEditCreditNote}
+ />
+
+ }
+ text={}
+ intent={Intent.DANGER}
+ onClick={handleDeleteCreditNote}
+ />
+
+
+ );
+}
+
+export default compose(
+ withDialogActions,
+ withAlertsActions,
+ withDrawerActions,
+)(CreditNoteDetailActionsBar);
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerContent.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerContent.js
new file mode 100644
index 000000000..9e9641a9c
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerContent.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { DrawerBody } from 'components';
+
+import CreditNoteDetail from './CreditNoteDetail';
+import { CreditNoteDetailDrawerProvider } from './CreditNoteDetailDrawerProvider';
+
+/**
+ * Credit note detail drawer content.
+ */
+export default function CreditNoteDetailDrawerContent({
+ // #ownProp
+ creditNoteId,
+}) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerFooter.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerFooter.js
new file mode 100644
index 000000000..143abcfd5
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerFooter.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import clsx from 'classnames';
+
+import { T, TotalLines, TotalLine, If } from 'components';
+import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
+import { FormatNumber } from '../../../components';
+
+import CreditNoteDetailCls from '../../../style/components/Drawers/CreditNoteDetails.module.scss';
+
+/**
+ * Credit note details panel footer.
+ */
+export default function CreditNoteDetailDrawerFooter() {
+ const { creditNote } = useCreditNoteDetailDrawerContext();
+
+ return (
+
+
+ }
+ value={}
+ className={CreditNoteDetailCls.total_line_subtotal}
+ />
+ }
+ value={creditNote.formatted_amount}
+ className={CreditNoteDetailCls.total_line_total}
+ />
+
+
+ );
+}
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerProvider.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerProvider.js
new file mode 100644
index 000000000..1b0673d8c
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailDrawerProvider.js
@@ -0,0 +1,39 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { useCreditNote } from 'hooks/query';
+import { DrawerHeaderContent, DrawerLoading } from 'components';
+
+const CreditNoteDetailDrawerContext = React.createContext();
+
+/**
+ * Credit note detail drawer provider.
+ */
+function CreditNoteDetailDrawerProvider({ creditNoteId, ...props }) {
+ // Handle fetch vendor credit details.
+ const { data: creditNote, isLoading: isCreditNoteLoading } = useCreditNote(
+ creditNoteId,
+ {
+ enabled: !!creditNoteId,
+ },
+ );
+
+ const provider = {
+ creditNote,
+ creditNoteId,
+ };
+
+ return (
+
+
+
+
+ );
+}
+
+const useCreditNoteDetailDrawerContext = () =>
+ React.useContext(CreditNoteDetailDrawerContext);
+
+export { CreditNoteDetailDrawerProvider, useCreditNoteDetailDrawerContext };
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailHeader.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailHeader.js
new file mode 100644
index 000000000..0ec121f61
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailHeader.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { defaultTo } from 'lodash';
+import clsx from 'classnames';
+
+import { FormatDate, T, DetailsMenu, DetailItem } from 'components';
+import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
+
+import CreditNoteDetailCls from '../../../style/components/Drawers/CreditNoteDetails.module.scss';
+
+/**
+ * Credit note details drawer header.
+ */
+export default function CreditNoteDetailHeader() {
+ const { creditNote } = useCreditNoteDetailDrawerContext();
+
+ return (
+
+
+
+ {creditNote.formatted_amount}
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ children={}
+ />
+
+
+ );
+}
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailPanel.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailPanel.js
new file mode 100644
index 000000000..9712a3d7a
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailPanel.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import clsx from 'classnames';
+
+import { Card } from 'components';
+
+import CreditNoteDetailActionsBar from './CreditNoteDetailActionsBar';
+import CreditNoteDetailHeader from './CreditNoteDetailHeader';
+import CreditNoteDetailTable from './CreditNoteDetailTable';
+import CreditNoteDetailDrawerFooter from './CreditNoteDetailDrawerFooter';
+
+import CreditNoteDetailCls from '../../../style/components/Drawers/CreditNoteDetails.module.scss';
+
+export default function CreditNoteDetailPanel() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTable.js b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTable.js
new file mode 100644
index 000000000..3d3d38dce
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/CreditNoteDetailTable.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import clsx from 'classnames';
+
+import { DataTable } from 'components';
+import { useCreditNoteDetailDrawerContext } from './CreditNoteDetailDrawerProvider';
+
+import { useCreditNoteReadOnlyEntriesColumns } from './utils';
+
+import CreditNoteDetailCls from '../../../style/components/Drawers/CreditNoteDetails.module.scss';
+
+/**
+ * Credit note detail table.
+ */
+export default function CreditNoteDetailTable() {
+ const {
+ creditNote: { entries },
+ } = useCreditNoteDetailDrawerContext();
+
+ // Credit note entries table columns.
+ const columns = useCreditNoteReadOnlyEntriesColumns();
+
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/index.js b/src/containers/Drawers/CreditNoteDetailDrawer/index.js
new file mode 100644
index 000000000..abcd19afb
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/index.js
@@ -0,0 +1,33 @@
+import React from 'react';
+import { Drawer, DrawerSuspense } from 'components';
+import withDrawers from 'containers/Drawer/withDrawers';
+
+import { compose } from 'utils';
+
+const CreditNoteDetailDrawerContent = React.lazy(() =>
+ import('./CreditNoteDetailDrawerContent'),
+);
+
+/**
+ * Credit note detail drawer.
+ */
+function CreditNoteDetailDrawer({
+ name,
+ // #withDrawer
+ isOpen,
+ payload: { creditNoteId },
+}) {
+ return (
+
+
+
+
+
+ );
+}
+export default compose(withDrawers())(CreditNoteDetailDrawer);
diff --git a/src/containers/Drawers/CreditNoteDetailDrawer/utils.js b/src/containers/Drawers/CreditNoteDetailDrawer/utils.js
new file mode 100644
index 000000000..ea3743197
--- /dev/null
+++ b/src/containers/Drawers/CreditNoteDetailDrawer/utils.js
@@ -0,0 +1,47 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { FormatNumberCell } from '../../../components';
+
+export const useCreditNoteReadOnlyEntriesColumns = () =>
+ React.useMemo(
+ () => [
+ {
+ Header: intl.get('product_and_service'),
+ accessor: 'item.name',
+ width: 150,
+ className: 'name',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('description'),
+ accessor: 'description',
+ className: 'description',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('quantity'),
+ accessor: 'quantity',
+ Cell: FormatNumberCell,
+ width: 100,
+ align: 'right',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('rate'),
+ accessor: 'rate',
+ Cell: FormatNumberCell,
+ width: 100,
+ align: 'right',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('amount'),
+ accessor: 'amount',
+ Cell: FormatNumberCell,
+ width: 100,
+ align: 'right',
+ disableSortBy: true,
+ },
+ ],
+ [],
+ );
diff --git a/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.js b/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.js
index d512f4386..f86b0e42d 100644
--- a/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.js
+++ b/src/containers/Drawers/EstimateDetailDrawer/EstimateDetailActionsBar.js
@@ -90,11 +90,13 @@ function EstimateDetailActionsBar({
/>
-
+
+
+
);
diff --git a/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.js b/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.js
index e71ce8243..1afe626e5 100644
--- a/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.js
+++ b/src/containers/Drawers/InvoiceDetailDrawer/InvoiceDetailActionsBar.js
@@ -120,9 +120,9 @@ function InvoiceDetailActionsBar({
intent={Intent.DANGER}
onClick={handleDeleteInvoice}
/>
-
+
- }
- />
+
+ }
+ />
+
}
>
diff --git a/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveActionsBar.js b/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveActionsBar.js
index 61af4b4e2..f1c725c8e 100644
--- a/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveActionsBar.js
+++ b/src/containers/Drawers/PaymentReceiveDetailDrawer/PaymentReceiveActionsBar.js
@@ -80,11 +80,13 @@ function PaymentReceiveActionsBar({
/>
-
+
+
+
);
diff --git a/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailActionBar.js b/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailActionBar.js
index 1931905b8..b099ac08c 100644
--- a/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailActionBar.js
+++ b/src/containers/Drawers/ReceiptDetailDrawer/ReceiptDetailActionBar.js
@@ -85,11 +85,13 @@ function ReceiptDetailActionBar({
/>
-
+
+
+
);
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetail.js b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetail.js
new file mode 100644
index 000000000..e603c667f
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetail.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import { Tab } from '@blueprintjs/core';
+import intl from 'react-intl-universal';
+import { DrawerMainTabs } from 'components';
+
+import VendorCreditDetailPanel from './VendorCreditDetailPanel';
+import clsx from 'classnames';
+
+import VendorCreditDetailCls from '../../../style/components/Drawers/VendorCreditDetail.module.scss';
+
+/**
+ * Vendor credit view detail.
+ */
+export default function VendorCreditDetail() {
+ return (
+
+
+ }
+ />
+
+
+ );
+}
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailActionsBar.js b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailActionsBar.js
new file mode 100644
index 000000000..9c720ea52
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailActionsBar.js
@@ -0,0 +1,76 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+
+import {
+ Button,
+ NavbarGroup,
+ Classes,
+ NavbarDivider,
+ Intent,
+} from '@blueprintjs/core';
+import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
+import { useVendorCreditDetailDrawerContext } from './VendorCreditDetailDrawerProvider';
+
+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 { compose } from 'utils';
+
+/**
+ * Vendor credit detail actions bar.
+ */
+function VendorCreditDetailActionsBar({
+ // #withDialogActions
+ openDialog,
+
+ // #withAlertsActions
+ openAlert,
+
+ // #withDrawerActions
+ closeDrawer,
+}) {
+ const { vendorCreditId } = useVendorCreditDetailDrawerContext();
+
+ const history = useHistory();
+
+ // Handle edit credit note.
+ const handleEditVendorCredit = () => {
+ history.push(`/vendor-credits/${vendorCreditId}/edit`);
+ closeDrawer('vendor-credit-detail-drawer');
+ };
+
+ // Handle delete credit note.
+ const handleDeleteVendorCredit = () => {
+ openAlert('vendor-credit-delete', { vendorCreditId });
+ };
+
+ return (
+
+
+ }
+ text={}
+ onClick={handleEditVendorCredit}
+ />
+
+ }
+ text={}
+ intent={Intent.DANGER}
+ onClick={handleDeleteVendorCredit}
+ />
+
+
+ );
+}
+
+export default compose(
+ withDialogActions,
+ withAlertsActions,
+ withDrawerActions,
+)(VendorCreditDetailActionsBar);
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerContent.js b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerContent.js
new file mode 100644
index 000000000..8ac7786e6
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerContent.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { DrawerBody } from 'components';
+
+import VendorCreditDetail from './VendorCreditDetail';
+import { VendorCreditDetailDrawerProvider } from './VendorCreditDetailDrawerProvider';
+
+/**
+ * Vendor credit detail drawer content.
+ */
+export default function VendorCreditDetailDrawerContent({ vendorCreditId }) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.js b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.js
new file mode 100644
index 000000000..6e0579c0b
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerFooter.js
@@ -0,0 +1,32 @@
+import React from 'react';
+import clsx from 'classnames';
+
+import { T, TotalLines, TotalLine, If } from 'components';
+import { useVendorCreditDetailDrawerContext } from './VendorCreditDetailDrawerProvider';
+import { FormatNumber } from '../../../components';
+
+import VendorCreditDetailCls from '../../../style/components/Drawers/VendorCreditDetail.module.scss';
+
+/**
+ * Vendor Credit detail panel footer.
+ */
+export default function VendorCreditDetailDrawerFooter() {
+ const { vendorCredit } = useVendorCreditDetailDrawerContext();
+
+ return (
+
+
+ }
+ value={}
+ className={VendorCreditDetailCls.total_line_subtotal}
+ />
+ }
+ value={vendorCredit.formatted_amount}
+ className={VendorCreditDetailCls.total_line_total}
+ />
+
+
+ );
+}
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerProvider.js b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerProvider.js
new file mode 100644
index 000000000..74236d87c
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailDrawerProvider.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { useVendorCredit } from 'hooks/query';
+import { DrawerHeaderContent, DrawerLoading } from 'components';
+
+const VendorCreditDetailDrawerContext = React.createContext();
+
+/**
+ * Vendor credit drawer provider.
+ */
+function VendorCreditDetailDrawerProvider({ vendorCreditId, ...props }) {
+ // Handle fetch vendor credit details.
+ const { data: vendorCredit, isLoading: isVendorCreditLoading } =
+ useVendorCredit(vendorCreditId, {
+ enabled: !!vendorCreditId,
+ });
+
+ const provider = {
+ vendorCredit,
+ vendorCreditId,
+ };
+
+ return (
+
+
+
+
+ );
+}
+
+const useVendorCreditDetailDrawerContext = () =>
+ React.useContext(VendorCreditDetailDrawerContext);
+
+export { VendorCreditDetailDrawerProvider, useVendorCreditDetailDrawerContext };
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailHeader.js b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailHeader.js
new file mode 100644
index 000000000..3ecfd6afb
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailHeader.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { defaultTo } from 'lodash';
+import clsx from 'classnames';
+
+import { FormatDate, T, DetailsMenu, DetailItem } from 'components';
+import { useVendorCreditDetailDrawerContext } from './VendorCreditDetailDrawerProvider';
+
+import VendorCreditDetailCls from '../../../style/components/Drawers/VendorCreditDetail.module.scss';
+
+/**
+ * Vendor credit detail drawer header.
+ */
+export default function VendorCreditDetailHeader() {
+ const { vendorCredit } = useVendorCreditDetailDrawerContext();
+ return (
+
+
+
+ {vendorCredit.formatted_amount}
+
+
+
+
+ }
+ />
+
+
+
+
+
+ }
+ children={}
+ />
+
+
+ );
+}
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailPanel.js b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailPanel.js
new file mode 100644
index 000000000..474284741
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailPanel.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import clsx from 'classnames';
+
+import { Card } from 'components';
+
+import VendorCreditDetailActionsBar from './VendorCreditDetailActionsBar';
+import VendorCreditDetailHeader from './VendorCreditDetailHeader';
+import VendorCreditDetailTable from './VendorCreditDetailTable';
+import VendorCreditDetailDrawerFooter from './VendorCreditDetailDrawerFooter';
+
+import VendorCreditDetailCls from '../../../style/components/Drawers/VendorCreditDetail.module.scss';
+
+export default function VendorCreditDetailPanel() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailTable.js b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailTable.js
new file mode 100644
index 000000000..668d076c6
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/VendorCreditDetailTable.js
@@ -0,0 +1,31 @@
+import React from 'react';
+import clsx from 'classnames';
+
+import { DataTable } from 'components';
+import { useVendorCreditDetailDrawerContext } from './VendorCreditDetailDrawerProvider';
+
+import { useVendorCreditReadonlyEntriesTableColumns } from './utils';
+
+import VendorCreditDetailCls from '../../../style/components/Drawers/VendorCreditDetail.module.scss';
+
+/**
+ * Vendor Credit detail table.
+ */
+export default function VendorCreditDetailTable() {
+ const {
+ vendorCredit: { entries },
+ } = useVendorCreditDetailDrawerContext();
+
+ // Vendor Credit entries table columns.
+ const columns = useVendorCreditReadonlyEntriesTableColumns();
+
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/index.js b/src/containers/Drawers/VendorCreditDetailDrawer/index.js
new file mode 100644
index 000000000..38b3ea3ed
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/index.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Drawer, DrawerSuspense } from 'components';
+import withDrawers from 'containers/Drawer/withDrawers';
+
+import { compose } from 'utils';
+
+const VendorCreditDetailDrawerContent = React.lazy(() =>
+ import('./VendorCreditDetailDrawerContent'),
+);
+
+/**
+ * Vendor Credit detail drawer.
+ */
+function VendorCreditDetailDrawer({
+ name,
+ // #withDrawer
+ isOpen,
+ payload: { vendorCreditId },
+}) {
+ return (
+
+
+
+
+
+ );
+}
+
+export default compose(withDrawers())(VendorCreditDetailDrawer);
diff --git a/src/containers/Drawers/VendorCreditDetailDrawer/utils.js b/src/containers/Drawers/VendorCreditDetailDrawer/utils.js
new file mode 100644
index 000000000..1f0d67df5
--- /dev/null
+++ b/src/containers/Drawers/VendorCreditDetailDrawer/utils.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+
+import { FormatNumberCell } from '../../../components';
+
+/**
+ * Retrieve vendor credit readonly details entries table columns.
+ */
+export const useVendorCreditReadonlyEntriesTableColumns = () =>
+ React.useMemo(
+ () => [
+ {
+ Header: intl.get('product_and_service'),
+ accessor: 'item.name',
+ width: 150,
+ className: 'item',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('description'),
+ accessor: 'description',
+ className: 'description',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('quantity'),
+ accessor: 'quantity',
+ Cell: FormatNumberCell,
+ width: 100,
+ align: 'right',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('rate'),
+ accessor: 'rate',
+ Cell: FormatNumberCell,
+ width: 100,
+ align: 'right',
+ disableSortBy: true,
+ },
+ {
+ Header: intl.get('amount'),
+ accessor: 'amount',
+ Cell: FormatNumberCell,
+ width: 100,
+ align: 'right',
+ disableSortBy: true,
+ },
+ ],
+ [],
+ );
diff --git a/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.js b/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.js
index a1af99e4d..1f0a766c3 100644
--- a/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.js
+++ b/src/containers/Preferences/Users/Roles/RolesForm/RolesForm.js
@@ -21,6 +21,7 @@ import {
import RolesFormContent from './RolesFormContent';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
+import { handleDeleteErrors } from '../utils';
import { compose, transformToForm } from 'utils';
@@ -82,8 +83,14 @@ function RolesForm({
history.push('/preferences/users');
};
- const onError = (errors) => {
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
setSubmitting(false);
+
+ handleDeleteErrors(errors);
};
if (isNewMode) {
createRolePermissionMutate(form).then(onSuccess).catch(onError);
diff --git a/src/containers/Preferences/Users/Roles/utils.js b/src/containers/Preferences/Users/Roles/utils.js
index 5e5bd0e14..8cb43dbea 100644
--- a/src/containers/Preferences/Users/Roles/utils.js
+++ b/src/containers/Preferences/Users/Roles/utils.js
@@ -10,4 +10,22 @@ export const handleDeleteErrors = (errors) => {
intent: Intent.DANGER,
});
}
+ if (errors.find((error) => error.type === 'INVALIDATE_PERMISSIONS')) {
+ AppToaster.show({
+ message: intl.get('roles.error.the_submit_role_has_invalid_permissions'),
+ intent: Intent.DANGER,
+ });
+ }
+ if (
+ errors.find(
+ (error) => error.type === 'CANNOT_DELETE_ROLE_ASSOCIATED_TO_USERS',
+ )
+ ) {
+ AppToaster.show({
+ message: intl.get(
+ 'roles.error.you_cannot_delete_role_that_associated_to_users',
+ ),
+ intent: Intent.DANGER,
+ });
+ }
};
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFloatingActions.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFloatingActions.js
new file mode 100644
index 000000000..d831d0666
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFloatingActions.js
@@ -0,0 +1,159 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { useFormikContext } from 'formik';
+import {
+ Intent,
+ Button,
+ ButtonGroup,
+ Popover,
+ PopoverInteractionKind,
+ Position,
+ Menu,
+ MenuItem,
+} from '@blueprintjs/core';
+import { If, Icon, FormattedMessage as T } from 'components';
+import { CLASSES } from 'common/classes';
+import classNames from 'classnames';
+import { useVendorCreditNoteFormContext } from './VendorCreditNoteFormProvider';
+
+/**
+ * Purchases Credit note floating actions.
+ */
+export default function VendorCreditNoteFloatingActions() {
+ const history = useHistory();
+
+ // Formik context.
+ const { resetForm, submitForm, isSubmitting } = useFormikContext();
+
+ // Credit note form context.
+ const { setSubmitPayload, isNewMode } = useVendorCreditNoteFormContext();
+
+ // Handle submit, save and anothe new button click.
+ const handleSubmitAndNewBtnClick = (event) => {
+ setSubmitPayload({ redirect: false, status: true, resetForm: true });
+ submitForm();
+ };
+
+ // Handle submit as save & continue editing button click.
+ const handleSubmitSaveContinueEditingBtnClick = (event) => {
+ setSubmitPayload({ redirect: false, status: true });
+ submitForm();
+ };
+
+ // Handle submit as draft button click.
+ const handleSubmitDraftBtnClick = (event) => {
+ setSubmitPayload({ redirect: true, status: false });
+ submitForm();
+ };
+
+ // handle submit as draft & new button click.
+ const handleSubmitDraftAndNewBtnClick = (event) => {
+ setSubmitPayload({ redirect: false, status: false, resetForm: true });
+ submitForm();
+ };
+
+ // Handle submit as draft & continue editing button click.
+ const handleSubmitDraftContinueEditingBtnClick = (event) => {
+ setSubmitPayload({ redirect: false, status: false });
+ submitForm();
+ };
+
+ // Handle cancel button click.
+ const handleCancelBtnClick = (event) => {
+ history.goBack();
+ };
+
+ // Handle submit button click.
+ const handleSubmitBtnClick = (event) => {
+ setSubmitPayload({ redirect: true });
+ submitForm();
+ };
+
+ const handleClearBtnClick = (event) => {
+ resetForm();
+ };
+
+ return (
+
+ {/* ----------- Save ----------- */}
+
+ }
+ />
+
+
+ }
+ onClick={handleSubmitAndNewBtnClick}
+ />
+ }
+ onClick={handleSubmitSaveContinueEditingBtnClick}
+ />
+
+ }
+ minimal={true}
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ />
+
+
+ {/* ----------- Save As Draft ----------- */}
+
+ }
+ />
+
+ }
+ onClick={handleSubmitDraftAndNewBtnClick}
+ />
+ }
+ onClick={handleSubmitDraftContinueEditingBtnClick}
+ />
+
+ }
+ minimal={true}
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ />
+
+
+ {/* ----------- Clear & Reset----------- */}
+
:
}
+ />
+ {/* ----------- Cancel ----------- */}
+
}
+ />
+
+ );
+}
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.js
new file mode 100644
index 000000000..05fed2f74
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.js
@@ -0,0 +1,169 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { Formik, Form } from 'formik';
+import { Button, Intent } from '@blueprintjs/core';
+import intl from 'react-intl-universal';
+import { sumBy, omit, isEmpty } from 'lodash';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import {
+ CreateCreditNoteFormSchema,
+ EditCreditNoteFormSchema,
+} from './VendorCreditNoteForm.schema';
+
+import VendorCreditNoteFormHeader from './VendorCreditNoteFormHeader';
+import VendorCreditNoteItemsEntriesEditor from './VendorCreditNoteItemsEntriesEditor';
+import VendorCreditNoteFormFooter from './VendorCreditNoteFormFooter';
+import VendorCreditNoteFloatingActions from './VendorCreditNoteFloatingActions';
+import VendorCreditNoteFormDialogs from './VendorCreditNoteFormDialogs';
+
+import { useVendorCreditNoteFormContext } from './VendorCreditNoteFormProvider';
+
+import { AppToaster } from 'components';
+import { compose, safeSumBy, transactionNumber } from 'utils';
+import {
+ defaultVendorsCreditNote,
+ filterNonZeroEntries,
+ transformToEditForm,
+ transformFormValuesToRequest,
+} from './utils';
+
+import withSettings from 'containers/Settings/withSettings';
+import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
+
+/**
+ * Vendor Credit note form.
+ */
+function VendorCreditNoteForm({
+ // #withSettings
+ vendorcreditAutoIncrement,
+ vendorcreditNumberPrefix,
+ vendorcreditNextNumber,
+
+ // #withCurrentOrganization
+ organization: { base_currency },
+}) {
+ const history = useHistory();
+
+ // Vendor Credit note form context.
+ const {
+ isNewMode,
+ submitPayload,
+ vendorCredit,
+ createVendorCreditMutate,
+ editVendorCreditMutate,
+ } = useVendorCreditNoteFormContext();
+
+ // Credit number.
+ const vendorCreditNumber = transactionNumber(
+ vendorcreditNumberPrefix,
+ vendorcreditNextNumber,
+ );
+
+ // Initial values.
+ const initialValues = React.useMemo(
+ () => ({
+ ...(!isEmpty(vendorCredit)
+ ? {
+ ...transformToEditForm(vendorCredit),
+ }
+ : {
+ ...defaultVendorsCreditNote,
+ ...(vendorcreditAutoIncrement && {
+ vendor_credit_number: vendorCreditNumber,
+ }),
+ }),
+ }),
+ [vendorCredit, base_currency],
+ );
+
+ // Handles form submit.
+ const handleFormSubmit = (
+ values,
+ { setSubmitting, setErrors, resetForm },
+ ) => {
+ const entries = filterNonZeroEntries(values.entries);
+ const totalQuantity = safeSumBy(entries, 'quantity');
+
+ if (totalQuantity === 0) {
+ AppToaster.show({
+ message: intl.get('quantity_cannot_be_zero_or_empty'),
+ intent: Intent.DANGER,
+ });
+ setSubmitting(false);
+ return;
+ }
+ const form = {
+ ...transformFormValuesToRequest(values),
+ };
+ // Handle the request success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: intl.get(
+ isNewMode
+ ? 'vendor_credits.success_message'
+ : 'vendor_credits.edit_success_message',
+ ),
+ intent: Intent.SUCCESS,
+ });
+ setSubmitting(false);
+
+ if (submitPayload.redirect) {
+ history.push('/vendor-credits');
+ }
+ if (submitPayload.resetForm) {
+ resetForm();
+ }
+ };
+ // Handle the request error.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ setSubmitting(false);
+ };
+ if (isNewMode) {
+ createVendorCreditMutate(form).then(onSuccess).catch(onError);
+ } else {
+ editVendorCreditMutate([vendorCredit.id, form])
+ .then(onSuccess)
+ .catch(onError);
+ }
+ };
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default compose(
+ withSettings(({ vendorsCreditNoteSetting }) => ({
+ vendorcreditAutoIncrement: vendorsCreditNoteSetting?.autoIncrement,
+ vendorcreditNextNumber: vendorsCreditNoteSetting?.nextNumber,
+ vendorcreditNumberPrefix: vendorsCreditNoteSetting?.numberPrefix,
+ })),
+ withCurrentOrganization(),
+)(VendorCreditNoteForm);
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.schema.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.schema.js
new file mode 100644
index 000000000..d3c5cabe4
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteForm.schema.js
@@ -0,0 +1,41 @@
+import * as Yup from 'yup';
+import intl from 'react-intl-universal';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+import { isBlank } from 'utils';
+
+const getSchema = Yup.object().shape({
+ vendor_id: Yup.number().required().label(intl.get('vendor_name_')),
+ vendor_credit_date: Yup.date().required().label(intl.get('bill_date_')),
+ vendor_credit_number: Yup.string()
+ .max(DATATYPES_LENGTH.STRING)
+ .label(intl.get('bill_number_')),
+ reference_no: Yup.string().nullable().min(1).max(DATATYPES_LENGTH.STRING),
+ note: Yup.string()
+ .trim()
+ .min(1)
+ .max(DATATYPES_LENGTH.TEXT)
+ .label(intl.get('note')),
+ entries: Yup.array().of(
+ Yup.object().shape({
+ quantity: Yup.number()
+ .nullable()
+ .max(DATATYPES_LENGTH.INT_10)
+ .when(['rate'], {
+ is: (rate) => rate,
+ then: Yup.number().required(),
+ }),
+ rate: Yup.number().nullable().max(DATATYPES_LENGTH.INT_10),
+ item_id: Yup.number()
+ .nullable()
+ .when(['quantity', 'rate'], {
+ is: (quantity, rate) => !isBlank(quantity) && !isBlank(rate),
+ then: Yup.number().required(),
+ }),
+ discount: Yup.number().nullable().min(0).max(DATATYPES_LENGTH.INT_10),
+ description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
+ }),
+ ),
+});
+
+export const CreateCreditNoteFormSchema = getSchema;
+export const EditCreditNoteFormSchema = getSchema;
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormDialogs.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormDialogs.js
new file mode 100644
index 000000000..5d5d69df2
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormDialogs.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import VendorCreditNumberDialog from '../../../Dialogs/VendorCreditNumberDialog';
+import { useFormikContext } from 'formik';
+
+/**
+ * Vendor credit form dialog.
+ */
+export default function VendorCreditNoteFormDialogs() {
+ // Update the form once the vendor credit number form submit confirm.
+ const handleVendorCreditNumberFormConfirm = ({
+ incrementNumber,
+ manually,
+ }) => {
+ setFieldValue('vendor_credit_number', incrementNumber || '');
+ setFieldValue('vendor_credit_no_manually', manually);
+ };
+ const { setFieldValue } = useFormikContext();
+
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.js
new file mode 100644
index 000000000..ea4ee2d99
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormFooter.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import { FastField } from 'formik';
+import { FormGroup, TextArea } from '@blueprintjs/core';
+import { FormattedMessage as T } from 'components';
+import { CLASSES } from 'common/classes';
+import { Row, Col, Postbox } from 'components';
+import { inputIntent } from 'utils';
+import classNames from 'classnames';
+
+/**
+ * Vendor Credit note form footer.
+ */
+export default function VendorCreditNoteFormFooter() {
+ return (
+
+ );
+}
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormHeader.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormHeader.js
new file mode 100644
index 000000000..e8a552268
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormHeader.js
@@ -0,0 +1,44 @@
+import React from 'react';
+import { useFormikContext } from 'formik';
+import intl from 'react-intl-universal';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import VendorCreditNoteFormHeaderFields from './VendorCreditNoteFormHeaderFields';
+
+import { getEntriesTotal } from 'containers/Entries/utils';
+import { PageFormBigNumber } from 'components';
+
+import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
+
+import { compose } from 'utils';
+
+/**
+ * Vendor Credit note header.
+ */
+function VendorCreditNoteFormHeader({
+ // #withCurrentOrganization
+ organization: { base_currency },
+}) {
+ const { values } = useFormikContext();
+
+ // Calculate the total amount.
+ const totalAmount = React.useMemo(
+ () => getEntriesTotal(values.entries),
+ [values.entries],
+ );
+
+ return (
+
+ );
+}
+
+export default compose(withCurrentOrganization())(
+ VendorCreditNoteFormHeader,
+);
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormHeaderFields.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormHeaderFields.js
new file mode 100644
index 000000000..e93db9c63
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormHeaderFields.js
@@ -0,0 +1,200 @@
+import React from 'react';
+import {
+ FormGroup,
+ InputGroup,
+ Position,
+ ControlGroup,
+} from '@blueprintjs/core';
+import { DateInput } from '@blueprintjs/datetime';
+import { FastField, Field, ErrorMessage } from 'formik';
+import { CLASSES } from 'common/classes';
+import classNames from 'classnames';
+import {
+ ContactSelecetList,
+ FieldRequiredHint,
+ InputPrependButton,
+ Icon,
+ FormattedMessage as T,
+} from 'components';
+import {
+ vendorsFieldShouldUpdate,
+ useObserveVendorCreditNoSettings,
+} from './utils';
+
+import { useVendorCreditNoteFormContext } from './VendorCreditNoteFormProvider';
+
+import {
+ momentFormatter,
+ compose,
+ tansformDateValue,
+ inputIntent,
+ handleDateChange,
+} from 'utils';
+
+import withSettings from 'containers/Settings/withSettings';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+
+/**
+ * Vendor Credit note form header fields.
+ */
+function VendorCreditNoteFormHeaderFields({
+ // #withDialogActions
+ openDialog,
+
+ // #withSettings
+ vendorcreditAutoIncrement,
+ vendorcreditNumberPrefix,
+ vendorcreditNextNumber,
+}) {
+ // Vendor Credit form context.
+ const { vendors } = useVendorCreditNoteFormContext();
+
+ // Handle vendor credit number changing.
+ const handleVendorCreditNumberChange = () => {
+ openDialog('vendor-credit-form');
+ };
+
+ // Handle vendor credit no. field blur.
+ const handleVendorCreditNoBlur = (form, field) => (event) => {
+ const newValue = event.target.value;
+
+ if (field.value !== newValue && vendorcreditAutoIncrement) {
+ openDialog('vendor-credit-form', {
+ initialFormValues: {
+ manualTransactionNo: newValue,
+ incrementMode: 'manual-transaction',
+ },
+ });
+ }
+ };
+ // Syncs vendor credit number settings with form.
+ useObserveVendorCreditNoSettings(
+ vendorcreditNumberPrefix,
+ vendorcreditNextNumber,
+ );
+
+ return (
+
+ {/* ----------- Vendor name ----------- */}
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+ }
+ inline={true}
+ className={classNames(
+ 'form-group--vendor-name',
+ 'form-group--select-list',
+ CLASSES.FILL,
+ )}
+ labelInfo={}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+ }
+ onContactSelected={(vendor) => {
+ form.setFieldValue('vendor_id', vendor.id);
+ }}
+ popoverFill={true}
+ />
+
+ )}
+
+
+ {/* ------- Vendor Credit date ------- */}
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+ }
+ inline={true}
+ labelInfo={}
+ className={classNames(
+ 'form-group--vendor_credit_date',
+ CLASSES.FILL,
+ )}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+ {
+ form.setFieldValue('vendor_credit_date', formattedDate);
+ })}
+ popoverProps={{ position: Position.BOTTOM_LEFT, minimal: true }}
+ inputProps={{
+ leftIcon: ,
+ }}
+ />
+
+ )}
+
+
+ {/* ----------- Vendor Credit No # ----------- */}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ inline={true}
+ className={('form-group--vendor_credit_number', CLASSES.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+
+
+ ,
+ }}
+ tooltip={true}
+ tooltipProps={{
+ content: (
+
+ ),
+ position: Position.BOTTOM_LEFT,
+ }}
+ />
+
+
+ )}
+
+ {/* ----------- Reference ----------- */}
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ inline={true}
+ className={classNames('form-group--reference', CLASSES.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+
+
+ )}
+
+
+ );
+}
+
+export default compose(
+ withDialogActions,
+ withSettings(({ vendorsCreditNoteSetting }) => ({
+ vendorcreditAutoIncrement: vendorsCreditNoteSetting?.autoIncrement,
+ vendorcreditNextNumber: vendorsCreditNoteSetting?.nextNumber,
+ vendorcreditNumberPrefix: vendorsCreditNoteSetting?.numberPrefix,
+ })),
+)(VendorCreditNoteFormHeaderFields);
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormPage.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormPage.js
new file mode 100644
index 000000000..31a4c3e3e
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormPage.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { useParams } from 'react-router-dom';
+
+import '../../../../style/pages/VendorsCreditNote/PageForm.scss';
+
+import VendorCreditNoteForm from './VendorCreditNoteForm';
+import { VendorCreditNoteFormProvider } from './VendorCreditNoteFormProvider';
+
+/**
+ * Vendor Credit note form pages.
+ */
+export default function VendorCreditNoteFormPage() {
+ const { id } = useParams();
+ const idAsInteger = parseInt(id, 10);
+
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormProvider.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormProvider.js
new file mode 100644
index 000000000..184c68772
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormProvider.js
@@ -0,0 +1,80 @@
+import React from 'react';
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+
+import {
+ useCreateVendorCredit,
+ useEditVendorCredit,
+ useVendorCredit,
+ useItems,
+ useVendors,
+ useSettingsVendorCredits,
+} from 'hooks/query';
+
+const VendorCreditNoteFormContext = React.createContext();
+
+/**
+ * Vendor Credit note data provider.
+ */
+function VendorCreditNoteFormProvider({ vendorCreditId, ...props }) {
+ // Handle fetching the items table based on the given query.
+ const {
+ data: { items },
+ isLoading: isItemsLoading,
+ } = useItems({
+ page_size: 10000,
+ });
+
+ // Handle fetching settings.
+ useSettingsVendorCredits();
+
+ // Handle fetch vendors data table or list
+ const {
+ data: { vendors },
+ isLoading: isVendorsLoading,
+ } = useVendors({ page_size: 10000 });
+
+ // Handle fetch vendor credit details.
+ const { data: vendorCredit, isLoading: isVendorCreditLoading } =
+ useVendorCredit(vendorCreditId, {
+ enabled: !!vendorCreditId,
+ });
+
+ // Form submit payload.
+ const [submitPayload, setSubmitPayload] = React.useState();
+
+ // Create and edit vendor credit mutations.
+ const { mutateAsync: createVendorCreditMutate } = useCreateVendorCredit();
+ const { mutateAsync: editVendorCreditMutate } = useEditVendorCredit();
+
+ // Determines whether the form in new mode.
+ const isNewMode = !vendorCreditId;
+
+ // Provider payload.
+ const provider = {
+ items,
+ vendors,
+ vendorCredit,
+ submitPayload,
+ isNewMode,
+
+ isVendorCreditLoading,
+
+ createVendorCreditMutate,
+ editVendorCreditMutate,
+ setSubmitPayload,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useVendorCreditNoteFormContext = () =>
+ React.useContext(VendorCreditNoteFormContext);
+
+export { VendorCreditNoteFormProvider, useVendorCreditNoteFormContext };
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteItemsEntriesEditor.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteItemsEntriesEditor.js
new file mode 100644
index 000000000..d86eb1f17
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteItemsEntriesEditor.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import classNames from 'classnames';
+import { FastField } from 'formik';
+import { CLASSES } from 'common/classes';
+import { useVendorCreditNoteFormContext } from './VendorCreditNoteFormProvider';
+import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
+import { entriesFieldShouldUpdate } from './utils';
+import { ITEM_TYPE } from 'containers/Entries/utils';
+
+export default function VendorCreditNoteItemsEntriesEditor() {
+ const { items } = useVendorCreditNoteFormContext();
+ return (
+
+
+ {({
+ form: { values, setFieldValue },
+ field: { value },
+ meta: { error, touched },
+ }) => (
+ {
+ setFieldValue('entries', entries);
+ }}
+ items={items}
+ errors={error}
+ linesNumber={4}
+ currencyCode={values.currency_code}
+ />
+ )}
+
+
+ );
+}
diff --git a/src/containers/Purchases/CreditNotes/CreditNoteForm/utils.js b/src/containers/Purchases/CreditNotes/CreditNoteForm/utils.js
new file mode 100644
index 000000000..4e2232e70
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNoteForm/utils.js
@@ -0,0 +1,129 @@
+import React from 'react';
+import * as R from 'ramda';
+import moment from 'moment';
+
+import {
+ defaultFastFieldShouldUpdate,
+ transformToForm,
+ repeatValue,
+ transactionNumber,
+ orderingLinesIndexes,
+} from 'utils';
+import {
+ updateItemsEntriesTotal,
+ ensureEntriesHaveEmptyLine,
+} from 'containers/Entries/utils';
+import { useFormikContext } from 'formik';
+
+export const MIN_LINES_NUMBER = 4;
+
+// Default Vendors Credit Note entry.
+export const defaultCreditNoteEntry = {
+ index: 0,
+ item_id: '',
+ rate: '',
+ discount: '',
+ quantity: '',
+ description: '',
+ amount: '',
+};
+
+// Default Vendors Credit Note.
+export const defaultVendorsCreditNote = {
+ vendor_id: '',
+ vendor_credit_number: '',
+ vendor_credit_no_manually: false,
+ vendor_credit_date: moment(new Date()).format('YYYY-MM-DD'),
+ // reference_no: '',
+ note: '',
+ entries: [...repeatValue(defaultCreditNoteEntry, MIN_LINES_NUMBER)],
+};
+
+/**
+ * Transformes the credit note to initial values of edit form.
+ */
+export const transformToEditForm = (creditNote) => {
+ const initialEntries = [
+ ...creditNote.entries.map((entry) => ({
+ ...transformToForm(entry, defaultCreditNoteEntry),
+ })),
+ ...repeatValue(
+ defaultCreditNoteEntry,
+ Math.max(MIN_LINES_NUMBER - creditNote.entries.length, 0),
+ ),
+ ];
+ const entries = R.compose(
+ ensureEntriesHaveEmptyLine(defaultCreditNoteEntry),
+ updateItemsEntriesTotal,
+ )(initialEntries);
+
+ return {
+ ...transformToForm(creditNote, defaultVendorsCreditNote),
+ entries,
+ };
+};
+
+/**
+ * Transformes credit note entries to submit request.
+ */
+export const transformEntriesToSubmit = (entries) => {
+ const transformCreditNoteEntry = R.compose(
+ R.omit(['amount']),
+ R.curry(transformToForm)(R.__, defaultCreditNoteEntry),
+ );
+ return R.compose(
+ orderingLinesIndexes,
+ R.map(transformCreditNoteEntry),
+ )(entries);
+};
+
+/**
+ * Filters the givne non-zero entries.
+ */
+export const filterNonZeroEntries = (entries) => {
+ return entries.filter((item) => item.item_id && item.quantity);
+};
+
+/**
+ * Transformes form values to request body.
+ */
+export const transformFormValuesToRequest = (values) => {
+ const entries = filterNonZeroEntries(values.entries);
+
+ return {
+ ...values,
+ entries: transformEntriesToSubmit(entries),
+ };
+};
+
+/**
+ * Detarmines vendors fast field should update
+ */
+export const vendorsFieldShouldUpdate = (newProps, oldProps) => {
+ return (
+ newProps.vendors !== oldProps.vendors ||
+ defaultFastFieldShouldUpdate(newProps, oldProps)
+ );
+};
+
+/**
+ * Detarmines entries fast field should update.
+ */
+export const entriesFieldShouldUpdate = (newProps, oldProps) => {
+ return (
+ newProps.items !== oldProps.items ||
+ defaultFastFieldShouldUpdate(newProps, oldProps)
+ );
+};
+
+/**
+ * Syncs invoice no. settings with form.
+ */
+ export const useObserveVendorCreditNoSettings = (prefix, nextNumber) => {
+ const { setFieldValue } = useFormikContext();
+
+ React.useEffect(() => {
+ const creditNo = transactionNumber(prefix, nextNumber);
+ setFieldValue('vendor_credit_number', creditNo);
+ }, [setFieldValue, prefix, nextNumber]);
+};
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.js
new file mode 100644
index 000000000..1757f6863
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.js
@@ -0,0 +1,151 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import {
+ Button,
+ Classes,
+ NavbarDivider,
+ NavbarGroup,
+ Intent,
+ Alignment,
+} from '@blueprintjs/core';
+import {
+ Icon,
+ FormattedMessage as T,
+ DashboardActionViewsList,
+ AdvancedFilterPopover,
+ DashboardFilterButton,
+ DashboardRowsHeightButton,
+} from 'components';
+import DashboardActionsBar from '../../../../components/Dashboard/DashboardActionsBar';
+
+import { useVendorsCreditNoteListContext } from './VendorsCreditNoteListProvider';
+
+import withVendorsCreditNotes from './withVendorsCreditNotes';
+import withVendorsCreditNotesActions from './withVendorsCreditNotesActions';
+import withSettings from '../../../Settings/withSettings';
+import withSettingsActions from '../../../Settings/withSettingsActions';
+
+import withVendorActions from './withVendorActions';
+
+import { compose } from 'utils';
+
+/**
+ * Vendors Credit note table actions bar.
+ */
+function VendorsCreditNoteActionsBar({
+ setVendorCreditsTableState,
+
+ // #withVendorsCreditNotes
+ vendorCreditFilterRoles,
+
+ // #withVendorsCreditNotesActions
+ setVendorsCreditNoteTableState,
+
+ // #withSettings
+ creditNoteTableSize,
+
+ // #withSettingsActions
+ addSetting,
+}) {
+ const history = useHistory();
+
+ // vendor credit list context.
+ const { VendorCreditsViews, fields, refresh } =
+ useVendorsCreditNoteListContext();
+
+ // Handle click a new Vendor.
+ const handleClickNewVendorCredit = () => {
+ history.push('/vendor-credits/new');
+ };
+
+ // Handle view tab change.
+ const handleTabChange = (view) => {
+ setVendorCreditsTableState({ viewSlug: view ? view.slug : null });
+ };
+
+ // Handle click a refresh credit note.
+ const handleRefreshBtnClick = () => {
+ refresh();
+ };
+
+ // Handle table row size change.
+ const handleTableRowSizeChange = (size) => {
+ addSetting('vendorCredit', 'tableSize', size);
+ };
+
+ return (
+
+
+ }
+ onChange={handleTabChange}
+ />
+
+ }
+ text={}
+ onClick={handleClickNewVendorCredit}
+ />
+ {
+ setVendorsCreditNoteTableState({ filterRoles: filterConditions });
+ },
+ }}
+ >
+
+
+ }
+ text={}
+ />
+ }
+ text={}
+ />
+ }
+ text={}
+ />
+
+
+
+
+
+ }
+ onClick={handleRefreshBtnClick}
+ />
+
+
+ );
+}
+
+export default compose(
+ withVendorsCreditNotesActions,
+ withVendorActions,
+ withSettingsActions,
+ withVendorsCreditNotes(({ vendorsCreditNoteTableState }) => ({
+ vendorCreditFilterRoles: vendorsCreditNoteTableState.filterRoles,
+ })),
+ withSettings(({ vendorsCreditNoteSetting }) => ({
+ creditNoteTableSize: vendorsCreditNoteSetting?.tableSize,
+ })),
+)(VendorsCreditNoteActionsBar);
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteDataTable.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteDataTable.js
new file mode 100644
index 000000000..05e4389a7
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteDataTable.js
@@ -0,0 +1,135 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+
+import VendorsCreditNoteEmptyStatus from './VendorsCreditNoteEmptyStatus';
+import { DataTable, DashboardContentTable } from 'components';
+import { TABLES } from 'common/tables';
+import { useMemorizedColumnsWidths } from 'hooks';
+
+import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
+import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
+
+import withDashboardActions from 'containers/Dashboard/withDashboardActions';
+import withVendorsCreditNotesActions from './withVendorsCreditNotesActions';
+import withAlertsActions from 'containers/Alert/withAlertActions';
+import withDrawerActions from 'containers/Drawer/withDrawerActions';
+import withSettings from '../../../Settings/withSettings';
+
+import { useVendorsCreditNoteTableColumns, ActionsMenu } from './components';
+import { useVendorsCreditNoteListContext } from './VendorsCreditNoteListProvider';
+
+import { compose } from 'utils';
+
+/**
+ * Vendors Credit note data table.
+ */
+function VendorsCreditNoteDataTable({
+ // #withVendorsCreditNotesActions
+ setVendorsCreditNoteTableState,
+
+ // #withAlertsActions
+ openAlert,
+
+ // #withDrawerActions
+ openDrawer,
+
+ // #withSettings
+ creditNoteTableSize,
+}) {
+ const history = useHistory();
+
+ // Vendor credits context.
+ const {
+ vendorCredits,
+ pagination,
+ isEmptyStatus,
+ isVendorCreditsFetching,
+ isVendorCreditsLoading,
+ } = useVendorsCreditNoteListContext();
+
+ // Credit note table columns.
+ const columns = useVendorsCreditNoteTableColumns();
+
+ // Local storage memorizing columns widths.
+ const [initialColumnsWidths, , handleColumnResizing] =
+ useMemorizedColumnsWidths(TABLES.VENDOR_CREDITS);
+
+ // Handles fetch data once the table state change.
+ const handleDataTableFetchData = React.useCallback(
+ ({ pageSize, pageIndex, sortBy }) => {
+ setVendorsCreditNoteTableState({
+ pageSize,
+ pageIndex,
+ sortBy,
+ });
+ },
+ [setVendorsCreditNoteTableState],
+ );
+
+ // Display create note empty status instead of the table.
+ if (isEmptyStatus) {
+ return
;
+ }
+
+ const handleViewDetailVendorCredit = ({ id }) => {
+ openDrawer('vendor-credit-detail-drawer', { vendorCreditId: id });
+ };
+
+ // Handle delete credit note.
+ const handleDeleteVendorCreditNote = ({ id }) => {
+ openAlert('vendor-credit-delete', { vendorCreditId: id });
+ };
+
+ // Handle edit credit note.
+ const hanldeEditVendorCreditNote = (vendorCredit) => {
+ history.push(`/vendor-credits/${vendorCredit.id}/edit`);
+ };
+
+ // Handle cell click.
+ const handleCellClick = (cell, event) => {
+ openDrawer('vendor-credit-detail-drawer', {
+ vendorCreditId: cell.row.original.id,
+ });
+ };
+
+ return (
+
+
+
+ );
+}
+
+export default compose(
+ withDashboardActions,
+ withVendorsCreditNotesActions,
+ withAlertsActions,
+ withDrawerActions,
+ withSettings(({ vendorsCreditNoteSetting }) => ({
+ creditNoteTableSize: vendorsCreditNoteSetting?.tableSize,
+ })),
+)(VendorsCreditNoteDataTable);
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteEmptyStatus.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteEmptyStatus.js
new file mode 100644
index 000000000..de5238b9c
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteEmptyStatus.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Button, Intent } from '@blueprintjs/core';
+import { useHistory } from 'react-router-dom';
+import { EmptyStatus } from 'components';
+import { FormattedMessage as T } from 'components';
+
+export default function VendorsCreditNoteEmptyStatus() {
+ const history = useHistory();
+ return (
+
}
+ description={
+
+
+
+ }
+ action={
+ <>
+
+
+
+ >
+ }
+ />
+ );
+}
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteListProvider.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteListProvider.js
new file mode 100644
index 000000000..b4c7b80c2
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteListProvider.js
@@ -0,0 +1,77 @@
+import React from 'react';
+import { isEmpty } from 'lodash';
+
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+import {
+ useResourceViews,
+ useResourceMeta,
+ useVendorCredits,
+ useRefreshVendorCredits,
+} from 'hooks/query';
+
+import { getFieldsFromResourceMeta } from 'utils';
+
+const VendorsCreditNoteListContext = React.createContext();
+
+/**
+ * Vendors Credit note data provider.
+ */
+function VendorsCreditNoteListProvider({ query, tableStateChanged, ...props }) {
+ // Vendor Credits refresh action.
+ const { refresh } = useRefreshVendorCredits();
+
+ // Fetch accounts resource views and fields.
+ const { data: VendorCreditsViews, isLoading: isViewsLoading } =
+ useResourceViews('vendor_credits');
+
+ // Fetch the accounts resource fields.
+ const {
+ data: resourceMeta,
+ isLoading: isResourceLoading,
+ isFetching: isResourceFetching,
+ } = useResourceMeta('vendor_credits');
+
+ // Fetch vendor credits list.
+ const {
+ data: { vendorCredits, pagination, filterMeta },
+ isLoading: isVendorCreditsLoading,
+ isFetching: isVendorCreditsFetching,
+ } = useVendorCredits(query, { keepPreviousData: true });
+
+ // Detarmines the datatable empty status.
+ const isEmptyStatus =
+ isEmpty(vendorCredits) && !isVendorCreditsLoading && !tableStateChanged;
+
+ // Provider payload.
+ const provider = {
+ vendorCredits,
+ pagination,
+ VendorCreditsViews,
+ refresh,
+
+
+ resourceMeta,
+ fields: getFieldsFromResourceMeta(resourceMeta.fields),
+ isResourceLoading,
+ isResourceFetching,
+
+ isVendorCreditsFetching,
+ isVendorCreditsLoading,
+ isViewsLoading,
+ isEmptyStatus,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useVendorsCreditNoteListContext = () =>
+ React.useContext(VendorsCreditNoteListContext);
+
+export { VendorsCreditNoteListProvider, useVendorsCreditNoteListContext };
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteViewTabs.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteViewTabs.js
new file mode 100644
index 000000000..6bdcac9ed
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteViewTabs.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
+
+import { DashboardViewsTabs } from 'components';
+
+import withVendorsCreditNotes from './withVendorsCreditNotes';
+import withVendorsCreditNotesActions from './withVendorsCreditNotesActions';
+
+import { compose, transfromViewsToTabs } from 'utils';
+import { useVendorsCreditNoteListContext } from './VendorsCreditNoteListProvider';
+
+/**
+ * Vendors Credit note views tabs.
+ */
+function VendorsCreditNoteViewTabs({
+ // #withVendorsCreditNotes
+ vendorCreditCurrentView,
+
+ // #withVendorsCreditNotesActions
+ setVendorsCreditNoteTableState,
+}) {
+ // vendor credit list context.
+ const { VendorCreditsViews } = useVendorsCreditNoteListContext();
+
+ // Handle tab change.
+ const handleTabsChange = (viewSlug) => {
+ setVendorsCreditNoteTableState({ viewSlug: viewSlug || null });
+ };
+
+ const tabs = transfromViewsToTabs(VendorCreditsViews);
+ return (
+
+
+
+
+
+ );
+}
+
+export default compose(
+ withVendorsCreditNotesActions,
+ withVendorsCreditNotes(({ vendorsCreditNoteTableState }) => ({
+ vendorCreditCurrentView: vendorsCreditNoteTableState.viewSlug,
+ })),
+)(VendorsCreditNoteViewTabs);
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNotesList.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNotesList.js
new file mode 100644
index 000000000..444d44c05
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNotesList.js
@@ -0,0 +1,54 @@
+import React from 'react';
+
+import '../../../../style/pages/VendorsCreditNote/List.scss';
+
+import { DashboardPageContent } from 'components';
+import VendorsCreditNoteActionsBar from './VendorsCreditNoteActionsBar';
+import VendorsCreditNoteViewTabs from './VendorsCreditNoteViewTabs';
+import VendorsCreditNoteDataTable from './VendorsCreditNoteDataTable';
+
+import withVendorsCreditNotes from './withVendorsCreditNotes';
+import withVendorsCreditNotesActions from './withVendorsCreditNotesActions';
+
+import { VendorsCreditNoteListProvider } from './VendorsCreditNoteListProvider';
+import { transformTableStateToQuery, compose } from 'utils';
+
+function VendorsCreditNotesList({
+ // #withVendorsCreditNotes
+ vendorsCreditNoteTableState,
+ vendorsCreditNoteTableStateChanged,
+
+ // #withVendorsCreditNotesActions
+ resetVendorsCreditNoteTableState,
+}) {
+ // Resets the credit note table state once the page unmount.
+ React.useEffect(
+ () => () => {
+ resetVendorsCreditNoteTableState();
+ },
+ [resetVendorsCreditNoteTableState],
+ );
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export default compose(
+ withVendorsCreditNotesActions,
+ withVendorsCreditNotes(
+ ({ vendorsCreditNoteTableState, vendorsCreditNoteTableStateChanged }) => ({
+ vendorsCreditNoteTableState,
+ vendorsCreditNoteTableStateChanged,
+ }),
+ ),
+)(VendorsCreditNotesList);
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/components.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/components.js
new file mode 100644
index 000000000..20f182f2d
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/components.js
@@ -0,0 +1,118 @@
+import React from 'react';
+import {
+ Intent,
+ Tag,
+ Menu,
+ MenuItem,
+ MenuDivider,
+ ProgressBar,
+} from '@blueprintjs/core';
+import intl from 'react-intl-universal';
+import clsx from 'classnames';
+
+import { CLASSES } from '../../../../common/classes';
+import {
+ FormatDateCell,
+ FormattedMessage as T,
+ AppToaster,
+ Choose,
+ If,
+ Icon,
+} from 'components';
+import { formattedAmount, safeCallback, calculateStatus } from 'utils';
+
+/**
+ * Actions menu.
+ */
+export function ActionsMenu({
+ payload: { onEdit, onDelete, onViewDetails },
+ row: { original },
+}) {
+ return (
+
+ );
+}
+
+/**
+ * Retrieve vendors credit note table columns.
+ */
+export function useVendorsCreditNoteTableColumns() {
+ return React.useMemo(
+ () => [
+ {
+ id: 'vendor_credit_date',
+ Header: intl.get('date'),
+ accessor: 'formatted_vendor_credit_date',
+ Cell: FormatDateCell,
+ width: 110,
+ className: 'vendor_credit_date',
+ clickable: true,
+ textOverview: true,
+ },
+ {
+ id: 'vendor',
+ Header: intl.get('vendor_name'),
+ accessor: 'vendor.display_name',
+ width: 180,
+ className: 'vendor_id',
+ clickable: true,
+ textOverview: true,
+ },
+ {
+ id: 'vendor_credit_number',
+ Header: intl.get('vendor_credits.column.vendor_credit_no'),
+ accessor: 'vendor_credit_number',
+ width: 100,
+ className: 'vendor_credit_number',
+ clickable: true,
+ textOverview: true,
+ },
+ {
+ id: 'amount',
+ Header: intl.get('amount'),
+ accessor: 'formatted_amount',
+ width: 120,
+ align: 'right',
+ clickable: true,
+ textOverview: true,
+ className: clsx(CLASSES.FONT_BOLD),
+ },
+ {
+ id: 'status',
+ Header: intl.get('status'),
+ // accessor:
+ width: 120, // 160
+ className: 'status',
+ clickable: true,
+ },
+ {
+ id: 'reference_no',
+ Header: intl.get('reference_no'),
+ accessor: 'reference_no',
+ width: 90,
+ className: 'reference_no',
+ clickable: true,
+ textOverview: true,
+ },
+ ],
+ [],
+ );
+}
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorActions.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorActions.js
new file mode 100644
index 000000000..fc9a533e1
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorActions.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux';
+import {
+ setVendorCreditTableState,
+ resetVendorCreditTableState,
+} from '../../../../store/VendorCredit/vendorCredit.actions';
+
+const mapDipatchToProps = (dispatch) => ({
+ setVendorCreditsTableState: (queries) =>
+ dispatch(setVendorCreditTableState(queries)),
+ resetVendorCreditsTableState: () => dispatch(resetVendorCreditTableState()),
+});
+
+export default connect(null, mapDipatchToProps);
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotes.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotes.js
new file mode 100644
index 000000000..1b3544513
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotes.js
@@ -0,0 +1,23 @@
+import { connect } from 'react-redux';
+import {
+ getVendorCreditTableStateFactory,
+ isVendorCreditTableStateChangedFactory,
+} from '../../../../store/VendorCredit/vendorCredit.selector';
+
+export default (mapState) => {
+ const getVendorsCreditNoteTableState = getVendorCreditTableStateFactory();
+ const isVendorsCreditNoteTableChanged =
+ isVendorCreditTableStateChangedFactory();
+
+ const mapStateToProps = (state, props) => {
+ const mapped = {
+ vendorsCreditNoteTableState: getVendorsCreditNoteTableState(state, props),
+ vendorsCreditNoteTableStateChanged: isVendorsCreditNoteTableChanged(
+ state,
+ props,
+ ),
+ };
+ return mapState ? mapState(mapped, state, props) : mapped;
+ };
+ return connect(mapStateToProps);
+};
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotesActions.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotesActions.js
new file mode 100644
index 000000000..d397cd1ac
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotesActions.js
@@ -0,0 +1,14 @@
+import { connect } from 'react-redux';
+import {
+ setVendorCreditTableState,
+ resetVendorCreditTableState,
+} from '../../../../store/VendorCredit/vendorCredit.actions';
+
+const mapDispatchToProps = (dispatch) => ({
+ setVendorsCreditNoteTableState: (queries) =>
+ dispatch(setVendorCreditTableState(queries)),
+ resetVendorsCreditNoteTableState: () =>
+ dispatch(resetVendorCreditTableState()),
+});
+
+export default connect(null, mapDispatchToProps);
diff --git a/src/containers/Purchases/CreditNotes/VendorCreditNotesAlerts.js b/src/containers/Purchases/CreditNotes/VendorCreditNotesAlerts.js
new file mode 100644
index 000000000..fb0b1b3ff
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/VendorCreditNotesAlerts.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+const VendorCreditDeleteAlert = React.lazy(() =>
+ import('../../Alerts/VendorCeditNotes/VendorCreditDeleteAlert'),
+);
+
+/**
+ * Vendor Credit notes alerts.
+ */
+export default [
+ {
+ name: 'vendor-credit-delete',
+ component: VendorCreditDeleteAlert,
+ },
+];
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.js
new file mode 100644
index 000000000..727643a7b
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.js
@@ -0,0 +1,159 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import { useFormikContext } from 'formik';
+import {
+ Intent,
+ Button,
+ ButtonGroup,
+ Popover,
+ PopoverInteractionKind,
+ Position,
+ Menu,
+ MenuItem,
+} from '@blueprintjs/core';
+import { If, Icon, FormattedMessage as T } from 'components';
+import { CLASSES } from 'common/classes';
+import classNames from 'classnames';
+import { useCreditNoteFormContext } from './CreditNoteFormProvider';
+
+/**
+ * Credit note floating actions.
+ */
+export default function CreditNoteFloatingActions() {
+ const history = useHistory();
+
+ // Formik context.
+ const { resetForm, submitForm, isSubmitting } = useFormikContext();
+
+ // Credit note form context.
+ const { setSubmitPayload, isNewMode } = useCreditNoteFormContext();
+
+ // Handle submit, save and anothe new button click.
+ const handleSubmitAndNewBtnClick = (event) => {
+ setSubmitPayload({ redirect: false, status: true, resetForm: true });
+ submitForm();
+ };
+
+ // Handle submit as save & continue editing button click.
+ const handleSubmitSaveContinueEditingBtnClick = (event) => {
+ setSubmitPayload({ redirect: false, status: true });
+ submitForm();
+ };
+
+ // Handle submit as draft button click.
+ const handleSubmitDraftBtnClick = (event) => {
+ setSubmitPayload({ redirect: true, status: false });
+ submitForm();
+ };
+
+ // handle submit as draft & new button click.
+ const handleSubmitDraftAndNewBtnClick = (event) => {
+ setSubmitPayload({ redirect: false, status: false, resetForm: true });
+ submitForm();
+ };
+
+ // Handle submit as draft & continue editing button click.
+ const handleSubmitDraftContinueEditingBtnClick = (event) => {
+ setSubmitPayload({ redirect: false, status: false });
+ submitForm();
+ };
+
+ // Handle cancel button click.
+ const handleCancelBtnClick = (event) => {
+ history.goBack();
+ };
+
+ // Handle submit button click.
+ const handleSubmitBtnClick = (event) => {
+ setSubmitPayload({ redirect: true });
+ submitForm();
+ };
+
+ const handleClearBtnClick = (event) => {
+ resetForm();
+ };
+
+ return (
+
+ {/* ----------- Save ----------- */}
+
+ }
+ />
+
+
+ }
+ onClick={handleSubmitAndNewBtnClick}
+ />
+ }
+ onClick={handleSubmitSaveContinueEditingBtnClick}
+ />
+
+ }
+ minimal={true}
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ />
+
+
+ {/* ----------- Save As Draft ----------- */}
+
+ }
+ />
+
+ }
+ onClick={handleSubmitDraftAndNewBtnClick}
+ />
+ }
+ onClick={handleSubmitDraftContinueEditingBtnClick}
+ />
+
+ }
+ minimal={true}
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ />
+
+
+ {/* ----------- Clear & Reset----------- */}
+
:
}
+ />
+ {/* ----------- Cancel ----------- */}
+
}
+ />
+
+ );
+}
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.js
new file mode 100644
index 000000000..b75e6130d
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.js
@@ -0,0 +1,170 @@
+import React from 'react';
+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 classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import {
+ CreateCreditNoteFormSchema,
+ EditCreditNoteFormSchema,
+} from './CreditNoteForm.schema';
+
+import CreditNoteFormHeader from './CreditNoteFormHeader';
+import CreditNoteItemsEntriesEditorField from './CreditNoteItemsEntriesEditorField';
+import CreditNoteFormFooter from './CreditNoteFormFooter';
+import CreditNoteFloatingActions from './CreditNoteFloatingActions';
+import CreditNoteFormDialogs from './CreditNoteFormDialogs';
+
+import { AppToaster } from 'components';
+
+import { useCreditNoteFormContext } from './CreditNoteFormProvider';
+import {
+ filterNonZeroEntries,
+ transformToEditForm,
+ transformFormValuesToRequest,
+ defaultCreditNote,
+} from './utils';
+
+import {
+ compose,
+ orderingLinesIndexes,
+ transactionNumber,
+ safeSumBy,
+} from 'utils';
+
+import withSettings from 'containers/Settings/withSettings';
+import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
+
+/**
+ * Credit note form.
+ */
+function CreditNoteForm({
+ // #withSettings
+ creditAutoIncrement,
+ creditNumberPrefix,
+ creditNextNumber,
+
+ // #withCurrentOrganization
+ organization: { base_currency },
+}) {
+ const history = useHistory();
+
+ // Credit note form context.
+ const {
+ isNewMode,
+ submitPayload,
+ creditNote,
+ createCreditNoteMutate,
+ editCreditNoteMutate,
+ } = useCreditNoteFormContext();
+
+ // Credit number.
+ const creditNumber = transactionNumber(creditNumberPrefix, creditNextNumber);
+
+ // Initial values.
+ const initialValues = React.useMemo(
+ () => ({
+ ...(!isEmpty(creditNote)
+ ? { ...transformToEditForm(creditNote), currency_code: base_currency }
+ : {
+ ...defaultCreditNote,
+ ...(creditAutoIncrement && {
+ credit_note_number: creditNumber,
+ }),
+ entries: orderingLinesIndexes(defaultCreditNote.entries),
+ }),
+ }),
+ [],
+ );
+
+ // Handles form submit.
+ const handleFormSubmit = (
+ values,
+ { setSubmitting, setErrors, resetForm },
+ ) => {
+ const entries = filterNonZeroEntries(values.entries);
+ const totalQuantity = safeSumBy(entries, 'quantity');
+
+ if (totalQuantity === 0) {
+ AppToaster.show({
+ message: intl.get('quantity_cannot_be_zero_or_empty'),
+ intent: Intent.DANGER,
+ });
+ setSubmitting(false);
+ return;
+ }
+ const form = {
+ ...transformFormValuesToRequest(values),
+ };
+ // Handle the request success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: intl.get(
+ isNewMode
+ ? 'credit_note.success_message'
+ : 'credit_note.edit_success_message',
+ ),
+ intent: Intent.SUCCESS,
+ });
+ setSubmitting(false);
+
+ if (submitPayload.redirect) {
+ history.push('/credit-notes');
+ }
+ if (submitPayload.resetForm) {
+ resetForm();
+ }
+ };
+ // Handle the request error.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ setSubmitting(false);
+ };
+ if (isNewMode) {
+ createCreditNoteMutate(form).then(onSuccess).catch(onError);
+ } else {
+ editCreditNoteMutate([creditNote.id, form])
+ .then(onSuccess)
+ .catch(onError);
+ }
+ };
+
+ return (
+
+
+
+
+
+ );
+}
+export default compose(
+ withSettings(({ creditNoteSettings }) => ({
+ creditAutoIncrement: creditNoteSettings?.autoIncrement,
+ creditNextNumber: creditNoteSettings?.nextNumber,
+ creditNumberPrefix: creditNoteSettings?.numberPrefix,
+ })),
+ withCurrentOrganization(),
+)(CreditNoteForm);
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.schema.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.schema.js
new file mode 100644
index 000000000..dad38888e
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.schema.js
@@ -0,0 +1,47 @@
+import * as Yup from 'yup';
+import intl from 'react-intl-universal';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+import { isBlank } from 'utils';
+
+const getSchema = () =>
+ Yup.object().shape({
+ customer_id: Yup.string().label(intl.get('customer_name_')).required(),
+ credit_note_date: Yup.date().required().label(intl.get('invoice_date_')),
+ credit_note_number: Yup.string()
+ .max(DATATYPES_LENGTH.STRING)
+ .label(intl.get('invoice_no_')),
+ reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING),
+ note: Yup.string()
+ .trim()
+ .min(1)
+ .max(DATATYPES_LENGTH.TEXT)
+ .label(intl.get('note')),
+ terms_conditions: Yup.string()
+ .trim()
+ .min(1)
+ .max(DATATYPES_LENGTH.TEXT)
+ .label(intl.get('note')),
+ entries: Yup.array().of(
+ Yup.object().shape({
+ quantity: Yup.number()
+ .nullable()
+ .max(DATATYPES_LENGTH.INT_10)
+ .when(['rate'], {
+ is: (rate) => rate,
+ then: Yup.number().required(),
+ }),
+ rate: Yup.number().nullable().max(DATATYPES_LENGTH.INT_10),
+ item_id: Yup.number()
+ .nullable()
+ .when(['quantity', 'rate'], {
+ is: (quantity, rate) => !isBlank(quantity) && !isBlank(rate),
+ then: Yup.number().required(),
+ }),
+ discount: Yup.number().nullable().min(0).max(100),
+ description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
+ }),
+ ),
+ });
+
+export const CreateCreditNoteFormSchema = getSchema;
+export const EditCreditNoteFormSchema = getSchema;
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormDialogs.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormDialogs.js
new file mode 100644
index 000000000..bfb2d99c7
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormDialogs.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import CreditNoteNumberDialog from '../../../Dialogs/CreditNoteNumberDialog';
+import { useFormikContext } from 'formik';
+
+/**
+ * Credit note form dialogs.
+ */
+export default function CreditNoteFormDialogs() {
+ // Update the form once the credit number form submit confirm.
+ const handleCreditNumberFormConfirm = ({ incrementNumber, manually }) => {
+ setFieldValue('credit_note_number', incrementNumber || '');
+ setFieldValue('credit_note_no_manually', manually);
+ };
+
+ const { setFieldValue } = useFormikContext();
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooter.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooter.js
new file mode 100644
index 000000000..1bf8cd89c
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooter.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import { FastField } from 'formik';
+import { FormGroup, TextArea } from '@blueprintjs/core';
+import { FormattedMessage as T } from 'components';
+import { CLASSES } from 'common/classes';
+import { Row, Col, Postbox } from 'components';
+import { inputIntent } from 'utils';
+import classNames from 'classnames';
+
+/**
+ * Credit note form footer.
+ */
+export default function CreditNoteFormFooter() {
+ return (
+
+
}
+ defaultOpen={false}
+ >
+
+
+ {/* --------- Customer notes --------- */}
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ className={'form-group--note'}
+ intent={inputIntent({ error, touched })}
+ >
+
+
+ )}
+
+ {/* --------- Terms and conditions --------- */}
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ className={'form-group--terms_conditions'}
+ intent={inputIntent({ error, touched })}
+ >
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeader.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeader.js
new file mode 100644
index 000000000..8407cb027
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeader.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import { useFormikContext } from 'formik';
+import intl from 'react-intl-universal';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import CreditNoteFormHeaderFields from './CreditNoteFormHeaderFields';
+
+import { getEntriesTotal } from 'containers/Entries/utils';
+import { PageFormBigNumber } from 'components';
+
+import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
+
+import { compose } from 'utils';
+
+/**
+ * Credit note header.
+ */
+function CreditNoteFormHeader({
+ // #withCurrentOrganization
+ organization: { base_currency },
+}) {
+ const { values } = useFormikContext();
+
+ // Calculate the total amount.
+ const totalAmount = React.useMemo(
+ () => getEntriesTotal(values.entries),
+ [values.entries],
+ );
+
+ return (
+
+ );
+}
+
+export default compose(withCurrentOrganization())(CreditNoteFormHeader);
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.js
new file mode 100644
index 000000000..00ac441d2
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.js
@@ -0,0 +1,193 @@
+import React from 'react';
+import {
+ FormGroup,
+ InputGroup,
+ Position,
+ ControlGroup,
+} from '@blueprintjs/core';
+import { DateInput } from '@blueprintjs/datetime';
+import { FastField, Field, ErrorMessage } from 'formik';
+import { CLASSES } from 'common/classes';
+import classNames from 'classnames';
+import {
+ ContactSelecetList,
+ FieldRequiredHint,
+ InputPrependButton,
+ Icon,
+ FormattedMessage as T,
+} from 'components';
+import {
+ customerNameFieldShouldUpdate,
+ useObserveCreditNoSettings,
+} from './utils';
+
+import { useCreditNoteFormContext } from './CreditNoteFormProvider';
+import withSettings from 'containers/Settings/withSettings';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+
+import {
+ momentFormatter,
+ compose,
+ tansformDateValue,
+ inputIntent,
+ handleDateChange,
+} from 'utils';
+
+/**
+ * Credit note form header fields.
+ */
+function CreditNoteFormHeaderFields({
+ // #withDialogActions
+ openDialog,
+
+ // #withSettings
+ creditAutoIncrement,
+ creditNumberPrefix,
+ creditNextNumber,
+}) {
+ // Credit note form context.
+ const { customers } = useCreditNoteFormContext();
+
+ // Handle credit number changing.
+ const handleCreditNumberChange = () => {
+ openDialog('credit-number-form');
+ };
+
+ // Handle credit no. field blur.
+ const handleCreditNoBlur = (form, field) => (event) => {
+ const newValue = event.target.value;
+
+ if (field.value !== newValue && creditAutoIncrement) {
+ openDialog('credit-number-form', {
+ initialFormValues: {
+ manualTransactionNo: newValue,
+ incrementMode: 'manual-transaction',
+ },
+ });
+ }
+ };
+
+ // Syncs credit number settings with form.
+ useObserveCreditNoSettings(creditNumberPrefix, creditNextNumber);
+
+ return (
+
+ {/* ----------- Customer name ----------- */}
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+ }
+ inline={true}
+ className={classNames(
+ 'form-group--customer-name',
+ 'form-group--select-list',
+ CLASSES.FILL,
+ )}
+ labelInfo={}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+ }
+ onContactSelected={(customer) => {
+ form.setFieldValue('customer_id', customer.id);
+ }}
+ popoverFill={true}
+ />
+
+ )}
+
+ {/* ----------- Credit note date ----------- */}
+
+ {({ form, field: { value }, meta: { error, touched } }) => (
+ }
+ inline={true}
+ labelInfo={}
+ className={classNames('form-group--credit_note_date', CLASSES.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+ {
+ form.setFieldValue('credit_note_date', formattedDate);
+ })}
+ popoverProps={{ position: Position.BOTTOM_LEFT, minimal: true }}
+ inputProps={{
+ leftIcon: ,
+ }}
+ />
+
+ )}
+
+ {/* ----------- Credit note # ----------- */}
+
+ {({ form, field, meta: { error, touched } }) => (
+ }
+ labelInfo={}
+ inline={true}
+ className={classNames(
+ 'form-group--credit_note_number',
+ CLASSES.FILL,
+ )}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+
+
+ ,
+ }}
+ tooltip={true}
+ tooltipProps={{
+ content: (
+
+ ),
+ position: Position.BOTTOM_LEFT,
+ }}
+ />
+
+
+ )}
+
+ {/* ----------- Reference ----------- */}
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ inline={true}
+ className={classNames('form-group--reference', CLASSES.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+
+
+ )}
+
+
+ );
+}
+export default compose(
+ withDialogActions,
+ withSettings(({ creditNoteSettings }) => ({
+ creditAutoIncrement: creditNoteSettings?.autoIncrement,
+ creditNextNumber: creditNoteSettings?.nextNumber,
+ creditNumberPrefix: creditNoteSettings?.numberPrefix,
+ })),
+)(CreditNoteFormHeaderFields);
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage.js
new file mode 100644
index 000000000..7acfc56b3
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage.js
@@ -0,0 +1,21 @@
+import React from 'react';
+import { useParams } from 'react-router-dom';
+
+import '../../../../style/pages/CreditNote/PageForm.scss';
+
+import CreditNoteForm from './CreditNoteForm';
+import { CreditNoteFormProvider } from './CreditNoteFormProvider';
+
+/**
+ * Credit note form page.
+ */
+export default function CreditNoteFormPage() {
+ const { id } = useParams();
+ const idAsInteger = parseInt(id, 10);
+
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.js
new file mode 100644
index 000000000..210875d51
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import { useLocation } from 'react-router-dom';
+import { isEmpty, pick } from 'lodash';
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+import { transformToEditForm } from './utils';
+
+import {
+ useCreditNote,
+ useCreateCreditNote,
+ useEditCreditNote,
+ useItems,
+ useCustomers,
+ useSettingsCreditNotes,
+} from 'hooks/query';
+
+const CreditNoteFormContext = React.createContext();
+
+/**
+ * Credit note data provider.
+ */
+function CreditNoteFormProvider({ creditNoteId, ...props }) {
+ // Handle fetch customers data table or list
+ const {
+ data: { customers },
+ isLoading: isCustomersLoading,
+ } = useCustomers({ page_size: 10000 });
+
+ // Handle fetching the items table based on the given query.
+ const {
+ data: { items },
+ isLoading: isItemsLoading,
+ } = useItems({
+ page_size: 10000,
+ });
+
+ // Handle fetch vendor credit details.
+ const { data: creditNote, isLoading: isCreditNoteLoading } = useCreditNote(
+ creditNoteId,
+ {
+ enabled: !!creditNoteId,
+ },
+ );
+
+ // Handle fetching settings.
+ useSettingsCreditNotes();
+
+ // Create and edit credit note mutations.
+ const { mutateAsync: createCreditNoteMutate } = useCreateCreditNote();
+ const { mutateAsync: editCreditNoteMutate } = useEditCreditNote();
+
+ // Form submit payload.
+ const [submitPayload, setSubmitPayload] = React.useState();
+
+ // Determines whether the form in new mode.
+ const isNewMode = !creditNoteId;
+
+ // Provider payload.
+ const provider = {
+ items,
+ customers,
+ creditNote,
+ submitPayload,
+ isNewMode,
+
+ isItemsLoading,
+ isCustomersLoading,
+
+ createCreditNoteMutate,
+ editCreditNoteMutate,
+ setSubmitPayload,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useCreditNoteFormContext = () => React.useContext(CreditNoteFormContext);
+
+export { CreditNoteFormProvider, useCreditNoteFormContext };
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteItemsEntriesEditorField.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteItemsEntriesEditorField.js
new file mode 100644
index 000000000..8ebcd5617
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteItemsEntriesEditorField.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import { FastField } from 'formik';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
+import { useCreditNoteFormContext } from './CreditNoteFormProvider';
+import { entriesFieldShouldUpdate } from './utils';
+
+/**
+ * Credit note items entries editor field.
+ */
+export default function CreditNoteItemsEntriesEditorField() {
+ const { items } = useCreditNoteFormContext();
+
+ return (
+
+
+ {({
+ form: { values, setFieldValue },
+ field: { value },
+ meta: { error, touched },
+ }) => (
+ {
+ setFieldValue('entries', entries);
+ }}
+ items={items}
+ errors={error}
+ linesNumber={4}
+ currencyCode={values.currency_code}
+ />
+ )}
+
+
+ );
+}
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/utils.js b/src/containers/Sales/CreditNotes/CreditNoteForm/utils.js
new file mode 100644
index 000000000..807029d9f
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/utils.js
@@ -0,0 +1,131 @@
+import React from 'react';
+import * as R from 'ramda';
+import moment from 'moment';
+
+import {
+ defaultFastFieldShouldUpdate,
+ transformToForm,
+ repeatValue,
+ transactionNumber,
+ orderingLinesIndexes,
+} from 'utils';
+import { useFormikContext } from 'formik';
+
+import {
+ updateItemsEntriesTotal,
+ ensureEntriesHaveEmptyLine,
+} from 'containers/Entries/utils';
+
+export const MIN_LINES_NUMBER = 4;
+
+// Default entry object.
+export const defaultCreditNoteEntry = {
+ index: 0,
+ item_id: '',
+ rate: '',
+ discount: '',
+ quantity: '',
+ description: '',
+ amount: '',
+};
+
+// Default credit note object.
+export const defaultCreditNote = {
+ customer_id: '',
+ credit_note_date: moment(new Date()).format('YYYY-MM-DD'),
+ credit_note_number: '',
+ credit_note_no_manually: false,
+ // reference_no: '',
+ note: '',
+ terms_conditions: '',
+ entries: [...repeatValue(defaultCreditNoteEntry, MIN_LINES_NUMBER)],
+};
+
+/**
+ * Transform credit note to initial values in edit mode.
+ */
+export function transformToEditForm(creditNote) {
+ const initialEntries = [
+ ...creditNote.entries.map((creditNote) => ({
+ ...transformToForm(creditNote, defaultCreditNoteEntry),
+ })),
+ ...repeatValue(
+ defaultCreditNoteEntry,
+ Math.max(MIN_LINES_NUMBER - creditNote.entries.length, 0),
+ ),
+ ];
+ const entries = R.compose(
+ ensureEntriesHaveEmptyLine(defaultCreditNoteEntry),
+ updateItemsEntriesTotal,
+ )(initialEntries);
+
+ return {
+ ...transformToForm(creditNote, defaultCreditNote),
+ entries,
+ };
+}
+
+/**
+ * Transformes credit note entries to submit request.
+ */
+export const transformEntriesToSubmit = (entries) => {
+ const transformCreditNoteEntry = R.compose(
+ R.omit(['amount']),
+ R.curry(transformToForm)(R.__, defaultCreditNoteEntry),
+ );
+ return R.compose(
+ orderingLinesIndexes,
+ R.map(transformCreditNoteEntry),
+ )(entries);
+};
+
+/**
+ * Filters the givne non-zero entries.
+ */
+ export const filterNonZeroEntries = (entries) => {
+ return entries.filter((item) => item.item_id && item.quantity);
+};
+
+/**
+ * Transformes form values to request body.
+ */
+ export const transformFormValuesToRequest = (values) => {
+ const entries = filterNonZeroEntries(values.entries);
+
+ return {
+ ...values,
+ entries: transformEntriesToSubmit(entries),
+ };
+};
+
+/**
+ * Determines customer name field when should update.
+ */
+export const customerNameFieldShouldUpdate = (newProps, oldProps) => {
+ return (
+ newProps.customers !== oldProps.customers ||
+ defaultFastFieldShouldUpdate(newProps, oldProps)
+ );
+};
+
+/**
+ * Determines invoice entries field when should update.
+ */
+export const entriesFieldShouldUpdate = (newProps, oldProps) => {
+ return (
+ newProps.items !== oldProps.items ||
+ defaultFastFieldShouldUpdate(newProps, oldProps)
+ );
+};
+
+/**
+ * Syncs invoice no. settings with form.
+ */
+ export const useObserveCreditNoSettings = (prefix, nextNumber) => {
+ const { setFieldValue } = useFormikContext();
+
+ React.useEffect(() => {
+ const creditNo = transactionNumber(prefix, nextNumber);
+ setFieldValue('credit_note_number', creditNo);
+ }, [setFieldValue, prefix, nextNumber]);
+};
diff --git a/src/containers/Sales/CreditNotes/CreditNotesAlerts.js b/src/containers/Sales/CreditNotes/CreditNotesAlerts.js
new file mode 100644
index 000000000..58a1f4de6
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesAlerts.js
@@ -0,0 +1,15 @@
+import React from 'react';
+
+const CreditNoteDeleteAlert = React.lazy(() =>
+ import('../../Alerts/CreditNotes/CreditNoteDeleteAlert'),
+);
+
+/**
+ * Credit notes alerts.
+ */
+export default [
+ {
+ name: 'credit-note-delete',
+ component: CreditNoteDeleteAlert,
+ },
+];
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.js
new file mode 100644
index 000000000..cb1b864c6
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.js
@@ -0,0 +1,144 @@
+import React from 'react';
+import {
+ Button,
+ Classes,
+ NavbarDivider,
+ NavbarGroup,
+ Intent,
+ Alignment,
+} from '@blueprintjs/core';
+import { useHistory } from 'react-router-dom';
+import {
+ Icon,
+ FormattedMessage as T,
+ DashboardActionViewsList,
+ AdvancedFilterPopover,
+ DashboardFilterButton,
+ DashboardRowsHeightButton,
+} from 'components';
+import DashboardActionsBar from '../../../../components/Dashboard/DashboardActionsBar';
+
+import { useCreditNoteListContext } from './CreditNotesListProvider';
+
+import withCreditNotes from './withCreditNotes';
+import withCreditNotesActions from './withCreditNotesActions';
+import withSettings from '../../../Settings/withSettings';
+import withSettingsActions from '../../../Settings/withSettingsActions';
+
+import { compose } from 'utils';
+
+/**
+ * Credit note table actions bar.
+ */
+function CreditNotesActionsBar({
+ // #withCreditNotes
+ creditNoteFilterRoles,
+
+ // #withCreditNotesActions
+ setCreditNotesTableState,
+
+ // #withSettings
+ creditNoteTableSize,
+
+ // #withSettingsActions
+ addSetting,
+}) {
+ const history = useHistory();
+
+ // credit note list context.
+ const { CreditNotesView, fields, refresh } = useCreditNoteListContext();
+
+ // Handle view tab change.
+ const handleTabChange = (view) => {
+ setCreditNotesTableState({ viewSlug: view ? view.slug : null });
+ };
+
+ // Handle click a new Credit.
+ const handleClickNewCreateNote = () => {
+ history.push('/credit-notes/new');
+ };
+
+ // Handle click a refresh credit note.
+ const handleRefreshBtnClick = () => {
+ refresh();
+ };
+
+ // Handle table row size change.
+ const handleTableRowSizeChange = (size) => {
+ addSetting('creditNote', 'tableSize', size);
+ };
+
+ return (
+
+
+
+
+ }
+ text={}
+ onClick={handleClickNewCreateNote}
+ />
+ {
+ setCreditNotesTableState({ filterRoles: filterConditions });
+ },
+ }}
+ >
+
+
+
+ }
+ text={}
+ />
+ }
+ text={}
+ />
+ }
+ text={}
+ />
+
+
+
+
+
+ }
+ onClick={handleRefreshBtnClick}
+ />
+
+
+ );
+}
+
+export default compose(
+ withCreditNotesActions,
+ withSettingsActions,
+ withCreditNotes(({ creditNoteTableState }) => ({
+ creditNoteFilterRoles: creditNoteTableState.filterRoles,
+ })),
+ withSettings(({ creditNoteSettings }) => ({
+ creditNoteTableSize: creditNoteSettings?.tableSize,
+ })),
+)(CreditNotesActionsBar);
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesDataTable.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesDataTable.js
new file mode 100644
index 000000000..b66541b6f
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesDataTable.js
@@ -0,0 +1,134 @@
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+
+import CreditNoteEmptyStatus from './CreditNotesEmptyStatus';
+import { DataTable, DashboardContentTable } from 'components';
+import { TABLES } from 'common/tables';
+import { useMemorizedColumnsWidths } from 'hooks';
+
+import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
+import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
+
+import withDashboardActions from 'containers/Dashboard/withDashboardActions';
+import withCreditNotesActions from './withCreditNotesActions';
+import withAlertsActions from 'containers/Alert/withAlertActions';
+import withDrawerActions from 'containers/Drawer/withDrawerActions';
+import withSettings from '../../../Settings/withSettings';
+
+import { useCreditNoteTableColumns, ActionsMenu } from './components';
+import { useCreditNoteListContext } from './CreditNotesListProvider';
+
+import { compose } from 'utils';
+
+/**
+ * Credit note data table.
+ */
+function CreditNotesDataTable({
+ // #withCreditNotesActions
+ setCreditNotesTableState,
+
+ // #withAlertsActions
+ openAlert,
+
+ // #withDrawerActions
+ openDrawer,
+
+ // #withSettings
+ creditNoteTableSize,
+}) {
+ const history = useHistory();
+
+ // Credit note list context.
+ const {
+ creditNotes,
+ pagination,
+ isEmptyStatus,
+ isCreditNotesFetching,
+ isCreditNotesLoading,
+ } = useCreditNoteListContext();
+
+ // Credit note table columns.
+ const columns = useCreditNoteTableColumns();
+
+ // Local storage memorizing columns widths.
+ const [initialColumnsWidths, , handleColumnResizing] =
+ useMemorizedColumnsWidths(TABLES.CREDIT_NOTES);
+
+ // Handles fetch data once the table state change.
+ const handleDataTableFetchData = React.useCallback(
+ ({ pageSize, pageIndex, sortBy }) => {
+ setCreditNotesTableState({
+ pageSize,
+ pageIndex,
+ sortBy,
+ });
+ },
+ [setCreditNotesTableState],
+ );
+
+ // Display create note empty status instead of the table.
+ if (isEmptyStatus) {
+ return
;
+ }
+
+ const handleViewDetailCreditNote = ({ id }) => {
+ openDrawer('credit-note-detail-drawer', { creditNoteId: id });
+ };
+
+ // Handle delete credit note.
+ const handleDeleteCreditNote = ({ id }) => {
+ openAlert('credit-note-delete', { creditNoteId: id });
+ };
+
+ // Handle edit credit note.
+ const hanldeEditCreditNote = (creditNote) => {
+ history.push(`/credit-notes/${creditNote.id}/edit`);
+ };
+
+ // Handle cell click.
+ const handleCellClick = (cell, event) => {
+ openDrawer('credit-note-detail-drawer', {
+ creditNoteId: cell.row.original.id,
+ });
+ };
+
+ return (
+
+
+
+ );
+}
+
+export default compose(
+ withDashboardActions,
+ withCreditNotesActions,
+ withDrawerActions,
+ withAlertsActions,
+ withSettings(({ creditNoteSettings }) => ({
+ creditNoteTableSize: creditNoteSettings?.tableSize,
+ })),
+)(CreditNotesDataTable);
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesEmptyStatus.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesEmptyStatus.js
new file mode 100644
index 000000000..6e50c4cdf
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesEmptyStatus.js
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Button, Intent } from '@blueprintjs/core';
+import { useHistory } from 'react-router-dom';
+import { EmptyStatus } from 'components';
+import { FormattedMessage as T } from 'components';
+
+export default function CreditNotesEmptyStatus() {
+ const history = useHistory();
+ return (
+
}
+ description={
+
+
+
+ }
+ action={
+ <>
+
+
+
+ >
+ }
+ />
+ );
+}
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesList.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesList.js
new file mode 100644
index 000000000..43b054e16
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesList.js
@@ -0,0 +1,53 @@
+import React from 'react';
+
+import '../../../../style/pages/CreditNote/List.scss';
+
+import { DashboardPageContent } from 'components';
+import CreditNotesActionsBar from './CreditNotesActionsBar';
+import CreditNotesViewTabs from './CreditNotesViewTabs';
+import CreditNotesDataTable from './CreditNotesDataTable';
+
+import withCreditNotes from './withCreditNotes';
+import withCreditNotesActions from './withCreditNotesActions';
+
+import { CreditNotesListProvider } from './CreditNotesListProvider';
+import { transformTableStateToQuery, compose } from 'utils';
+
+function CreditNotesList({
+ // #withCreditNotes
+ creditNoteTableState,
+ creditNoteTableStateChanged,
+
+ // #withCreditNotesActions
+ resetCreditNotesTableState,
+}) {
+ // Resets the credit note table state once the page unmount.
+ React.useEffect(
+ () => () => {
+ resetCreditNotesTableState();
+ },
+ [resetCreditNotesTableState],
+ );
+
+
+ return (
+
+
+
+
+
+
+
+ );
+}
+
+export default compose(
+ withCreditNotesActions,
+ withCreditNotes(({ creditNoteTableState, creditNoteTableStateChanged }) => ({
+ creditNoteTableState,
+ creditNoteTableStateChanged,
+ })),
+)(CreditNotesList);
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesListProvider.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesListProvider.js
new file mode 100644
index 000000000..07a778892
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesListProvider.js
@@ -0,0 +1,75 @@
+import React from 'react';
+import { isEmpty } from 'lodash';
+
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+import {
+ useResourceViews,
+ useResourceMeta,
+ useCreditNotes,
+ useRefreshCreditNotes,
+} from 'hooks/query';
+
+import { getFieldsFromResourceMeta } from 'utils';
+
+const CreditNoteListContext = React.createContext();
+
+/**
+ * Credit note data provider.
+ */
+function CreditNotesListProvider({ query, tableStateChanged, ...props }) {
+ // Credit notes refresh action.
+ const { refresh } = useRefreshCreditNotes();
+
+ // Fetch create notes resource views and fields.
+ const { data: CreditNotesView, isLoading: isViewsLoading } =
+ useResourceViews('credit_notes');
+
+ // Fetch the accounts resource fields.
+ const {
+ data: resourceMeta,
+ isLoading: isResourceLoading,
+ isFetching: isResourceFetching,
+ } = useResourceMeta('credit_notes');
+
+ // Fetch credit note list.
+ const {
+ data: { creditNotes, pagination, filterMeta },
+ isFetching: isCreditNotesFetching,
+ isLoading: isCreditNotesLoading,
+ } = useCreditNotes(query, { keepPreviousData: true });
+
+ // Detarmines the datatable empty status.S
+ const isEmptyStatus = isEmpty(creditNotes) && !isCreditNotesLoading && !tableStateChanged;
+
+ // Provider payload.
+ const provider = {
+ creditNotes,
+ pagination,
+
+ CreditNotesView,
+ refresh,
+
+ resourceMeta,
+ fields: getFieldsFromResourceMeta(resourceMeta.fields),
+ isResourceLoading,
+ isResourceFetching,
+
+ isCreditNotesFetching,
+ isCreditNotesLoading,
+ isViewsLoading,
+ isEmptyStatus,
+ };
+
+ return (
+
+
+
+ );
+}
+
+const useCreditNoteListContext = () => React.useContext(CreditNoteListContext);
+
+export { CreditNotesListProvider, useCreditNoteListContext };
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesViewTabs.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesViewTabs.js
new file mode 100644
index 000000000..5ef16b4e9
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesViewTabs.js
@@ -0,0 +1,51 @@
+import React from 'react';
+import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
+
+import { DashboardViewsTabs } from 'components';
+
+import withCreditNotes from './withCreditNotes';
+import withCreditNotesActions from './withCreditNotesActions';
+
+import { compose, transfromViewsToTabs } from 'utils';
+import { useCreditNoteListContext } from './CreditNotesListProvider';
+
+/**
+ * Credit Note views tabs.
+ */
+function CreditNotesViewTabs({
+ // #withCreditNotes
+ creditNoteCurrentView,
+
+ // #withCreditNotesActions
+ setCreditNotesTableState,
+}) {
+ // Credit note list context.
+ const { CreditNotesView } = useCreditNoteListContext();
+
+ const tabs = transfromViewsToTabs(CreditNotesView);
+
+ // Handle tab change.
+ const handleTabsChange = (viewSlug) => {
+ setCreditNotesTableState({ viewSlug });
+ };
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default compose(
+ withCreditNotesActions,
+ withCreditNotes(({ creditNoteTableState }) => ({
+ creditNoteCurrentView: creditNoteTableState.viewSlug,
+ })),
+)(CreditNotesViewTabs);
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/components.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/components.js
new file mode 100644
index 000000000..e7b4a31bc
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/components.js
@@ -0,0 +1,115 @@
+import React from 'react';
+import {
+ Intent,
+ Tag,
+ Menu,
+ MenuItem,
+ MenuDivider,
+ ProgressBar,
+} from '@blueprintjs/core';
+import intl from 'react-intl-universal';
+import clsx from 'classnames';
+
+import { CLASSES } from '../../../../common/classes';
+import {
+ FormatDateCell,
+ FormattedMessage as T,
+ AppToaster,
+ Choose,
+ If,
+ Icon,
+} from 'components';
+import { formattedAmount, safeCallback, calculateStatus } from 'utils';
+
+export function ActionsMenu({
+ payload: { onEdit, onDelete, onViewDetails },
+ row: { original },
+}) {
+ return (
+
+ );
+}
+
+/**
+ * Retrieve credit note table columns.
+ */
+export function useCreditNoteTableColumns() {
+ return React.useMemo(
+ () => [
+ {
+ id: 'credit_note_date',
+ Header: intl.get('credit_note.column.credit_date'),
+ accessor: 'formatted_credit_note_date',
+ Cell: FormatDateCell,
+ width: 110,
+ className: 'credit_note_date',
+ clickable: true,
+ textOverview: true,
+ },
+ {
+ id: 'customer',
+ Header: intl.get('customer_name'),
+ accessor: 'customer.display_name',
+ width: 180,
+ className: 'customer_id',
+ clickable: true,
+ textOverview: true,
+ },
+ {
+ id: 'credit_note_number',
+ Header: intl.get('credit_note.column.credit_note_no'),
+ accessor: 'credit_note_number',
+ width: 100,
+ className: 'credit_note_number',
+ clickable: true,
+ textOverview: true,
+ },
+ {
+ id: 'amount',
+ Header: intl.get('amount'),
+ accessor: 'formatted_amount',
+ width: 120,
+ align: 'right',
+ clickable: true,
+ textOverview: true,
+ className: clsx(CLASSES.FONT_BOLD),
+ },
+ {
+ id: 'status',
+ Header: intl.get('status'),
+ // accessor:
+ width: 120, // 160
+ className: 'status',
+ clickable: true,
+ },
+ {
+ id: 'reference_no',
+ Header: intl.get('reference_no'),
+ accessor: 'reference_no',
+ width: 90,
+ className: 'reference_no',
+ clickable: true,
+ textOverview: true,
+ },
+ ],
+ [],
+ );
+}
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotes.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotes.js
new file mode 100644
index 000000000..f9c8b0b9a
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotes.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux';
+import {
+ getCreditNotesTableStateFactory,
+ isCreditNotesTableStateChangedFactory,
+} from '../../../../store/CreditNote/creditNote.selector';
+
+export default (mapState) => {
+ const getCreditNoteTableState = getCreditNotesTableStateFactory();
+ const isCreditNoteTableChanged = isCreditNotesTableStateChangedFactory();
+
+ const mapStateToProps = (state, props) => {
+ const mapped = {
+ creditNoteTableState: getCreditNoteTableState(state, props),
+ creditNoteTableStateChanged: isCreditNoteTableChanged(state, props),
+ };
+ return mapState ? mapState(mapped, state, props) : mapped;
+ };
+ return connect(mapStateToProps);
+};
diff --git a/src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotesActions.js b/src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotesActions.js
new file mode 100644
index 000000000..cedc9dbd4
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotesActions.js
@@ -0,0 +1,13 @@
+import { connect } from 'react-redux';
+import {
+ setCreditNoteTableState,
+ resetCreditNoteTableState,
+} from '../../../../store/CreditNote/creditNote.actions';
+
+const mapDipatchToProps = (dispatch) => ({
+ setCreditNotesTableState: (queries) =>
+ dispatch(setCreditNoteTableState(queries)),
+ resetCreditNotesTableState: () => dispatch(resetCreditNoteTableState()),
+});
+
+export default connect(null, mapDipatchToProps);
diff --git a/src/containers/Settings/withSettings.js b/src/containers/Settings/withSettings.js
index 77ec97cc8..867e56dc7 100644
--- a/src/containers/Settings/withSettings.js
+++ b/src/containers/Settings/withSettings.js
@@ -19,6 +19,8 @@ export default (mapState) => {
cashflowSettings: state.settings.data.cashflowAccounts,
cashflowTransactionsSettings: state.settings.data.cashflowTransactions,
cashflowSetting: state.settings.data.cashflow,
+ creditNoteSettings: state.settings.data.creditNote,
+ vendorsCreditNoteSetting: state.settings.data.vendorCredit,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
diff --git a/src/hooks/query/bills.js b/src/hooks/query/bills.js
index d46225079..cb8fcf56d 100644
--- a/src/hooks/query/bills.js
+++ b/src/hooks/query/bills.js
@@ -20,6 +20,10 @@ const commonInvalidateQueries = (queryClient) => {
queryClient.invalidateQueries(t.ACCOUNTS);
queryClient.invalidateQueries(t.ACCOUNT);
+ // Invalidate landed cost.
+ queryClient.invalidateQueries(t.LANDED_COST);
+ queryClient.invalidateQueries(t.LANDED_COST_TRANSACTION);
+
// Invalidate financial reports.
queryClient.invalidateQueries(t.FINANCIAL_REPORT);
};
diff --git a/src/hooks/query/creditNote.js b/src/hooks/query/creditNote.js
new file mode 100644
index 000000000..ee3f8f61e
--- /dev/null
+++ b/src/hooks/query/creditNote.js
@@ -0,0 +1,145 @@
+import { useQueryClient, useMutation } from 'react-query';
+import { useRequestQuery } from '../useQueryRequest';
+import { transformPagination } from 'utils';
+import useApiRequest from '../useRequest';
+import t from './types';
+
+const commonInvalidateQueries = (queryClient) => {
+ // Invalidate credit note.
+ queryClient.invalidateQueries(t.CREDIT_NOTES);
+ queryClient.invalidateQueries(t.CREDIT_NOTE);
+
+ // Invalidate items.
+ queryClient.invalidateQueries(t.ITEMS);
+ queryClient.invalidateQueries(t.ITEM);
+
+ // Invalidate customers.
+ queryClient.invalidateQueries([t.CUSTOMER]);
+ queryClient.invalidateQueries(t.CUSTOMERS);
+
+ // Invalidate accounts.
+ queryClient.invalidateQueries(t.ACCOUNTS);
+ queryClient.invalidateQueries(t.ACCOUNT);
+
+ // Invalidate settings.
+ queryClient.invalidateQueries([t.SETTING, t.SETTING_CREDIT_NOTES]);
+
+ // Invalidate financial reports.
+ queryClient.invalidateQueries(t.FINANCIAL_REPORT);
+};
+
+/**
+ * Create a new credit note.
+ */
+export function useCreateCreditNote(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ (values) => apiRequest.post('sales/credit_notes', values),
+ {
+ onSuccess: (res, values) => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Edit the given credit note.
+ */
+export function useEditCreditNote(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ ([id, values]) => apiRequest.post(`sales/credit_notes/${id}`, values),
+ {
+ onSuccess: (res, [id, values]) => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+
+ // Invalidate credit note query.
+ queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Delete the given credit note.
+ */
+export function useDeleteCreditNote(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation((id) => apiRequest.delete(`sales/credit_notes/${id}`), {
+ onSuccess: (res, id) => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+
+ // Invalidate vendor credit query.
+ queryClient.invalidateQueries([t.CREDIT_NOTE, id]);
+ },
+ ...props,
+ });
+}
+
+const transformCreditNotes = (res) => ({
+ creditNotes: res.data.credit_notes,
+ pagination: transformPagination(res.data.pagination),
+ filterMeta: res.data.filter_meta,
+});
+
+/**
+ * Retrieve credit notes list with pagination meta.
+ */
+export function useCreditNotes(query, props) {
+ return useRequestQuery(
+ [t.CREDIT_NOTES, query],
+ { method: 'get', url: 'sales/credit_notes', params: query },
+ {
+ select: transformCreditNotes,
+ defaultData: {
+ creditNotes: [],
+ pagination: {
+ page: 1,
+ pageSize: 20,
+ total: 0,
+ },
+ filterMeta: {},
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Retrieve credit note detail of the given id.
+ * @param {number} id
+ *
+ */
+export function useCreditNote(id, props, requestProps) {
+ return useRequestQuery(
+ [t.CREDIT_NOTE, id],
+ { method: 'get', url: `sales/credit_notes/${id}`, ...requestProps },
+ {
+ select: (res) => res.data.credit_note,
+ defaultData: {},
+ ...props,
+ },
+ );
+}
+
+export function useRefreshCreditNotes() {
+ const queryClient = useQueryClient();
+
+ return {
+ refresh: () => {
+ queryClient.invalidateQueries(t.CREDIT_NOTES);
+ },
+ };
+}
diff --git a/src/hooks/query/index.js b/src/hooks/query/index.js
index e73c09307..b1dcf9d13 100644
--- a/src/hooks/query/index.js
+++ b/src/hooks/query/index.js
@@ -28,5 +28,7 @@ export * from './UniversalSearch/UniversalSearch';
export * from './GenericResource';
export * from './jobs';
export * from './misc';
-export * from './cashflowAccounts'
-export * from './roles'
+export * from './cashflowAccounts';
+export * from './roles';
+export * from './creditNote';
+export * from './vendorCredit';
diff --git a/src/hooks/query/invoices.js b/src/hooks/query/invoices.js
index aabe2fa07..a999764f7 100644
--- a/src/hooks/query/invoices.js
+++ b/src/hooks/query/invoices.js
@@ -102,6 +102,7 @@ const transformInvoices = (res) => ({
* Retrieve sale invoices list with pagination meta.
*/
export function useInvoices(query, props) {
+
return useRequestQuery(
[t.SALE_INVOICES, query],
{ method: 'get', url: 'sales/invoices', params: query },
diff --git a/src/hooks/query/settings.js b/src/hooks/query/settings.js
index b7c70b335..4575307aa 100644
--- a/src/hooks/query/settings.js
+++ b/src/hooks/query/settings.js
@@ -123,6 +123,27 @@ export function useSettingCashFlow(props) {
);
}
+/**
+ * Retrieve credit notes settings.
+ */
+export function useSettingsCreditNotes(props) {
+ return useSettingsQuery(
+ [t.SETTING, t.SETTING_CREDIT_NOTES],
+ { group: 'credit_note' },
+ props,
+ );
+}
+/**
+ * Retrieve vendor credit settings.
+ */
+export function useSettingsVendorCredits(props) {
+ return useSettingsQuery(
+ [t.SETTING, t.SETTING_VENDOR_CREDITS],
+ { group: 'vendor_credit' },
+ props,
+ );
+}
+
/**
* Retrieve SMS Notifications settings.
*/
diff --git a/src/hooks/query/types.js b/src/hooks/query/types.js
index edec96183..374c6540c 100644
--- a/src/hooks/query/types.js
+++ b/src/hooks/query/types.js
@@ -108,6 +108,16 @@ const ROLES = {
ROLES_PERMISSIONS_SCHEMA: 'ROLES_PERMISSIONS_SCHEMA',
};
+const CREDIT_NOTES = {
+ CREDIT_NOTE: 'CREDIT_NOTE',
+ CREDIT_NOTES: 'CREDIT_NOTES',
+};
+
+const VENDOR_CREDIT_NOTES = {
+ VENDOR_CREDITS: 'VENDOR_CREDITS',
+ VENDOR_CREDIT: 'VENDOR_CREDIT',
+};
+
const SETTING = {
SETTING: 'SETTING',
SETTING_INVOICES: 'SETTING_INVOICES',
@@ -120,6 +130,8 @@ const SETTING = {
SETTING_SMS_NOTIFICATION: 'SETTING_SMS_NOTIFICATION',
SETTING_SMS_NOTIFICATIONS: 'SETTING_SMS_NOTIFICATIONS',
SETTING_EDIT_SMS_NOTIFICATION: 'SETTING_EDIT_SMS_NOTIFICATION',
+ SETTING_CREDIT_NOTES: 'SETTING_CREDIT_NOTES',
+ SETTING_VENDOR_CREDITS: 'SETTING_VENDOR_CREDITS',
};
const ORGANIZATIONS = {
@@ -184,4 +196,6 @@ export default {
...CONTACTS,
...CASH_FLOW_ACCOUNTS,
...ROLES,
+ ...CREDIT_NOTES,
+ ...VENDOR_CREDIT_NOTES,
};
diff --git a/src/hooks/query/vendorCredit.js b/src/hooks/query/vendorCredit.js
new file mode 100644
index 000000000..d4b5206f6
--- /dev/null
+++ b/src/hooks/query/vendorCredit.js
@@ -0,0 +1,152 @@
+import { useQueryClient, useMutation } from 'react-query';
+import { useRequestQuery } from '../useQueryRequest';
+import { transformPagination } from 'utils';
+import useApiRequest from '../useRequest';
+import t from './types';
+
+const commonInvalidateQueries = (queryClient) => {
+ // Invalidate vendor credit.
+ queryClient.invalidateQueries(t.VENDOR_CREDITS);
+ queryClient.invalidateQueries(t.VENDOR_CREDIT);
+
+ // Invalidate items.
+ queryClient.invalidateQueries(t.ITEMS);
+ queryClient.invalidateQueries(t.ITEM);
+
+ // Invalidate vendors.
+ queryClient.invalidateQueries([t.VENDORS]);
+ queryClient.invalidateQueries(t.VENDOR);
+
+ // Invalidate accounts.
+ queryClient.invalidateQueries(t.ACCOUNTS);
+ queryClient.invalidateQueries(t.ACCOUNT);
+
+ // Invalidate settings.
+ queryClient.invalidateQueries([t.SETTING, t.SETTING_VENDOR_CREDITS]);
+
+ // Invalidate financial reports.
+ queryClient.invalidateQueries(t.FINANCIAL_REPORT);
+};
+
+/**
+ * Create a new vendor credit.
+ */
+export function useCreateVendorCredit(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ (values) => apiRequest.post('purchases/vendor-credit', values),
+ {
+ onSuccess: (res, values) => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Edit the given vendor credit.
+ */
+export function useEditVendorCredit(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ ([id, values]) => apiRequest.post(`purchases/vendor-credit/${id}`, values),
+ {
+ onSuccess: (res, [id, values]) => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+
+ // Invalidate vendor credit query.
+ queryClient.invalidateQueries([t.VENDOR_CREDIT, id]);
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Delete the given vendor credit.
+ */
+export function useDeleteVendorCredit(props) {
+ const queryClient = useQueryClient();
+ const apiRequest = useApiRequest();
+
+ return useMutation(
+ (id) => apiRequest.delete(`purchases/vendor-credit/${id}`),
+ {
+ onSuccess: (res, id) => {
+ // Common invalidate queries.
+ commonInvalidateQueries(queryClient);
+
+ // Invalidate vendor credit query.
+ queryClient.invalidateQueries([t.VENDOR_CREDIT_NOTE, id]);
+ },
+ ...props,
+ },
+ );
+}
+
+const transformVendorCreditsResponse = (response) => ({
+ vendorCredits: response.data.vendor_credits,
+ pagination: transformPagination(response.data.pagination),
+ filterMeta: response.data.filter_meta,
+});
+
+/**
+ * Retrieve vendor credit notes list with pagination meta.
+ */
+export function useVendorCredits(query, props) {
+ return useRequestQuery(
+ [t.VENDOR_CREDITS, query],
+ {
+ method: 'get',
+ url: 'purchases/vendor-credit',
+ params: query,
+ },
+ {
+ select: transformVendorCreditsResponse,
+ defaultData: {
+ vendorCredits: [],
+ pagination: {
+ page: 1,
+ page_size: 12,
+ total: 0,
+ },
+ filterMeta: {},
+ },
+ ...props,
+ },
+ );
+}
+
+/**
+ * Retrieve vendor credit detail of the given id.
+ * @param {number} id
+ *
+ */
+export function useVendorCredit(id, props, requestProps) {
+ return useRequestQuery(
+ [t.VENDOR_CREDIT, id],
+ { method: 'get', url: `purchases/vendor-credit/${id}`, ...requestProps },
+ {
+ select: (res) => res.data.data,
+ defaultData: {},
+ ...props,
+ },
+ );
+}
+
+export function useRefreshVendorCredits() {
+ const queryClient = useQueryClient();
+
+ return {
+ refresh: () => {
+ queryClient.invalidateQueries(t.VENDOR_CREDITS);
+ },
+ };
+}
diff --git a/src/lang/ar/index.json b/src/lang/ar/index.json
index 64d09d443..8309e7d23 100644
--- a/src/lang/ar/index.json
+++ b/src/lang/ar/index.json
@@ -1496,5 +1496,6 @@
"roles.permission_schema.once_delete_this_role_you_will_able_to_restore_it":"بمجرد حذف دور المستخدم ، لن تتمكن من استعادتها لاحقًا. هل أنت متأكد أنك تريد حذف هذا الدور؟",
"users.column.role_name":"دور المستخدم",
"roles.error.you_cannot_edit_predefined_roles":"لا يمكنك تعديل أدوار المستخدم المحددة مسبقا.",
- "roles.error.you_cannot_delete_predefined_roles":"لا يمكنك حذف أدوار المستخدم المحددة مسبقا"
+ "roles.error.you_cannot_delete_predefined_roles":"لا يمكنك حذف أدوار المستخدم المحددة مسبقا",
+ "roles.error.you_cannot_delete_role_that_associated_to_users":"لا يمكن حذف دور المستخدم لأنه لديه مستخدمين مرتبطة به."
}
\ No newline at end of file
diff --git a/src/lang/en/index.json b/src/lang/en/index.json
index 6af818a9b..3c7434950 100644
--- a/src/lang/en/index.json
+++ b/src/lang/en/index.json
@@ -1467,23 +1467,76 @@
"sms_notification.payment_details.type": "Payment receive thank you.",
"sms_notification.receipt_details.type": "Sale receipt details",
"personal": "Personal",
- "list.create":"Create {value}",
- "roles.label":"Roles",
- "roles.column.name":"Name",
- "roles.column.description":"Description",
- "roles.edit_roles":"Edit Roles",
- "roles.delete_roles":"Delete Roles",
- "roles.label.role_name":"Role Name",
- "roles.label.role_name_":"Role name",
- "roles.error.role_is_predefined":"Role is predefined, you cannot delete predefined roles.",
- "roles.error.you_cannot_change_your_own_role":"You cannot change your own role.",
- "sidebar.transactions_locaking":"Transactions Locaking",
- "transactions_locking.dialog.label":"Transactions locking",
- "roles.permission_schema.success_message":"The role has been created successfully.",
- "roles.permission_schema.upload_message":"The given role has been updated successfully.",
- "roles.permission_schema.delete.alert_message":"The given role has been deleted successfully.",
- "roles.permission_schema.once_delete_this_role_you_will_able_to_restore_it":"Once you delete this role, you won't be able to restore it later. Are you sure you want to delete this role?",
- "users.column.role_name":"Role Name",
- "roles.error.you_cannot_edit_predefined_roles":"You cannot edit predefined roles.",
- "roles.error.you_cannot_delete_predefined_roles":"You cannot delete predefined roles."
+ "list.create": "Create {value}",
+ "roles.label": "Roles",
+ "roles.column.name": "Name",
+ "roles.column.description": "Description",
+ "roles.edit_roles": "Edit Roles",
+ "roles.delete_roles": "Delete Roles",
+ "roles.label.role_name": "Role Name",
+ "roles.label.role_name_": "Role name",
+ "roles.error.role_is_predefined": "Role is predefined, you cannot delete predefined roles.",
+ "roles.error.you_cannot_change_your_own_role": "You cannot change your own role.",
+ "sidebar.transactions_locaking": "Transactions Locaking",
+ "transactions_locking.dialog.label": "Transactions locking",
+ "roles.permission_schema.success_message": "The role has been created successfully.",
+ "roles.permission_schema.upload_message": "The given role has been updated successfully.",
+ "roles.permission_schema.delete.alert_message": "The given role has been deleted successfully.",
+ "roles.permission_schema.once_delete_this_role_you_will_able_to_restore_it": "Once you delete this role, you won't be able to restore it later. Are you sure you want to delete this role?",
+ "users.column.role_name": "Role Name",
+ "roles.error.you_cannot_edit_predefined_roles": "You cannot edit predefined roles.",
+ "roles.error.you_cannot_delete_predefined_roles": "You cannot delete predefined roles.",
+ "roles.error.the_submit_role_has_invalid_permissions": "The submit role has invalid permissions.",
+ "roles.error.you_cannot_delete_role_that_associated_to_users": "You cannot delete role that associated to users",
+ "sidebar_credit_note": "Credit Notes",
+ "credit_note.label_create_note_list": "Credit Note List",
+ "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.delete_credit_note": "Delete Credit note",
+ "credit_note.column.credit_note_no": "Credit Note #",
+ "credit_note.column.credit_date": "Credit Date",
+ "credit_note.empty_status_description": "Record sales transactions from customers who make no or partial payment and help you keep track of accounts receivable with a customer.",
+ "credit_note.label_credit_note_date": "Credit Note date",
+ "credit_note.label_credit_note": "Credit Note #",
+ "credit_note.label_credit_note_details": "Credit Note details",
+ "credit_note.label_customer_note": "Customer notes",
+ "credit_note.once_delete_this_credit_note": "Once you delete this credit note, you won't be able to restore it later. Are you sure you want to delete this credit note?",
+ "credit_note.alert.success_message": "The credit note has been deleted successfully",
+ "credit_note.success_message": "The credit note has been created successfully.",
+ "credit_note.edit_success_message": "The credit note has been edited successfully.",
+ "sidebar_vendor_credits": "Vendor Credits",
+ "vendor_credits.lable_vendor_credit_list": "Vendor Credits List",
+ "vendor_credits.label.new_vendor_credit": "New Vendor Credit",
+ "vendor_credits.label.edit_vendor_credit": "Edit Vendor Credit",
+ "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.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.",
+ "vendor_credits.alert.success_message": "The given vendor credit has been deleted successfully.",
+ "vendor_credits.note.once_delete_this_vendor_credit_note": "Once you delete this vendor credit , you won't be able to restore it later. Are you sure you want to delete this vendor credit ?",
+ "credit_note_number_settings": "Credit Note Number Settings",
+ "credit_note.auto_increment.auto": "Your credit note numbers are set on auto-increment mode. Are you sure changing this setting?",
+ "credit_note.auto_increment.manually": "Your credit note numbers are set on manual mode. Are you sure chaning this settings?",
+ "setting_your_auto_generated_credit_note_number": "Setting your auto-generated credit note number",
+ "vendor_credit_number_settings": "Vendor Credit Number Settings",
+ "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.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",
+ "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"
}
\ No newline at end of file
diff --git a/src/routes/dashboard.js b/src/routes/dashboard.js
index dd9764664..d1e25dc84 100644
--- a/src/routes/dashboard.js
+++ b/src/routes/dashboard.js
@@ -630,6 +630,50 @@ export const getDashboardRoutes = () => [
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
+ // Sales Credit notes.
+ {
+ path: `/credit-notes/:id/edit`,
+ component: lazy(() =>
+ import(
+ '../containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage'
+ ),
+ ),
+ name: 'credit-note-edit',
+ breadcrumb: intl.get('edit'),
+ pageTitle: intl.get('credit_note.label.edit_credit_note'),
+ backLink: true,
+ sidebarExpand: false,
+ defaultSearchResource: RESOURCES_TYPES.CREDIT_NOTE,
+ subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
+ },
+ {
+ path: '/credit-notes/new',
+ component: lazy(() =>
+ import(
+ '../containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage'
+ ),
+ ),
+ name: 'credit-note-new',
+ breadcrumb: intl.get('credit_note.label.new_credit_note'),
+ backLink: true,
+ sidebarExpand: false,
+ pageTitle: intl.get('credit_note.label.new_credit_note'),
+ defaultSearchResource: RESOURCES_TYPES.CREDIT_NOTE,
+ subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
+ },
+ {
+ path: '/credit-notes',
+ component: lazy(() =>
+ import(
+ '../containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesList'
+ ),
+ ),
+ breadcrumb: intl.get('credit_note.label_create_note_list'),
+ pageTitle: intl.get('credit_note.label_create_note_list'),
+ defaultSearchResource: RESOURCES_TYPES.CREDIT_NOTE,
+ subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
+ },
+
// Payment receives
{
path: `/payment-receives/:id/edit`,
@@ -713,6 +757,49 @@ export const getDashboardRoutes = () => [
defaultSearchResource: RESOURCES_TYPES.BILL,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
+ // Purchases Credit note.
+ {
+ path: `/vendor-credits/:id/edit`,
+ component: lazy(() =>
+ import(
+ 'containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormPage'
+ ),
+ ),
+ name: 'vendor-credits-edit',
+ breadcrumb: intl.get('edit'),
+ pageTitle: intl.get('vendor_credits.label.edit_vendor_credit'),
+ backLink: true,
+ sidebarExpand: false,
+ defaultSearchResource: RESOURCES_TYPES.VENDOR_CREDIT,
+ subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
+ },
+ {
+ path: '/vendor-credits/new',
+ component: lazy(() =>
+ import(
+ 'containers/Purchases/CreditNotes/CreditNoteForm/VendorCreditNoteFormPage'
+ ),
+ ),
+ name: 'vendor-credits-new',
+ backLink: true,
+ sidebarExpand: false,
+ breadcrumb: intl.get('vendor_credits.label.new_vendor_credit'),
+ pageTitle: intl.get('vendor_credits.label.new_vendor_credit'),
+ defaultSearchResource: RESOURCES_TYPES.VENDOR_CREDIT,
+ subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
+ },
+ {
+ path: '/vendor-credits',
+ component: lazy(() =>
+ import(
+ '../containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNotesList'
+ ),
+ ),
+ breadcrumb: intl.get('vendor_credits.lable_vendor_credit_list'),
+ pageTitle: intl.get('vendor_credits.lable_vendor_credit_list'),
+ defaultSearchResource: RESOURCES_TYPES.VENDOR_CREDIT,
+ subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
+ },
// Subscription billing.
{
@@ -785,13 +872,13 @@ export const getDashboardRoutes = () => [
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
defaultSearchResource: RESOURCES_TYPES.ACCOUNT,
},
- {
- path: `/transactions-locking`,
- component: lazy(() =>
- import('../containers/TransactionsLocking/TransactionsLockingList'),
- ),
- pageTitle: intl.get('sidebar.transactions_locaking'),
- },
+ // {
+ // path: `/transactions-locking`,
+ // component: lazy(() =>
+ // import('../containers/TransactionsLocking/TransactionsLockingList'),
+ // ),
+ // pageTitle: intl.get('sidebar.transactions_locaking'),
+ // },
// Homepage
{
path: `/`,
diff --git a/src/store/CreditNote/creditNote.actions.js b/src/store/CreditNote/creditNote.actions.js
new file mode 100644
index 000000000..2592bfe54
--- /dev/null
+++ b/src/store/CreditNote/creditNote.actions.js
@@ -0,0 +1,16 @@
+import t from 'store/types';
+
+export const setCreditNoteTableState = (queries) => {
+ return {
+ type: t.CREDIT_NOTES_TABLE_STATE_SET,
+ payload: { queries },
+ };
+};
+
+export const resetCreditNoteTableState = () => {
+ return {
+ type: t.CREDIT_NOTES_TABLE_STATE_RESET,
+ };
+};
+
+export const setSelectedRowsItems = () => {};
diff --git a/src/store/CreditNote/creditNote.reducer.js b/src/store/CreditNote/creditNote.reducer.js
new file mode 100644
index 000000000..8a543fabc
--- /dev/null
+++ b/src/store/CreditNote/creditNote.reducer.js
@@ -0,0 +1,34 @@
+import { createReducer } from '@reduxjs/toolkit';
+import { persistReducer, purgeStoredState } from 'redux-persist';
+import storage from 'redux-persist/lib/storage';
+import { createTableStateReducers } from 'store/tableState.reducer';
+import t from 'store/types';
+
+export const defaultTableQuery = {
+ pageSize: 20,
+ pageIndex: 0,
+ filterRoles: [],
+ viewSlug: null,
+};
+
+const initialState = {
+ tableState: defaultTableQuery,
+};
+
+const STORAGE_KEY = 'bigcapital:credit_notes';
+
+const CONFIG = {
+ key: STORAGE_KEY,
+ whitelist: [],
+ storage,
+};
+
+const reducerInstance = createReducer(initialState, {
+ ...createTableStateReducers('CREDIT_NOTES', defaultTableQuery),
+
+ [t.RESET]: () => {
+ purgeStoredState(CONFIG);
+ },
+});
+
+export default persistReducer(CONFIG, reducerInstance);
diff --git a/src/store/CreditNote/creditNote.selector.js b/src/store/CreditNote/creditNote.selector.js
new file mode 100644
index 000000000..77f761260
--- /dev/null
+++ b/src/store/CreditNote/creditNote.selector.js
@@ -0,0 +1,29 @@
+import { isEqual } from 'lodash';
+import { paginationLocationQuery } from 'store/selectors';
+import { createDeepEqualSelector } from 'utils';
+import { defaultTableQuery } from './creditNote.reducer';
+
+const creditsTableStateSelector = (state) => state.creditNotes.tableState;
+
+/**
+ * Retrieve credit notes table state.
+ */
+export const getCreditNotesTableStateFactory = () =>
+ createDeepEqualSelector(
+ paginationLocationQuery,
+ creditsTableStateSelector,
+ (locationQuery, tableState) => {
+ return {
+ ...locationQuery,
+ ...tableState,
+ };
+ },
+ );
+
+/**
+ * Retrieve credit notes table state.
+ */
+export const isCreditNotesTableStateChangedFactory = () =>
+ createDeepEqualSelector(creditsTableStateSelector, (tableState) => {
+ return !isEqual(tableState, defaultTableQuery);
+ });
diff --git a/src/store/CreditNote/creditNote.type.js b/src/store/CreditNote/creditNote.type.js
new file mode 100644
index 000000000..85585381a
--- /dev/null
+++ b/src/store/CreditNote/creditNote.type.js
@@ -0,0 +1,4 @@
+export default {
+ CREDIT_NOTES_TABLE_STATE_SET: 'CREDIT_NOTES/TABLE_STATE_SET',
+ CREDIT_NOTES_TABLE_STATE_RESET: 'CREDIT_NOTE/TABLE_STATE_RESET',
+};
diff --git a/src/store/VendorCredit/VendorCredit.reducer.js b/src/store/VendorCredit/VendorCredit.reducer.js
new file mode 100644
index 000000000..ac35b0e2b
--- /dev/null
+++ b/src/store/VendorCredit/VendorCredit.reducer.js
@@ -0,0 +1,34 @@
+import { createReducer } from '@reduxjs/toolkit';
+import { persistReducer, purgeStoredState } from 'redux-persist';
+import storage from 'redux-persist/lib/storage';
+import { createTableStateReducers } from 'store/tableState.reducer';
+import t from 'store/types';
+
+export const defaultTableQuery = {
+ pageSize: 20,
+ pageIndex: 0,
+ filterRoles: [],
+ viewSlug: null,
+};
+
+const initialState = {
+ tableState: defaultTableQuery,
+};
+
+const STORAGE_KEY = 'bigcapital:vendor_credits';
+
+const CONFIG = {
+ key: STORAGE_KEY,
+ whitelist: [],
+ storage,
+};
+
+const reducerInstance = createReducer(initialState, {
+ ...createTableStateReducers('VENDOR_CREDITS', defaultTableQuery),
+
+ [t.RESET]: () => {
+ purgeStoredState(CONFIG);
+ },
+});
+
+export default persistReducer(CONFIG, reducerInstance);
diff --git a/src/store/VendorCredit/vendorCredit.actions.js b/src/store/VendorCredit/vendorCredit.actions.js
new file mode 100644
index 000000000..423d9df76
--- /dev/null
+++ b/src/store/VendorCredit/vendorCredit.actions.js
@@ -0,0 +1,16 @@
+import t from 'store/types';
+
+export const setVendorCreditTableState = (queries) => {
+ return {
+ type: t.VENDOR_CREDITS_TABLE_STATE_SET,
+ payload: { queries },
+ };
+};
+
+export const resetVendorCreditTableState = () => {
+ return {
+ type: t.VENDOR_CREDITS_NOTES_TABLE_STATE_RESET,
+ };
+};
+
+export const setSelectedRowsItems = () => {};
diff --git a/src/store/VendorCredit/vendorCredit.selector.js b/src/store/VendorCredit/vendorCredit.selector.js
new file mode 100644
index 000000000..e66ffdd77
--- /dev/null
+++ b/src/store/VendorCredit/vendorCredit.selector.js
@@ -0,0 +1,31 @@
+import { isEqual } from 'lodash';
+import { paginationLocationQuery } from 'store/selectors';
+import { createDeepEqualSelector } from 'utils';
+import { defaultTableQuery } from './VendorCredit.reducer';
+
+const vendorCreditsTableStateSelector = (state) => {
+ return state.vendorCredit.tableState;
+};
+
+/**
+ * Retrieve vendor credit table state.
+ */
+export const getVendorCreditTableStateFactory = () =>
+ createDeepEqualSelector(
+ paginationLocationQuery,
+ vendorCreditsTableStateSelector,
+ (locationQuery, tableState) => {
+ return {
+ ...locationQuery,
+ ...tableState,
+ };
+ },
+ );
+
+/**
+ * Retrieve vendor credit table state.
+ */
+export const isVendorCreditTableStateChangedFactory = () =>
+ createDeepEqualSelector(vendorCreditsTableStateSelector, (tableState) => {
+ return !isEqual(tableState, defaultTableQuery);
+ });
diff --git a/src/store/VendorCredit/vendorCredit.type.js b/src/store/VendorCredit/vendorCredit.type.js
new file mode 100644
index 000000000..b68fcad28
--- /dev/null
+++ b/src/store/VendorCredit/vendorCredit.type.js
@@ -0,0 +1,4 @@
+export default {
+ VENDOR_CREDITS_TABLE_STATE_SET: 'VENDOR_CREDITS/TABLE_STATE_SET',
+ VENDOR_CREDITS_NOTES_TABLE_STATE_RESET: 'VENDOR_CREDITS/TABLE_STATE_RESET',
+};
diff --git a/src/store/reducers.js b/src/store/reducers.js
index db31ab4db..ad28a32e6 100644
--- a/src/store/reducers.js
+++ b/src/store/reducers.js
@@ -32,6 +32,8 @@ import organizations from './organizations/organizations.reducers';
import subscriptions from './subscription/subscription.reducer';
import inventoryAdjustments from './inventoryAdjustments/inventoryAdjustment.reducer';
import plans from './plans/plans.reducer';
+import creditNotes from './CreditNote/creditNote.reducer';
+import vendorCredit from './VendorCredit/VendorCredit.reducer';
const appReducer = combineReducers({
authentication,
@@ -63,7 +65,9 @@ const appReducer = combineReducers({
paymentReceives,
paymentMades,
inventoryAdjustments,
- plans
+ plans,
+ creditNotes,
+ vendorCredit,
});
// Reset the state of a redux store
@@ -71,7 +75,7 @@ const rootReducer = (state, action) => {
if (action.type === types.RESET) {
state = undefined;
}
- return appReducer(state, action)
-}
+ return appReducer(state, action);
+};
-export default rootReducer;
\ No newline at end of file
+export default rootReducer;
diff --git a/src/store/settings/settings.reducer.js b/src/store/settings/settings.reducer.js
index 28ceaa9e8..853ab0589 100644
--- a/src/store/settings/settings.reducer.js
+++ b/src/store/settings/settings.reducer.js
@@ -52,6 +52,12 @@ const initialState = {
cashflowTransactions: {
tableSize: 'medium',
},
+ creditNote: {
+ tableSize: 'medium',
+ },
+ vendorCredit: {
+ tableSize: 'medium',
+ },
},
};
diff --git a/src/store/types.js b/src/store/types.js
index b79f0d1ba..3e509142f 100644
--- a/src/store/types.js
+++ b/src/store/types.js
@@ -28,6 +28,8 @@ import paymentMades from './PaymentMades/paymentMades.type';
import organizations from './organizations/organizations.types';
import subscription from './subscription/subscription.types';
import inventoryAdjustments from './inventoryAdjustments/inventoryAdjustment.type';
+import creditNote from './CreditNote/creditNote.type';
+import vendorCredit from './VendorCredit/vendorCredit.type';
import plans from './plans/plans.types';
export default {
@@ -61,5 +63,7 @@ export default {
...organizations,
...subscription,
...inventoryAdjustments,
- ...plans
+ ...plans,
+ ...creditNote,
+ ...vendorCredit
};
diff --git a/src/style/components/Drawers/CreditNoteDetails.module.scss b/src/style/components/Drawers/CreditNoteDetails.module.scss
new file mode 100644
index 000000000..5f0de943f
--- /dev/null
+++ b/src/style/components/Drawers/CreditNoteDetails.module.scss
@@ -0,0 +1,68 @@
+.root {
+}
+
+.detail_panel {
+ :global .card {
+ padding: 22px 15px;
+ margin: 15px;
+ }
+
+ &_table {
+ :global .bigcapital-datatable {
+ margin-top: 30px;
+
+ .thead,
+ .tbody {
+ .quantity,
+ .rate,
+ .amount {
+ text-align: right;
+ }
+ }
+ }
+ }
+
+ &_footer {
+ :global .total_lines {
+ margin-left: auto;
+
+ &_line {
+ .title {
+ padding-left: 0;
+ }
+ .amount,
+ .title {
+ width: 180px;
+ }
+ .amount {
+ text-align: right;
+ }
+ }
+ }
+ }
+
+ &_footer {
+ display: flex;
+ margin-bottom: 20px;
+ }
+
+ .total_line {
+ &_subtotal {
+ border-bottom: 1px solid #000;
+ }
+
+ &_total {
+ border-bottom: 3px double #000;
+ font-weight: 600;
+ }
+ &_dueAmount {
+ font-weight: 600;
+ }
+ }
+
+ &_note {
+ b {
+ color: #727983;
+ }
+ }
+}
diff --git a/src/style/components/Drawers/VendorCreditDetail.module.scss b/src/style/components/Drawers/VendorCreditDetail.module.scss
new file mode 100644
index 000000000..5f0de943f
--- /dev/null
+++ b/src/style/components/Drawers/VendorCreditDetail.module.scss
@@ -0,0 +1,68 @@
+.root {
+}
+
+.detail_panel {
+ :global .card {
+ padding: 22px 15px;
+ margin: 15px;
+ }
+
+ &_table {
+ :global .bigcapital-datatable {
+ margin-top: 30px;
+
+ .thead,
+ .tbody {
+ .quantity,
+ .rate,
+ .amount {
+ text-align: right;
+ }
+ }
+ }
+ }
+
+ &_footer {
+ :global .total_lines {
+ margin-left: auto;
+
+ &_line {
+ .title {
+ padding-left: 0;
+ }
+ .amount,
+ .title {
+ width: 180px;
+ }
+ .amount {
+ text-align: right;
+ }
+ }
+ }
+ }
+
+ &_footer {
+ display: flex;
+ margin-bottom: 20px;
+ }
+
+ .total_line {
+ &_subtotal {
+ border-bottom: 1px solid #000;
+ }
+
+ &_total {
+ border-bottom: 3px double #000;
+ font-weight: 600;
+ }
+ &_dueAmount {
+ font-weight: 600;
+ }
+ }
+
+ &_note {
+ b {
+ color: #727983;
+ }
+ }
+}
diff --git a/src/style/pages/AllocateLandedCost/AllocateLandedCostForm.scss b/src/style/pages/AllocateLandedCost/AllocateLandedCostForm.scss
index 1040746e2..d0b23d633 100644
--- a/src/style/pages/AllocateLandedCost/AllocateLandedCostForm.scss
+++ b/src/style/pages/AllocateLandedCost/AllocateLandedCostForm.scss
@@ -8,7 +8,7 @@
}
.bp3-form-group.bp3-inline {
.bp3-label {
- min-width: 150px;
+ min-width: 165px;
}
.bp3-form-content {
width: 300px;
diff --git a/src/style/pages/AllocateLandedCost/List.scss b/src/style/pages/AllocateLandedCost/List.scss
new file mode 100644
index 000000000..a09b2fddf
--- /dev/null
+++ b/src/style/pages/AllocateLandedCost/List.scss
@@ -0,0 +1,22 @@
+.datatable--landed-cost-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;
+ }
+ }
+ }
+}
diff --git a/src/style/pages/CreditNote/List.scss b/src/style/pages/CreditNote/List.scss
new file mode 100644
index 000000000..087b2f910
--- /dev/null
+++ b/src/style/pages/CreditNote/List.scss
@@ -0,0 +1,20 @@
+.dashboard__insider--credit-note-list {
+ .bigcapital-datatable {
+ .tbody {
+ .amount.td {
+ .cell-inner {
+ > span {
+ font-weight: 600;
+ }
+ }
+ }
+ }
+ .table-size--small {
+ .status.td {
+ .bp3-progress-bar {
+ height: 3px;
+ }
+ }
+ }
+ }
+}
diff --git a/src/style/pages/CreditNote/PageForm.scss b/src/style/pages/CreditNote/PageForm.scss
new file mode 100644
index 000000000..a5dab2707
--- /dev/null
+++ b/src/style/pages/CreditNote/PageForm.scss
@@ -0,0 +1,50 @@
+body.page-credit-note-new,
+body.page-credit-note-edit {
+ .dashboard__footer {
+ display: none;
+ }
+}
+
+.dashboard__insider--credit-note-form {
+ padding-bottom: 64px;
+}
+
+.page-form--credit-note {
+ $self: '.page-form';
+
+ #{$self}__header {
+ display: flex;
+
+ &-fields {
+ flex: 1 0 0;
+ }
+
+ .bp3-label {
+ min-width: 150px;
+ }
+ .bp3-form-content {
+ width: 100%;
+ }
+
+ .bp3-form-group {
+ &.bp3-inline {
+ max-width: 450px;
+ }
+ }
+ .col--credit-note-date {
+ max-width: 435px;
+ }
+ }
+ #{$self}__footer {
+ .form-group--customer_notes,
+ .form-group--terms_conditions {
+ max-width: 450px;
+ width: 100%;
+
+ textarea {
+ width: 100%;
+ min-height: 60px;
+ }
+ }
+ }
+}
diff --git a/src/style/pages/JournalEntries/List.scss b/src/style/pages/JournalEntries/List.scss
index eb032d1c7..79350d01d 100644
--- a/src/style/pages/JournalEntries/List.scss
+++ b/src/style/pages/JournalEntries/List.scss
@@ -1,8 +1,23 @@
.datatable--journal-entries {
+ // margin: 12px;
+ 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 {
- min-height: 42px;
+ border-bottom: 0;
+ padding-top: 0.4rem;
+ padding-bottom: 0.4rem;
&.credit,
&.debit {
diff --git a/src/style/pages/VendorsCreditNote/List.scss b/src/style/pages/VendorsCreditNote/List.scss
new file mode 100644
index 000000000..5119f3081
--- /dev/null
+++ b/src/style/pages/VendorsCreditNote/List.scss
@@ -0,0 +1,20 @@
+.dashboard__insider--vendors-credit-note-list {
+ .bigcapital-datatable {
+ .tbody {
+ .amount.td {
+ .cell-inner {
+ > span {
+ font-weight: 600;
+ }
+ }
+ }
+ }
+ .table-size--small {
+ .status.td {
+ .bp3-progress-bar {
+ height: 3px;
+ }
+ }
+ }
+ }
+}
diff --git a/src/style/pages/VendorsCreditNote/PageForm.scss b/src/style/pages/VendorsCreditNote/PageForm.scss
new file mode 100644
index 000000000..ebddd4d3a
--- /dev/null
+++ b/src/style/pages/VendorsCreditNote/PageForm.scss
@@ -0,0 +1,49 @@
+body.page-vendor-credit-note-new,
+body.page-vendor-credit-note-edit {
+ .dashboard__footer {
+ display: none;
+ }
+}
+
+.dashboard__insider--vendor-credit-note-form {
+ padding-bottom: 64px;
+}
+
+.page-form--vendor-credit-note {
+ $self: '.page-form';
+
+ #{$self}__header {
+ display: flex;
+
+ &-fields {
+ flex: 1 0 0;
+ }
+
+ .bp3-label {
+ min-width: 150px;
+ }
+ .bp3-form-content {
+ width: 100%;
+ }
+
+ .bp3-form-group {
+ &.bp3-inline {
+ max-width: 450px;
+ }
+ }
+ .col--invoice-date {
+ max-width: 435px;
+ }
+ }
+ #{$self}__footer {
+ .form-group--note {
+ max-width: 450px;
+ width: 100%;
+
+ textarea {
+ width: 100%;
+ min-height: 60px;
+ }
+ }
+ }
+}