This commit is contained in:
a.bouhuolia
2021-01-11 11:21:39 +02:00
23 changed files with 1031 additions and 20 deletions

View File

@@ -0,0 +1,4 @@
export default [
{ name: 'Decrement', value: 'decrement' },
{ name: 'Increment', value: 'increment' },
]

View File

@@ -12,6 +12,7 @@ import PaymentReceiveNumberDialog from 'containers/Dialogs/PaymentReceiveNumberD
import EstimateNumberDialog from 'containers/Dialogs/EstimateNumberDialog';
import ReceiptNumberDialog from 'containers/Dialogs/ReceiptNumberDialog';
import InvoiceNumberDialog from 'containers/Dialogs/InvoiceNumberDialog';
import InventoryAdjustmentDialog from 'containers/Dialogs/InventoryAdjustmentFormDialog';
export default function DialogsContainer() {
return (
@@ -27,6 +28,7 @@ export default function DialogsContainer() {
<InviteUserDialog dialogName={'invite-user'} />
<ExchangeRateFormDialog dialogName={'exchangeRate-form'} />
<ItemCategoryDialog dialogName={'item-category-form'} />
<InventoryAdjustmentDialog dialogName={'inventory-adjustment-form'} />
</div>
);
}

View File

@@ -25,8 +25,8 @@ export default [
href: '/items',
},
{
text: <T id={'new_item'} />,
href: '/items/new',
text: <T id={'inventory_adjustments'} />,
href: '/inventory-adjustments',
},
{
text: <T id={'category_list'} />,

View File

@@ -54,21 +54,21 @@ function ManualJournalActionsBar({
history.push('/make-journal-entry');
}, [history]);
const filterDropdown = FilterDropdown({
fields: resourceFields,
initialCondition: {
fieldKey: 'journal_number',
compatator: 'contains',
value: '',
},
onFilterChange: (filterConditions) => {
setFilterCount(filterConditions.length || 0);
addManualJournalsTableQueries({
filter_roles: filterConditions || '',
});
onFilterChanged && onFilterChanged(filterConditions);
},
});
// const filterDropdown = FilterDropdown({
// fields: resourceFields,
// initialCondition: {
// fieldKey: 'journal_number',
// compatator: 'contains',
// value: '',
// },
// onFilterChange: (filterConditions) => {
// setFilterCount(filterConditions.length || 0);
// addManualJournalsTableQueries({
// filter_roles: filterConditions || '',
// });
// onFilterChanged && onFilterChanged(filterConditions);
// },
// });
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [
selectedRows,
]);
@@ -103,7 +103,7 @@ function ManualJournalActionsBar({
/>
<Popover
minimal={true}
content={filterDropdown}
// content={filterDropdown}
interactionKind={PopoverInteractionKind.CLICK}
position={Position.BOTTOM_LEFT}
>

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { FastField, ErrorMessage } from 'formik';
import { FormGroup, InputGroup } from '@blueprintjs/core';
import { Row, Col, FieldRequiredHint } from 'components';
import { inputIntent } from 'utils';
import { FormattedMessage as T } from 'react-intl';
function DecrementAdjustmentFields() {
return (
<Row>
{/*------------ Quantity on hand -----------*/}
<Col sm={3}>
<FastField name={'quantity'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'qty_on_hand'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="quantity" />}
>
<InputGroup disabled={true} medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
</Col>
{/*------------ Decrement -----------*/}
<Col sm={2}>
<FastField name={'decrement'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'decrement'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="decrement" />}
fill={true}
>
<InputGroup medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
</Col>
{/*------------ New quantity -----------*/}
<Col sm={4}>
<FastField name={'new_quantity'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'new_quantity'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="new_quantity" />}
>
<InputGroup medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
</Col>
</Row>
);
}
export default DecrementAdjustmentFields;

View File

@@ -0,0 +1,72 @@
import React from 'react';
import { FastField, ErrorMessage } from 'formik';
import { FormGroup, InputGroup, Intent } from '@blueprintjs/core';
import { Row, Col, FieldRequiredHint } from 'components';
import { inputIntent } from 'utils';
import { FormattedMessage as T } from 'react-intl';
function IncrementAdjustmentFields() {
return (
<Row>
{/*------------ Quantity on hand -----------*/}
<Col sm={3}>
<FastField name={'quantity'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'qty_on_hand'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="quantity" />}
>
<InputGroup disabled={true} medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
</Col>
{/*------------ Increment -----------*/}
<Col sm={2}>
<FastField name={'increment'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'increment'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="increment" />}
fill={true}
>
<InputGroup medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
</Col>
{/*------------ Cost -----------*/}
<Col sm={2}>
<FastField name={'cost'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'cost'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="cost" />}
>
<InputGroup medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
</Col>
{/*------------ New quantity -----------*/}
<Col sm={4}>
<FastField name={'new_quantity'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'new_quantity'} />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="new_quantity" />}
>
<InputGroup medium={'true'} {...field} />
</FormGroup>
)}
</FastField>
</Col>
</Row>
);
}
export default IncrementAdjustmentFields;

