+ Clearing the table lines will delete all entries were applied, Is this
+ okay?
+
+
+ >
+ );
+}
diff --git a/client/src/containers/Entries/ItemsEntriesTable.js b/client/src/containers/Entries/ItemsEntriesTable.js
new file mode 100644
index 000000000..e5c5c9c07
--- /dev/null
+++ b/client/src/containers/Entries/ItemsEntriesTable.js
@@ -0,0 +1,267 @@
+import React, { useState, useMemo, useEffect, useCallback } from 'react';
+import { omit } from 'lodash';
+import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
+import { FormattedMessage as T, useIntl } from 'react-intl';
+import classNames from 'classnames';
+import { CLASSES } from 'common/classes';
+import { Hint, Icon } from 'components';
+import DataTable from 'components/DataTable';
+import {
+ InputGroupCell,
+ MoneyFieldCell,
+ ItemsListCell,
+ PercentFieldCell,
+ DivFieldCell,
+} from 'components/DataTableCells';
+
+import withItems from 'containers/Items/withItems';
+import {
+ compose,
+ formattedAmount,
+ orderingLinesIndexes,
+ saveInvoke,
+} from 'utils';
+
+// Actions cell renderer component.
+const ActionsCellRenderer = ({
+ row: { index },
+ column: { id },
+ cell: { value },
+ data,
+ payload,
+}) => {
+ if (data.length <= index + 1) {
+ return '';
+ }
+ const onRemoveRole = () => {
+ payload.removeRow(index);
+ };
+ return (
+
@@ -15,11 +18,11 @@ export default function BillFloatingActions({
loading={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
- onClick={() => {
- onSubmitClick({ redirect: true });
+ onClick={(event) => {
+ saveInvoke(onSubmitClick, event);
}}
>
- {bill && bill.id ?
:
}
+ {billId ?
:
}
{
- onSubmitClick({ redirect: false });
+ onClick={(event) => {
+ saveInvoke(onSubmitAndNewClick, event);
}}
>
-
+
-
+ {
+ saveInvoke(onClearClick, event);
+ }}
+ >
{
- onCancelClick && onCancelClick();
+ onClick={(event) => {
+ saveInvoke(onCancelClick, event);
}}
>
diff --git a/client/src/containers/Purchases/Bill/BillForm.js b/client/src/containers/Purchases/Bill/BillForm.js
index bf8d7087c..7b591ab2b 100644
--- a/client/src/containers/Purchases/Bill/BillForm.js
+++ b/client/src/containers/Purchases/Bill/BillForm.js
@@ -1,23 +1,18 @@
-import React, {
- useMemo,
- useState,
- useCallback,
- useEffect,
- useRef,
-} from 'react';
-import * as Yup from 'yup';
-import { useFormik } from 'formik';
+import React, { useState, useMemo, useCallback, useEffect } from 'react';
+import { Formik, Form } from 'formik';
import moment from 'moment';
import { Intent } from '@blueprintjs/core';
import classNames from 'classnames';
-import { FormattedMessage as T, useIntl } from 'react-intl';
-import { pick, sumBy } from 'lodash';
+import { useIntl } from 'react-intl';
+import { useHistory } from 'react-router-dom';
+import { pick, sumBy, omit } from 'lodash';
import { CLASSES } from 'common/classes';
+import { EditBillFormSchema, CreateBillFormSchema } from './BillForm.schema';
import BillFormHeader from './BillFormHeader';
-import EstimatesItemsTable from 'containers/Sales/Estimate/EntriesItemsTable';
import BillFloatingActions from './BillFloatingActions';
import BillFormFooter from './BillFormFooter';
+import EditableItemsEntriesTable from 'containers/Entries/EditableItemsEntriesTable';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withMediaActions from 'containers/Media/withMediaActions';
@@ -25,12 +20,31 @@ import withBillActions from './withBillActions';
import withBillDetail from './withBillDetail';
import { AppToaster } from 'components';
-import useMedia from 'hooks/useMedia';
+
import { ERROR } from 'common/errors';
-import { compose, repeatValue } from 'utils';
+import { compose, repeatValue, orderingLinesIndexes } from 'utils';
const MIN_LINES_NUMBER = 5;
+const defaultBill = {
+ index: 0,
+ item_id: '',
+ rate: '',
+ discount: '',
+ quantity: '',
+ description: '',
+};
+
+const defaultInitialValues = {
+ vendor_id: '',
+ bill_number: '',
+ bill_date: moment(new Date()).format('YYYY-MM-DD'),
+ due_date: moment(new Date()).format('YYYY-MM-DD'),
+ reference_no: '',
+ note: '',
+ entries: [...repeatValue(defaultBill, MIN_LINES_NUMBER)],
+};
+
function BillForm({
//#WithMedia
requestSubmitMedia,
@@ -54,27 +68,10 @@ function BillForm({
onCancelForm,
}) {
const { formatMessage } = useIntl();
- const [payload, setPayload] = useState({});
+ const history = useHistory();
- const {
- setFiles,
- saveMedia,
- deletedFiles,
- setDeletedFiles,
- deleteMedia,
- } = useMedia({
- saveCallback: requestSubmitMedia,
- deleteCallback: requestDeleteMedia,
- });
-
- const handleDropFiles = useCallback((_files) => {
- setFiles(_files.filter((file) => file.uploaded === false));
- }, []);
-
- const savedMediaIds = useRef([]);
- const clearSavedMediaIds = () => {
- savedMediaIds.current = [];
- };
+ const [submitPayload, setSubmitPayload] = useState({});
+ const isNewMode = !billId;
useEffect(() => {
if (bill && bill.id) {
@@ -84,88 +81,7 @@ function BillForm({
}
}, [changePageTitle, bill, formatMessage]);
- const validationSchema = Yup.object().shape({
- vendor_id: Yup.number()
- .required()
- .label(formatMessage({ id: 'vendor_name_' })),
- bill_date: Yup.date()
- .required()
- .label(formatMessage({ id: 'bill_date_' })),
- due_date: Yup.date()
- .required()
- .label(formatMessage({ id: 'due_date_' })),
- bill_number: Yup.string()
- .required()
- .label(formatMessage({ id: 'bill_number_' })),
- reference_no: Yup.string().nullable().min(1).max(255),
- // status: Yup.string().required().nullable(),
- note: Yup.string()
- .trim()
- .min(1)
- .max(1024)
- .label(formatMessage({ id: 'note' })),
- entries: Yup.array().of(
- Yup.object().shape({
- quantity: Yup.number()
- .nullable()
- .when(['rate'], {
- is: (rate) => rate,
- then: Yup.number().required(),
- }),
- rate: Yup.number().nullable(),
- item_id: Yup.number()
- .nullable()
- .when(['quantity', 'rate'], {
- is: (quantity, rate) => quantity || rate,
- then: Yup.number().required(),
- }),
- total: Yup.number().nullable(),
- discount: Yup.number().nullable().min(0).max(100),
- description: Yup.string().nullable(),
- }),
- ),
- });
-
- const saveBillSubmit = useCallback(
- (payload) => {
- onFormSubmit && onFormSubmit(payload);
- },
- [onFormSubmit],
- );
-
- const defaultBill = useMemo(
- () => ({
- index: 0,
- item_id: null,
- rate: null,
- discount: 0,
- quantity: null,
- description: '',
- }),
- [],
- );
-
- const defaultInitialValues = useMemo(
- () => ({
- vendor_id: '',
- bill_number: '',
- bill_date: moment(new Date()).format('YYYY-MM-DD'),
- due_date: moment(new Date()).format('YYYY-MM-DD'),
- // status: 'Bill',
- reference_no: '',
- note: '',
- entries: [...repeatValue(defaultBill, MIN_LINES_NUMBER)],
- }),
- [defaultBill],
- );
-
- const orderingIndex = (_bill) => {
- return _bill.map((item, index) => ({
- ...item,
- index: index + 1,
- }));
- };
-
+ // Initial values in create and edit mode.
const initialValues = useMemo(
() => ({
...(bill
@@ -183,147 +99,88 @@ function BillForm({
}
: {
...defaultInitialValues,
- entries: orderingIndex(defaultInitialValues.entries),
+ entries: orderingLinesIndexes(defaultInitialValues.entries),
}),
}),
- [bill, defaultInitialValues, defaultBill],
+ [bill],
);
- const initialAttachmentFiles = useMemo(() => {
- return bill && bill.media
- ? bill.media.map((attach) => ({
- preview: attach.attachment_file,
- uploaded: true,
- metadata: { ...attach },
- }))
- : [];
- }, [bill]);
-
+ // Transform response error to fields.
const handleErrors = (errors, { setErrors }) => {
if (errors.some((e) => e.type === ERROR.BILL_NUMBER_EXISTS)) {
setErrors({
- bill_number: formatMessage({
- id: 'bill_number_exists',
- }),
+ bill_number: formatMessage({ id: 'bill_number_exists' }),
});
}
};
- const formik = useFormik({
- validationSchema,
- initialValues: {
- ...initialValues,
- },
- onSubmit: (values, { setSubmitting, setErrors, resetForm }) => {
- const entries = values.entries.filter(
- (item) => item.item_id && item.quantity,
- );
- const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity));
+ // Handles form submit.
+ const handleFormSubmit = (
+ values,
+ { setSubmitting, setErrors, resetForm },
+ ) => {
+ const entries = values.entries.filter(
+ (item) => item.item_id && item.quantity,
+ );
+ const totalQuantity = sumBy(entries, (entry) => parseInt(entry.quantity));
- if (totalQuantity === 0) {
- AppToaster.show({
- message: formatMessage({
- id: 'quantity_cannot_be_zero_or_empty',
- }),
- intent: Intent.DANGER,
- });
- setSubmitting(false);
- return;
- }
-
- const form = {
- ...values,
- entries,
- };
- const requestForm = { ...form };
- if (bill && bill.id) {
- requestEditBill(bill.id, requestForm)
- .then((response) => {
- AppToaster.show({
- message: formatMessage(
- {
- id: 'the_bill_has_been_successfully_edited',
- },
- { number: values.bill_number },
- ),
- intent: Intent.SUCCESS,
- });
- setSubmitting(false);
- saveBillSubmit({ action: 'update', ...payload });
- resetForm();
- changePageSubtitle('');
- })
- .catch((errors) => {
- handleErrors(errors, { setErrors });
- setSubmitting(false);
- });
- } else {
- requestSubmitBill(requestForm)
- .then((response) => {
- AppToaster.show({
- message: formatMessage(
- { id: 'the_bill_has_been_successfully_created' },
- { number: values.bill_number },
- ),
- intent: Intent.SUCCESS,
- });
- setSubmitting(false);
- saveBillSubmit({ action: 'new', ...payload });
- resetForm();
- })
- .catch((errors) => {
- handleErrors(errors, { setErrors });
- setSubmitting(false);
- });
- }
- },
- });
-
- const handleSubmitClick = useCallback(
- (payload) => {
- setPayload(payload);
- formik.submitForm();
- formik.setSubmitting(false);
- },
- [setPayload, formik],
- );
-
- const handleCancelClick = useCallback(
- (payload) => {
- onCancelForm && onCancelForm(payload);
- },
- [onCancelForm],
- );
-
- const handleDeleteFile = useCallback(
- (_deletedFiles) => {
- _deletedFiles.forEach((deletedFile) => {
- if (deletedFile.uploaded && deletedFile.metadata.id) {
- setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
- }
+ if (totalQuantity === 0) {
+ AppToaster.show({
+ message: formatMessage({
+ id: 'quantity_cannot_be_zero_or_empty',
+ }),
+ intent: Intent.DANGER,
});
+ setSubmitting(false);
+ return;
+ }
+
+ const form = {
+ ...values,
+ entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
+ };
+ // Handle the request success.
+ const onSuccess = (response) => {
+ AppToaster.show({
+ message: formatMessage(
+ {
+ id: isNewMode
+ ? 'the_bill_has_been_successfully_created'
+ : 'the_bill_has_been_successfully_edited',
+ },
+ { number: values.bill_number },
+ ),
+ intent: Intent.SUCCESS,
+ });
+ setSubmitting(false);
+
+ resetForm();
+ changePageSubtitle('');
+
+ if (submitPayload.redirect) {
+ history.go('/bills');
+ }
+ };
+ // Handle the request error.
+ const onError = (errors) => {
+ handleErrors(errors, { setErrors });
+ setSubmitting(false);
+ };
+ if (isNewMode) {
+ requestEditBill(bill.id, form).then(onSuccess).catch(onError);
+ } else {
+ requestSubmitBill(form).then(onSuccess).catch(onError);
+ }
+ };
+
+ // Handle bill number changed once the field blur.
+ const handleBillNumberChanged = useCallback(
+ (billNumber) => {
+ changePageSubtitle(billNumber);
},
- [setDeletedFiles, deletedFiles],
+ [changePageSubtitle],
);
- const onClickCleanAllLines = () => {
- formik.setFieldValue(
- 'entries',
- orderingIndex([...repeatValue(defaultBill, MIN_LINES_NUMBER)]),
- );
- };
-
- const onClickAddNewRow = () => {
- formik.setFieldValue(
- 'entries',
- orderingIndex([...formik.values.entries, defaultBill]),
- );
- };
-
- const handleBillNumberChanged = useCallback((billNumber) => {
- changePageSubtitle(billNumber);
- }, []);
-
// Clear page subtitle once unmount bill form page.
useEffect(
() => () => {
@@ -332,32 +189,38 @@ function BillForm({
[changePageSubtitle],
);
+ const handleSubmitClick = useCallback(() => {
+ setSubmitPayload({ redirect: true });
+ }, [setSubmitPayload]);
+
+ const handleCancelClick = useCallback(() => {
+ history.goBack();
+ }, [history]);
+
return (
-
-
+
+ {({ isSubmitting, values }) => (
+
+ )}
+
);
}
diff --git a/client/src/containers/Purchases/Bill/BillForm.schema.js b/client/src/containers/Purchases/Bill/BillForm.schema.js
new file mode 100644
index 000000000..2bffd5e94
--- /dev/null
+++ b/client/src/containers/Purchases/Bill/BillForm.schema.js
@@ -0,0 +1,51 @@
+import * as Yup from 'yup';
+import { formatMessage } from 'services/intl';
+
+const BillFormSchema = Yup.object().shape({
+ vendor_id: Yup.number()
+ .required()
+ .label(formatMessage({ id: 'vendor_name_' })),
+ bill_date: Yup.date()
+ .required()
+ .label(formatMessage({ id: 'bill_date_' })),
+ due_date: Yup.date()
+ .required()
+ .label(formatMessage({ id: 'due_date_' })),
+ bill_number: Yup.string()
+ .required()
+ .label(formatMessage({ id: 'bill_number_' })),
+ reference_no: Yup.string().nullable().min(1).max(255),
+ note: Yup.string()
+ .trim()
+ .min(1)
+ .max(1024)
+ .label(formatMessage({ id: 'note' })),
+ entries: Yup.array().of(
+ Yup.object().shape({
+ quantity: Yup.number()
+ .nullable()
+ .when(['rate'], {
+ is: (rate) => rate,
+ then: Yup.number().required(),
+ }),
+ rate: Yup.number().nullable(),
+ item_id: Yup.number()
+ .nullable()
+ .when(['quantity', 'rate'], {
+ is: (quantity, rate) => quantity || rate,
+ then: Yup.number().required(),
+ }),
+ total: Yup.number().nullable(),
+ discount: Yup.number().nullable().min(0).max(100),
+ description: Yup.string().nullable(),
+ }),
+ ),
+});
+
+const CreateBillFormSchema = BillFormSchema;
+const EditBillFormSchema = BillFormSchema;
+
+export {
+ CreateBillFormSchema,
+ EditBillFormSchema,
+};
diff --git a/client/src/containers/Purchases/Bill/BillFormFooter.js b/client/src/containers/Purchases/Bill/BillFormFooter.js
index 4475b15d8..28ae25381 100644
--- a/client/src/containers/Purchases/Bill/BillFormFooter.js
+++ b/client/src/containers/Purchases/Bill/BillFormFooter.js
@@ -1,21 +1,33 @@
import React from 'react';
import { FormGroup, TextArea } from '@blueprintjs/core';
import { FormattedMessage as T } from 'react-intl';
+import { FastField } from 'formik';
+import classNames from 'classnames';
import { Row, Col } from 'components';
+import { CLASSES } from 'common/classes';
import Dragzone from 'components/Dragzone';
+import { inputIntent } from 'utils';
-export default function BillFloatingActions({
- formik: { getFieldProps },
+// Bill form floating actions.
+export default function BillFormFooter({
oninitialFiles,
onDropFiles,
}) {
return (
-