mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
Merge branch 'develop' into disconnect-bank-account
This commit is contained in:
@@ -4,9 +4,9 @@ export const ACCOUNT_TYPE = {
|
||||
BANK: 'bank',
|
||||
ACCOUNTS_RECEIVABLE: 'accounts-receivable',
|
||||
INVENTORY: 'inventory',
|
||||
OTHER_CURRENT_ASSET: 'other-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||
OTHER_CURRENT_ASSET: 'other-current-asset',
|
||||
FIXED_ASSET: 'fixed-asset',
|
||||
NON_CURRENT_ASSET: 'non-ACCOUNT_PARENT_TYPE.CURRENT_ASSET',
|
||||
NON_CURRENT_ASSET: 'non-current-asset',
|
||||
|
||||
ACCOUNTS_PAYABLE: 'accounts-payable',
|
||||
CREDIT_CARD: 'credit-card',
|
||||
|
||||
@@ -7,7 +7,6 @@ import { DashboardPageContent } from '@/components';
|
||||
import { transformTableStateToQuery, compose } from '@/utils';
|
||||
|
||||
import { ManualJournalsListProvider } from './ManualJournalsListProvider';
|
||||
import ManualJournalsViewTabs from './ManualJournalsViewTabs';
|
||||
import ManualJournalsDataTable from './ManualJournalsDataTable';
|
||||
import ManualJournalsActionsBar from './ManualJournalActionsBar';
|
||||
import withManualJournals from './withManualJournals';
|
||||
@@ -29,7 +28,6 @@ function ManualJournalsTable({
|
||||
<ManualJournalsActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<ManualJournalsViewTabs />
|
||||
<ManualJournalsDataTable />
|
||||
</DashboardPageContent>
|
||||
</ManualJournalsListProvider>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import '@/style/pages/Accounts/List.scss';
|
||||
import { DashboardPageContent, DashboardContentTable } from '@/components';
|
||||
|
||||
import { DashboardPageContent, DashboardContentTable } from '@/components';
|
||||
import { AccountsChartProvider } from './AccountsChartProvider';
|
||||
import AccountsViewsTabs from './AccountsViewsTabs';
|
||||
import AccountsActionsBar from './AccountsActionsBar';
|
||||
import AccountsDataTable from './AccountsDataTable';
|
||||
|
||||
import withAccounts from '@/containers/Accounts/withAccounts';
|
||||
import withAccountsTableActions from './withAccountsTableActions';
|
||||
|
||||
import { transformAccountsStateToQuery } from './utils';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
@@ -41,8 +41,6 @@ function AccountsChart({
|
||||
<AccountsActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<AccountsViewsTabs />
|
||||
|
||||
<DashboardContentTable>
|
||||
<AccountsDataTable />
|
||||
</DashboardContentTable>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
MenuDivider,
|
||||
} from '@blueprintjs/core';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { isEmpty } from 'lodash';
|
||||
import {
|
||||
Icon,
|
||||
DashboardActionsBar,
|
||||
@@ -41,8 +42,10 @@ import { compose } from '@/utils';
|
||||
import {
|
||||
useDisconnectBankAccount,
|
||||
useUpdateBankAccount,
|
||||
useExcludeUncategorizedTransactions,
|
||||
useUnexcludeUncategorizedTransactions,
|
||||
} from '@/hooks/query/bank-rules';
|
||||
import { current } from '@reduxjs/toolkit';
|
||||
import { withBanking } from '../withBanking';
|
||||
|
||||
function AccountTransactionsActionsBar({
|
||||
// #withDialogActions
|
||||
@@ -53,6 +56,10 @@ function AccountTransactionsActionsBar({
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
|
||||
// #withBanking
|
||||
uncategorizedTransationsIdsSelected,
|
||||
excludedTransactionsIdsSelected,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { accountId, currentAccount } = useAccountTransactionsContext();
|
||||
@@ -136,6 +143,54 @@ function AccountTransactionsActionsBar({
|
||||
refresh();
|
||||
};
|
||||
|
||||
const {
|
||||
mutateAsync: excludeUncategorizedTransactions,
|
||||
isLoading: isExcludingLoading,
|
||||
} = useExcludeUncategorizedTransactions();
|
||||
|
||||
const {
|
||||
mutateAsync: unexcludeUncategorizedTransactions,
|
||||
isLoading: isUnexcludingLoading,
|
||||
} = useUnexcludeUncategorizedTransactions();
|
||||
|
||||
// Handles the exclude uncategorized transactions in bulk.
|
||||
const handleExcludeUncategorizedBtnClick = () => {
|
||||
excludeUncategorizedTransactions({
|
||||
ids: uncategorizedTransationsIdsSelected,
|
||||
})
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The selected transactions have been excluded.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Handles the unexclude categorized button click.
|
||||
const handleUnexcludeUncategorizedBtnClick = () => {
|
||||
unexcludeUncategorizedTransactions({
|
||||
ids: excludedTransactionsIdsSelected,
|
||||
})
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: 'The selected excluded transactions have been unexcluded.',
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
AppToaster.show({
|
||||
message: 'Something went wrong',
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
@@ -196,6 +251,27 @@ function AccountTransactionsActionsBar({
|
||||
/>
|
||||
</Tooltip>
|
||||
</If>
|
||||
|
||||
{!isEmpty(uncategorizedTransationsIdsSelected) && (
|
||||
<Button
|
||||
icon={<Icon icon="disable" iconSize={16} />}
|
||||
text={'Exclude'}
|
||||
onClick={handleExcludeUncategorizedBtnClick}
|
||||
className={Classes.MINIMAL}
|
||||
intent={Intent.DANGER}
|
||||
disabled={isExcludingLoading}
|
||||
/>
|
||||
)}
|
||||
{!isEmpty(excludedTransactionsIdsSelected) && (
|
||||
<Button
|
||||
icon={<Icon icon="disable" iconSize={16} />}
|
||||
text={'Unexclude'}
|
||||
onClick={handleUnexcludeUncategorizedBtnClick}
|
||||
className={Classes.MINIMAL}
|
||||
intent={Intent.DANGER}
|
||||
disabled={isUnexcludingLoading}
|
||||
/>
|
||||
)}
|
||||
</NavbarGroup>
|
||||
|
||||
<NavbarGroup align={Alignment.RIGHT}>
|
||||
@@ -239,4 +315,13 @@ export default compose(
|
||||
withSettings(({ cashflowTransactionsSettings }) => ({
|
||||
cashflowTansactionsTableSize: cashflowTransactionsSettings?.tableSize,
|
||||
})),
|
||||
withBanking(
|
||||
({
|
||||
uncategorizedTransationsIdsSelected,
|
||||
excludedTransactionsIdsSelected,
|
||||
}) => ({
|
||||
uncategorizedTransationsIdsSelected,
|
||||
excludedTransactionsIdsSelected,
|
||||
}),
|
||||
),
|
||||
)(AccountTransactionsActionsBar);
|
||||
|
||||
@@ -33,6 +33,7 @@ function AccountTransactionsDataTable({
|
||||
|
||||
// #withBankingActions
|
||||
setUncategorizedTransactionIdForMatching,
|
||||
setUncategorizedTransactionsSelected,
|
||||
}) {
|
||||
// Retrieve table columns.
|
||||
const columns = useAccountUncategorizedTransactionsColumns();
|
||||
@@ -73,12 +74,19 @@ function AccountTransactionsDataTable({
|
||||
});
|
||||
};
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = (selected) => {
|
||||
const _selectedIds = selected?.map((row) => row.original.id);
|
||||
setUncategorizedTransactionsSelected(_selectedIds);
|
||||
};
|
||||
|
||||
return (
|
||||
<CashflowTransactionsTable
|
||||
noInitialFetch={true}
|
||||
columns={columns}
|
||||
data={uncategorizedTransactions || []}
|
||||
sticky={true}
|
||||
selectionColumn={true}
|
||||
loading={isUncategorizedTransactionsLoading}
|
||||
headerLoading={isUncategorizedTransactionsLoading}
|
||||
expandColumnSpace={1}
|
||||
@@ -99,6 +107,7 @@ function AccountTransactionsDataTable({
|
||||
'There is no uncategorized transactions in the current account.'
|
||||
}
|
||||
className="table-constrant"
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
payload={{
|
||||
onExclude: handleExcludeTransaction,
|
||||
onCategorize: handleCategorizeBtnClick,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
DataTable,
|
||||
TableFastCell,
|
||||
@@ -19,11 +19,20 @@ import { useExcludedTransactionsBoot } from './ExcludedTransactionsTableBoot';
|
||||
|
||||
import { ActionsMenu } from './_components';
|
||||
import { useUnexcludeUncategorizedTransaction } from '@/hooks/query/bank-rules';
|
||||
import {
|
||||
WithBankingActionsProps,
|
||||
withBankingActions,
|
||||
} from '../../withBankingActions';
|
||||
|
||||
interface ExcludeTransactionsTableProps extends WithBankingActionsProps {}
|
||||
|
||||
/**
|
||||
* Renders the recognized account transactions datatable.
|
||||
*/
|
||||
export function ExcludedTransactionsTable() {
|
||||
function ExcludedTransactionsTableRoot({
|
||||
// #withBankingActions
|
||||
setExcludedTransactionsSelected,
|
||||
}: ExcludeTransactionsTableProps) {
|
||||
const { excludedBankTransactions } = useExcludedTransactionsBoot();
|
||||
const { mutateAsync: unexcludeBankTransaction } =
|
||||
useUnexcludeUncategorizedTransaction();
|
||||
@@ -55,6 +64,12 @@ export function ExcludedTransactionsTable() {
|
||||
});
|
||||
};
|
||||
|
||||
// Handle selected rows change.
|
||||
const handleSelectedRowsChange = (selected) => {
|
||||
const _selectedIds = selected?.map((row) => row.original.id);
|
||||
setExcludedTransactionsSelected(_selectedIds);
|
||||
};
|
||||
|
||||
return (
|
||||
<CashflowTransactionsTable
|
||||
noInitialFetch={true}
|
||||
@@ -80,6 +95,8 @@ export function ExcludedTransactionsTable() {
|
||||
onColumnResizing={handleColumnResizing}
|
||||
noResults={'There is no excluded bank transactions.'}
|
||||
className="table-constrant"
|
||||
selectionColumn={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
payload={{
|
||||
onRestore: handleRestoreClick,
|
||||
}}
|
||||
@@ -87,6 +104,10 @@ export function ExcludedTransactionsTable() {
|
||||
);
|
||||
}
|
||||
|
||||
export const ExcludedTransactionsTable = R.compose(withBankingActions)(
|
||||
ExcludedTransactionsTableRoot,
|
||||
);
|
||||
|
||||
const DashboardConstrantTable = styled(DataTable)`
|
||||
.table {
|
||||
.thead {
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
import { ExcludedTransactionsTable } from "../ExcludedTransactions/ExcludedTransactionsTable";
|
||||
import { ExcludedBankTransactionsTableBoot } from "../ExcludedTransactions/ExcludedTransactionsTableBoot";
|
||||
import { AccountTransactionsCard } from "./AccountTransactionsCard";
|
||||
// @ts-nocheck
|
||||
import { useEffect } from 'react';
|
||||
import * as R from 'ramda';
|
||||
import {
|
||||
WithBankingActionsProps,
|
||||
withBankingActions,
|
||||
} from '../../withBankingActions';
|
||||
import { ExcludedTransactionsTable } from '../ExcludedTransactions/ExcludedTransactionsTable';
|
||||
import { ExcludedBankTransactionsTableBoot } from '../ExcludedTransactions/ExcludedTransactionsTableBoot';
|
||||
import { AccountTransactionsCard } from './AccountTransactionsCard';
|
||||
|
||||
interface AccountExcludedTransactionsProps extends WithBankingActionsProps {}
|
||||
|
||||
function AccountExcludedTransactionsRoot({
|
||||
// #withBankingActions
|
||||
resetExcludedTransactionsSelected,
|
||||
}: AccountExcludedTransactionsProps) {
|
||||
useEffect(
|
||||
() => () => {
|
||||
resetExcludedTransactionsSelected();
|
||||
},
|
||||
[resetExcludedTransactionsSelected],
|
||||
);
|
||||
|
||||
export function AccountExcludedTransactions() {
|
||||
return (
|
||||
<ExcludedBankTransactionsTableBoot>
|
||||
<AccountTransactionsCard>
|
||||
@@ -11,3 +30,7 @@ export function AccountExcludedTransactions() {
|
||||
</ExcludedBankTransactionsTableBoot>
|
||||
);
|
||||
}
|
||||
|
||||
export const AccountExcludedTransactions = R.compose(withBankingActions)(
|
||||
AccountExcludedTransactionsRoot,
|
||||
);
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
import * as R from 'ramda';
|
||||
import { useEffect } from 'react';
|
||||
import AccountTransactionsUncategorizedTable from '../AccountTransactionsUncategorizedTable';
|
||||
import { AccountUncategorizedTransactionsBoot } from '../AllTransactionsUncategorizedBoot';
|
||||
import { AccountTransactionsCard } from './AccountTransactionsCard';
|
||||
import {
|
||||
WithBankingActionsProps,
|
||||
withBankingActions,
|
||||
} from '../../withBankingActions';
|
||||
|
||||
interface AccountUncategorizedTransactionsAllRootProps
|
||||
extends WithBankingActionsProps {}
|
||||
|
||||
function AccountUncategorizedTransactionsAllRoot({
|
||||
resetUncategorizedTransactionsSelected,
|
||||
}: AccountUncategorizedTransactionsAllRootProps) {
|
||||
useEffect(
|
||||
() => () => {
|
||||
resetUncategorizedTransactionsSelected();
|
||||
},
|
||||
[resetUncategorizedTransactionsSelected],
|
||||
);
|
||||
|
||||
export function AccountUncategorizedTransactionsAll() {
|
||||
return (
|
||||
<AccountUncategorizedTransactionsBoot>
|
||||
<AccountTransactionsCard>
|
||||
@@ -11,3 +29,7 @@ export function AccountUncategorizedTransactionsAll() {
|
||||
</AccountUncategorizedTransactionsBoot>
|
||||
);
|
||||
}
|
||||
|
||||
export const AccountUncategorizedTransactionsAll = R.compose(
|
||||
withBankingActions,
|
||||
)(AccountUncategorizedTransactionsAllRoot);
|
||||
|
||||
@@ -13,6 +13,11 @@ export const withBanking = (mapState) => {
|
||||
|
||||
reconcileMatchingTransactionPendingAmount:
|
||||
state.plaid.openReconcileMatchingTransaction.pending,
|
||||
|
||||
uncategorizedTransationsIdsSelected:
|
||||
state.plaid.uncategorizedTransactionsSelected,
|
||||
|
||||
excludedTransactionsIdsSelected: state.plaid.excludedTransactionsSelected,
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
|
||||
@@ -4,6 +4,10 @@ import {
|
||||
setUncategorizedTransactionIdForMatching,
|
||||
openReconcileMatchingTransaction,
|
||||
closeReconcileMatchingTransaction,
|
||||
setUncategorizedTransactionsSelected,
|
||||
resetUncategorizedTransactionsSelected,
|
||||
resetExcludedTransactionsSelected,
|
||||
setExcludedTransactionsSelected,
|
||||
} from '@/store/banking/banking.reducer';
|
||||
|
||||
export interface WithBankingActionsProps {
|
||||
@@ -13,6 +17,12 @@ export interface WithBankingActionsProps {
|
||||
) => void;
|
||||
openReconcileMatchingTransaction: (pendingAmount: number) => void;
|
||||
closeReconcileMatchingTransaction: () => void;
|
||||
|
||||
setUncategorizedTransactionsSelected: (ids: Array<string | number>) => void;
|
||||
resetUncategorizedTransactionsSelected: () => void;
|
||||
|
||||
setExcludedTransactionsSelected: (ids: Array<string | number>) => void;
|
||||
resetExcludedTransactionsSelected: () => void;
|
||||
}
|
||||
|
||||
const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
||||
@@ -28,6 +38,40 @@ const mapDipatchToProps = (dispatch: any): WithBankingActionsProps => ({
|
||||
dispatch(openReconcileMatchingTransaction({ pending: pendingAmount })),
|
||||
closeReconcileMatchingTransaction: () =>
|
||||
dispatch(closeReconcileMatchingTransaction()),
|
||||
|
||||
/**
|
||||
* Sets the selected uncategorized transactions.
|
||||
* @param {Array<string | number>} ids
|
||||
*/
|
||||
setUncategorizedTransactionsSelected: (ids: Array<string | number>) =>
|
||||
dispatch(
|
||||
setUncategorizedTransactionsSelected({
|
||||
transactionIds: ids,
|
||||
}),
|
||||
),
|
||||
|
||||
/**
|
||||
* Resets the selected uncategorized transactions.
|
||||
*/
|
||||
resetUncategorizedTransactionsSelected: () =>
|
||||
dispatch(resetUncategorizedTransactionsSelected()),
|
||||
|
||||
/**
|
||||
* Sets excluded selected transactions.
|
||||
* @param {Array<string | number>} ids
|
||||
*/
|
||||
setExcludedTransactionsSelected: (ids: Array<string | number>) =>
|
||||
dispatch(
|
||||
setExcludedTransactionsSelected({
|
||||
ids,
|
||||
}),
|
||||
),
|
||||
|
||||
/**
|
||||
* Resets the excluded selected transactions
|
||||
*/
|
||||
resetExcludedTransactionsSelected: () =>
|
||||
dispatch(resetExcludedTransactionsSelected()),
|
||||
});
|
||||
|
||||
export const withBankingActions = connect<
|
||||
|
||||
@@ -6,7 +6,6 @@ import '@/style/pages/Customers/List.scss';
|
||||
import { DashboardPageContent } from '@/components';
|
||||
|
||||
import CustomersActionsBar from './CustomersActionsBar';
|
||||
import CustomersViewsTabs from './CustomersViewsTabs';
|
||||
import CustomersTable from './CustomersTable';
|
||||
import { CustomersListProvider } from './CustomersListProvider';
|
||||
|
||||
@@ -42,7 +41,6 @@ function CustomersList({
|
||||
<CustomersActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<CustomersViewsTabs />
|
||||
<CustomersTable />
|
||||
</DashboardPageContent>
|
||||
</CustomersListProvider>
|
||||
|
||||
@@ -6,7 +6,6 @@ import '@/style/pages/Expense/List.scss';
|
||||
import { DashboardPageContent } from '@/components';
|
||||
|
||||
import ExpenseActionsBar from './ExpenseActionsBar';
|
||||
import ExpenseViewTabs from './ExpenseViewTabs';
|
||||
import ExpenseDataTable from './ExpenseDataTable';
|
||||
|
||||
import withExpenses from './withExpenses';
|
||||
@@ -42,7 +41,6 @@ function ExpensesList({
|
||||
<ExpenseActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<ExpenseViewTabs />
|
||||
<ExpenseDataTable />
|
||||
</DashboardPageContent>
|
||||
</ExpensesListProvider>
|
||||
|
||||
@@ -8,7 +8,6 @@ import { DashboardPageContent } from '@/components';
|
||||
import { ItemsListProvider } from './ItemsListProvider';
|
||||
|
||||
import ItemsActionsBar from './ItemsActionsBar';
|
||||
import ItemsViewsTabs from './ItemsViewsTabs';
|
||||
import ItemsDataTable from './ItemsDataTable';
|
||||
|
||||
import withItems from './withItems';
|
||||
@@ -41,7 +40,6 @@ function ItemsList({
|
||||
<ItemsActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<ItemsViewsTabs />
|
||||
<ItemsDataTable />
|
||||
</DashboardPageContent>
|
||||
</ItemsListProvider>
|
||||
|
||||
@@ -7,7 +7,6 @@ import '@/style/pages/Bills/List.scss';
|
||||
import { BillsListProvider } from './BillsListProvider';
|
||||
|
||||
import BillsActionsBar from './BillsActionsBar';
|
||||
import BillsViewsTabs from './BillsViewsTabs';
|
||||
import BillsTable from './BillsTable';
|
||||
|
||||
import withBills from './withBills';
|
||||
@@ -42,7 +41,6 @@ function BillsList({
|
||||
<BillsActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<BillsViewsTabs />
|
||||
<BillsTable />
|
||||
</DashboardPageContent>
|
||||
</BillsListProvider>
|
||||
|
||||
@@ -5,7 +5,6 @@ import '@/style/pages/VendorsCreditNote/List.scss';
|
||||
|
||||
import { DashboardPageContent } from '@/components';
|
||||
import VendorsCreditNoteActionsBar from './VendorsCreditNoteActionsBar';
|
||||
import VendorsCreditNoteViewTabs from './VendorsCreditNoteViewTabs';
|
||||
import VendorsCreditNoteDataTable from './VendorsCreditNoteDataTable';
|
||||
|
||||
import withVendorsCreditNotes from './withVendorsCreditNotes';
|
||||
@@ -37,7 +36,6 @@ function VendorsCreditNotesList({
|
||||
>
|
||||
<VendorsCreditNoteActionsBar />
|
||||
<DashboardPageContent>
|
||||
<VendorsCreditNoteViewTabs />
|
||||
<VendorsCreditNoteDataTable />
|
||||
</DashboardPageContent>
|
||||
</VendorsCreditNoteListProvider>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ExcessPaymentDialog } from './dialogs/PaymentMadeExcessDialog';
|
||||
|
||||
export function PaymentMadeDialogs() {
|
||||
return (
|
||||
<>
|
||||
<ExcessPaymentDialog dialogName={'payment-made-excessed-payment'} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
import { Formik, Form } from 'formik';
|
||||
import { Formik, Form, FormikHelpers } from 'formik';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { sumBy, defaultTo } from 'lodash';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
@@ -14,6 +14,7 @@ import PaymentMadeFloatingActions from './PaymentMadeFloatingActions';
|
||||
import PaymentMadeFooter from './PaymentMadeFooter';
|
||||
import PaymentMadeFormBody from './PaymentMadeFormBody';
|
||||
import PaymentMadeFormTopBar from './PaymentMadeFormTopBar';
|
||||
import { PaymentMadeDialogs } from './PaymentMadeDialogs';
|
||||
|
||||
import { PaymentMadeInnerProvider } from './PaymentMadeInnerProvider';
|
||||
import { usePaymentMadeFormContext } from './PaymentMadeFormProvider';
|
||||
@@ -21,6 +22,7 @@ import { compose, orderingLinesIndexes } from '@/utils';
|
||||
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import {
|
||||
EditPaymentMadeFormSchema,
|
||||
@@ -31,6 +33,7 @@ import {
|
||||
transformToEditForm,
|
||||
transformErrors,
|
||||
transformFormToRequest,
|
||||
getPaymentExcessAmountFromValues,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
@@ -42,6 +45,9 @@ function PaymentMadeForm({
|
||||
|
||||
// #withCurrentOrganization
|
||||
organization: { base_currency },
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -54,6 +60,7 @@ function PaymentMadeForm({
|
||||
submitPayload,
|
||||
createPaymentMadeMutate,
|
||||
editPaymentMadeMutate,
|
||||
isExcessConfirmed,
|
||||
} = usePaymentMadeFormContext();
|
||||
|
||||
// Form initial values.
|
||||
@@ -76,13 +83,11 @@ function PaymentMadeForm({
|
||||
// Handle the form submit.
|
||||
const handleSubmitForm = (
|
||||
values,
|
||||
{ setSubmitting, resetForm, setFieldError },
|
||||
{ setSubmitting, resetForm, setFieldError }: FormikHelpers<any>,
|
||||
) => {
|
||||
setSubmitting(true);
|
||||
// Total payment amount of entries.
|
||||
const totalPaymentAmount = sumBy(values.entries, 'payment_amount');
|
||||
|
||||
if (totalPaymentAmount <= 0) {
|
||||
if (values.amount <= 0) {
|
||||
AppToaster.show({
|
||||
message: intl.get('you_cannot_make_payment_with_zero_total_amount'),
|
||||
intent: Intent.DANGER,
|
||||
@@ -90,6 +95,16 @@ function PaymentMadeForm({
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
const excessAmount = getPaymentExcessAmountFromValues(values);
|
||||
|
||||
// Show the confirmation popup if the excess amount bigger than zero and
|
||||
// has not been confirmed yet.
|
||||
if (excessAmount > 0 && !isExcessConfirmed) {
|
||||
openDialog('payment-made-excessed-payment');
|
||||
setSubmitting(false);
|
||||
|
||||
return;
|
||||
}
|
||||
// Transformes the form values to request body.
|
||||
const form = transformFormToRequest(values);
|
||||
|
||||
@@ -119,11 +134,12 @@ function PaymentMadeForm({
|
||||
}
|
||||
setSubmitting(false);
|
||||
};
|
||||
|
||||
if (!isNewMode) {
|
||||
editPaymentMadeMutate([paymentMadeId, form]).then(onSaved).catch(onError);
|
||||
return editPaymentMadeMutate([paymentMadeId, form])
|
||||
.then(onSaved)
|
||||
.catch(onError);
|
||||
} else {
|
||||
createPaymentMadeMutate(form).then(onSaved).catch(onError);
|
||||
return createPaymentMadeMutate(form).then(onSaved).catch(onError);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -149,6 +165,7 @@ function PaymentMadeForm({
|
||||
<PaymentMadeFormBody />
|
||||
<PaymentMadeFooter />
|
||||
<PaymentMadeFloatingActions />
|
||||
<PaymentMadeDialogs />
|
||||
</PaymentMadeInnerProvider>
|
||||
</Form>
|
||||
</Formik>
|
||||
@@ -163,4 +180,5 @@ export default compose(
|
||||
preferredPaymentAccount: parseInt(billPaymentSettings?.withdrawalAccount),
|
||||
})),
|
||||
withCurrentOrganization(),
|
||||
withDialogActions,
|
||||
)(PaymentMadeForm);
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useFormikContext } from 'formik';
|
||||
import {
|
||||
T,
|
||||
TotalLines,
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
FormatNumber,
|
||||
} from '@/components';
|
||||
import { usePaymentMadeTotals } from './utils';
|
||||
import { usePaymentMadeExcessAmount, usePaymentMadeTotals } from './utils';
|
||||
|
||||
export function PaymentMadeFormFooterRight() {
|
||||
const { formattedSubtotal, formattedTotal } = usePaymentMadeTotals();
|
||||
const excessAmount = usePaymentMadeExcessAmount();
|
||||
const {
|
||||
values: { currency_code: currencyCode },
|
||||
} = useFormikContext();
|
||||
|
||||
return (
|
||||
<PaymentMadeTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
|
||||
@@ -25,6 +31,11 @@ export function PaymentMadeFormFooterRight() {
|
||||
value={formattedTotal}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
<TotalLine
|
||||
title={'Excess Amount'}
|
||||
value={<FormatNumber value={excessAmount} currency={currencyCode} />}
|
||||
textStyle={TotalLineTextStyle.Regular}
|
||||
/>
|
||||
</PaymentMadeTotalLines>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// @ts-nocheck
|
||||
import React, { useMemo } from 'react';
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { sumBy } from 'lodash';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import { Money, FormattedMessage as T } from '@/components';
|
||||
|
||||
import PaymentMadeFormHeaderFields from './PaymentMadeFormHeaderFields';
|
||||
import { usePaymentmadeTotalAmount } from './utils';
|
||||
|
||||
/**
|
||||
* Payment made header form.
|
||||
@@ -14,11 +14,10 @@ import PaymentMadeFormHeaderFields from './PaymentMadeFormHeaderFields';
|
||||
function PaymentMadeFormHeader() {
|
||||
// Formik form context.
|
||||
const {
|
||||
values: { entries, currency_code },
|
||||
values: { currency_code },
|
||||
} = useFormikContext();
|
||||
|
||||
// Calculate the payment amount of the entries.
|
||||
const amountPaid = useMemo(() => sumBy(entries, 'payment_amount'), [entries]);
|
||||
const totalAmount = usePaymentmadeTotalAmount();
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
|
||||
@@ -30,8 +29,9 @@ function PaymentMadeFormHeader() {
|
||||
<span class="big-amount__label">
|
||||
<T id={'amount_received'} />
|
||||
</span>
|
||||
|
||||
<h1 class="big-amount__number">
|
||||
<Money amount={amountPaid} currency={currency_code} />
|
||||
<Money amount={totalAmount} currency={currency_code} />
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import classNames from 'classnames';
|
||||
import { isEmpty, toSafeInteger } from 'lodash';
|
||||
import {
|
||||
FormGroup,
|
||||
InputGroup,
|
||||
@@ -13,7 +14,6 @@ import {
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { FastField, Field, useFormikContext, ErrorMessage } from 'formik';
|
||||
import { FormattedMessage as T, VendorsSelect } from '@/components';
|
||||
import { toSafeInteger } from 'lodash';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
|
||||
import {
|
||||
@@ -68,7 +68,7 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
|
||||
const fullAmount = safeSumBy(newEntries, 'payment_amount');
|
||||
|
||||
setFieldValue('entries', newEntries);
|
||||
setFieldValue('full_amount', fullAmount);
|
||||
setFieldValue('amount', fullAmount);
|
||||
};
|
||||
|
||||
// Handles the full-amount field blur.
|
||||
@@ -115,10 +115,10 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
|
||||
</FastField>
|
||||
|
||||
{/* ------------ Full amount ------------ */}
|
||||
<Field name={'full_amount'}>
|
||||
<Field name={'amount'}>
|
||||
{({
|
||||
form: {
|
||||
values: { currency_code },
|
||||
values: { currency_code, entries },
|
||||
},
|
||||
field: { value },
|
||||
meta: { error, touched },
|
||||
@@ -129,28 +129,30 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
|
||||
className={('form-group--full-amount', Classes.FILL)}
|
||||
intent={inputIntent({ error, touched })}
|
||||
labelInfo={<Hint />}
|
||||
helperText={<ErrorMessage name="full_amount" />}
|
||||
helperText={<ErrorMessage name="amount" />}
|
||||
>
|
||||
<ControlGroup>
|
||||
<InputPrependText text={currency_code} />
|
||||
<MoneyInputGroup
|
||||
value={value}
|
||||
onChange={(value) => {
|
||||
setFieldValue('full_amount', value);
|
||||
setFieldValue('amount', value);
|
||||
}}
|
||||
onBlurValue={onFullAmountBlur}
|
||||
/>
|
||||
</ControlGroup>
|
||||
|
||||
<Button
|
||||
onClick={handleReceiveFullAmountClick}
|
||||
className={'receive-full-amount'}
|
||||
small={true}
|
||||
minimal={true}
|
||||
>
|
||||
<T id={'receive_full_amount'} /> (
|
||||
<Money amount={payableFullAmount} currency={currency_code} />)
|
||||
</Button>
|
||||
{!isEmpty(entries) && (
|
||||
<Button
|
||||
onClick={handleReceiveFullAmountClick}
|
||||
className={'receive-full-amount'}
|
||||
small={true}
|
||||
minimal={true}
|
||||
>
|
||||
<T id={'receive_full_amount'} /> (
|
||||
<Money amount={payableFullAmount} currency={currency_code} />)
|
||||
</Button>
|
||||
)}
|
||||
</FormGroup>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { Features } from '@/constants';
|
||||
import { useFeatureCan } from '@/hooks/state';
|
||||
import {
|
||||
@@ -71,6 +71,8 @@ function PaymentMadeFormProvider({ query, paymentMadeId, ...props }) {
|
||||
|
||||
const isFeatureLoading = isBranchesLoading;
|
||||
|
||||
const [isExcessConfirmed, setIsExcessConfirmed] = useState<boolean>(false);
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
paymentMadeId,
|
||||
@@ -98,6 +100,9 @@ function PaymentMadeFormProvider({ query, paymentMadeId, ...props }) {
|
||||
|
||||
setSubmitPayload,
|
||||
setPaymentVendorId,
|
||||
|
||||
isExcessConfirmed,
|
||||
setIsExcessConfirmed,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Dialog, DialogSuspense } from '@/components';
|
||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
const ExcessPaymentDialogContent = React.lazy(() =>
|
||||
import('./PaymentMadeExcessDialogContent').then((module) => ({
|
||||
default: module.ExcessPaymentDialogContent,
|
||||
})),
|
||||
);
|
||||
|
||||
/**
|
||||
* Exess payment dialog of the payment made form.
|
||||
*/
|
||||
function ExcessPaymentDialogRoot({ dialogName, isOpen }) {
|
||||
return (
|
||||
<Dialog
|
||||
name={dialogName}
|
||||
title={'Excess Payment'}
|
||||
isOpen={isOpen}
|
||||
canEscapeJeyClose={true}
|
||||
autoFocus={true}
|
||||
style={{ width: 500 }}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<ExcessPaymentDialogContent dialogName={dialogName} />
|
||||
</DialogSuspense>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExcessPaymentDialog = compose(withDialogRedux())(
|
||||
ExcessPaymentDialogRoot,
|
||||
);
|
||||
|
||||
ExcessPaymentDialog.displayName = 'ExcessPaymentDialog';
|
||||
@@ -0,0 +1,93 @@
|
||||
// @ts-nocheck
|
||||
import * as R from 'ramda';
|
||||
import React from 'react';
|
||||
import { Button, Classes, Intent } from '@blueprintjs/core';
|
||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||
import { FormatNumber } from '@/components';
|
||||
import { usePaymentMadeFormContext } from '../../PaymentMadeFormProvider';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { usePaymentMadeExcessAmount } from '../../utils';
|
||||
|
||||
interface ExcessPaymentValues {}
|
||||
function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
||||
const {
|
||||
submitForm,
|
||||
values: { currency_code: currencyCode },
|
||||
} = useFormikContext();
|
||||
const { setIsExcessConfirmed } = usePaymentMadeFormContext();
|
||||
|
||||
// Handles the form submitting.
|
||||
const handleSubmit = (
|
||||
values: ExcessPaymentValues,
|
||||
{ setSubmitting }: FormikHelpers<ExcessPaymentValues>,
|
||||
) => {
|
||||
setSubmitting(true);
|
||||
setIsExcessConfirmed(true);
|
||||
|
||||
return submitForm().then(() => {
|
||||
setSubmitting(false);
|
||||
closeDialog(dialogName);
|
||||
});
|
||||
};
|
||||
// Handle close button click.
|
||||
const handleCloseBtn = () => {
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
const excessAmount = usePaymentMadeExcessAmount();
|
||||
|
||||
return (
|
||||
<Formik initialValues={{}} onSubmit={handleSubmit}>
|
||||
<Form>
|
||||
<ExcessPaymentDialogContentForm
|
||||
excessAmount={
|
||||
<FormatNumber value={excessAmount} currency={currencyCode} />
|
||||
}
|
||||
onClose={handleCloseBtn}
|
||||
/>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExcessPaymentDialogContent = R.compose(withDialogActions)(
|
||||
ExcessPaymentDialogContentRoot,
|
||||
);
|
||||
|
||||
interface ExcessPaymentDialogContentFormProps {
|
||||
excessAmount: string | number | React.ReactNode;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function ExcessPaymentDialogContentForm({
|
||||
excessAmount,
|
||||
onClose,
|
||||
}: ExcessPaymentDialogContentFormProps) {
|
||||
const { submitForm, isSubmitting } = useFormikContext();
|
||||
|
||||
const handleCloseBtn = () => {
|
||||
onClose && onClose();
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<p style={{ marginBottom: 20 }}>
|
||||
Would you like to record the excess amount of{' '}
|
||||
<strong>{excessAmount}</strong> as credit payment from the vendor.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
loading={isSubmitting}
|
||||
onClick={() => submitForm()}
|
||||
>
|
||||
Save Payment as Credit
|
||||
</Button>
|
||||
<Button onClick={handleCloseBtn}>Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './PaymentMadeExcessDialog';
|
||||
@@ -37,7 +37,7 @@ export const defaultPaymentMadeEntry = {
|
||||
|
||||
// Default initial values of payment made.
|
||||
export const defaultPaymentMade = {
|
||||
full_amount: '',
|
||||
amount: '',
|
||||
vendor_id: '',
|
||||
payment_account_id: '',
|
||||
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
@@ -53,10 +53,10 @@ export const defaultPaymentMade = {
|
||||
|
||||
export const transformToEditForm = (paymentMade, paymentMadeEntries) => {
|
||||
const attachments = transformAttachmentsToForm(paymentMade);
|
||||
const appliedAmount = safeSumBy(paymentMadeEntries, 'payment_amount');
|
||||
|
||||
return {
|
||||
...transformToForm(paymentMade, defaultPaymentMade),
|
||||
full_amount: safeSumBy(paymentMadeEntries, 'payment_amount'),
|
||||
entries: [
|
||||
...paymentMadeEntries.map((paymentMadeEntry) => ({
|
||||
...transformToForm(paymentMadeEntry, defaultPaymentMadeEntry),
|
||||
@@ -177,6 +177,30 @@ export const usePaymentMadeTotals = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const usePaymentmadeTotalAmount = () => {
|
||||
const {
|
||||
values: { amount },
|
||||
} = useFormikContext();
|
||||
|
||||
return amount;
|
||||
};
|
||||
|
||||
export const usePaymentMadeAppliedAmount = () => {
|
||||
const {
|
||||
values: { entries },
|
||||
} = useFormikContext();
|
||||
|
||||
// Retrieves the invoice entries total.
|
||||
return React.useMemo(() => sumBy(entries, 'payment_amount'), [entries]);
|
||||
};
|
||||
|
||||
export const usePaymentMadeExcessAmount = () => {
|
||||
const appliedAmount = usePaymentMadeAppliedAmount();
|
||||
const totalAmount = usePaymentmadeTotalAmount();
|
||||
|
||||
return Math.abs(totalAmount - appliedAmount);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the bill has foreign customer.
|
||||
* @returns {boolean}
|
||||
@@ -191,3 +215,10 @@ export const usePaymentMadeIsForeignCustomer = () => {
|
||||
);
|
||||
return isForeignCustomer;
|
||||
};
|
||||
|
||||
export const getPaymentExcessAmountFromValues = (values) => {
|
||||
const appliedAmount = sumBy(values.entries, 'payment_amount');
|
||||
const totalAmount = values.amount;
|
||||
|
||||
return Math.abs(totalAmount - appliedAmount);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@ import { DashboardPageContent } from '@/components';
|
||||
import { PaymentMadesListProvider } from './PaymentMadesListProvider';
|
||||
import PaymentMadeActionsBar from './PaymentMadeActionsBar';
|
||||
import PaymentMadesTable from './PaymentMadesTable';
|
||||
import PaymentMadeViewTabs from './PaymentMadeViewTabs';
|
||||
|
||||
import withPaymentMades from './withPaymentMade';
|
||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||
@@ -41,7 +40,6 @@ function PaymentMadeList({
|
||||
<PaymentMadeActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<PaymentMadeViewTabs />
|
||||
<PaymentMadesTable />
|
||||
</DashboardPageContent>
|
||||
</PaymentMadesListProvider>
|
||||
|
||||
@@ -5,7 +5,6 @@ import '@/style/pages/CreditNote/List.scss';
|
||||
|
||||
import { DashboardPageContent } from '@/components';
|
||||
import CreditNotesActionsBar from './CreditNotesActionsBar';
|
||||
import CreditNotesViewTabs from './CreditNotesViewTabs';
|
||||
import CreditNotesDataTable from './CreditNotesDataTable';
|
||||
|
||||
import withCreditNotes from './withCreditNotes';
|
||||
@@ -36,8 +35,8 @@ function CreditNotesList({
|
||||
tableStateChanged={creditNoteTableStateChanged}
|
||||
>
|
||||
<CreditNotesActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<CreditNotesViewTabs />
|
||||
<CreditNotesDataTable />
|
||||
</DashboardPageContent>
|
||||
</CreditNotesListProvider>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { DashboardContentTable, DashboardPageContent } from '@/components';
|
||||
import { DashboardPageContent } from '@/components';
|
||||
|
||||
import '@/style/pages/SaleEstimate/List.scss';
|
||||
|
||||
import EstimatesActionsBar from './EstimatesActionsBar';
|
||||
import EstimatesViewTabs from './EstimatesViewTabs';
|
||||
import EstimatesDataTable from './EstimatesDataTable';
|
||||
|
||||
import withEstimates from './withEstimates';
|
||||
@@ -41,7 +40,6 @@ function EstimatesList({
|
||||
<EstimatesActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<EstimatesViewTabs />
|
||||
<EstimatesDataTable />
|
||||
</DashboardPageContent>
|
||||
</EstimatesListProvider>
|
||||
|
||||
@@ -6,7 +6,6 @@ import '@/style/pages/SaleInvoice/List.scss';
|
||||
import { DashboardPageContent } from '@/components';
|
||||
import { InvoicesListProvider } from './InvoicesListProvider';
|
||||
|
||||
import InvoiceViewTabs from './InvoiceViewTabs';
|
||||
import InvoicesDataTable from './InvoicesDataTable';
|
||||
import InvoicesActionsBar from './InvoicesActionsBar';
|
||||
|
||||
@@ -43,7 +42,6 @@ function InvoicesList({
|
||||
<InvoicesActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<InvoiceViewTabs />
|
||||
<InvoicesDataTable />
|
||||
</DashboardPageContent>
|
||||
</InvoicesListProvider>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import { sumBy, isEmpty, defaultTo } from 'lodash';
|
||||
import intl from 'react-intl-universal';
|
||||
import classNames from 'classnames';
|
||||
@@ -21,6 +21,7 @@ import { PaymentReceiveInnerProvider } from './PaymentReceiveInnerProvider';
|
||||
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import {
|
||||
EditPaymentReceiveFormSchema,
|
||||
@@ -36,6 +37,7 @@ import {
|
||||
transformFormToRequest,
|
||||
transformErrors,
|
||||
resetFormState,
|
||||
getExceededAmountFromValues,
|
||||
} from './utils';
|
||||
import { PaymentReceiveSyncIncrementSettingsToForm } from './components';
|
||||
|
||||
@@ -51,6 +53,9 @@ function PaymentReceiveForm({
|
||||
|
||||
// #withCurrentOrganization
|
||||
organization: { base_currency },
|
||||
|
||||
// #withDialogActions
|
||||
openDialog,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
@@ -63,6 +68,7 @@ function PaymentReceiveForm({
|
||||
submitPayload,
|
||||
editPaymentReceiveMutate,
|
||||
createPaymentReceiveMutate,
|
||||
isExcessConfirmed,
|
||||
} = usePaymentReceiveFormContext();
|
||||
|
||||
// Payment receive number.
|
||||
@@ -94,18 +100,16 @@ function PaymentReceiveForm({
|
||||
preferredDepositAccount,
|
||||
],
|
||||
);
|
||||
|
||||
// Handle form submit.
|
||||
const handleSubmitForm = (
|
||||
values,
|
||||
{ setSubmitting, resetForm, setFieldError },
|
||||
) => {
|
||||
setSubmitting(true);
|
||||
const exceededAmount = getExceededAmountFromValues(values);
|
||||
|
||||
// Calculates the total payment amount of entries.
|
||||
const totalPaymentAmount = sumBy(values.entries, 'payment_amount');
|
||||
|
||||
if (totalPaymentAmount <= 0) {
|
||||
// Validates the amount should be bigger than zero.
|
||||
if (values.amount <= 0) {
|
||||
AppToaster.show({
|
||||
message: intl.get('you_cannot_make_payment_with_zero_total_amount'),
|
||||
intent: Intent.DANGER,
|
||||
@@ -113,6 +117,13 @@ function PaymentReceiveForm({
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
// Show the confirm popup if the excessed amount bigger than zero and
|
||||
// excess confirmation has not been confirmed yet.
|
||||
if (exceededAmount > 0 && !isExcessConfirmed) {
|
||||
setSubmitting(false);
|
||||
openDialog('payment-received-excessed-payment');
|
||||
return;
|
||||
}
|
||||
// Transformes the form values to request body.
|
||||
const form = transformFormToRequest(values);
|
||||
|
||||
@@ -148,11 +159,11 @@ function PaymentReceiveForm({
|
||||
};
|
||||
|
||||
if (paymentReceiveId) {
|
||||
editPaymentReceiveMutate([paymentReceiveId, form])
|
||||
return editPaymentReceiveMutate([paymentReceiveId, form])
|
||||
.then(onSaved)
|
||||
.catch(onError);
|
||||
} else {
|
||||
createPaymentReceiveMutate(form).then(onSaved).catch(onError);
|
||||
return createPaymentReceiveMutate(form).then(onSaved).catch(onError);
|
||||
}
|
||||
};
|
||||
return (
|
||||
@@ -202,4 +213,5 @@ export default compose(
|
||||
preferredDepositAccount: paymentReceiveSettings?.preferredDepositAccount,
|
||||
})),
|
||||
withCurrentOrganization(),
|
||||
withDialogActions,
|
||||
)(PaymentReceiveForm);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import React from 'react';
|
||||
import { useFormikContext } from 'formik';
|
||||
import PaymentReceiveNumberDialog from '@/containers/Dialogs/PaymentReceiveNumberDialog';
|
||||
import { ExcessPaymentDialog } from './dialogs/ExcessPaymentDialog';
|
||||
|
||||
/**
|
||||
* Payment receive form dialogs.
|
||||
@@ -21,9 +22,12 @@ export default function PaymentReceiveFormDialogs() {
|
||||
};
|
||||
|
||||
return (
|
||||
<PaymentReceiveNumberDialog
|
||||
dialogName={'payment-receive-number-form'}
|
||||
onConfirm={handleUpdatePaymentNumber}
|
||||
/>
|
||||
<>
|
||||
<PaymentReceiveNumberDialog
|
||||
dialogName={'payment-receive-number-form'}
|
||||
onConfirm={handleUpdatePaymentNumber}
|
||||
/>
|
||||
<ExcessPaymentDialog dialogName={'payment-received-excessed-payment'} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,11 +7,16 @@ import {
|
||||
TotalLine,
|
||||
TotalLineBorderStyle,
|
||||
TotalLineTextStyle,
|
||||
FormatNumber,
|
||||
} from '@/components';
|
||||
import { usePaymentReceiveTotals } from './utils';
|
||||
import {
|
||||
usePaymentReceiveTotals,
|
||||
usePaymentReceivedTotalExceededAmount,
|
||||
} from './utils';
|
||||
|
||||
export function PaymentReceiveFormFootetRight() {
|
||||
const { formattedSubtotal, formattedTotal } = usePaymentReceiveTotals();
|
||||
const exceededAmount = usePaymentReceivedTotalExceededAmount();
|
||||
|
||||
return (
|
||||
<PaymentReceiveTotalLines labelColWidth={'180px'} amountColWidth={'180px'}>
|
||||
@@ -25,6 +30,11 @@ export function PaymentReceiveFormFootetRight() {
|
||||
value={formattedTotal}
|
||||
textStyle={TotalLineTextStyle.Bold}
|
||||
/>
|
||||
<TotalLine
|
||||
title={'Exceeded Amount'}
|
||||
value={<FormatNumber value={exceededAmount} />}
|
||||
textStyle={TotalLineTextStyle.Regular}
|
||||
/>
|
||||
</PaymentReceiveTotalLines>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,15 +30,9 @@ function PaymentReceiveFormHeader() {
|
||||
function PaymentReceiveFormBigTotal() {
|
||||
// Formik form context.
|
||||
const {
|
||||
values: { currency_code, entries },
|
||||
values: { currency_code, amount },
|
||||
} = useFormikContext();
|
||||
|
||||
// Calculates the total payment amount from due amount.
|
||||
const paymentFullAmount = useMemo(
|
||||
() => sumBy(entries, 'payment_amount'),
|
||||
[entries],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classNames(CLASSES.PAGE_FORM_HEADER_BIG_NUMBERS)}>
|
||||
<div class="big-amount">
|
||||
@@ -46,7 +40,7 @@ function PaymentReceiveFormBigTotal() {
|
||||
<T id={'amount_received'} />
|
||||
</span>
|
||||
<h1 class="big-amount__number">
|
||||
<Money amount={paymentFullAmount} currency={currency_code} />
|
||||
<Money amount={amount} currency={currency_code} />
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
import React, { createContext, useContext } from 'react';
|
||||
import React, { createContext, useContext, useState } from 'react';
|
||||
import { Features } from '@/constants';
|
||||
import { useFeatureCan } from '@/hooks/state';
|
||||
import { DashboardInsider } from '@/components';
|
||||
@@ -74,6 +74,8 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
|
||||
const { mutateAsync: editPaymentReceiveMutate } = useEditPaymentReceive();
|
||||
const { mutateAsync: createPaymentReceiveMutate } = useCreatePaymentReceive();
|
||||
|
||||
const [isExcessConfirmed, setIsExcessConfirmed] = useState<boolean>(false);
|
||||
|
||||
// Provider payload.
|
||||
const provider = {
|
||||
paymentReceiveId,
|
||||
@@ -97,6 +99,9 @@ function PaymentReceiveFormProvider({ query, paymentReceiveId, ...props }) {
|
||||
|
||||
editPaymentReceiveMutate,
|
||||
createPaymentReceiveMutate,
|
||||
|
||||
isExcessConfirmed,
|
||||
setIsExcessConfirmed,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
Button,
|
||||
} from '@blueprintjs/core';
|
||||
import { DateInput } from '@blueprintjs/datetime';
|
||||
import { toSafeInteger } from 'lodash';
|
||||
import { isEmpty, toSafeInteger } from 'lodash';
|
||||
import { FastField, Field, useFormikContext, ErrorMessage } from 'formik';
|
||||
|
||||
import {
|
||||
@@ -124,11 +124,11 @@ export default function PaymentReceiveHeaderFields() {
|
||||
</FastField>
|
||||
|
||||
{/* ------------ Full amount ------------ */}
|
||||
<Field name={'full_amount'}>
|
||||
<Field name={'amount'}>
|
||||
{({
|
||||
form: {
|
||||
setFieldValue,
|
||||
values: { currency_code },
|
||||
values: { currency_code, entries },
|
||||
},
|
||||
field: { value, onChange },
|
||||
meta: { error, touched },
|
||||
@@ -146,21 +146,23 @@ export default function PaymentReceiveHeaderFields() {
|
||||
<MoneyInputGroup
|
||||
value={value}
|
||||
onChange={(value) => {
|
||||
setFieldValue('full_amount', value);
|
||||
setFieldValue('amount', value);
|
||||
}}
|
||||
onBlurValue={onFullAmountBlur}
|
||||
/>
|
||||
</ControlGroup>
|
||||
|
||||
<Button
|
||||
onClick={handleReceiveFullAmountClick}
|
||||
className={'receive-full-amount'}
|
||||
small={true}
|
||||
minimal={true}
|
||||
>
|
||||
<T id={'receive_full_amount'} /> (
|
||||
<Money amount={totalDueAmount} currency={currency_code} />)
|
||||
</Button>
|
||||
{!isEmpty(entries) && (
|
||||
<Button
|
||||
onClick={handleReceiveFullAmountClick}
|
||||
className={'receive-full-amount'}
|
||||
small={true}
|
||||
minimal={true}
|
||||
>
|
||||
<T id={'receive_full_amount'} /> (
|
||||
<Money amount={totalDueAmount} currency={currency_code} />)
|
||||
</Button>
|
||||
)}
|
||||
</FormGroup>
|
||||
)}
|
||||
</Field>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Dialog, DialogSuspense } from '@/components';
|
||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
||||
import { compose } from '@/utils';
|
||||
|
||||
const ExcessPaymentDialogContent = React.lazy(() =>
|
||||
import('./ExcessPaymentDialogContent').then((module) => ({
|
||||
default: module.ExcessPaymentDialogContent,
|
||||
})),
|
||||
);
|
||||
|
||||
/**
|
||||
* Excess payment dialog of the payment received form.
|
||||
*/
|
||||
function ExcessPaymentDialogRoot({ dialogName, isOpen }) {
|
||||
return (
|
||||
<Dialog
|
||||
name={dialogName}
|
||||
title={'Excess Payment'}
|
||||
isOpen={isOpen}
|
||||
canEscapeJeyClose={true}
|
||||
autoFocus={true}
|
||||
style={{ width: 500 }}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<ExcessPaymentDialogContent dialogName={dialogName} />
|
||||
</DialogSuspense>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExcessPaymentDialog = compose(withDialogRedux())(
|
||||
ExcessPaymentDialogRoot,
|
||||
);
|
||||
|
||||
ExcessPaymentDialog.displayName = 'ExcessPaymentDialog';
|
||||
@@ -0,0 +1,86 @@
|
||||
// @ts-nocheck
|
||||
import * as Yup from 'yup';
|
||||
import * as R from 'ramda';
|
||||
import { Button, Classes, Intent } from '@blueprintjs/core';
|
||||
import { Form, Formik, FormikHelpers, useFormikContext } from 'formik';
|
||||
import { FormatNumber } from '@/components';
|
||||
import { usePaymentReceiveFormContext } from '../../PaymentReceiveFormProvider';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
import { usePaymentReceivedTotalExceededAmount } from '../../utils';
|
||||
|
||||
interface ExcessPaymentValues {}
|
||||
|
||||
export function ExcessPaymentDialogContentRoot({ dialogName, closeDialog }) {
|
||||
const {
|
||||
submitForm,
|
||||
values: { currency_code: currencyCode },
|
||||
} = useFormikContext();
|
||||
const { setIsExcessConfirmed } = usePaymentReceiveFormContext();
|
||||
const exceededAmount = usePaymentReceivedTotalExceededAmount();
|
||||
|
||||
const handleSubmit = (
|
||||
values: ExcessPaymentValues,
|
||||
{ setSubmitting }: FormikHelpers<ExcessPaymentValues>,
|
||||
) => {
|
||||
setSubmitting(true);
|
||||
setIsExcessConfirmed(true);
|
||||
|
||||
submitForm().then(() => {
|
||||
closeDialog(dialogName);
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
const handleClose = () => {
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
|
||||
return (
|
||||
<Formik initialValues={{}} onSubmit={handleSubmit}>
|
||||
<Form>
|
||||
<ExcessPaymentDialogContentForm
|
||||
exceededAmount={
|
||||
<FormatNumber value={exceededAmount} currency={currencyCode} />
|
||||
}
|
||||
onClose={handleClose}
|
||||
/>
|
||||
</Form>
|
||||
</Formik>
|
||||
);
|
||||
}
|
||||
|
||||
export const ExcessPaymentDialogContent = R.compose(withDialogActions)(
|
||||
ExcessPaymentDialogContentRoot,
|
||||
);
|
||||
|
||||
function ExcessPaymentDialogContentForm({ onClose, exceededAmount }) {
|
||||
const { submitForm, isSubmitting } = useFormikContext();
|
||||
|
||||
const handleCloseBtn = () => {
|
||||
onClose && onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
<p style={{ marginBottom: 20 }}>
|
||||
Would you like to record the excess amount of{' '}
|
||||
<strong>{exceededAmount}</strong> as credit payment from the customer.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={Classes.DIALOG_FOOTER}>
|
||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
loading={isSubmitting}
|
||||
disabled={isSubmitting}
|
||||
onClick={() => submitForm()}
|
||||
>
|
||||
Save Payment as Credit
|
||||
</Button>
|
||||
<Button onClick={handleCloseBtn}>Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ExcessPaymentDialog';
|
||||
@@ -42,12 +42,12 @@ export const defaultPaymentReceive = {
|
||||
// Holds the payment number that entered manually only.
|
||||
payment_receive_no_manually: '',
|
||||
statement: '',
|
||||
full_amount: '',
|
||||
amount: '',
|
||||
currency_code: '',
|
||||
branch_id: '',
|
||||
exchange_rate: 1,
|
||||
entries: [],
|
||||
attachments: []
|
||||
attachments: [],
|
||||
};
|
||||
|
||||
export const defaultRequestPaymentEntry = {
|
||||
@@ -249,6 +249,30 @@ export const usePaymentReceiveTotals = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const usePaymentReceivedTotalAppliedAmount = () => {
|
||||
const {
|
||||
values: { entries },
|
||||
} = useFormikContext();
|
||||
|
||||
// Retrieves the invoice entries total.
|
||||
return React.useMemo(() => sumBy(entries, 'payment_amount'), [entries]);
|
||||
};
|
||||
|
||||
export const usePaymentReceivedTotalAmount = () => {
|
||||
const {
|
||||
values: { amount },
|
||||
} = useFormikContext();
|
||||
|
||||
return amount;
|
||||
};
|
||||
|
||||
export const usePaymentReceivedTotalExceededAmount = () => {
|
||||
const totalAmount = usePaymentReceivedTotalAmount();
|
||||
const totalApplied = usePaymentReceivedTotalAppliedAmount();
|
||||
|
||||
return Math.abs(totalAmount - totalApplied);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detarmines whether the payment has foreign customer.
|
||||
* @returns {boolean}
|
||||
@@ -273,3 +297,10 @@ export const resetFormState = ({ initialValues, values, resetForm }) => {
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getExceededAmountFromValues = (values) => {
|
||||
const totalApplied = sumBy(values.entries, 'payment_amount');
|
||||
const totalAmount = values.amount;
|
||||
|
||||
return totalAmount - totalApplied;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import '@/style/pages/PaymentReceive/List.scss';
|
||||
|
||||
import { DashboardPageContent } from '@/components';
|
||||
import { PaymentReceivesListProvider } from './PaymentReceiptsListProvider';
|
||||
import PaymentReceiveViewTabs from './PaymentReceiveViewTabs';
|
||||
import PaymentReceivesTable from './PaymentReceivesTable';
|
||||
import PaymentReceiveActionsBar from './PaymentReceiveActionsBar';
|
||||
|
||||
@@ -41,7 +40,6 @@ function PaymentReceiveList({
|
||||
<PaymentReceiveActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<PaymentReceiveViewTabs />
|
||||
<PaymentReceivesTable />
|
||||
</DashboardPageContent>
|
||||
</PaymentReceivesListProvider>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { DashboardPageContent } from '@/components';
|
||||
|
||||
import { VendorsListProvider } from './VendorsListProvider';
|
||||
import VendorActionsBar from './VendorActionsBar';
|
||||
import VendorViewsTabs from './VendorViewsTabs';
|
||||
import VendorsTable from './VendorsTable';
|
||||
|
||||
import withVendors from './withVendors';
|
||||
@@ -42,7 +41,6 @@ function VendorsList({
|
||||
<VendorActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<VendorViewsTabs />
|
||||
<VendorsTable />
|
||||
</DashboardPageContent>
|
||||
</VendorsListProvider>
|
||||
|
||||
@@ -3,7 +3,6 @@ import React from 'react';
|
||||
|
||||
import { DashboardPageContent } from '@/components';
|
||||
import WarehouseTransfersActionsBar from './WarehouseTransfersActionsBar';
|
||||
import WarehouseTransfersViewTabs from './WarehouseTransfersViewTabs';
|
||||
import WarehouseTransfersDataTable from './WarehouseTransfersDataTable';
|
||||
import withWarehouseTransfers from './withWarehouseTransfers';
|
||||
import withWarehouseTransfersActions from './withWarehouseTransfersActions';
|
||||
@@ -33,8 +32,8 @@ function WarehouseTransfersList({
|
||||
tableStateChanged={warehouseTransferTableStateChanged}
|
||||
>
|
||||
<WarehouseTransfersActionsBar />
|
||||
|
||||
<DashboardPageContent>
|
||||
<WarehouseTransfersViewTabs />
|
||||
<WarehouseTransfersDataTable />
|
||||
</DashboardPageContent>
|
||||
</WarehouseTransfersListProvider>
|
||||
|
||||
@@ -265,6 +265,20 @@ export function useGetBankTransactionsMatches(
|
||||
);
|
||||
}
|
||||
|
||||
const onValidateExcludeUncategorizedTransaction = (queryClient) => {
|
||||
// Invalidate queries.
|
||||
queryClient.invalidateQueries(QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY);
|
||||
queryClient.invalidateQueries(
|
||||
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
// Invalidate accounts.
|
||||
queryClient.invalidateQueries(t.ACCOUNTS);
|
||||
queryClient.invalidateQueries(t.ACCOUNT);
|
||||
|
||||
// invalidate bank account summary.
|
||||
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
};
|
||||
|
||||
type ExcludeUncategorizedTransactionValue = number;
|
||||
|
||||
interface ExcludeUncategorizedTransactionRes {}
|
||||
@@ -298,19 +312,7 @@ export function useExcludeUncategorizedTransaction(
|
||||
),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate queries.
|
||||
queryClient.invalidateQueries(
|
||||
QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
// Invalidate accounts.
|
||||
queryClient.invalidateQueries(t.ACCOUNTS);
|
||||
queryClient.invalidateQueries(t.ACCOUNT);
|
||||
|
||||
// invalidate bank account summary.
|
||||
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
||||
},
|
||||
...options,
|
||||
},
|
||||
@@ -351,19 +353,83 @@ export function useUnexcludeUncategorizedTransaction(
|
||||
),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate queries.
|
||||
queryClient.invalidateQueries(
|
||||
QUERY_KEY.EXCLUDED_BANK_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
queryClient.invalidateQueries(
|
||||
t.CASHFLOW_ACCOUNT_UNCATEGORIZED_TRANSACTIONS_INFINITY,
|
||||
);
|
||||
// Invalidate accounts.
|
||||
queryClient.invalidateQueries(t.ACCOUNTS);
|
||||
queryClient.invalidateQueries(t.ACCOUNT);
|
||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
||||
},
|
||||
...options,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Invalidate bank account summary.
|
||||
queryClient.invalidateQueries(QUERY_KEY.BANK_ACCOUNT_SUMMARY_META);
|
||||
type ExcludeBankTransactionsValue = { ids: Array<number | string> };
|
||||
interface ExcludeBankTransactionsResponse {}
|
||||
|
||||
/**
|
||||
* Excludes the uncategorized bank transactions in bulk.
|
||||
* @param {UseMutationResult<ExcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>} options
|
||||
* @returns {UseMutationResult<ExcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>}
|
||||
*/
|
||||
export function useExcludeUncategorizedTransactions(
|
||||
options?: UseMutationOptions<
|
||||
ExcludeBankTransactionsResponse,
|
||||
Error,
|
||||
ExcludeBankTransactionsValue
|
||||
>,
|
||||
): UseMutationResult<
|
||||
ExcludeBankTransactionsResponse,
|
||||
Error,
|
||||
ExcludeBankTransactionsValue
|
||||
> {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation<
|
||||
ExcludeBankTransactionsResponse,
|
||||
Error,
|
||||
ExcludeBankTransactionsValue
|
||||
>(
|
||||
(value: { ids: Array<number | string> }) =>
|
||||
apiRequest.put(`/cashflow/transactions/exclude`, { ids: value.ids }),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
||||
},
|
||||
...options,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
type UnexcludeBankTransactionsValue = { ids: Array<number | string> };
|
||||
interface UnexcludeBankTransactionsResponse {}
|
||||
|
||||
/**
|
||||
* Excludes the uncategorized bank transactions in bulk.
|
||||
* @param {UseMutationResult<UnexcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>} options
|
||||
* @returns {UseMutationResult<UnexcludeBankTransactionsResponse, Error, ExcludeBankTransactionValue>}
|
||||
*/
|
||||
export function useUnexcludeUncategorizedTransactions(
|
||||
options?: UseMutationOptions<
|
||||
UnexcludeBankTransactionsResponse,
|
||||
Error,
|
||||
UnexcludeBankTransactionsValue
|
||||
>,
|
||||
): UseMutationResult<
|
||||
UnexcludeBankTransactionsResponse,
|
||||
Error,
|
||||
UnexcludeBankTransactionsValue
|
||||
> {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation<
|
||||
UnexcludeBankTransactionsResponse,
|
||||
Error,
|
||||
UnexcludeBankTransactionsValue
|
||||
>(
|
||||
(value: { ids: Array<number | string> }) =>
|
||||
apiRequest.put(`/cashflow/transactions/unexclude`, { ids: value.ids }),
|
||||
{
|
||||
onSuccess: (res, id) => {
|
||||
onValidateExcludeUncategorizedTransaction(queryClient);
|
||||
},
|
||||
...options,
|
||||
},
|
||||
|
||||
@@ -5,6 +5,9 @@ interface StorePlaidState {
|
||||
openMatchingTransactionAside: boolean;
|
||||
uncategorizedTransactionIdForMatching: number | null;
|
||||
openReconcileMatchingTransaction: { isOpen: boolean; pending: number };
|
||||
|
||||
uncategorizedTransactionsSelected: Array<number | string>;
|
||||
excludedTransactionsSelected: Array<number | string>;
|
||||
}
|
||||
|
||||
export const PlaidSlice = createSlice({
|
||||
@@ -17,6 +20,8 @@ export const PlaidSlice = createSlice({
|
||||
isOpen: false,
|
||||
pending: 0,
|
||||
},
|
||||
uncategorizedTransactionsSelected: [],
|
||||
excludedTransactionsSelected: [],
|
||||
} as StorePlaidState,
|
||||
reducers: {
|
||||
setPlaidId: (state: StorePlaidState, action: PayloadAction<string>) => {
|
||||
@@ -52,6 +57,46 @@ export const PlaidSlice = createSlice({
|
||||
state.openReconcileMatchingTransaction.isOpen = false;
|
||||
state.openReconcileMatchingTransaction.pending = 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the selected uncategorized transactions.
|
||||
* @param {StorePlaidState} state
|
||||
* @param {PayloadAction<{ transactionIds: Array<string | number> }>} action
|
||||
*/
|
||||
setUncategorizedTransactionsSelected: (
|
||||
state: StorePlaidState,
|
||||
action: PayloadAction<{ transactionIds: Array<string | number> }>,
|
||||
) => {
|
||||
state.uncategorizedTransactionsSelected = action.payload.transactionIds;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the selected uncategorized transactions.
|
||||
* @param {StorePlaidState} state
|
||||
*/
|
||||
resetUncategorizedTransactionsSelected: (state: StorePlaidState) => {
|
||||
state.uncategorizedTransactionsSelected = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets excluded selected transactions.
|
||||
* @param {StorePlaidState} state
|
||||
* @param {PayloadAction<{ ids: Array<string | number> }>} action
|
||||
*/
|
||||
setExcludedTransactionsSelected: (
|
||||
state: StorePlaidState,
|
||||
action: PayloadAction<{ ids: Array<string | number> }>,
|
||||
) => {
|
||||
state.excludedTransactionsSelected = action.payload.ids;
|
||||
},
|
||||
|
||||
/**
|
||||
* Resets the excluded selected transactions
|
||||
* @param {StorePlaidState} state
|
||||
*/
|
||||
resetExcludedTransactionsSelected: (state: StorePlaidState) => {
|
||||
state.excludedTransactionsSelected = [];
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -62,6 +107,10 @@ export const {
|
||||
closeMatchingTransactionAside,
|
||||
openReconcileMatchingTransaction,
|
||||
closeReconcileMatchingTransaction,
|
||||
setUncategorizedTransactionsSelected,
|
||||
resetUncategorizedTransactionsSelected,
|
||||
setExcludedTransactionsSelected,
|
||||
resetExcludedTransactionsSelected,
|
||||
} = PlaidSlice.actions;
|
||||
|
||||
export const getPlaidToken = (state: any) => state.plaid.plaidToken;
|
||||
|
||||
@@ -124,22 +124,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-control.bp4-checkbox .bp4-control-indicator {
|
||||
.bp4-control.bp4-checkbox .bp4-control-indicator {
|
||||
cursor: auto;
|
||||
|
||||
&,
|
||||
&:hover {
|
||||
&::before {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.bp4-control.bp4-checkbox {
|
||||
|
||||
input:checked~.bp4-control-indicator,
|
||||
input:indeterminate~.bp4-control-indicator {
|
||||
border-color: #0052ff;
|
||||
}
|
||||
.bp4-control.bp4-checkbox input:not(:checked):not(:indeterminate) ~ .bp4-control-indicator{
|
||||
box-shadow: inset 0 0 0 1px #C5CBD3;
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
|
||||
@@ -208,12 +208,16 @@ $dashboard-views-bar-height: 44px;
|
||||
}
|
||||
|
||||
&.#{$ns}-minimal.#{$ns}-intent-danger {
|
||||
color: #c23030;
|
||||
color: rgb(194, 48, 48);
|
||||
|
||||
&:not(.bp4-disabled)
|
||||
&:hover,
|
||||
&:focus {
|
||||
background: rgba(219, 55, 55, 0.1);
|
||||
}
|
||||
&.bp4-disabled{
|
||||
color: rgb(194, 48, 48, 0.6);
|
||||
}
|
||||
}
|
||||
&.#{$ns}-minimal.#{$ns}-intent-success{
|
||||
color: #1c6e42;
|
||||
|
||||
Reference in New Issue
Block a user