View File

@@ -0,0 +1,27 @@
import * as Yup from 'yup';
import { formatMessage } from 'services/intl';
import { DATATYPES_LENGTH } from 'common/dataTypes';
const Schema = Yup.object().shape({
date: Yup.date()
.required()
.label(formatMessage({ id: 'date' })),
type: Yup.number().required(),
adjustment_account_id: Yup.number().required(),
reason: Yup.string()
.required()
.label(formatMessage({ id: 'reason' })),
quantity: Yup.number().when(['type'], {
is: (type) => type,
then: Yup.number().required(),
}),
cost: Yup.number().when(['type'], {
is: (type) => type,
then: Yup.number().required(),
}),
reference_no: Yup.string(),
new_quantity: Yup.number(),
description: Yup.string().min(3).max(DATATYPES_LENGTH.TEXT).nullable().trim(),
});
export const CreateInventoryAdjustmentFormSchema = Schema;

View File

@@ -0,0 +1,103 @@
import React, { useCallback, useMemo } from 'react';
import { Intent } from '@blueprintjs/core';
import { Formik } 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 { CreateInventoryAdjustmentFormSchema } from './InventoryAdjustmentForm.schema';
import InventoryAdjustmentFormDialogFields from './InventoryAdjustmentFormDialogFields';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose } from 'utils';
const defaultInitialValues = {
date: moment(new Date()).format('YYYY-MM-DD'),
type: 'decrement',
adjustment_account_id: '',
reason: '',
reference_no: '',
description: '',
};
/**
* Inventory adjustment form dialog content.
*/
function InventoryAdjustmentFormDialogContent({
// #withDialogActions
closeDialog,
// #withAccountsActions
requestFetchAccounts,
// #ownProp
dialogName,
action,
}) {
const { formatMessage } = useIntl();
// Fetches accounts list.
const fetchAccountsList = useQuery('accounts-list', () =>
requestFetchAccounts(),
);
const initialValues = useMemo(
() => ({
...defaultInitialValues,
}),
[],
);
// Handles the form submit.
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const form = { ...values };
const onSuccess = ({ response }) => {
closeDialog(dialogName);
queryCache.invalidateQueries('accounts-list');
AppToaster.show({
message: formatMessage({
id: 'the_make_adjustment_has_been_successfully_created',
}),
intent: Intent.SUCCESS,
});
};
const onError = (error) => {
setSubmitting(false);
};
//requestInventoryAdjustment
};
// Handles dialog close.
const handleClose = useCallback(() => {
closeDialog(dialogName);
}, [closeDialog, dialogName]);
return (
<DialogContent>
<Formik
validationSchema={CreateInventoryAdjustmentFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
>
<InventoryAdjustmentFormDialogFields
dialogName={dialogName}
onClose={handleClose}
/>
</Formik>
</DialogContent>
);
}
export default compose(withDialogActions)(InventoryAdjustmentFormDialogContent);

View File

@@ -0,0 +1,200 @@
import React from 'react';
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 { DateInput } from '@blueprintjs/datetime';
import { ListSelect, Choose, If, FieldRequiredHint } from 'components';
import {
inputIntent,
momentFormatter,
tansformDateValue,
handleDateChange,
} from 'utils';
import { CLASSES } from 'common/classes';
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';
/**
* Inventory adjustment form dialogs fields.
*/
function InventoryAdjustmentFormDialogFields({
// #ownProps
onClose,
//# withAccount
accountsList,
}) {
const { values, isSubmitting } = useFormikContext();
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"
>
{<T id={'save_as_draft'} />}
</Button>
<Button
intent={Intent.PRIMARY}
disabled={isSubmitting}
style={{ minWidth: '75px' }}
type="submit"
>
{<T id={'make_adjustment'} />}
</Button>
</div>
</div>
</Form>
);
}
export default compose(
withAccounts(({ accountsList }) => ({
accountsList,
})),
)(InventoryAdjustmentFormDialogFields);

