feat: adjustments.

This commit is contained in:
elforjani3
2021-01-13 07:14:38 +02:00
parent c2af716225
commit 244a32e5fd
18 changed files with 582 additions and 272 deletions

View File

@@ -1,21 +1,23 @@
import React from 'react';
import { FastField, ErrorMessage } from 'formik';
import { FormGroup, InputGroup } from '@blueprintjs/core';
import { Row, Col, FieldRequiredHint } from 'components';
import { sumBy, subtract, add } from 'lodash';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { FormGroup, InputGroup, Intent } from '@blueprintjs/core';
import { inputIntent } from 'utils';
import { Row, Col, If, FieldRequiredHint } from 'components';
import { FormattedMessage as T } from 'react-intl';
import { calculate } from './utils';
function DecrementAdjustmentFields() {
return (
<Row>
{/*------------ Quantity on hand -----------*/}
<Col sm={3}>
<FastField name={'quantity'}>
<FastField name={'quantity_on_hand'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'qty_on_hand'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="quantity" />}
helperText={<ErrorMessage name="quantity_on_hand" />}
>
<InputGroup disabled={true} medium={'true'} {...field} />
</FormGroup>
@@ -24,15 +26,24 @@ function DecrementAdjustmentFields() {
</Col>
{/*------------ Decrement -----------*/}
<Col sm={2}>
<FastField name={'decrement'}>
{({ field, meta: { error, touched } }) => (
<FastField name={'quantity'}>
{({
form: { values, setFieldValue },
field,
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'decrement'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="decrement" />}
helperText={<ErrorMessage name="quantity" />}
fill={true}
>
<InputGroup medium={'true'} {...field} />
<InputGroup
{...field}
onBlur={(event) => {
setFieldValue('new_quantity', calculate(values, field.value));
}}
/>
</FormGroup>
)}
</FastField>
@@ -40,13 +51,26 @@ function DecrementAdjustmentFields() {
{/*------------ New quantity -----------*/}
<Col sm={4}>
<FastField name={'new_quantity'}>
{({ field, meta: { error, touched } }) => (
{({
form: { values, setFieldValue },
field,
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'new_quantity'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="new_quantity" />}
>
<InputGroup medium={'true'} {...field} />
<InputGroup
medium={'true'}
{...field}
onBlur={(event) => {
setFieldValue(
'quantity',
subtract(field.value, values.quantity_on_hand),
);
}}
/>
</FormGroup>
)}
</FastField>

View File

@@ -1,21 +1,23 @@
import React from 'react';
import { FastField, ErrorMessage } from 'formik';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import { add, sumBy, subtract } from 'lodash';
import { FormGroup, InputGroup, Intent } from '@blueprintjs/core';
import { Row, Col, FieldRequiredHint } from 'components';
import { inputIntent } from 'utils';
import { FormattedMessage as T } from 'react-intl';
import { calculate } from './utils';
function IncrementAdjustmentFields() {
return (
<Row>
{/*------------ Quantity on hand -----------*/}
<Col sm={3}>
<FastField name={'quantity'}>
<FastField name={'quantity_on_hand'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'qty_on_hand'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="quantity" />}
helperText={<ErrorMessage name="quantity_on_hand" />}
>
<InputGroup disabled={true} medium={'true'} {...field} />
</FormGroup>
@@ -24,15 +26,28 @@ function IncrementAdjustmentFields() {
</Col>
{/*------------ Increment -----------*/}
<Col sm={2}>
<FastField name={'increment'}>
{({ field, meta: { error, touched } }) => (
<FastField name={'quantity'}>
{({
form: { values, setFieldValue },
field,
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'increment'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="increment" />}
helperText={<ErrorMessage name="quantity" />}
fill={true}
>
<InputGroup medium={'true'} {...field} />
<InputGroup
medium={'true'}
{...field}
onBlur={(event) => {
setFieldValue(
'new_quantity',
calculate(values, event.currentTarget.value),
);
}}
/>
</FormGroup>
)}
</FastField>
@@ -54,13 +69,26 @@ function IncrementAdjustmentFields() {
{/*------------ New quantity -----------*/}
<Col sm={4}>
<FastField name={'new_quantity'}>
{({ field, meta: { error, touched } }) => (
{({
form: { values, setFieldValue },
field,
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'new_quantity'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="new_quantity" />}
>
<InputGroup medium={'true'} {...field} />
<InputGroup
medium={'true'}
{...field}
onBlur={(event) => {
setFieldValue(
'quantity',
subtract(field.value, values.quantity_on_hand),
);
}}
/>
</FormGroup>
)}
</FastField>

View File

@@ -0,0 +1,51 @@
import React from 'react';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { useFormikContext } from 'formik';
import { FormattedMessage as T } from 'react-intl';
import { saveInvoke } from 'utils';
export default function InventoryAdjustmentFloatingActions({
onCloseClick,
onSubmitClick,
}) {
const { isSubmitting } = useFormikContext();
const handleSubmitDraftBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
publish: false,
});
};
const handleSubmitMakeAdjustmentBtnClick = (event) => {
saveInvoke(onSubmitClick, event, {
publish: true,
});
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={onCloseClick} style={{ minWidth: '75px' }}>
<T id={'close'} />
</Button>
<Button
disabled={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
onClick={handleSubmitDraftBtnClick}
>
{<T id={'save_as_draft'} />}
</Button>
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
onClick={handleSubmitMakeAdjustmentBtnClick}
>
{<T id={'make_adjustment'} />}
</Button>
</div>
</div>
);
}

View File

@@ -6,22 +6,25 @@ const Schema = Yup.object().shape({
date: Yup.date()
.required()
.label(formatMessage({ id: 'date' })),
type: Yup.number().required(),
adjustment_account_id: Yup.number().required(),
type: Yup.string().required(),
adjustment_account_id: Yup.string().required(),
item_id: Yup.number().required(),
reason: Yup.string()
.required()
.label(formatMessage({ id: 'reason' })),
quantity: Yup.number().when(['type'], {
is: (type) => type,
then: Yup.number().required(),
}),
quantity_on_hand: Yup.number()
.min(0)
.required()
.label(formatMessage({ id: 'qty' })),
quantity: Yup.number().integer().max(Yup.ref('quantity_on_hand')).required(),
cost: Yup.number().when(['type'], {
is: (type) => type,
then: Yup.number().required(),
then: Yup.number(),
}),
reference_no: Yup.string(),
new_quantity: Yup.number(),
new_quantity: Yup.number().min(Yup.ref('quantity')).required(),
description: Yup.string().min(3).max(DATATYPES_LENGTH.TEXT).nullable().trim(),
publish: Yup.boolean(),
});
export const CreateInventoryAdjustmentFormSchema = Schema;

View File

@@ -1,33 +1,31 @@
import React, { useCallback, useMemo } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import { Intent } from '@blueprintjs/core';
import { Formik } from 'formik';
import { Formik, Form } from 'formik';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { useQuery, queryCache } from 'react-query';
import moment from 'moment';
import { omit } from 'lodash';
import {
AppToaster,
DialogContent,
Row,
Col,
ListSelect,
IF,
} from 'components';
import { AppToaster, DialogContent } from 'components';
import { CreateInventoryAdjustmentFormSchema } from './InventoryAdjustmentForm.schema';
import InventoryAdjustmentFormDialogFields from './InventoryAdjustmentFormDialogFields';
import InventoryAdjustmentFloatingActions from './InventoryAdjustmentFloatingActions';
import withDialogActions from 'containers/Dialog/withDialogActions';
import withInventoryAdjustmentActions from 'containers/Items/withInventoryAdjustmentActions';
import { compose } from 'utils';
const defaultInitialValues = {
date: moment(new Date()).format('YYYY-MM-DD'),
type: 'decrement',
adjustment_account_id: '',
item_id: '',
reason: '',
cost: '',
quantity: '',
reference_no: '',
quantity_on_hand: '',
description: '',
publish: '',
};
/**
@@ -40,31 +38,38 @@ function InventoryAdjustmentFormDialogContent({
// #withAccountsActions
requestFetchAccounts,
// #withInventoryAdjustmentActions
requestSubmitInventoryAdjustment,
// #ownProp
itemDetail,
dialogName,
action,
}) {
const { formatMessage } = useIntl();
const [submitPayload, setSubmitPayload] = useState({});
// Fetches accounts list.
const fetchAccountsList = useQuery('accounts-list', () =>
requestFetchAccounts(),
);
const fetchAccount = useQuery('accounts-list', () => requestFetchAccounts());
const initialValues = useMemo(
() => ({
...defaultInitialValues,
...itemDetail,
}),
[],
);
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = { ...values };
const form = {
...omit(values, ['quantity_on_hand', 'new_quantity', 'action']),
publish: submitPayload.publish,
};
const onSuccess = ({ response }) => {
closeDialog(dialogName);
queryCache.invalidateQueries('accounts-list');
queryCache.invalidateQueries('items-table');
AppToaster.show({
message: formatMessage({
@@ -76,14 +81,21 @@ function InventoryAdjustmentFormDialogContent({
const onError = (error) => {
setSubmitting(false);
};
//requestInventoryAdjustment
requestSubmitInventoryAdjustment({ form }).then(onSuccess).catch(onError);
};
// Handles dialog close.
const handleClose = useCallback(() => {
const handleCloseClick = useCallback(() => {
closeDialog(dialogName);
}, [closeDialog, dialogName]);
const handleSubmitClick = useCallback(
(event, payload) => {
setSubmitPayload({ ...payload });
},
[setSubmitPayload],
);
return (
<DialogContent>
<Formik
@@ -91,13 +103,19 @@ function InventoryAdjustmentFormDialogContent({
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<InventoryAdjustmentFormDialogFields
dialogName={dialogName}
onClose={handleClose}
/>
<Form>
<InventoryAdjustmentFormDialogFields dialogName={dialogName} />
<InventoryAdjustmentFloatingActions
onSubmitClick={handleSubmitClick}
onCloseClick={handleCloseClick}
/>
</Form>
</Formik>
</DialogContent>
);
}
export default compose(withDialogActions)(InventoryAdjustmentFormDialogContent);
export default compose(
withInventoryAdjustmentActions,
withDialogActions,
)(InventoryAdjustmentFormDialogContent);

View File

@@ -1,24 +1,16 @@
import React from 'react';
import { FastField, ErrorMessage, useFormikContext } from 'formik';
import {
Form,
FastField,
ErrorMessage,
useFormikContext,
useField,
} from 'formik';
import {
Button,
Classes,
FormGroup,
InputGroup,
Intent,
TextArea,
Position,
} from '@blueprintjs/core';
import classNames from 'classnames';
import { FormattedMessage as T } from 'react-intl';
import { FormattedMessage as T, useIntl } from 'react-intl';
import { DateInput } from '@blueprintjs/datetime';
import { ListSelect, Choose, If, FieldRequiredHint } from 'components';
import { ListSelect, Choose, FieldRequiredHint } from 'components';
import {
inputIntent,
momentFormatter,
@@ -30,7 +22,6 @@ import adjustmentType from 'common/adjustmentType';
import IncrementAdjustmentFields from './IncrementAdjustmentFields';
import DecrementAdjustmentFields from './DecrementAdjustmentFields';
import AccountsSuggestField from 'components/AccountsSuggestField';
import withAccounts from 'containers/Accounts/withAccounts';
import { compose } from 'redux';
@@ -38,158 +29,135 @@ import { compose } from 'redux';
* Inventory adjustment form dialogs fields.
*/
function InventoryAdjustmentFormDialogFields({
// #ownProps
onClose,
//# withAccount
accountsList,
}) {
const { values, isSubmitting } = useFormikContext();
const { values } = useFormikContext();
const { formatMessage } = useIntl();
// console.log(values, 'EE');
return (
<Form>
<div className={Classes.DIALOG_BODY}>
{/*------------ Date -----------*/}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
minimal={true}
className={classNames(CLASSES.FILL)}
>
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{
position: Position.BOTTOM,
minimal: true,
}}
/>
</FormGroup>
)}
</FastField>
{/*------------ Adjustment type -----------*/}
<FastField name={'type'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'adjustment_type'} />}
labelInfo={<FieldRequiredHint />}
helperText={<ErrorMessage name="type" />}
intent={inputIntent({ error, touched })}
className={classNames(CLASSES.FILL)}
>
<ListSelect
items={adjustmentType}
onItemSelect={(type) => {
console.log(type.value, 'EE');
form.setFieldValue('type', type.value);
}}
selectedItem={value}
selectedItemProp={'value'}
labelProp={'name'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
<Choose>
<Choose.When condition={values.type === 'decrement'}>
<DecrementAdjustmentFields />
</Choose.When>
<Choose.When condition={values.type === 'increment'}>
<IncrementAdjustmentFields />
</Choose.When>
</Choose>
{/*------------ Reason -----------*/}
<FastField name={'reason'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reason'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reason" />}
>
<InputGroup fill={true} {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Adjustment account -----------*/}
<FastField name={'adjustment_account_id'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'adjustment_account'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reason" />}
>
<AccountsSuggestField
accounts={accountsList}
onAccountSelected={(item) =>
form.setFieldValue('adjustment_account_id', item)
}
/>
</FormGroup>
)}
</FastField>
{/*------------ Reference -----------*/}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
className={classNames(CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
>
<TextArea growVertically={true} large={true} {...field} />
</FormGroup>
)}
</FastField>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={onClose} style={{ minWidth: '75px' }}>
<T id={'close'} />
</Button>
<Button
disabled={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
<div className={Classes.DIALOG_BODY}>
{/*------------ Date -----------*/}
<FastField name={'date'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'date'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="date" />}
minimal={true}
className={classNames(CLASSES.FILL)}
>
{<T id={'save_as_draft'} />}
</Button>
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
<DateInput
{...momentFormatter('YYYY/MM/DD')}
onChange={handleDateChange((formattedDate) => {
form.setFieldValue('date', formattedDate);
})}
value={tansformDateValue(value)}
popoverProps={{
position: Position.BOTTOM,
minimal: true,
}}
/>
</FormGroup>
)}
</FastField>
{/*------------ Adjustment type -----------*/}
<FastField name={'type'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'adjustment_type'} />}
labelInfo={<FieldRequiredHint />}
helperText={<ErrorMessage name="type" />}
intent={inputIntent({ error, touched })}
className={classNames(CLASSES.FILL)}
>
{<T id={'make_adjustment'} />}
</Button>
</div>
</div>
</Form>
<ListSelect
items={adjustmentType}
onItemSelect={(type) => {
form.setFieldValue('type', type.value);
}}
filterable={false}
selectedItem={value}
selectedItemProp={'value'}
labelProp={'name'}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
<Choose>
<Choose.When condition={values.type === 'decrement'}>
<DecrementAdjustmentFields />
</Choose.When>
<Choose.When condition={values.type === 'increment'}>
<IncrementAdjustmentFields />
</Choose.When>
</Choose>
{/*------------ Reason -----------*/}
<FastField name={'reason'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reason'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reason" />}
>
<InputGroup fill={true} {...field} />
</FormGroup>
)}
</FastField>
{/*------------ Adjustment account -----------*/}
<FastField name={'adjustment_account_id'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'adjustment_account'} />}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reason" />}
>
<AccountsSuggestField
accounts={accountsList}
onAccountSelected={(item) =>
form.setFieldValue('adjustment_account_id', item.id)
}
inputProps={{
placeholder: formatMessage({
id: 'select_adjustment_account',
}),
}}
/>
</FormGroup>
)}
</FastField>
{/*------------ Reference -----------*/}
<FastField name={'reference_no'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'reference_no'} />}
className={classNames(CLASSES.FILL)}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="reference_no" />}
>
<InputGroup {...field} />
</FormGroup>
)}
</FastField>
{/*------------ description -----------*/}
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
>
<TextArea growVertically={true} large={true} {...field} />
</FormGroup>
)}
</FastField>
</div>
);
}

