refactoring: invoice form.

refactoring: receipt form.
refactoring: bill form.
refactoring: estimate form.
This commit is contained in:
a.bouhuolia
2021-02-15 16:23:58 +02:00
parent 151bd9bc54
commit e8458e2b36
37 changed files with 410 additions and 903 deletions

View File

@@ -1,276 +0,0 @@
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 } from 'utils';
const ActionsCellRenderer = ({
row: { index },
column: { id },
cell: { value },
data,
payload,
}) => {
if (data.length <= index + 1) {
return '';
}
const onRemoveRole = () => {
payload.removeRow(index);
};
return (
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
<Button
icon={<Icon icon={'times-circle'} iconSize={14} />}
iconSize={14}
className="m12"
intent={Intent.DANGER}
onClick={onRemoveRole}
/>
</Tooltip>
);
};
const TotalEstimateCellRederer = (content, type) => (props) => {
if (props.data.length === props.row.index + 1) {
const total = props.data.reduce((total, entry) => {
const amount = parseInt(entry[type], 10);
const computed = amount ? total + amount : total;
return computed;
}, 0);
return <span>{formattedAmount(total, 'USD')}</span>;
}
return content(props);
};
const calculateDiscount = (discount, quantity, rate) =>
quantity * rate - (quantity * rate * discount) / 100;
const CellRenderer = (content, type) => (props) => {
if (props.data.length === props.row.index + 1) {
return '';
}
return content(props);
};
const ItemHeaderCell = () => {
return (
<>
<T id={'product_and_service'} />
<Hint />
</>
);
};
function EntriesItemsTable({
//#withitems
itemsCurrentPage,
//#ownProps
onClickRemoveRow,
onClickAddNewRow,
onClickClearAllLines,
entries,
errors,
setFieldValue,
values,
}) {
const [rows, setRows] = useState([]);
const { formatMessage } = useIntl();
useEffect(() => {
setRows([...entries.map((e) => ({ ...e }))]);
}, [entries]);
const columns = useMemo(
() => [
{
Header: '#',
accessor: 'index',
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
width: 40,
disableResizing: true,
disableSortBy: true,
className: 'index',
},
{
Header: ItemHeaderCell,
id: 'item_id',
accessor: 'item_id',
Cell: ItemsListCell,
// ItemsListCell
disableSortBy: true,
width: 180,
},
{
Header: formatMessage({ id: 'description' }),
accessor: 'description',
Cell: InputGroupCell,
disableSortBy: true,
className: 'description',
width: 100,
},
{
Header: formatMessage({ id: 'quantity' }),
accessor: 'quantity',
Cell: CellRenderer(InputGroupCell, 'quantity'),
disableSortBy: true,
width: 80,
className: 'quantity',
},
{
Header: formatMessage({ id: 'rate' }),
accessor: 'rate',
Cell: TotalEstimateCellRederer(MoneyFieldCell, 'rate'),
disableSortBy: true,
width: 80,
className: 'rate',
},
{
Header: formatMessage({ id: 'discount' }),
accessor: 'discount',
Cell: CellRenderer(PercentFieldCell, InputGroupCell),
disableSortBy: true,
width: 80,
className: 'discount',
},
{
Header: formatMessage({ id: 'total' }),
accessor: (row) =>
calculateDiscount(row.discount, row.quantity, row.rate),
Cell: TotalEstimateCellRederer(DivFieldCell, 'total'),
disableSortBy: true,
width: 120,
className: 'total',
},
{
Header: '',
accessor: 'action',
Cell: ActionsCellRenderer,
className: 'actions',
disableSortBy: true,
disableResizing: true,
width: 45,
},
],
[formatMessage],
);
const handleUpdateData = useCallback(
(rowIndex, columnId, value) => {
const newRow = rows.map((row, index) => {
if (index === rowIndex) {
const newRow = { ...rows[rowIndex], [columnId]: value };
return {
...newRow,
total: calculateDiscount(
newRow.discount,
newRow.quantity,
newRow.rate,
),
};
}
return row;
});
setFieldValue(
'entries',
newRow.map((row) => ({
...omit(row, ['total']),
})),
);
},
[rows, setFieldValue],
);
const handleRemoveRow = useCallback(
(rowIndex) => {
if (rows.length <= 1) {
return;
}
const removeIndex = parseInt(rowIndex, 10);
const newRows = rows.filter((row, index) => index !== removeIndex);
setFieldValue(
'entries',
newRows.map((row, index) => ({
...omit(row),
index: index + 1,
})),
);
onClickRemoveRow && onClickRemoveRow(removeIndex);
},
[rows, setFieldValue, onClickRemoveRow],
);
const onClickNewRow = () => {
onClickAddNewRow && onClickAddNewRow();
};
const handleClickClearAllLines = () => {
onClickClearAllLines && onClickClearAllLines();
};
const rowClassNames = useCallback(
(row) => ({
'row--total': rows.length === row.index + 1,
}),
[rows],
);
return (
<div className={classNames(
CLASSES.DATATABLE_EDITOR,
CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES,
)}>
<DataTable
columns={columns}
data={rows}
rowClassNames={rowClassNames}
payload={{
items: itemsCurrentPage,
errors: errors.entries || [],
updateData: handleUpdateData,
removeRow: handleRemoveRow,
}}
/>
<div className={classNames(CLASSES.DATATABLE_EDITOR_ACTIONS, 'mt1')}>
<Button
small={true}
className={'button--secondary button--new-line'}
onClick={onClickNewRow}
>
<T id={'new_lines'} />
</Button>
<Button
small={true}
className={'button--secondary button--clear-lines ml1'}
onClick={handleClickClearAllLines}
>
<T id={'clear_all_lines'} />
</Button>
</div>
</div>
);
}
export default compose(
withItems(({ itemsCurrentPage }) => ({
itemsCurrentPage,
})),
)(EntriesItemsTable);