View File

@@ -0,0 +1,37 @@
import React, { lazy } from 'react';
import { FormattedMessage as T } from 'react-intl';
import { Dialog, DialogSuspense } from 'components';
import withDialogRedux from 'components/DialogReduxConnect';
import { compose } from 'redux';
const InventoryAdjustmentFormDialogContent = lazy(() =>
import('./InventoryAdjustmentFormDialogContent'),
);
/**
* Inventory adjustments form dialog.
*/
function InventoryAdjustmentFormDialog({
dialogName,
payload = { action: '', id: null },
isOpen,
}) {
return (
<Dialog
name={dialogName}
title={<T id={'make_adjustment'} />}
isOpen={isOpen}
canEscapeJeyClose={true}
autoFocus={true}
>
<DialogSuspense>
<InventoryAdjustmentFormDialogContent
dialogName={dialogName}
action={payload.action}
/>
</DialogSuspense>
</Dialog>
);
}
export default compose(withDialogRedux())(InventoryAdjustmentFormDialog);

View File

@@ -0,0 +1,176 @@
import React, { useCallback, useMemo } from 'react';
import {
Button,
Popover,
Menu,
Intent,
MenuItem,
MenuDivider,
Position,
} 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 { CLASSES } from 'common/classes';
import { useIsValuePassed } from 'hooks';
import withDialogActions from 'containers/Dialog/withDialogActions';
// withInventoryAdjustments
// withInventoryAdjustmentsActions
import { compose, saveInvoke } from 'utils';
import { withRouter } from 'react-router-dom';
function InventoryAdjustmentDataTable({
// #ownProps
onDeleteInventoryAdjustment,
onSelectedRowsChange,
}) {
const { formatMessage } = useIntl();
const handleDeleteInventoryAdjustment = useCallback(
(_inventory) => {
saveInvoke(onDeleteInventoryAdjustment, _inventory);
},
[onDeleteInventoryAdjustment],
);
const actionMenuList = useCallback(
(adjustment) => (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={formatMessage({ id: 'view_details' })}
/>
<MenuDivider />
<MenuItem
text={formatMessage({ id: 'delete_adjustment' })}
icon={<Icon icon="trash-16" iconSize={16} />}
intent={Intent.DANGER}
onClick={handleDeleteInventoryAdjustment(adjustment)}
/>
</Menu>
),
[handleDeleteInventoryAdjustment, formatMessage],
);
const onRowContextMenu = useCallback(
(cell) => actionMenuList(cell.row.original),
[actionMenuList],
);
const columns = useMemo(
() => [
{
id: 'date',
Header: formatMessage({ id: 'date' }),
accessor: (r) => moment(r.date).format('YYYY MMM DD'),
width: 115,
className: 'date',
},
{
id: 'type',
Header: formatMessage({ id: 'type' }),
accessor: 'type',
className: 'type',
width: 100,
},
{
id: 'reason',
Header: formatMessage({ id: 'reason' }),
// accessor: (r) => (
// <Tooltip
// content={}
// position={Position.RIGHT_BOTTOM}
// >
// </Tooltip>
// ),
className: 'reason',
width: 115,
},
{
id: 'reference',
Header: formatMessage({ id: 'reference' }),
accessor: (row) => `#${row.reference}`,
className: 'reference',
width: 100,
},
{
id: 'status',
Header: formatMessage({ id: 'status' }),
accessor: 'status',
width: 95,
className: 'status',
},
{
id: 'description',
Header: formatMessage({ id: 'description' }),
disableSorting: true,
width: 85,
className: 'description',
},
{
id: 'created_at',
Header: formatMessage({ id: 'created_at' }),
accessor: (r) => moment(r.created_at).format('YYYY MMM DD'),
width: 125,
className: 'created_at',
},
{
id: 'actions',
Header: '',
Cell: ({ cell }) => (
<Popover
content={actionMenuList(cell.row.original)}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
),
className: 'actions',
width: 50,
disableResizing: true,
},
],
[actionMenuList, formatMessage],
);
const handleSelectedRowsChange = useCallback(
(selectedRows) => {
saveInvoke(
onSelectedRowsChange,
selectedRows.map((s) => s.original),
);
},
[onSelectedRowsChange],
);
// const showEmptyStatus = [
// ].every((condition) => condition === true);
return (
<div className={classNames(CLASSES.DASHBOARD_DATATABLE)}>
<LoadingIndicator
// loading={}
>
<DataTable noInitialFetch={true} columns={columns} data={[]} />
</LoadingIndicator>
</div>
);
}
export default compose(
withRouter,
withDialogActions,
)(InventoryAdjustmentDataTable);

