From 0a9798e7a731d3c006663a94f5ecce1ed41e66d9 Mon Sep 17 00:00:00 2001
From: elforjani13 <39470382+elforjani13@users.noreply.github.com>
Date: Mon, 29 Nov 2021 16:14:22 +0200
Subject: [PATCH] feat: Credit note.
---
src/common/classes.js | 2 +
src/common/resourcesTypes.js | 2 +
src/config/sidebarMenu.js | 8 ++
.../CreditNotes/CreditNoteDeleteAlert.js | 57 ++++++++
src/containers/AlertsContainer/registered.js | 2 +
.../withVendorsCreditNotesActions.js | 14 ++
.../CreditNoteFloatingActions.js | 109 +++++++++++++++
.../CreditNoteForm/CreditNoteForm.js | 118 +++++++++++++++++
.../CreditNoteForm/CreditNoteForm.schema.js | 51 +++++++
.../CreditNoteForm/CreditNoteFormFooter.js | 51 +++++++
.../CreditNoteForm/CreditNoteFormHeader.js | 42 ++++++
.../CreditNoteFormHeaderFields.js | 125 ++++++++++++++++++
.../CreditNoteForm/CreditNoteFormPage.js | 21 +++
.../CreditNoteForm/CreditNoteFormProvider.js | 74 +++++++++++
.../CreditNoteItemsEntriesEditorField.js | 41 ++++++
.../Sales/CreditNotes/CreditNoteForm/utils.js | 88 ++++++++++++
.../Sales/CreditNotes/CreditNotesAlerts.js | 15 +++
.../CreditNotesActionsBar.js | 117 ++++++++++++++++
.../CreditNotesDataTable.js | 104 +++++++++++++++
.../CreditNotesEmptyStatus.js | 29 ++++
.../CreditNotesLanding/CreditNotesList.js | 51 +++++++
.../CreditNotesListProvider.js | 28 ++++
.../CreditNotesLanding/CreditNotesViewTabs.js | 46 +++++++
.../CreditNotesLanding/components.js | 122 +++++++++++++++++
.../CreditNotesLanding/withCreditNotes.js | 19 +++
.../withCreditNotesActions.js | 12 ++
src/containers/Settings/withSettings.js | 1 +
src/lang/en/index.json | 15 ++-
src/routes/dashboard.js | 44 ++++++
src/store/CreditNotes/creditNotes.actions.js | 14 ++
src/store/CreditNotes/creditNotes.reducer.js | 34 +++++
src/store/CreditNotes/creditNotes.selector.js | 30 +++++
src/store/CreditNotes/creditNotes.type.js | 4 +
src/store/reducers.js | 2 +
src/store/types.js | 4 +-
src/style/pages/CreditNote/List.scss | 20 +++
src/style/pages/CreditNote/PageForm.scss | 50 +++++++
37 files changed, 1564 insertions(+), 2 deletions(-)
create mode 100644 src/containers/Alerts/CreditNotes/CreditNoteDeleteAlert.js
create mode 100644 src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotesActions.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.schema.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooter.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeader.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormPage.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteItemsEntriesEditorField.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNoteForm/utils.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesAlerts.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesDataTable.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesEmptyStatus.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesList.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesListProvider.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesViewTabs.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/components.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotes.js
create mode 100644 src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotesActions.js
create mode 100644 src/store/CreditNotes/creditNotes.actions.js
create mode 100644 src/store/CreditNotes/creditNotes.reducer.js
create mode 100644 src/store/CreditNotes/creditNotes.selector.js
create mode 100644 src/store/CreditNotes/creditNotes.type.js
create mode 100644 src/style/pages/CreditNote/List.scss
create mode 100644 src/style/pages/CreditNote/PageForm.scss
diff --git a/src/common/classes.js b/src/common/classes.js
index e1df0b33f..6b0872396 100644
--- a/src/common/classes.js
+++ b/src/common/classes.js
@@ -39,6 +39,8 @@ const CLASSES = {
PAGE_FORM_ITEM: 'page-form--item',
PAGE_FORM_MAKE_JOURNAL: 'page-form--make-journal-entries',
PAGE_FORM_EXPENSE: 'page-form--expense',
+ PAGE_FORM_CREDIT_NOTE:'page-form--credit-note',
+ PAGE_FORM_VENDOR_CREDIT_NOTE:'page-form--vendor-credit-note',
FORM_GROUP_LIST_SELECT: 'form-group--select-list',
diff --git a/src/common/resourcesTypes.js b/src/common/resourcesTypes.js
index 12ec85401..e6de2706d 100644
--- a/src/common/resourcesTypes.js
+++ b/src/common/resourcesTypes.js
@@ -11,4 +11,6 @@ export const RESOURCES_TYPES = {
EXPENSE: 'expense',
MANUAL_JOURNAL: 'manual_journal',
ACCOUNT: 'account',
+ CREDIT_NOTE: 'credit_note',
+ VENDOR_CREDIT_NOTE:'vendor_credit_note'
};
diff --git a/src/config/sidebarMenu.js b/src/config/sidebarMenu.js
index b303d4561..66e5c7a7f 100644
--- a/src/config/sidebarMenu.js
+++ b/src/config/sidebarMenu.js
@@ -158,6 +158,10 @@ export default [
ability: SaleReceiptAction.View,
},
},
+ {
+ text: ,
+ href: '/credit-notes',
+ },
{
text: ,
href: '/payment-receives',
@@ -254,6 +258,10 @@ export default [
ability: BillAction.View,
},
},
+ {
+ text: ,
+ href: '/credit-notes',
+ },
{
text: ,
href: '/payment-mades',
diff --git a/src/containers/Alerts/CreditNotes/CreditNoteDeleteAlert.js b/src/containers/Alerts/CreditNotes/CreditNoteDeleteAlert.js
new file mode 100644
index 000000000..28d6a3bc8
--- /dev/null
+++ b/src/containers/Alerts/CreditNotes/CreditNoteDeleteAlert.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
+import { Intent, Alert } from '@blueprintjs/core';
+import { AppToaster } from 'components';
+
+import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
+import withAlertActions from 'containers/Alert/withAlertActions';
+import withDrawerActions from 'containers/Drawer/withDrawerActions';
+
+import { compose } from 'utils';
+
+/**
+ * Credit note delete alert.
+ */
+function CreditNoteDeleteAlert({
+ name,
+
+ // #withAlertStoreConnect
+ isOpen,
+ payload: { creditNoteId },
+
+ // #withAlertActions
+ closeAlert,
+
+ // #withDrawerActions
+ closeDrawer,
+}) {
+ // handle cancel delete credit note alert.
+ const handleCancelDeleteAlert = () => {
+ closeAlert(name);
+ };
+ const handleConfirmCreditNoteDelete = () => {};
+
+ return (
+ }
+ confirmButtonText={}
+ icon="trash"
+ intent={Intent.DANGER}
+ isOpen={isOpen}
+ onCancel={handleCancelDeleteAlert}
+ onConfirm={handleConfirmCreditNoteDelete}
+ // loading={isLoading}
+ >
+
+
+
+
+ );
+}
+
+export default compose(
+ withAlertStoreConnect(),
+ withAlertActions,
+ withDrawerActions,
+)(CreditNoteDeleteAlert);
diff --git a/src/containers/AlertsContainer/registered.js b/src/containers/AlertsContainer/registered.js
index d0e77fcfb..dd7327daf 100644
--- a/src/containers/AlertsContainer/registered.js
+++ b/src/containers/AlertsContainer/registered.js
@@ -17,6 +17,7 @@ import AccountTransactionsAlerts from '../CashFlow/AccountTransactions/AccountTr
import UsersAlerts from '../Preferences/Users/UsersAlerts';
import CurrenciesAlerts from '../Preferences/Currencies/CurrenciesAlerts';
import RolesAlerts from '../Preferences/Users/Roles/RolesAlerts';
+import CreditNotesAlerts from '../Sales/CreditNotes/CreditNotesAlerts';
export default [
...AccountsAlerts,
@@ -38,4 +39,5 @@ export default [
...UsersAlerts,
...CurrenciesAlerts,
...RolesAlerts,
+ ...CreditNotesAlerts,
];
diff --git a/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotesActions.js b/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotesActions.js
new file mode 100644
index 000000000..9f1ff19b2
--- /dev/null
+++ b/src/containers/Purchases/CreditNotes/CreditNotesLanding/withVendorsCreditNotesActions.js
@@ -0,0 +1,14 @@
+import { connect } from 'react-redux';
+import {
+ setVendorsCreditNoteTableState,
+ resetVendorsCreditNoteTableState,
+} from '../../../../store/vendorsCreditNotes/vendorsCreditNotes.actions';
+
+const mapDispatchToProps = (dispatch) => ({
+ setVendorsCreditNoteTableState: (state) =>
+ dispatch(setVendorsCreditNoteTableState(state)),
+ resetVendorsCreditNoteTableState: () =>
+ dispatch(resetVendorsCreditNoteTableState()),
+});
+
+export default connect(null, mapDispatchToProps);
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.js
new file mode 100644
index 000000000..398680493
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFloatingActions.js
@@ -0,0 +1,109 @@
+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 } = useCreditNoteFormContext();
+
+ // Handle cancel button click.
+ const handleCancelBtnClick = (event) => {
+ history.goBack();
+ };
+
+ const handleClearBtnClick = (event) => {
+ resetForm();
+ };
+
+ return (
+
+ {/* ----------- Save And Deliver ----------- */}
+
+ }
+ />
+
+ } />
+ } />
+
+ }
+ minimal={true}
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ />
+
+
+ {/* ----------- Save As Draft ----------- */}
+
+ }
+ />
+
+ } />
+ } />
+
+ }
+ minimal={true}
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ />
+
+
+ {/* ----------- Save and New ----------- */}
+
+ {/* ----------- 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..503ce1000
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.js
@@ -0,0 +1,118 @@
+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 {
+ getCreateCreditNoteFormSchema,
+ getEditCreditNoteFormSchema,
+} from './CreditNoteForm.schema';
+
+import CreditNoteFormHeader from './CreditNoteFormHeader';
+import CreditNoteItemsEntriesEditorField from './CreditNoteItemsEntriesEditorField';
+import CreditNoteFormFooter from './CreditNoteFormFooter';
+import CreditNoteFloatingActions from './CreditNoteFloatingActions';
+
+import { AppToaster } from 'components';
+import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
+import { useCreditNoteFormContext } from './CreditNoteFormProvider';
+import { transformToEditForm, defaultCreditNote } from './utils';
+
+import withSettings from 'containers/Settings/withSettings';
+import withCurrentOrganization from 'containers/Organization/withCurrentOrganization';
+
+/**
+ * Credit note form.
+ */
+function CreditNoteForm({
+ // #withSettings
+
+ // #withCurrentOrganization
+ organization: { base_currency },
+}) {
+ const history = useHistory();
+
+ // Credit note form context.
+ const { invoice } = useCreditNoteFormContext();
+
+ // Initial values.
+ const initialValues = React.useMemo(
+ () => ({
+ ...(!isEmpty(invoice)
+ ? { ...transformToEditForm(invoice), currency_code: base_currency }
+ : {
+ ...defaultCreditNote,
+ entries: orderingLinesIndexes(defaultCreditNote.entries),
+ currency_code: base_currency,
+ }),
+ }),
+ [],
+ );
+
+ // Handle form submit.
+ const handleSubmit = (values, { setSubmitting, setErrors, resetForm }) => {
+ setSubmitting(true);
+
+ const entries = values.entries.filter(
+ (item) => item.item_id && item.quantity,
+ );
+
+ const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity));
+
+ // Throw danger toaster in case total quantity equals zero.
+ if (totalQuantity === 0) {
+ AppToaster.show({
+ message: intl.get('quantity_cannot_be_zero_or_empty'),
+ intent: Intent.DANGER,
+ });
+ setSubmitting(false);
+ return;
+ }
+ // Transformes the values of the form to request.
+ const form = {
+ // ...transformValueToRequest(values),
+ };
+
+ // Handle the request success.
+ const onSuccess = () => {};
+ // Handle the request error.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {};
+ };
+
+ const CreateCreditNoteFormSchema = getCreateCreditNoteFormSchema();
+ const EditCreditNoteFormSchema = getEditCreditNoteFormSchema();
+
+ return (
+
+
+
+
+
+ );
+}
+
+export default compose(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..69aa5ee38
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteForm.schema.js
@@ -0,0 +1,51 @@
+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(),
+ invoice_date: Yup.date()
+ .required()
+ .label(intl.get('invoice_date_')),
+ invoice_no: Yup.string()
+ .max(DATATYPES_LENGTH.STRING)
+ .label(intl.get('invoice_no_')),
+ reference_no: Yup.string().min(1).max(DATATYPES_LENGTH.STRING),
+ delivered: Yup.boolean(),
+ invoice_message: 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 getCreateCreditNoteFormSchema = getSchema;
+export const getEditCreditNoteFormSchema = getSchema;
\ No newline at end of file
diff --git a/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooter.js b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormFooter.js
new file mode 100644
index 000000000..9f4b1af1b
--- /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--customer_notes'}
+ 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..23b8350b6
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormHeaderFields.js
@@ -0,0 +1,125 @@
+import React from 'react';
+import { FormGroup, InputGroup, Position } 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,
+ Icon,
+ FormattedMessage as T,
+} from 'components';
+import { customerNameFieldShouldUpdate } 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() {
+
+ // Credit note form context.
+ const { customers } = useCreditNoteFormContext();
+ 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('invoice_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--invoice-no', CLASSES.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+
+
+ )}
+
+ {/* ----------- Reference ----------- */}
+
+ {({ field, meta: { error, touched } }) => (
+ }
+ inline={true}
+ className={classNames('form-group--reference', CLASSES.FILL)}
+ intent={inputIntent({ error, touched })}
+ helperText={}
+ >
+
+
+ )}
+
+
+ );
+}
+
+export default 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..c4dae1103
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/CreditNoteFormProvider.js
@@ -0,0 +1,74 @@
+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 { useInvoice, useItems, useCustomers } from 'hooks/query';
+
+const CreditNoteFormContext = React.createContext();
+
+/**
+ * Credit note data provider.
+ */
+function CreditNoteFormProvider({ creditNoteId, ...props }) {
+ const { state } = useLocation();
+ const invoiceId = state?.action;
+
+ // Fetches the invoice by the given id.
+ const { data: invoice, isLoading: isInvoiceLoading } = useInvoice(invoiceId, {
+ enabled: !!invoiceId,
+ });
+
+ // 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,
+ });
+
+ // const newCreditNote =
+
+ // Create and edit credit note mutations.
+
+ // Form submit payload.
+ const [submitPayload, setSubmitPayload] = React.useState();
+
+ // Determines whether the form in new mode.
+ const isNewMode = !creditNoteId;
+
+ // Provider payload.
+ const provider = {
+ invoice,
+ items,
+ customers,
+ invoiceId,
+ submitPayload,
+
+ isItemsLoading,
+ isCustomersLoading,
+
+ setSubmitPayload,
+ isNewMode,
+ };
+
+ 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..2b48c8497
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNoteForm/utils.js
@@ -0,0 +1,88 @@
+import React from 'react';
+import moment from 'moment';
+import intl from 'react-intl-universal';
+import { useFormikContext } from 'formik';
+import { Intent } from '@blueprintjs/core';
+import { omit } from 'lodash';
+
+import {
+ compose,
+ transformToForm,
+ repeatValue,
+ transactionNumber,
+ defaultFastFieldShouldUpdate,
+} from 'utils';
+import { AppToaster } from 'components';
+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: '',
+ invoice_date: moment(new Date()).format('YYYY-MM-DD'),
+ invoice_no: '',
+ invoice_no_manually: false,
+ reference_no: '',
+ invoice_message: '',
+ 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 = compose(
+ ensureEntriesHaveEmptyLine(defaultCreditNoteEntry),
+ updateItemsEntriesTotal,
+ )(initialEntries);
+
+ return {
+ ...transformToForm(creditNote, defaultCreditNote),
+ 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)
+ );
+};
diff --git a/src/containers/Sales/CreditNotes/CreditNotesAlerts.js b/src/containers/Sales/CreditNotes/CreditNotesAlerts.js
new file mode 100644
index 000000000..b0c8a57fa
--- /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'),
+);
+
+/**
+ * Sales 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..13c018289
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.js
@@ -0,0 +1,117 @@
+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 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.
+
+ // credit note refresh action.
+
+ // Handle view tab change.
+ const handleTabChange = (view) => {
+ setCreditNotesTableState({ viewSlug: view ? view.slug : null });
+ };
+
+ // Handle click a refresh credit note.
+ const handleRefreshBtnClick = () => {};
+
+ // Handle table row size change.
+ const handleTableRowSizeChange = (size) => {
+ addSetting('salesCreditNote', 'tableSize', size);
+ };
+
+ return (
+
+
+
+
+
+ }
+ 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..d41dba267
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesDataTable.js
@@ -0,0 +1,104 @@
+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 withSettings from '../../../Settings/withSettings';
+import withAlertsActions from 'containers/Alert/withAlertActions';
+
+import { useCreditNoteTableColumns } from './components';
+import { useCreditNoteListContext } from './CreditNotesListProvider';
+
+import { compose } from 'utils';
+
+/**
+ * Credit note data table.
+ */
+function CreditNotesDataTable({
+ // #withCreditNotesActions
+ setCreditNotesTableState,
+
+ // #withAlertsActions
+ openAlert,
+
+ // #withSettings
+ creditNoteTableSize,
+}) {
+ const history = useHistory();
+
+ // Credit note list context.
+
+ // Credit note table columns.
+ const columns = useCreditNoteTableColumns();
+
+ // Local storage memorizing columns widths.
+ const [initialColumnsWidths, , handleColumnResizing] =
+ useMemorizedColumnsWidths(TABLES.CREDIT_NOTE);
+
+ // Handles fetch data once the table state change.
+ const handleDataTableFetchData = React.useCallback(
+ ({ pageSize, pageIndex, sortBy }) => {
+ setCreditNotesTableState({
+ pageSize,
+ pageIndex,
+ sortBy,
+ });
+ },
+ [setCreditNotesTableState],
+ );
+
+ // Handle delete credit note.
+ const handleDeleteCreditNote = ({ id }) => {
+ openAlert('sale-credit-note-delete', { creditNoteId: id });
+ };
+
+ // Handle edit credit note.
+ const hanldeEditCreditNote = (creditNote) => {
+ history.push(`/credit-notes/${creditNote.id}/edit`);
+ };
+
+ return (
+
+
+
+ );
+}
+
+export default compose(
+ withDashboardActions,
+ withCreditNotesActions,
+ 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..a0a62a706
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesEmptyStatus.js
@@ -0,0 +1,29 @@
+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() {
+ 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..c7d8b805d
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesList.js
@@ -0,0 +1,51 @@
+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..3b7515fe1
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesListProvider.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import { isEmpty } from 'lodash';
+
+import DashboardInsider from 'components/Dashboard/DashboardInsider';
+import { getFieldsFromResourceMeta } from 'utils';
+
+const CreditNoteListContext = React.createContext();
+
+/**
+ * Credit note data provider.
+ */
+function CreditNotesListProvider({ query, tableStateChanged, ...props }) {
+ // Provider payload.
+ const provider = {};
+
+ 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..457f052cf
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesViewTabs.js
@@ -0,0 +1,46 @@
+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.
+
+ // Handle click a new view tab.
+ const handleClickNewView = () => {};
+
+ // const tabs = transfromViewsToTabs(creditNoteCurrentView);
+
+ // 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..95a75927c
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/components.js
@@ -0,0 +1,122 @@
+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,
+ onDeliver,
+ onDelete,
+ onDrawer,
+ onQuick,
+ onViewDetails,
+ onPrint,
+ },
+ row: { original },
+}) {
+ return (
+
+ );
+}
+
+/**
+ * Retrieve credit note table columns.
+ */
+export function useCreditNoteTableColumns() {
+ return React.useMemo(
+ () => [
+ {
+ id: 'date',
+ Header: intl.get('date'),
+ accessor: 'date',
+ Cell: FormatDateCell,
+ width: 110,
+ className: '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',
+ Header: intl.get('credit_note.label_credit_note_no'),
+ accessor: 'credit_note',
+ width: 100,
+ className: 'credit_note',
+ 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..5f6aafdbe
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotes.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux';
+import {
+ getCreditNoteTableStateFactory,
+ isCreditNoteTableStateChangedFactory,
+} from '../../../../store/CreditNotes/creditNotes.selector';
+
+export default (mapState) => {
+ const getCreditNoteTableState = getCreditNoteTableStateFactory();
+ const isCreditNoteTableChanged = isCreditNoteTableStateChangedFactory();
+
+ 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..d48eb3f7b
--- /dev/null
+++ b/src/containers/Sales/CreditNotes/CreditNotesLanding/withCreditNotesActions.js
@@ -0,0 +1,12 @@
+import { connect } from 'react-redux';
+import {
+ setCreditNotesTableState,
+ resetCreditNotesTableState,
+} from '../../../../store/CreditNotes/creditNotes.actions';
+
+const mapDispatchToProps = (dispatch) => ({
+ setCreditNoteTableState: (state) => dispatch(setCreditNotesTableState(state)),
+ resetCreditNoteTableState: () => dispatch(resetCreditNotesTableState()),
+});
+
+export default connect(null, mapDispatchToProps);
diff --git a/src/containers/Settings/withSettings.js b/src/containers/Settings/withSettings.js
index 77ec97cc8..4cbb8c873 100644
--- a/src/containers/Settings/withSettings.js
+++ b/src/containers/Settings/withSettings.js
@@ -19,6 +19,7 @@ export default (mapState) => {
cashflowSettings: state.settings.data.cashflowAccounts,
cashflowTransactionsSettings: state.settings.data.cashflowTransactions,
cashflowSetting: state.settings.data.cashflow,
+ creditNoteSettings: state.settings.data.salesCreditNotes,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
diff --git a/src/lang/en/index.json b/src/lang/en/index.json
index 6cffe8753..de2a2a045 100644
--- a/src/lang/en/index.json
+++ b/src/lang/en/index.json
@@ -1487,5 +1487,18 @@
"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"
+ "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_list": "Credit Note List",
+ "credit_note.new_credit_note": "New Credit Note",
+ "credit_note.edit_credit_note": "Edit Credit Note",
+ "credit_note.label_credit_note_no": "Credit Note #",
+ "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.label_edit_credit_note": "Edit Credit note",
+ "credit_note.label_delete_credit_note": "Delete Credit note",
+ "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?"
}
\ No newline at end of file
diff --git a/src/routes/dashboard.js b/src/routes/dashboard.js
index 5e8b771b6..ef4b39d47 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.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.new_credit_note'),
+ backLink: true,
+ sidebarExpand: false,
+ pageTitle: intl.get('credit_note.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_list'),
+ pageTitle: intl.get('credit_note.label_list'),
+ defaultSearchResource: RESOURCES_TYPES.CREDIT_NOTE,
+ subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
+ },
+
// Payment receives
{
path: `/payment-receives/:id/edit`,
diff --git a/src/store/CreditNotes/creditNotes.actions.js b/src/store/CreditNotes/creditNotes.actions.js
new file mode 100644
index 000000000..9f64e36ab
--- /dev/null
+++ b/src/store/CreditNotes/creditNotes.actions.js
@@ -0,0 +1,14 @@
+import t from 'store/types';
+
+export const setCreditNotesTableState = (queries) => {
+ return {
+ type: t.CREDIT_NOTE_TABLE_STATE_SET,
+ payload: { queries },
+ };
+};
+
+export const resetCreditNotesTableState = () => {
+ return {
+ type: t.CREDIT_NOTE_TABLE_STATE_RESET,
+ };
+};
diff --git a/src/store/CreditNotes/creditNotes.reducer.js b/src/store/CreditNotes/creditNotes.reducer.js
new file mode 100644
index 000000000..3da7db673
--- /dev/null
+++ b/src/store/CreditNotes/creditNotes.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:CreditNotes';
+
+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/CreditNotes/creditNotes.selector.js b/src/store/CreditNotes/creditNotes.selector.js
new file mode 100644
index 000000000..76d2d4515
--- /dev/null
+++ b/src/store/CreditNotes/creditNotes.selector.js
@@ -0,0 +1,30 @@
+import { isEqual } from 'lodash';
+import { paginationLocationQuery } from 'store/selectors';
+import { createDeepEqualSelector } from 'utils';
+import { defaultTableQuery } from './creditNotes.reducer';
+
+const creditNotesTableStateSelector = (state) =>
+ state.creditNotes?.tableState;
+
+/**
+ * Retrieve credit note table state.
+ */
+export const getCreditNoteTableStateFactory = () =>
+ createDeepEqualSelector(
+ paginationLocationQuery,
+ creditNotesTableStateSelector,
+ (locationQuery, tableState) => {
+ return {
+ ...locationQuery,
+ ...tableState,
+ };
+ },
+ );
+
+/**
+ * Retrieve Credit note table state.
+ */
+export const isCreditNoteTableStateChangedFactory = () =>
+ createDeepEqualSelector(creditNotesTableStateSelector, (tableState) => {
+ return !isEqual(tableState, defaultTableQuery);
+ });
diff --git a/src/store/CreditNotes/creditNotes.type.js b/src/store/CreditNotes/creditNotes.type.js
new file mode 100644
index 000000000..f37eb4f0b
--- /dev/null
+++ b/src/store/CreditNotes/creditNotes.type.js
@@ -0,0 +1,4 @@
+export default {
+ CREDIT_NOTE_TABLE_STATE_SET: 'CREDIT_NOTE/TABLES_STATE_SET',
+ CREDIT_NOTE_TABLE_STATE_RESET: 'CREDIT_NOTE/TABLE_STATE/RESET',
+};
diff --git a/src/store/reducers.js b/src/store/reducers.js
index db31ab4db..6986efb80 100644
--- a/src/store/reducers.js
+++ b/src/store/reducers.js
@@ -31,6 +31,7 @@ import paymentMades from './PaymentMades/paymentMades.reducer';
import organizations from './organizations/organizations.reducers';
import subscriptions from './subscription/subscription.reducer';
import inventoryAdjustments from './inventoryAdjustments/inventoryAdjustment.reducer';
+import creditNotes from './CreditNotes/creditNotes.reducer'
import plans from './plans/plans.reducer';
const appReducer = combineReducers({
@@ -63,6 +64,7 @@ const appReducer = combineReducers({
paymentReceives,
paymentMades,
inventoryAdjustments,
+ creditNotes,
plans
});
diff --git a/src/store/types.js b/src/store/types.js
index b79f0d1ba..44df2a872 100644
--- a/src/store/types.js
+++ b/src/store/types.js
@@ -28,6 +28,7 @@ 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 creditNotes from './CreditNotes/creditNotes.type'
import plans from './plans/plans.types';
export default {
@@ -61,5 +62,6 @@ export default {
...organizations,
...subscription,
...inventoryAdjustments,
- ...plans
+ ...creditNotes,
+ ...plans,
};
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;
+ }
+ }
+ }
+}