View File

@@ -27,7 +27,8 @@ export default function EstimateFloatingActions() {
// Handle submit & deliver button click.
const handleSubmitDeliverBtnClick = (event) => {
setSubmitPayload({ redirect: true, deliver: true, });
setSubmitPayload({ redirect: true, deliver: true });
submitForm();
};
// Handle submit, deliver & new button click.
@@ -77,7 +78,6 @@ export default function EstimateFloatingActions() {
disabled={isSubmitting}
loading={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitDeliverBtnClick}
text={<T id={'save_and_deliver'} />}
/>
@@ -105,12 +105,12 @@ export default function EstimateFloatingActions() {
/>
</Popover>
</ButtonGroup>
{/* ----------- Save As Draft ----------- */}
<ButtonGroup>
<Button
disabled={isSubmitting}
className={'ml1'}
type="submit"
onClick={handleSubmitDraftBtnClick}
text={<T id={'save_as_draft'} />}
/>
@@ -138,13 +138,13 @@ export default function EstimateFloatingActions() {
</Popover>
</ButtonGroup>
</If>
{/* ----------- Save and New ----------- */}
<If condition={estimate && estimate?.is_delivered}>
<ButtonGroup>
<Button
disabled={isSubmitting}
intent={Intent.PRIMARY}
type="submit"
onClick={handleSubmitDeliverBtnClick}
text={<T id={'save'} />}
/>
@@ -177,6 +177,7 @@ export default function EstimateFloatingActions() {
onClick={handleClearBtnClick}
text={estimate ? <T id={'reset'} /> : <T id={'clear'} />}
/>
{/* ----------- Cancel ----------- */}
<Button
className={'ml1'}

View File

@@ -1,9 +1,8 @@
import React, { useMemo } from 'react';
import { Formik, Form } from 'formik';
import moment from 'moment';
import { Intent } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { pick, sumBy } from 'lodash';
import { omit, sumBy, isEmpty } from 'lodash';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
@@ -14,57 +13,31 @@ import {
} from './EstimateForm.schema';
import EstimateFormHeader from './EstimateFormHeader';
import EstimateFormBody from './EstimateFormBody';
import EstimateItemsEntriesField from './EstimateItemsEntriesField';
import EstimateFloatingActions from './EstimateFloatingActions';
import EstimateFormFooter from './EstimateFormFooter';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withMediaActions from 'containers/Media/withMediaActions';
import withSettings from 'containers/Settings/withSettings';
import { AppToaster } from 'components';
import { ERROR } from 'common/errors';
import {
compose,
repeatValue,
orderingLinesIndexes,
} from 'utils';
import { useEstimateFormContext } from './EstimateFormProvider';
const MIN_LINES_NUMBER = 4;
const defaultEstimate = {
index: 0,
item_id: '',
rate: '',
discount: 0,
quantity: 1,
description: '',
};
const defaultInitialValues = {
customer_id: '',
estimate_date: moment(new Date()).format('YYYY-MM-DD'),
expiration_date: moment(new Date()).format('YYYY-MM-DD'),
estimate_number: '',
delivered: '',
reference: '',
note: '',
terms_conditions: '',
entries: [...repeatValue(defaultEstimate, MIN_LINES_NUMBER)],
};
import { transformToEditForm, defaultEstimate } from './utils';
/**
* Estimate form.
*/
const EstimateForm = ({
function EstimateForm({
// #withSettings
estimateNextNumber,
estimateNumberPrefix,
}) => {
}) {
const { formatMessage } = useIntl();
const history = useHistory();
const {
estimate,
isNewMode,
@@ -80,23 +53,14 @@ const EstimateForm = ({
// Initial values in create and edit mode.
const initialValues = useMemo(
() => ({
...(estimate
...(!isEmpty(estimate)
? {
...pick(estimate, Object.keys(defaultInitialValues)),
entries: [
...estimate.entries.map((estimate) => ({
...pick(estimate, Object.keys(defaultEstimate)),
})),
...repeatValue(
defaultEstimate,
Math.max(MIN_LINES_NUMBER - estimate.entries.length, 0),
),
],
...transformToEditForm(estimate)
}
: {
...defaultInitialValues,
...defaultEstimate,
estimate_number: estimateNumber,
entries: orderingLinesIndexes(defaultInitialValues.entries),
entries: orderingLinesIndexes(defaultEstimate.entries),
}),
}),
[estimate, estimateNumber],
@@ -138,10 +102,7 @@ const EstimateForm = ({
const form = {
...values,
delivered: submitPayload.deliver,
// Exclude all entries properties that out of request schema.
entries: entries.map((entry) => ({
...pick(entry, Object.keys(defaultEstimate)),
})),
entries: entries.map((entry) => ({ ...omit(entry, ['total']) })),
};
const onSuccess = (response) => {
AppToaster.show({
@@ -172,7 +133,7 @@ const EstimateForm = ({
setSubmitting(false);
};
if (estimate && estimate.id) {
if (!isNewMode) {
editEstimateMutate([estimate.id, form]).then(onSuccess).catch(onError);
} else {
createEstimateMutate(form).then(onSuccess).catch(onError);
@@ -196,18 +157,19 @@ const EstimateForm = ({
>
<Form>
<EstimateFormHeader />
<EstimateFormBody defaultEstimate={defaultEstimate} />
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<EstimateItemsEntriesField />
</div>
<EstimateFormFooter />
<EstimateFloatingActions />
</Form>
</Formik>
</div>
);
};
}
export default compose(
withDashboardActions,
withMediaActions,
withSettings(({ estimatesSettings }) => ({
estimateNextNumber: estimatesSettings?.nextNumber,
estimateNumberPrefix: estimatesSettings?.numberPrefix,

View File

@@ -1,15 +0,0 @@
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
// import EditableItemsEntriesTable from 'containers/Entries/EditableItemsEntriesTable';
export default function EstimateFormBody({ defaultEstimate }) {
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
{/* <EditableItemsEntriesTable
defaultEntry={defaultEstimate}
filterSellableItems={true}
/> */}
</div>
);
}

View File

@@ -18,7 +18,6 @@ import {
InputPrependButton,
} from 'components';
import withCustomers from 'containers/Customers/withCustomers';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { inputIntent, handleDateChange } from 'utils';
@@ -169,8 +168,5 @@ function EstimateFormHeader({
}
export default compose(
withCustomers(({ customers }) => ({
customers,
})),
withDialogActions,
)(EstimateFormHeader);

View File

@@ -1,49 +1,20 @@
import React, { useEffect } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import React from 'react';
import { useParams } from 'react-router-dom';
import 'style/pages/SaleEstimate/PageForm.scss';
import EstimateForm from './EstimateForm';
import { EstimateFormProvider } from './EstimateFormProvider';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import { compose } from 'utils';
/**
* Estimate form page.
*/
function EstimateFormPage({
// #withDashboardActions
setSidebarShrink,
resetSidebarPreviousExpand,
setDashboardBackLink,
}) {
export default function EstimateFormPage() {
const { id } = useParams();
useEffect(() => {
// Shrink the sidebar by foce.
setSidebarShrink();
// Show the back link on dashboard topbar.
setDashboardBackLink(true);
return () => {
// Reset the sidebar to the previous status.
resetSidebarPreviousExpand();
// Hide the back link on dashboard topbar.
setDashboardBackLink(false);
};
}, [resetSidebarPreviousExpand, setSidebarShrink, setDashboardBackLink]);
return (
<EstimateFormProvider estimateId={id}>
<EstimateForm />
</EstimateFormProvider>
);
}
export default compose(
withDashboardActions,
)(EstimateFormPage);
}

View File

@@ -7,7 +7,7 @@ import {
useSettings,
useCreateEstimate,
useEditEstimate
} from 'query/hooks';
} from 'hooks/query';
const EstimateFormContext = createContext();
@@ -16,7 +16,7 @@ const EstimateFormContext = createContext();
*/
function EstimateFormProvider({ estimateId, ...props }) {
const { data: estimate, isFetching: isEstimateFetching } = useEstimate(
estimateId,
estimateId, { enabled: !!estimateId }
);
// Handle fetch Items data table or list
@@ -32,16 +32,16 @@ function EstimateFormProvider({ estimateId, ...props }) {
} = useCustomers();
// Handle fetch settings.
const {
data: { settings },
} = useSettings();
useSettings();
// Form submit payload.
const [submitPayload, setSubmitPayload] = React.useState({});
const isNewMode = !estimateId;
// Create and edit estimate form.
const { mutateAsync: createEstimateMutate } = useCreateEstimate();
const { mutateAsync: editEstimateMutate } = useEditEstimate();
const isNewMode = !estimateId;
// Provider payload.
const provider = {

View File

@@ -0,0 +1,27 @@
import React from 'react';
import { FastField } from 'formik';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { useEstimateFormContext } from './EstimateFormProvider';
/**
* Estimate form items entries editor.
*/
export default function EstimateFormItemsEntriesField() {
const { items } = useEstimateFormContext();
return (
<FastField name={'entries'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<ItemsEntriesTable
entries={value}
onUpdateData={(entries) => {
form.setFieldValue('entries', entries);
}}
items={items}
errors={error}
linesNumber={4}
/>
)}
</FastField>
);
}

View File

@@ -1,46 +0,0 @@
import { useEffect } from 'react';
import { useFormikContext } from 'formik';
import withEstimates from './withEstimates';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withEstimateActions from './withEstimateActions';
import { compose } from 'utils';
function EstimateNumberWatcher({
estimateNumberChanged,
// #WithEstimateActions
setEstimateNumberChanged,
// #withDashboardActions
changePageSubtitle,
// #ownProps
estimateNumber,
}) {
const { setFieldValue } = useFormikContext();
useEffect(() => {
if (estimateNumberChanged) {
setFieldValue('estimate_number', estimateNumber);
changePageSubtitle(`No. ${estimateNumber}`);
setEstimateNumberChanged(false);
}
}, [
estimateNumber,
estimateNumberChanged,
setEstimateNumberChanged,
setFieldValue,
changePageSubtitle,
]);
return null;
}
export default compose(
withEstimates(({ estimateNumberChanged }) => ({
estimateNumberChanged,
})),
withEstimateActions,
withDashboardActions
)(EstimateNumberWatcher)

View File

@@ -0,0 +1,38 @@
import moment from 'moment';
import { repeatValue, transformToForm } from 'utils';
export const MIN_LINES_NUMBER = 4;
export const defaultEstimateEntry = {
index: 0,
item_id: '',
rate: '',
discount: 0,
quantity: 1,
description: '',
};
export const defaultEstimate = {
customer_id: '',
estimate_date: moment(new Date()).format('YYYY-MM-DD'),
expiration_date: moment(new Date()).format('YYYY-MM-DD'),
estimate_number: '',
delivered: '',
reference: '',
note: '',
terms_conditions: '',
entries: [...repeatValue(defaultEstimateEntry, MIN_LINES_NUMBER)],
};
export const transformToEditForm = (estimate) => ({
...transformToForm(estimate, defaultEstimate),
entries: [
...estimate.entries.map((estimate) => ({
...transformToForm(estimate, defaultEstimateEntry),
})),
...repeatValue(
defaultEstimate,
Math.max(MIN_LINES_NUMBER - estimate.entries.length, 0),
),
],
});

View File

@@ -67,6 +67,7 @@ export function ActionsMenu({
/>
<If condition={!original.is_delivered}>
<MenuItem
icon={<Icon icon={'check'} iconSize={18} />}
text={formatMessage({ id: 'mark_as_delivered' })}
onClick={safeCallback(onDeliver, original)}
/>
@@ -80,16 +81,19 @@ export function ActionsMenu({
</Choose.When>
<Choose.When condition={original.is_delivered && original.is_rejected}>
<MenuItem
icon={<Icon icon={'check'} iconSize={18} />}
text={formatMessage({ id: 'mark_as_approved' })}
onClick={safeCallback(onApprove, original)}
/>
</Choose.When>
<Choose.When condition={original.is_delivered}>
<MenuItem
icon={<Icon icon={'check'} iconSize={18} />}
text={formatMessage({ id: 'mark_as_approved' })}
onClick={safeCallback(onApprove, original)}
/>
<MenuItem
icon={<Icon icon={'close-black'} />}
text={formatMessage({ id: 'mark_as_rejected' })}
onClick={safeCallback(onReject, original)}
/>

View File

@@ -22,17 +22,9 @@ import withSettings from 'containers/Settings/withSettings';
import { AppToaster } from 'components';
import { ERROR } from 'common/errors';
import {
compose,
orderingLinesIndexes,
transactionNumber,
} from 'utils';
import { compose, orderingLinesIndexes, transactionNumber } from 'utils';
import { useInvoiceFormContext } from './InvoiceFormProvider';
import { transformToEditForm } from './utils';
import {
MIN_LINES_NUMBER,
defaultInitialValues
} from './constants';
import { transformToEditForm, defaultInvoice } from './utils';
/**
* Invoice form.
@@ -64,11 +56,11 @@ function InvoiceForm({
const initialValues = useMemo(
() => ({
...(!isEmpty(invoice)
? transformToEditForm(invoice, defaultInitialValues, MIN_LINES_NUMBER)
? transformToEditForm(invoice)
: {
...defaultInitialValues,
...defaultInvoice,
invoice_no: invoiceNumber,
entries: orderingLinesIndexes(defaultInitialValues.entries),
entries: orderingLinesIndexes(defaultInvoice.entries),
}),
}),
[invoice, invoiceNumber],

View File

@@ -1,26 +0,0 @@
import { moment } from 'moment';
import { repeatValue } from 'utils';
export const MIN_LINES_NUMBER = 4;
export const defaultInvoice = {
index: 0,
item_id: '',
rate: '',
discount: 0,
quantity: 1,
description: '',
total: 0,
};
export const defaultInitialValues = {
customer_id: '',
invoice_date: moment(new Date()).format('YYYY-MM-DD'),
due_date: moment(new Date()).format('YYYY-MM-DD'),
delivered: '',
invoice_no: '',
reference_no: '',
invoice_message: '',
terms_conditions: '',
entries: [...repeatValue(defaultInvoice, MIN_LINES_NUMBER)],
};

View File

@@ -1,16 +1,43 @@
import moment from 'moment';
import { transformToForm, repeatValue } from 'utils';
export const MIN_LINES_NUMBER = 4;
export function transformToEditForm(invoice, defaultInvoice, linesNumber) {
export const defaultInvoiceEntry = {
index: 0,
item_id: '',
rate: '',
discount: 0,
quantity: 1,
description: '',
total: 0,
};
export const defaultInvoice = {
customer_id: '',
invoice_date: moment(new Date()).format('YYYY-MM-DD'),
due_date: moment().format('YYYY-MM-DD'),
delivered: '',
invoice_no: '',
reference_no: '',
invoice_message: '',
terms_conditions: '',
entries: [...repeatValue(defaultInvoiceEntry, MIN_LINES_NUMBER)],
};
/**
* Transform invoice to initial values in edit mode.
*/
export function transformToEditForm(invoice) {
return {
...transformToForm(invoice, defaultInvoice),
entries: [
...invoice.entries.map((invoice) => ({
...transformToForm(invoice, defaultInvoice.entries[0]),
...transformToForm(invoice, defaultInvoiceEntry),
})),
...repeatValue(
defaultInvoice,
Math.max(linesNumber - invoice.entries.length, 0),
defaultInvoiceEntry,
Math.max(MIN_LINES_NUMBER - invoice.entries.length, 0),
),
],
};

View File

@@ -1,9 +1,8 @@
import React, { useMemo } from 'react';
import { Formik, Form } from 'formik';
import moment from 'moment';
import { Intent } from '@blueprintjs/core';
import { useIntl } from 'react-intl';
import { pick, sumBy, isEmpty } from 'lodash';
import { omit, sumBy, isEmpty } from 'lodash';
import classNames from 'classnames';
import { useHistory } from 'react-router-dom';
@@ -19,7 +18,7 @@ import 'style/pages/SaleReceipt/PageForm.scss';
import { useReceiptFormContext } from './ReceiptFormProvider';
import ReceiptFromHeader from './ReceiptFormHeader';
import ReceiptFormBody from './ReceiptFormBody';
import ReceiptItemsEntriesEditor from './ReceiptItemsEntriesEditor';
import ReceiptFormFloatingActions from './ReceiptFormFloatingActions';
import ReceiptFormFooter from './ReceiptFormFooter';
@@ -29,33 +28,11 @@ import withSettings from 'containers/Settings/withSettings';
import { AppToaster } from 'components';
import {
compose,
repeatValue,
orderingLinesIndexes,
transactionNumber,
} from 'utils';
import { transformToEditForm, defaultReceipt } from './utils'
const MIN_LINES_NUMBER = 4;
const defaultReceipt = {
index: 0,
item_id: '',
rate: '',
discount: 0,
quantity: '',
description: '',
};
const defaultInitialValues = {
customer_id: '',
deposit_account_id: '',
receipt_number: '',
receipt_date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '',
receipt_message: '',
statement: '',
closed: '',
entries: [...repeatValue(defaultReceipt, MIN_LINES_NUMBER)],
};
/**
* Receipt form.
@@ -75,11 +52,10 @@ function ReceiptForm({
receipt,
editReceiptMutate,
createReceiptMutate,
submitPayload
submitPayload,
isNewMode
} = useReceiptFormContext();
const isNewMode = !receiptId;
// The next receipt number.
const receiptNumber = transactionNumber(
receiptNumberPrefix,
@@ -89,23 +65,12 @@ function ReceiptForm({
const initialValues = useMemo(
() => ({
...(!isEmpty(receipt)
? {
...pick(receipt, Object.keys(defaultInitialValues)),
entries: [
...receipt.entries.map((receipt) => ({
...pick(receipt, Object.keys(defaultReceipt)),
})),
...repeatValue(
defaultReceipt,
Math.max(MIN_LINES_NUMBER - receipt.entries.length, 0),
),
],
}
? transformToEditForm(receipt)
: {
...defaultInitialValues,
...defaultReceipt,
receipt_number: receiptNumber,
deposit_account_id: parseInt(preferredDepositAccount),
entries: orderingLinesIndexes(defaultInitialValues.entries),
entries: orderingLinesIndexes(defaultReceipt.entries),
}),
}),
[receipt, preferredDepositAccount, receiptNumber],
@@ -145,10 +110,7 @@ function ReceiptForm({
const form = {
...values,
closed: submitPayload.status,
entries: entries.map((entry) => ({
// Exclude all properties that out of request entries schema.
...pick(entry, Object.keys(defaultReceipt)),
})),
entries: entries.map((entry) => ({ ...omit(entry, ['total']), })),
};
// Handle the request success.
const onSuccess = (response) => {
@@ -179,8 +141,8 @@ function ReceiptForm({
setSubmitting(false);
};
if (receipt && receipt.id) {
editReceiptMutate(receipt.id, form).then(onSuccess).catch(onError);
if (!isNewMode) {
editReceiptMutate([receipt.id, form]).then(onSuccess).catch(onError);
} else {
createReceiptMutate(form).then(onSuccess).catch(onError);
}
@@ -203,7 +165,7 @@ function ReceiptForm({
>
<Form>
<ReceiptFromHeader />
<ReceiptFormBody defaultReceipt={defaultReceipt} />
<ReceiptItemsEntriesEditor />
<ReceiptFormFooter />
<ReceiptFormFloatingActions />
</Form>

View File

@@ -1,20 +0,0 @@
import React from 'react';
import classNames from 'classnames';
import { CLASSES } from 'common/classes';
// import EditableItemsEntriesTable from 'containers/Entries/EditableItemsEntriesTable';
import { useReceiptFormContext } from './ReceiptFormProvider';
export default function ExpenseFormBody({ defaultReceipt }) {
const { items } = useReceiptFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
{/* <EditableItemsEntriesTable
items={items}
defaultEntry={defaultReceipt}
filterSellableItems={true}
/> */}
</div>
);
}

View File

@@ -46,6 +46,8 @@ function ReceiptFormProvider({ receiptId, ...props }) {
const [submitPayload, setSubmitPayload] = useState({});
const isNewMode = !receiptId;
const provider = {
receiptId,
receipt,
@@ -54,6 +56,7 @@ function ReceiptFormProvider({ receiptId, ...props }) {
items,
submitPayload,
isNewMode,
isReceiptLoading,
isAccountsLoading,
isCustomersLoading,

View File

@@ -0,0 +1,28 @@
import React from 'react';
import classNames from 'classnames';
import { FastField } from 'formik';
import ItemsEntriesTable from 'containers/Entries/ItemsEntriesTable';
import { CLASSES } from 'common/classes';
import { useReceiptFormContext } from './ReceiptFormProvider';
export default function ReceiptItemsEntriesEditor({ defaultReceipt }) {
const { items } = useReceiptFormContext();
return (
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
<FastField name={'entries'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<ItemsEntriesTable
entries={value}
onUpdateData={(entries) => {
form.setFieldValue('entries', entries);
}}
items={items}
errors={error}
linesNumber={4}
/>
)}
</FastField>
</div>
);
}

View File

@@ -1,45 +0,0 @@
import { useEffect } from 'react';
import { useFormikContext } from 'formik';
import withDashboardActions from "containers/Dashboard/withDashboardActions";
import withReceipts from './withReceipts';
import withReceiptActions from './withReceiptActions';
import { compose } from 'utils';
function ReceiptNumberWatcher({
receiptNumber,
// #withDashboardActions
changePageSubtitle,
// #withReceipts
receiptNumberChanged,
//#withReceiptActions
setReceiptNumberChanged,
}) {
const { setFieldValue } = useFormikContext();
useEffect(() => {
if (receiptNumberChanged) {
setFieldValue('receipt_number', receiptNumber);
changePageSubtitle(`No. ${receiptNumber}`);
setReceiptNumberChanged(false);
}
}, [
receiptNumber,
receiptNumberChanged,
setReceiptNumberChanged,
setFieldValue,
changePageSubtitle,
]);
return null
}
export default compose(
withReceipts(({ receiptNumberChanged }) => ({ receiptNumberChanged })),
withReceiptActions,
withDashboardActions
)(ReceiptNumberWatcher);

View File

@@ -0,0 +1,41 @@
import moment from 'moment';
import { repeatValue, transformToForm } from 'utils';
export const MIN_LINES_NUMBER = 4;
export const defaultReceiptEntry = {
index: 0,
item_id: '',
rate: '',
discount: 0,
quantity: '',
description: '',
};
export const defaultReceipt = {
customer_id: '',
deposit_account_id: '',
receipt_number: '',
receipt_date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '',
receipt_message: '',
statement: '',
closed: '',
entries: [...repeatValue(defaultReceiptEntry, MIN_LINES_NUMBER)],
};
/**
* Transform to form in edit mode.
*/
export const transformToEditForm = (receipt) => ({
...transformToForm(receipt, defaultReceipt),
entries: [
...receipt.entries.map((entry) => ({
...transformToForm(entry, defaultReceiptEntry),
})),
...repeatValue(
defaultReceiptEntry,
Math.max(MIN_LINES_NUMBER - receipt.entries.length, 0),
),
],
});