View File

@@ -0,0 +1,81 @@
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { useQuery } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core';
import { FormattedMessage as T, useIntl } from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider';
import InventoryAdjustmentDataTable from './InventoryAdjustmentDataTable';
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
//withInventoryAdjustmentsActions
import { compose } from 'utils';
import { Route, Switch } from 'react-router-dom';
/**
* Inventory Adjustment List.
*/
function InventoryAdjustmentList({
// #withDashboardActions
changePageTitle,
// #withInventoryAdjustmentsActions
}) {
const { formatMessage } = useIntl();
const [selectedRows, setSelectedRows] = useState([]);
const [deleteInventoryAdjustment, setDeleteInventoryAdjustment] = useState(
false,
);
useEffect(() => {
changePageTitle(formatMessage({ id: 'inventory_adjustment_list' }));
}, [changePageTitle, formatMessage]);
const fetchInventoryAdjustments = useQuery(
['inventory-adjustment-list'],
() => {},
);
// Handle selected rows change.
const handleSelectedRowsChange = useCallback(
(inventory) => {
setSelectedRows(inventory);
},
[setSelectedRows],
);
const handleDeleteInventoryAdjustment = useCallback(
(adjustment) => {
setDeleteInventoryAdjustment(adjustment);
},
[setDeleteInventoryAdjustment],
);
const handleCancelInventoryAdjustmentDelete = useCallback(() => {
setDeleteInventoryAdjustment(false);
}, [setDeleteInventoryAdjustment]);
const handleConfirmInventoryAdjustmentDelete = useCallback(() => {}, []);
// Calculates the data table selected rows count.
const selectedRowsCount = useMemo(() => Object.values(selectedRows).length, [
selectedRows,
]);
return (
<DashboardInsider>
<DashboardPageContent>
<Switch>
<Route exact={true}>
<InventoryAdjustmentDataTable
onDeleteInventoryAdjustment={handleDeleteInventoryAdjustment}
onSelectedRowsChange={handleSelectedRowsChange}
/>
</Route>
</Switch>
</DashboardPageContent>
</DashboardInsider>
);
}
export default compose(withDashboardActions)(InventoryAdjustmentList);

View File