View File

@@ -27,7 +27,7 @@ function InventoryAdjustmentFormDialog({
<DialogSuspense>
<InventoryAdjustmentFormDialogContent
dialogName={dialogName}
action={payload.action}
itemDetail={payload}
/>
</DialogSuspense>
</Dialog>

View File

@@ -0,0 +1,20 @@
import { add, sumBy, subtract } from 'lodash';
export const calculate = ({ type, quantity_on_hand }, operator) => {
const qty = parseInt(quantity_on_hand);
const quantity = parseInt(operator);
if (type == 'decrement') {
return subtract(qty, quantity);
} else {
return add(qty, quantity);
}
};
// function calculate(qty, operator) {
// return operator > 0
// ? calculate(qty + 1, operator - 1)
// : operator < 0
// ? calculate(qty - 1, operator + 1)
// : qty;
// }

View File

@@ -7,38 +7,42 @@ import {
MenuItem,
MenuDivider,
Position,
Tag,
} from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment';
import classNames from 'classnames';
import {
DataTable,
If,
Money,
Choose,
Icon,
LoadingIndicator,
} from 'components';
import { DataTable, Icon, LoadingIndicator } from 'components';
import { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks';
import withDialogActions from 'containers/Dialog/withDialogActions';
// withInventoryAdjustments
// withInventoryAdjustmentsActions
import withInventoryAdjustments from './withInventoryAdjustments';
import withInventoryAdjustmentActions from './withInventoryAdjustmentActions';
import { compose, saveInvoke } from 'utils';
import { withRouter } from 'react-router-dom';
function InventoryAdjustmentDataTable({
// withInventoryAdjustments
inventoryAdjustmentItems,
inventoryAdjustmentCurrentPage,
inventoryAdjustmentLoading,
inventoryAdjustmentsPagination,
// withInventoryAdjustmentsActions
addInventoryAdjustmentTableQueries,
// #ownProps
onDeleteInventoryAdjustment,
onSelectedRowsChange,
}) {
const { formatMessage } = useIntl();
const isLoadedBefore = useIsValuePassed(inventoryAdjustmentLoading, false);
const handleDeleteInventoryAdjustment = useCallback(
(_inventory) => {
saveInvoke(onDeleteInventoryAdjustment, _inventory);
(_adjustment) => () => {
saveInvoke(onDeleteInventoryAdjustment, _adjustment);
},
[onDeleteInventoryAdjustment],
);
@@ -53,9 +57,9 @@ function InventoryAdjustmentDataTable({
<MenuDivider />
<MenuItem
text={formatMessage({ id: 'delete_adjustment' })}
icon={<Icon icon="trash-16" iconSize={16} />}
intent={Intent.DANGER}
onClick={handleDeleteInventoryAdjustment(adjustment)}
icon={<Icon icon="trash-16" iconSize={16} />}
/>
</Menu>
),
@@ -79,41 +83,53 @@ function InventoryAdjustmentDataTable({
{
id: 'type',
Header: formatMessage({ id: 'type' }),
accessor: 'type',
accessor: (row) =>
row.type ? (
<Tag minimal={true} round={true} intent={Intent.NONE}>
{formatMessage({ id: row.type })}
</Tag>
) : (
''
),
className: 'type',
width: 100,
},
{
id: 'reason',
Header: formatMessage({ id: 'reason' }),
// accessor: (r) => (
// <Tooltip
// content={}
// position={Position.RIGHT_BOTTOM}
// >
// </Tooltip>
// ),
accessor: 'reason',
className: 'reason',
width: 115,
},
{
id: 'reference',
Header: formatMessage({ id: 'reference' }),
accessor: (row) => `#${row.reference}`,
className: 'reference',
id: 'reference_no',
Header: formatMessage({ id: 'reference_no' }),
accessor: 'reference_no',
className: 'reference_no',
width: 100,
},
{
id: 'status',
id: 'publish',
Header: formatMessage({ id: 'status' }),
accessor: 'status',
accessor: (r) => {
return r.is_published ? (
<Tag minimal={true}>
<T id={'published'} />
</Tag>
) : (
<Tag minimal={true} intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
);
},
width: 95,
className: 'status',
className: 'publish',
},
{
id: 'description',
Header: formatMessage({ id: 'description' }),
accessor: 'description',
disableSorting: true,
width: 85,
className: 'description',
@@ -144,6 +160,23 @@ function InventoryAdjustmentDataTable({
[actionMenuList, formatMessage],
);
const handleDataTableFetchData = useCallback(
({ pageSize, pageIndex, sortBy }) => {
addInventoryAdjustmentTableQueries({
...(sortBy.length > 0
? {
column_sort_by: sortBy[0].id,
sort_order: sortBy[0].desc ? 'desc' : 'asc',
}
: {}),
page_size: pageSize,
page: pageIndex + 1,
});
},
[addInventoryAdjustmentTableQueries],
);
const handleSelectedRowsChange = useCallback(
(selectedRows) => {
saveInvoke(
@@ -160,11 +193,23 @@ function InventoryAdjustmentDataTable({
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator
// loading={}
>
<DataTable noInitialFetch={true} columns={columns} data={[]} />
<LoadingIndicator loading={inventoryAdjustmentLoading && !isLoadedBefore}>
<DataTable
columns={columns}
data={inventoryAdjustmentItems}
onFetchData={handleDataTableFetchData}
manualSortBy={true}
selectionColumn={true}
noInitialFetch={true}
onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu}
pagination={true}
autoResetSortBy={false}
autoResetPage={false}
pagesCount={inventoryAdjustmentsPagination.pagesCount}
initialPageSize={inventoryAdjustmentsPagination.pageSize}
initialPageIndex={inventoryAdjustmentsPagination.page - 1}
/>
</LoadingIndicator>
</div>
);
@@ -173,4 +218,18 @@ function InventoryAdjustmentDataTable({
export default compose(
withRouter,
withDialogActions,
withInventoryAdjustmentActions,
withInventoryAdjustments(
({
inventoryAdjustmentLoading,
inventoryAdjustmentItems,
inventoryAdjustmentCurrentPage,
inventoryAdjustmentsPagination,
}) => ({
inventoryAdjustmentLoading,
inventoryAdjustmentItems,
inventoryAdjustmentCurrentPage,
inventoryAdjustmentsPagination,
}),
),
)(InventoryAdjustmentDataTable);

View File

@@ -3,13 +3,15 @@ import { useQuery } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import AppToaster from 'components/AppToaster';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import InventoryAdjustmentDataTable from './InventoryAdjustmentDataTable';
import withInventoryAdjustmentActions from './withInventoryAdjustmentActions';
import withInventoryAdjustments from './withInventoryAdjustments';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
//withInventoryAdjustmentsActions
import { compose } from 'utils';
import { Route, Switch } from 'react-router-dom';
@@ -19,7 +21,13 @@ import { Route, Switch } from 'react-router-dom';
function InventoryAdjustmentList({
// #withDashboardActions
changePageTitle,
// #withInventoryAdjustments
inventoryAdjustmentTableQuery,
// #withInventoryAdjustmentsActions
requestFetchInventoryAdjustmentTable,
requestDeleteInventoryAdjustment,
}) {
const { formatMessage } = useIntl();
const [selectedRows, setSelectedRows] = useState([]);
@@ -32,8 +40,8 @@ function InventoryAdjustmentList({
}, [changePageTitle, formatMessage]);
const fetchInventoryAdjustments = useQuery(
['inventory-adjustment-list'],
() => {},
['inventory-adjustment-list' ,inventoryAdjustmentTableQuery],
(key, query) => requestFetchInventoryAdjustmentTable({ ...query }),
);
// Handle selected rows change.
@@ -55,7 +63,25 @@ function InventoryAdjustmentList({
setDeleteInventoryAdjustment(false);
}, [setDeleteInventoryAdjustment]);
const handleConfirmInventoryAdjustmentDelete = useCallback(() => {}, []);
const handleConfirmInventoryAdjustmentDelete = useCallback(() => {
requestDeleteInventoryAdjustment(deleteInventoryAdjustment.id)
.then(() => {
setDeleteInventoryAdjustment(false);
AppToaster.show({
message: formatMessage({
id: 'the_adjustment_has_been_successfully_deleted',
}),
intent: Intent.SUCCESS,
});
})
.catch((errors) => {
setDeleteInventoryAdjustment(false);
});
}, [
deleteInventoryAdjustment,
requestDeleteInventoryAdjustment,
formatMessage,
]);
// Calculates the data table selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
@@ -63,7 +89,7 @@ function InventoryAdjustmentList({
]);
return (
<DashboardInsider>
<DashboardInsider name={'inventory_adjustments'}>
<DashboardPageContent>
<Switch>
<Route exact={true}>
@@ -73,9 +99,32 @@ function InventoryAdjustmentList({
/>
</Route>
</Switch>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'delete'} />}
icon={'trash'}
intent={Intent.DANGER}
isOpen={deleteInventoryAdjustment}
onCancel={handleCancelInventoryAdjustmentDelete}
onConfirm={handleConfirmInventoryAdjustmentDelete}
>
<p>
<T
id={
'once_delete_this_inventory_a_adjustment_you_will_able_to_restore_it'
}
/>
</p>
</Alert>
</DashboardPageContent>
</DashboardInsider>
);
}
export default compose(withDashboardActions)(InventoryAdjustmentList);
export default compose(
withDashboardActions,
withInventoryAdjustmentActions,
withInventoryAdjustments(({ inventoryAdjustmentTableQuery }) => ({
inventoryAdjustmentTableQuery,
})),
)(InventoryAdjustmentList);

View File

@@ -75,6 +75,17 @@ function ItemsDataTable({
[addItemsTableQueries],
);
const handleMakeAdjustment = useCallback(
(item) => () => {
openDialog('inventory-adjustment-form', {
action: 'make_adjustment',
item_id: item.id,
quantity_on_hand: item.quantity_on_hand,
});
},
[openDialog],
);
const handleEditItem = useCallback(
(item) => () => {
onEditItem && onEditItem(item);
@@ -89,10 +100,6 @@ function ItemsDataTable({
[onDeleteItem],
);
const handleMakeAdjustment = useCallback(() => {
openDialog('inventory-adjustment-form', {});
}, [openDialog]);
const actionMenuList = useCallback(
(item) => (
<Menu>
@@ -120,11 +127,11 @@ function ItemsDataTable({
onClick={() => onActivateItem(item)}
/>
</If>
<If condition={item.type === 'inventory'}>
<MenuItem
text={formatMessage({ id: 'make_adjustment' })}
onClick={handleMakeAdjustment}
onClick={handleMakeAdjustment(item)}
/>
</If>
<MenuItem

View File

@@ -7,7 +7,7 @@ import {
import t from 'store/types';
const mapDispatchToProps = (dispatch) => ({
requestSubmitInventoryAdjustment: (form) =>
requestSubmitInventoryAdjustment: ({ form }) =>
dispatch(submitInventoryAdjustment({ form })),
requestFetchInventoryAdjustmentTable: (query = {}) =>
dispatch(fetchInventoryAdjustmentsTable({ query: { ...query } })),

View File

@@ -1,4 +1,34 @@
import { connect } from 'react-redux';
import {
getInvoiceTableQueryFactory,
getInventoryAdjustmentCurrentPageFactory,
getInventoryAdjustmentPaginationMetaFactory,
} from 'store/inventoryAdjustments/inventoryAdjustment.selector';
export default (mapState) => {
const getInventoryAdjustmentItems = getInventoryAdjustmentCurrentPageFactory();
const getInventoryAdjustmentTableQuery = getInvoiceTableQueryFactory();
const getInventoryAdjustmentsPaginationMeta = getInventoryAdjustmentPaginationMetaFactory();
const mapStateToProps = (state, props) => {
const query = getInventoryAdjustmentTableQuery(state, props);
const mapped = {
inventoryAdjustmentCurrentPage: getInventoryAdjustmentItems(
state,
props,
query,
),
inventoryAdjustmentItems: Object.values(state.inventoryAdjustments.items),
inventoryAdjustmentTableQuery: query,
inventoryAdjustmentsPagination: getInventoryAdjustmentsPaginationMeta(
state,
props,
query,
),
inventoryAdjustmentLoading: state.inventoryAdjustments.loading,
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -950,8 +950,13 @@ export default {
cost: 'Cost',
qty_on_hand: 'Qty on hand',
adjustment_account: 'Adjustment account',
inventory_adjustment_list:'Inventory Adjustment List',
delete_adjustment:'Delete Adjustment',
inventory_adjustment_list: 'Inventory Adjustment List',
delete_adjustment: 'Delete Adjustment',
the_make_adjustment_has_been_successfully_created:
'The make adjustment has been successfully created.',
the_adjustment_has_been_successfully_deleted:
'The adjustment has been successfully deleted.',
once_delete_this_inventory_a_adjustment_you_will_able_to_restore_it: `Once you delete this inventory a adjustment, you won\'t be able to restore it later. Are you sure you want to delete this invoice?`,
select_adjustment_account: 'Select Adjustment account',
qty: 'Quantity on hand',
};

View File

@@ -4,10 +4,11 @@ import t from 'store/types';
export const submitInventoryAdjustment = ({ form }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.post('inventory_adjustments', form).then((response) => {
resolve(response);
}),
caches((error) => {
ApiService.post('inventory_adjustments/quick', form)
.then((response) => {
resolve(response);
})
.catch((error) => {
const { response } = error;
const { data } = response;
@@ -51,7 +52,7 @@ export const fetchInventoryAdjustmentsTable = ({ query } = {}) => {
dispatch({
type: t.INVENTORY_ADJUSTMENTS_PAGE_SET,
payload: {
inventory_adjustments: response.data,
inventory_adjustments: response.data.inventoy_adjustments,
pagination: response.data.pagination,
customViewId:
response.data?.filter_meta?.view?.custom_view_id || -1,
@@ -60,7 +61,7 @@ export const fetchInventoryAdjustmentsTable = ({ query } = {}) => {
dispatch({
type: t.INVENTORY_ADJUSTMENT_ITEMS_SET,
payload: {
inventory_adjustment: response.data,
inventory_adjustment: response.data.inventoy_adjustments,
},
});
dispatch({

View File

@@ -14,9 +14,6 @@ const initialState = {
page_size: 12,
page: 1,
},
paginationMeta: {
total: 0,
},
};
export default createReducer(initialState, {
@@ -37,7 +34,6 @@ export default createReducer(initialState, {
const viewId = customViewId || -1;
const view = state.views[viewId] || {};
state.views[viewId] = {
...view,
pages: {
@@ -49,7 +45,6 @@ export default createReducer(initialState, {
};
},
//useless
[t.INVENTORY_ADJUSTMENT_ITEMS_SET]: (state, action) => {
const { inventory_adjustment } = action.payload;
const _inventory_adjustment = {};

View File

@@ -5,3 +5,55 @@ import {
defaultPaginationMeta,
} from 'store/selectors';
const inventoryAdjustmentTableQuery = (state) =>
state.inventoryAdjustments.tableQuery;
const inventoryAdjustmentsPaginationSelector = (state, props) => {
const viewId = state.inventoryAdjustments.currentViewId;
return state.inventoryAdjustments.views?.[viewId];
};
const inventoryAdjustmentItemsSelector = (state) =>
state.inventoryAdjustments.items;
const inventoryAdjustmentCurrentPageSelector = (state, props) => {
const currentViewId = state.inventoryAdjustments.currentViewId;
const currentView = state.inventoryAdjustments.views?.[currentViewId];
const currentPageId = currentView?.paginationMeta?.page;
return currentView?.pages?.[currentPageId];
};
const getinventoryAdjustmentCurrentViewIdSelector = (state) =>
state.inventoryAdjustments.currentViewId;
export const getInvoiceTableQueryFactory = () =>
createSelector(
paginationLocationQuery,
inventoryAdjustmentTableQuery,
(locationQuery, tableQuery) => {
return {
...locationQuery,
...tableQuery,
};
},
);
export const getInventoryAdjustmentCurrentPageFactory = () =>
createSelector(
inventoryAdjustmentCurrentPageSelector,
inventoryAdjustmentItemsSelector,
(currentPage, items) => {
return typeof currentPage === 'object'
? pickItemsFromIds(items, currentPage.ids) || []
: [];
},
);
export const getInventoryAdjustmentPaginationMetaFactory = () =>
createSelector(inventoryAdjustmentsPaginationSelector, (Page) => {
return {
...defaultPaginationMeta(),
...(Page?.paginationMeta || {}),
};
});

View File

@@ -8,7 +8,7 @@ export default {
INVENTORY_ADJUSTMENTS_TABLE_QUERIES_ADD:
'INVENTORY_ADJUSTMENTS_TABLE_QUERIES_ADD',
'INVENTORY_ADJUSTMENTS/TABLE_QUERIES_ADD',
INVENTORY_ADJUSTMENTS_SET_CURRENT_VIEW:
'INVENTORY_ADJUSTMENTS_SET_CURRENT_VIEW',
};