Merge remote-tracking branch 'origin/task_expense_journal'

This commit is contained in:
Ahmed Bouhuolia
2020-07-06 21:52:52 +02:00
11 changed files with 144 additions and 72 deletions

View File

@@ -34,6 +34,7 @@ const ERROR = {
VENDORS_NOT_WITH_PAYABLE_ACCOUNT: 'VENDORS.NOT.WITH.PAYABLE.ACCOUNT', VENDORS_NOT_WITH_PAYABLE_ACCOUNT: 'VENDORS.NOT.WITH.PAYABLE.ACCOUNT',
PAYABLE_ENTRIES_HAS_NO_VENDORS: 'PAYABLE.ENTRIES.HAS.NO.VENDORS', PAYABLE_ENTRIES_HAS_NO_VENDORS: 'PAYABLE.ENTRIES.HAS.NO.VENDORS',
RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS: 'RECEIVABLE.ENTRIES.HAS.NO.CUSTOMERS', RECEIVABLE_ENTRIES_HAS_NO_CUSTOMERS: 'RECEIVABLE.ENTRIES.HAS.NO.CUSTOMERS',
CREDIT_DEBIT_SUMATION_SHOULD_NOT_EQUAL_ZERO:'CREDIT.DEBIT.SUMATION.SHOULD.NOT.EQUAL.ZERO'
}; };
/** /**
@@ -57,6 +58,7 @@ function MakeJournalEntriesForm({
onFormSubmit, onFormSubmit,
onCancelForm, onCancelForm,
}) { }) {
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
const { const {
setFiles, setFiles,
@@ -68,10 +70,12 @@ function MakeJournalEntriesForm({
saveCallback: requestSubmitMedia, saveCallback: requestSubmitMedia,
deleteCallback: requestDeleteMedia, deleteCallback: requestDeleteMedia,
}); });
const handleDropFiles = useCallback((_files) => { const handleDropFiles = useCallback((_files) => {
setFiles(_files.filter((file) => file.uploaded === false)); setFiles(_files.filter((file) => file.uploaded === false));
}, []); }, []);
const savedMediaIds = useRef([]); const savedMediaIds = useRef([]);
const clearSavedMediaIds = () => { const clearSavedMediaIds = () => {
savedMediaIds.current = []; savedMediaIds.current = [];
@@ -238,12 +242,15 @@ function MakeJournalEntriesForm({
); );
} }
setErrors({ ...newErrors }); setErrors({ ...newErrors });
AppToaster.show({
message: toastMessages.map((message) => { if (toastMessages.length > 0) {
return <div>- {message}</div>; AppToaster.show({
}), message: toastMessages.map((message) => {
intent: Intent.DANGER, return <div>- {message}</div>;
}); }),
intent: Intent.DANGER,
});
}
}; };
const formik = useFormik({ const formik = useFormik({

View File

@@ -46,7 +46,7 @@ function MakeJournalEntriesPage({
); );
const handleCancel = useCallback(() => { const handleCancel = useCallback(() => {
history.push('/manual-journals'); history.goBack();
}, [history]); }, [history]);
return ( return (

View File

@@ -41,7 +41,7 @@ function ManualJournalActionsBar({
addManualJournalsTableQueries, addManualJournalsTableQueries,
onFilterChanged, onFilterChanged,
selectedRows, selectedRows = [],
onBulkDelete, onBulkDelete,
}) { }) {
const { path } = useRouteMatch(); const { path } = useRouteMatch();
@@ -73,9 +73,9 @@ function ManualJournalActionsBar({
onFilterChanged && onFilterChanged(filterConditions); onFilterChanged && onFilterChanged(filterConditions);
}, },
}); });
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [ const hasSelectedRows = useMemo(
selectedRows, () => selectedRows.length > 0,
]); [selectedRows]);
// Handle delete button click. // Handle delete button click.
const handleBulkDelete = useCallback(() => { const handleBulkDelete = useCallback(() => {

View File

@@ -25,7 +25,7 @@ import withManualJournalsActions from 'containers/Accounting/withManualJournalsA
/** /**
* Status column accessor. * Status column accessor.
*/ */
function StatusAccessor(row) { const StatusAccessor = (row) => {
return ( return (
<Choose> <Choose>
<Choose.When condition={!!row.status}> <Choose.When condition={!!row.status}>
@@ -41,7 +41,7 @@ function StatusAccessor(row) {
</Choose.Otherwise> </Choose.Otherwise>
</Choose> </Choose>
); );
} };
/** /**
* Note column accessor. * Note column accessor.
@@ -115,12 +115,12 @@ function ManualJournalsDataTable({
<Menu> <Menu>
<MenuItem text={formatMessage({ id: 'view_details' })} /> <MenuItem text={formatMessage({ id: 'view_details' })} />
<MenuDivider /> <MenuDivider />
{!journal.status && ( <If condition={!journal.status}>
<MenuItem <MenuItem
text={formatMessage({ id: 'publish_journal' })} text={formatMessage({ id: 'publish_journal' })}
onClick={handlePublishJournal(journal)} onClick={handlePublishJournal(journal)}
/> />
)} </If>
<MenuItem <MenuItem
text={formatMessage({ id: 'edit_journal' })} text={formatMessage({ id: 'edit_journal' })}
onClick={handleEditJournal(journal)} onClick={handleEditJournal(journal)}
@@ -230,20 +230,29 @@ function ManualJournalsDataTable({
}, },
[onSelectedRowsChange], [onSelectedRowsChange],
); );
const selectionColumn = useMemo(
() => ({
minWidth: 40,
width: 40,
maxWidth: 40,
}),
[],
);
return ( return (
<DataTable <DataTable
noInitialFetch={true}
columns={columns} columns={columns}
data={manualJournalsCurrentPage} data={manualJournalsCurrentPage}
onFetchData={handleDataTableFetchData} onFetchData={handleDataTableFetchData}
manualSortBy={true} manualSortBy={true}
selectionColumn={true} selectionColumn={selectionColumn}
noInitialFetch={true} expandable={true}
sticky={true} sticky={true}
loading={manualJournalsLoading && !isMounted}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
pagination={true} loading={manualJournalsLoading && !isMounted}
rowContextMenu={onRowContextMenu} rowContextMenu={onRowContextMenu}
pagination={true}
pagesCount={manualJournalsPagination.pagesCount} pagesCount={manualJournalsPagination.pagesCount}
initialPageSize={manualJournalsTableQuery.page_size} initialPageSize={manualJournalsTableQuery.page_size}
initialPageIndex={manualJournalsTableQuery.page - 1} initialPageIndex={manualJournalsTableQuery.page - 1}

View File

@@ -3,7 +3,11 @@ import { Route, Switch, useHistory, withRouter } from 'react-router-dom';
import { useQuery } from 'react-query'; import { useQuery } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core'; import { Alert, Intent } from '@blueprintjs/core';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import { FormattedMessage as T, useIntl } from 'react-intl'; import {
FormattedMessage as T,
useIntl,
FormattedHTMLMessage,
} from 'react-intl';
import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; import DashboardPageContent from 'components/Dashboard/DashboardPageContent';
import DashboardInsider from 'components/Dashboard/DashboardInsider'; import DashboardInsider from 'components/Dashboard/DashboardInsider';
@@ -114,6 +118,7 @@ function ManualJournalsTable({
const handleConfirmBulkDelete = useCallback(() => { const handleConfirmBulkDelete = useCallback(() => {
requestDeleteBulkManualJournals(bulkDelete) requestDeleteBulkManualJournals(bulkDelete)
.then(() => { .then(() => {
setBulkDelete(false);
AppToaster.show({ AppToaster.show({
message: formatMessage( message: formatMessage(
{ id: 'the_journals_has_been_successfully_deleted' }, { id: 'the_journals_has_been_successfully_deleted' },
@@ -121,7 +126,6 @@ function ManualJournalsTable({
), ),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
setBulkDelete(false);
}) })
.catch((error) => { .catch((error) => {
setBulkDelete(false); setBulkDelete(false);
@@ -181,6 +185,7 @@ function ManualJournalsTable({
message: formatMessage({ message: formatMessage({
id: 'the_manual_journal_id_has_been_published', id: 'the_manual_journal_id_has_been_published',
}), }),
intent: Intent.SUCCESS,
}); });
}); });
}, },
@@ -237,11 +242,7 @@ function ManualJournalsTable({
onConfirm={handleConfirmManualJournalDelete} onConfirm={handleConfirmManualJournalDelete}
> >
<p> <p>
<T <T id={'once_delete_this_journal_you_will_able_to_restore_it'} />
id={
'once_delete_this_journal_category_you_will_able_to_restore_it'
}
/>
</p> </p>
</Alert> </Alert>
@@ -258,7 +259,7 @@ function ManualJournalsTable({
> >
<p> <p>
<T <T
id={'once_delete_these_journalss_you_will_not_able_restore_them'} id={'once_delete_these_journals_you_will_not_able_restore_them'}
/> />
</p> </p>
</Alert> </Alert>

View File

@@ -1,11 +1,7 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
Alignment,
Navbar,
NavbarGroup,
} from '@blueprintjs/core';
import { useParams, withRouter } from 'react-router-dom'; import { useParams, withRouter } from 'react-router-dom';
import { pick, debounce } from 'lodash'; import { pick, debounce } from 'lodash';
@@ -38,13 +34,14 @@ function AccountsViewsTabs({
customViewChanged, customViewChanged,
onViewChanged, onViewChanged,
}) { }) {
const history = useHistory(); const history = useHistory();
const { custom_view_id: customViewId = null } = useParams(); const { custom_view_id: customViewId = null } = useParams();
useEffect(() => { useEffect(() => {
changeAccountsCurrentView(customViewId || -1); changeAccountsCurrentView(customViewId || -1);
setTopbarEditView(customViewId); setTopbarEditView(customViewId);
changePageSubtitle((customViewId && viewItem) ? viewItem.name : ''); changePageSubtitle(customViewId && viewItem ? viewItem.name : '');
addAccountsTableQueries({ addAccountsTableQueries({
custom_view_id: customViewId, custom_view_id: customViewId,

View File

@@ -12,6 +12,7 @@ import {
Tag, Tag,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { withRouter } from 'react-router';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
import moment from 'moment'; import moment from 'moment';
@@ -28,6 +29,7 @@ import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withViewDetails from 'containers/Views/withViewDetails'; import withViewDetails from 'containers/Views/withViewDetails';
import withExpenses from 'containers/Expenses/withExpenses'; import withExpenses from 'containers/Expenses/withExpenses';
import withExpensesActions from 'containers/Expenses/withExpensesActions'; import withExpensesActions from 'containers/Expenses/withExpensesActions';
import withCurrentView from 'containers/Views/withCurrentView';
function ExpensesDataTable({ function ExpensesDataTable({
//#withExpenes //#withExpenes
@@ -55,6 +57,10 @@ function ExpensesDataTable({
const [initialMount, setInitialMount] = useState(false); const [initialMount, setInitialMount] = useState(false);
const { formatMessage } = useIntl(); const { formatMessage } = useIntl();
useEffect(()=>{
setInitialMount(false)
},[customViewId])
useUpdateEffect(() => { useUpdateEffect(() => {
if (!expensesLoading) { if (!expensesLoading) {
setInitialMount(true); setInitialMount(true);
@@ -260,7 +266,7 @@ function ExpensesDataTable({
selectionColumn={true} selectionColumn={true}
noInitialFetch={true} noInitialFetch={true}
sticky={true} sticky={true}
loading={expensesLoading} loading={expensesLoading && !initialMount}
onSelectedRowsChange={handleSelectedRowsChange} onSelectedRowsChange={handleSelectedRowsChange}
rowContextMenu={onRowContextMenu} rowContextMenu={onRowContextMenu}
pagination={true} pagination={true}
@@ -274,6 +280,8 @@ function ExpensesDataTable({
} }
export default compose( export default compose(
withRouter,
withCurrentView,
withDialogActions, withDialogActions,
withDashboardActions, withDashboardActions,
withExpensesActions, withExpensesActions,

View File

@@ -29,6 +29,9 @@ import useMedia from 'hooks/useMedia';
import { compose, repeatValue } from 'utils'; import { compose, repeatValue } from 'utils';
const MIN_LINES_NUMBER = 4; const MIN_LINES_NUMBER = 4;
const ERROR = {
EXPENSE_ALREADY_PUBLISHED: 'EXPENSE.ALREADY.PUBLISHED',
};
function ExpenseForm({ function ExpenseForm({
// #withMedia // #withMedia
@@ -187,6 +190,21 @@ function ExpenseForm({
: []; : [];
}, [expense]); }, [expense]);
// Transform API errors in toasts messages.
const transformErrors = (errors, { setErrors }) => {
const hasError = (errorType) => errors.some((e) => e.type === errorType);
if (hasError(ERROR.EXPENSE_ALREADY_PUBLISHED)) {
setErrors(
AppToaster.show({
message: formatMessage({
id: 'the_expense_is_already_published',
}),
}),
);
}
};
const formik = useFormik({ const formik = useFormik({
enableReinitialize: true, enableReinitialize: true,
validationSchema, validationSchema,
@@ -237,16 +255,7 @@ function ExpenseForm({
resetForm(); resetForm();
}) })
.catch((errors) => { .catch((errors) => {
if (errors.find((e) => e.type === 'TOTAL.AMOUNT.EQUALS.ZERO')) { transformErrors(errors, { setErrors });
}
setErrors(
AppToaster.show({
message: formatMessage({
id: 'total_amount_equals_zero',
}),
intent: Intent.DANGER,
}),
);
setSubmitting(false); setSubmitting(false);
}); });
} else { } else {
@@ -265,6 +274,7 @@ function ExpenseForm({
clearSavedMediaIds(); clearSavedMediaIds();
}) })
.catch((errors) => { .catch((errors) => {
transformErrors(errors, { setErrors });
setSubmitting(false); setSubmitting(false);
}); });
} }
@@ -335,7 +345,6 @@ function ExpenseForm({
formik={formik} formik={formik}
defaultRow={defaultCategory} defaultRow={defaultCategory}
/> />
<div class="expense-form-footer"> <div class="expense-form-footer">
<FormGroup <FormGroup
label={<T id={'description'} />} label={<T id={'description'} />}

View File

@@ -1,15 +1,12 @@
import React, { useEffect, useRef } from 'react'; import React, { useEffect, useRef } from 'react';
import { useHistory } from 'react-router'; import { useHistory } from 'react-router';
import { import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
Alignment,
Navbar,
NavbarGroup,
} from '@blueprintjs/core';
import { useParams, withRouter } from 'react-router-dom'; import { useParams, withRouter } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { pick, debounce } from 'lodash'; import { pick, debounce } from 'lodash';
import { DashboardViewsTabs } from 'components'; import { DashboardViewsTabs } from 'components';
import { useUpdateEffect } from 'hooks';
import withExpenses from './withExpenses'; import withExpenses from './withExpenses';
import withExpensesActions from './withExpensesActions'; import withExpensesActions from './withExpensesActions';
@@ -32,9 +29,13 @@ function ExpenseViewTabs({
// #withDashboardActions // #withDashboardActions
setTopbarEditView, setTopbarEditView,
changePageSubtitle, changePageSubtitle,
// props
customViewChanged,
onViewChanged,
}) { }) {
const history = useHistory(); const history = useHistory();
const { custom_view_id: customViewId } = useParams(); const { custom_view_id: customViewId = null } = useParams();
useEffect(() => { useEffect(() => {
changeExpensesView(customViewId || -1); changeExpensesView(customViewId || -1);
@@ -51,6 +52,10 @@ function ExpenseViewTabs({
}; };
}, [customViewId, addExpensesTableQueries, changeExpensesView]); }, [customViewId, addExpensesTableQueries, changeExpensesView]);
useUpdateEffect(() => {
onViewChanged && onViewChanged(customViewId);
}, [customViewId]);
const debounceChangeHistory = useRef( const debounceChangeHistory = useRef(
debounce((toUrl) => { debounce((toUrl) => {
history.push(toUrl); history.push(toUrl);
@@ -59,7 +64,7 @@ function ExpenseViewTabs({
const handleTabsChange = (viewId) => { const handleTabsChange = (viewId) => {
const toPath = viewId ? `${viewId}/custom_view` : ''; const toPath = viewId ? `${viewId}/custom_view` : '';
debounceChangeHistory.current(`/expenses/${toPath}`); debounceChangeHistory.current(`/expenses-list/${toPath}`);
setTopbarEditView(viewId); setTopbarEditView(viewId);
}; };
@@ -78,7 +83,7 @@ function ExpenseViewTabs({
<NavbarGroup align={Alignment.LEFT}> <NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs <DashboardViewsTabs
initialViewId={customViewId} initialViewId={customViewId}
baseUrl={'/expenses'} baseUrl={'/expenses-list'}
tabs={tabs} tabs={tabs}
onNewViewTabClick={handleClickNewView} onNewViewTabClick={handleClickNewView}
onChange={handleTabsChange} onChange={handleTabsChange}

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState, useMemo, useCallback } from 'react'; import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { Route, Switch, useHistory, useParams } from 'react-router-dom'; import { Route, Switch, useHistory, useParams } from 'react-router-dom';
import { useQuery } from 'react-query'; import { useQuery, queryCache } from 'react-query';
import { Alert, Intent } from '@blueprintjs/core'; import { Alert, Intent } from '@blueprintjs/core';
import AppToaster from 'components/AppToaster'; import AppToaster from 'components/AppToaster';
import { FormattedMessage as T, useIntl } from 'react-intl'; import { FormattedMessage as T, useIntl } from 'react-intl';
@@ -45,6 +45,7 @@ function ExpensesList({
const [deleteExpense, setDeleteExpense] = useState(false); const [deleteExpense, setDeleteExpense] = useState(false);
const [selectedRows, setSelectedRows] = useState([]); const [selectedRows, setSelectedRows] = useState([]);
const [bulkDelete, setBulkDelete] = useState(false); const [bulkDelete, setBulkDelete] = useState(false);
const [publishExpense, setPublishExpense] = useState(false);
const fetchResourceViews = useQuery( const fetchResourceViews = useQuery(
['resource-views', 'expenses'], ['resource-views', 'expenses'],
@@ -155,18 +156,35 @@ function ExpensesList({
[addExpensesTableQueries], [addExpensesTableQueries],
); );
const handlePublishExpense = useCallback( // const fetchExpenses = useQuery(['expenses-table', expensesTableQuery], () =>
(expense) => { // requestFetchExpensesTable(),
requestPublishExpense(expense.id).then(() => { // );
const handlePublishExpense = useCallback((expense) => {
setPublishExpense(expense);
}, []);
const handleCancelPublishExpense = useCallback(() => {
setPublishExpense(false);
});
// Handle publish expense confirm.
const handleConfirmPublishExpense = useCallback(() => {
requestPublishExpense(publishExpense.id)
.then(() => {
setPublishExpense(false);
AppToaster.show({ AppToaster.show({
message: formatMessage({ id: 'the_expense_id_has_been_published' }), message: formatMessage({
id: 'the_expense_has_been_published',
}),
intent: Intent.SUCCESS, intent: Intent.SUCCESS,
}); });
queryCache.invalidateQueries('expenses-table');
})
.catch((error) => {
setPublishExpense(false);
}); });
fetchExpenses.refetch(); }, [publishExpense, requestPublishExpense, formatMessage]);
},
[requestPublishExpense, formatMessage],
);
// Handle selected rows change. // Handle selected rows change.
const handleSelectedRowsChange = useCallback( const handleSelectedRowsChange = useCallback(
@@ -190,11 +208,11 @@ function ExpensesList({
<DashboardPageContent> <DashboardPageContent>
<Switch> <Switch>
<Route <Route
// exact={true} exact={true}
// path={[ // path={[
// '/expenses/:custom_view_id/custom_view', // '/expenses/:custom_view_id/custom_view',
// '/expenses/new', // 'expenses',
// ]} // ]}
> >
<ExpenseViewTabs /> <ExpenseViewTabs />
@@ -240,14 +258,26 @@ function ExpensesList({
/> />
</p> </p>
</Alert> </Alert>
<Alert
cancelButtonText={<T id={'cancel'} />}
confirmButtonText={<T id={'publish'} />}
intent={Intent.WARNING}
isOpen={publishExpense}
onCancel={handleCancelPublishExpense}
onConfirm={handleConfirmPublishExpense}
>
<p>
<T id={'are_sure_to_publish_this_expense'} />
</p>
</Alert>
</DashboardPageContent> </DashboardPageContent>
</DashboardInsider> </DashboardInsider>
); );
} }
export default compose( export default compose(
withDashboardActions,
withExpensesActions, withExpensesActions,
withDashboardActions,
withViewsActions, withViewsActions,
withResourceActions, withResourceActions,
withExpenses(({ expensesTableQuery }) => ({ expensesTableQuery })), withExpenses(({ expensesTableQuery }) => ({ expensesTableQuery })),

View File

@@ -440,7 +440,7 @@ export default {
'The expenses #{number} have been successfully deleted', 'The expenses #{number} have been successfully deleted',
once_delete_these_expenses_you_will_not_able_restore_them: once_delete_these_expenses_you_will_not_able_restore_them:
"Once you delete these expenses, you won't be able to retrieve them later. Are you sure you want to delete them?", "Once you delete these expenses, you won't be able to retrieve them later. Are you sure you want to delete them?",
the_expense_id_has_been_published: 'The expense id has been published', the_expense_has_been_published: 'The expense has been published',
select_beneficiary_account: 'Select Beneficiary Account', select_beneficiary_account: 'Select Beneficiary Account',
total_amount_equals_zero: 'Total amount equals zero', total_amount_equals_zero: 'Total amount equals zero',
value: 'Value', value: 'Value',
@@ -449,7 +449,7 @@ export default {
as_date: 'As Date', as_date: 'As Date',
aging_before_days: 'Aging before days', aging_before_days: 'Aging before days',
aging_periods: 'Aging periods', aging_periods: 'Aging periods',
name_:'name', name_: 'name',
as: 'As', as: 'As',
receivable_aging_summary: 'Receivable Aging Summary', receivable_aging_summary: 'Receivable Aging Summary',
customers: 'Customers', customers: 'Customers',
@@ -536,5 +536,11 @@ export default {
should_total_of_credit_and_debit_be_equal: 'Should total of credit and debit be equal.', should_total_of_credit_and_debit_be_equal: 'Should total of credit and debit be equal.',
no_accounts: 'No Accounts', no_accounts: 'No Accounts',
the_accounts_have_been_successfully_inactivated: 'The accounts have been successfully inactivated.', the_accounts_have_been_successfully_inactivated: 'The accounts have been successfully inactivated.',
account_code_is_not_unique: 'Account code is not unqiue.' account_code_is_not_unique: 'Account code is not unqiue.',
are_sure_to_publish_this_expense:
'Are you sure you want to publish this expense?',
once_delete_these_journals_you_will_not_able_restore_them:
"Once you delete these journalss, you won't be able to retrieve them later. Are you sure you want to delete them?",
once_delete_this_journal_you_will_able_to_restore_it: `Once you delete this journal, you won\'t be able to restore it later. Are you sure you want to delete ?`,
the_expense_is_already_published: 'The expense is already published.',
}; };