@@ -27,6 +27,7 @@ import { CLASSES } from 'common/classes';
import withItems from 'containers/Items/withItems';
import withItemsActions from 'containers/Items/withItemsActions';
import withSettings from 'containers/Settings/withSettings';
import withDialogActions from 'containers/Dialog/withDialogActions';
import { compose, saveInvoke, isBlank, defaultToTransform } from 'utils';
// Items datatable.
@@ -38,6 +39,9 @@ function ItemsDataTable({
itemsCurrentViewId,
itemsPagination,
// #withDialogActions
openDialog,
// #withItemsActions
addItemsTableQueries,
@@ -84,6 +88,11 @@ function ItemsDataTable({
},
[onDeleteItem],
);
const handleMakeAdjustment = useCallback(() => {
openDialog('inventory-adjustment-form', {});
}, [openDialog]);
const actionMenuList = useCallback(
(item) => (
<Menu>
@@ -111,6 +120,13 @@ function ItemsDataTable({
onClick={() => onActivateItem(item)}
/>
</If>
<If condition={item.type === 'inventory'}>
<MenuItem
text={formatMessage({ id: 'make_adjustment' })}
onClick={handleMakeAdjustment}
/>
</If>
<MenuItem
text={formatMessage({ id: 'delete_item' })}
icon={<Icon icon="trash-16" iconSize={16} />}
@@ -300,4 +316,5 @@ export default compose(
baseCurrency: organizationSettings?.baseCurrency,
})),
withItemsActions,
withDialogActions,
)(ItemsDataTable);

View File

@@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import {
submitInventoryAdjustment,
deleteInventoryAdjustment,
fetchInventoryAdjustmentsTable,
} from 'store/inventoryAdjustments/inventoryAdjustment.actions';
import t from 'store/types';
const mapDispatchToProps = (dispatch) => ({
requestSubmitInventoryAdjustment: (form) =>
dispatch(submitInventoryAdjustment({ form })),
requestFetchInventoryAdjustmentTable: (query = {}) =>
dispatch(fetchInventoryAdjustmentsTable({ query: { ...query } })),
requestDeleteInventoryAdjustment: (id) =>
dispatch(deleteInventoryAdjustment({ id })),
addInventoryAdjustmentTableQueries: (queries) =>
dispatch({
type: t.INVENTORY_ADJUSTMENTS_TABLE_QUERIES_ADD,
payload: { queries },
}),
});
export default connect(null, mapDispatchToProps);

View File

@@ -0,0 +1,4 @@
import { connect } from 'react-redux';

View File

@@ -940,4 +940,18 @@ export default {
'The invoice cannot be deleted cause has associated payment transactions',
category_name_exists: 'Category name exists',
some_customers_have_sales_invoices: 'Some customers have sales invoices',
inventory_adjustments: 'Inventory adjustments',
make_adjustment: 'Make a adjustment',
adjustment_type: 'Adjustment type',
decrement: 'Decrement',
new_quantity: 'New quantity',
reason: 'Reason',
increment: 'Increment',
cost: 'Cost',
qty_on_hand: 'Qty on hand',
adjustment_account: 'Adjustment account',
inventory_adjustment_list:'Inventory Adjustment List',
delete_adjustment:'Delete Adjustment',
the_make_adjustment_has_been_successfully_created:
'The make adjustment has been successfully created.',
};

View File

@@ -89,6 +89,14 @@ export default [
}),
breadcrumb: 'Items',
},
// Inventory adjustments
{
path: `/inventory-adjustments`,
component: LazyLoader({
loader: () => import('containers/Items/InventoryAdjustmentList'),
}),
breadcrumb: 'Inventory a adjustments',
},
// Financial Reports.
{
@@ -112,7 +120,7 @@ export default [
component: LazyLoader({
loader: () =>
import(
'containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet'
'containers/FinancialStatements/TrialBalanceSheet/TrialBalanceSheet'
),
}),
breadcrumb: 'Trial Balance Sheet',
@@ -252,7 +260,7 @@ export default [
},
// Invoices.
{
{
path: `/invoices/:id/edit`,
component: LazyLoader({
loader: () => import('containers/Sales/Invoice/Invoices'),

View File

@@ -0,0 +1,86 @@
import ApiService from 'services/ApiService';
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) => {
const { response } = error;
const { data } = response;
reject(data?.errors);
});
});
};
export const deleteInventoryAdjustment = ({ id }) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete(`inventory_adjustments/${id}`)
.then((response) => {
dispatch({
type: t.INVENTORY_ADJUSTMENT_DELETE,
payload: { id },
});
resolve(response);
})
.catch((error) => {
reject(error.response.data.errors || []);
});
});
};
export const fetchInventoryAdjustmentsTable = ({ query } = {}) => {
return (dispatch, getState) =>
new Promise((resolve, reject) => {
const pageQuery = getState().inventoryAdjustments.tableQuery;
dispatch({
type: t.INVENTORY_ADJUSTMENTS_LOADING,
payload: {
loading: true,
},
});
ApiService.get('inventory_adjustments', {
params: { ...pageQuery, ...query },
})
.then((response) => {
dispatch({
type: t.INVENTORY_ADJUSTMENTS_PAGE_SET,
payload: {
inventory_adjustments: response.data,
pagination: response.data.pagination,
customViewId:
response.data?.filter_meta?.view?.custom_view_id || -1,
},
});
dispatch({
type: t.INVENTORY_ADJUSTMENT_ITEMS_SET,
payload: {
inventory_adjustment: response.data,
},
});
dispatch({
type: t.INVENTORY_ADJUSTMENTS_PAGINATION_SET,
payload: {
pagination: response.data.pagination,
customViewId:
response.data?.filter_meta?.view?.custom_view_id || -1,
},
});
dispatch({
type: t.INVENTORY_ADJUSTMENTS_LOADING,
payload: {
loading: false,
},
});
resolve(response);
})
.catch((error) => {
reject(error);
});
});
};

View File

@@ -0,0 +1,73 @@
import { createReducer } from '@reduxjs/toolkit';
import {
viewPaginationSetReducer,
createTableQueryReducers,
} from 'store/journalNumber.reducer';
import t from 'store/types';
const initialState = {
items: {},
views: {},
loading: false,
currentViewId: -1,
tableQuery: {
page_size: 12,
page: 1,
},
paginationMeta: {
total: 0,
},
};
export default createReducer(initialState, {
[t.INVENTORY_ADJUSTMENTS_LOADING]: (state, action) => {
const { loading } = action.payload;
state.loading = loading;
},
[t.INVENTORY_ADJUSTMENT_DELETE]: (state, action) => {
const { id } = action.payload;
if (typeof state.items[id] !== 'undefined') {
delete state.items[id];
}
},
[t.INVENTORY_ADJUSTMENTS_PAGE_SET]: (state, action) => {
const { customViewId, inventory_adjustments, pagination } = action.payload;
const viewId = customViewId || -1;
const view = state.views[viewId] || {};
state.views[viewId] = {
...view,
pages: {
...(state.views?.[viewId]?.pages || {}),
[pagination.page]: {
ids: inventory_adjustments.map((i) => i.id),
},
},
};
},
//useless
[t.INVENTORY_ADJUSTMENT_ITEMS_SET]: (state, action) => {
const { inventory_adjustment } = action.payload;
const _inventory_adjustment = {};
inventory_adjustment.forEach((_inventory) => {
_inventory_adjustment[_inventory.id] = {
..._inventory,
};
});
state.items = {
...state.items,
..._inventory_adjustment,
};
},
[t.INVENTORY_ADJUSTMENTS_SET_CURRENT_VIEW]: (state, action) => {
state.currentViewId = action.currentViewId;
},
...viewPaginationSetReducer(t.INVENTORY_ADJUSTMENTS_PAGINATION_SET),
...createTableQueryReducers('INVENTORY_ADJUSTMENTS'),
});

View File

@@ -0,0 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import {
pickItemsFromIds,
paginationLocationQuery,
defaultPaginationMeta,
} from 'store/selectors';

View File

@@ -0,0 +1,14 @@
export default {
INVENTORY_ADJUSTMENT_ITEMS_SET: 'INVENTORY_ADJUSTMENT_ITEMS_SET',
INVENTORY_ADJUSTMENT_DELETE: 'INVENTORY_ADJUSTMENT_DELETE',
INVENTORY_ADJUSTMENTS_LOADING: 'INVENTORY_ADJUSTMENTS_LOADING',
INVENTORY_ADJUSTMENTS_PAGE_SET: 'INVENTORY_ADJUSTMENTS_PAGE_SET',
INVENTORY_ADJUSTMENTS_PAGINATION_SET: 'INVENTORY_ADJUSTMENTS_PAGINATION_SET',
INVENTORY_ADJUSTMENTS_TABLE_QUERIES_ADD:
'INVENTORY_ADJUSTMENTS_TABLE_QUERIES_ADD',
INVENTORY_ADJUSTMENTS_SET_CURRENT_VIEW:
'INVENTORY_ADJUSTMENTS_SET_CURRENT_VIEW',
};

View File

@@ -27,6 +27,7 @@ import paymentReceives from './PaymentReceive/paymentReceive.reducer';
import paymentMades from './PaymentMades/paymentMade.reducer';
import organizations from './organizations/organizations.reducers';
import subscriptions from './subscription/subscription.reducer';
import inventoryAdjustments from './inventoryAdjustments/inventoryAdjustment.reducer';
export default combineReducers({
authentication,
@@ -56,4 +57,5 @@ export default combineReducers({
vendors,
paymentReceives,
paymentMades,
inventoryAdjustments,
});

View File

@@ -26,6 +26,7 @@ import paymentReceives from './PaymentReceive/paymentReceive.type';
import paymentMades from './PaymentMades/paymentMade.type';
import organizations from './organizations/organizations.types';
import subscription from './subscription/subscription.types';
import inventoryAdjustments from './inventoryAdjustments/inventoryAdjustment.type';
export default {
...authentication,
@@ -56,4 +57,5 @@ export default {
...paymentMades,
...organizations,
...subscription,
...inventoryAdjustments
};