mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
refactoring: WIP payment receive and made form.
This commit is contained in:
@@ -15,7 +15,6 @@ import { useFormikContext } from 'formik';
|
|||||||
import { usePaymentMadeFormContext } from './PaymentMadeFormProvider';
|
import { usePaymentMadeFormContext } from './PaymentMadeFormProvider';
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
|
|
||||||
import { Icon } from 'components';
|
import { Icon } from 'components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { CLASSES } from 'common/classes';
|
|||||||
/**
|
/**
|
||||||
* Payment made form footer.
|
* Payment made form footer.
|
||||||
*/
|
*/
|
||||||
export default function PaymentMadeFooter({ getFieldProps }) {
|
export default function PaymentMadeFooter() {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
<div className={classNames(CLASSES.PAGE_FORM_FOOTER)}>
|
||||||
<Row>
|
<Row>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { AppToaster } from 'components';
|
|||||||
import PaymentMadeHeader from './PaymentMadeFormHeader';
|
import PaymentMadeHeader from './PaymentMadeFormHeader';
|
||||||
import PaymentMadeFloatingActions from './PaymentMadeFloatingActions';
|
import PaymentMadeFloatingActions from './PaymentMadeFloatingActions';
|
||||||
import PaymentMadeFooter from './PaymentMadeFooter';
|
import PaymentMadeFooter from './PaymentMadeFooter';
|
||||||
|
import PaymentMadeItemsTable from './PaymentMadeItemsTable';
|
||||||
|
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
import {
|
import {
|
||||||
@@ -135,7 +136,7 @@ function PaymentMadeForm() {
|
|||||||
<PaymentMadeHeader />
|
<PaymentMadeHeader />
|
||||||
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||||
|
<PaymentMadeItemsTable />
|
||||||
</div>
|
</div>
|
||||||
<PaymentMadeFooter />
|
<PaymentMadeFooter />
|
||||||
<PaymentMadeFloatingActions />
|
<PaymentMadeFloatingActions />
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ import {
|
|||||||
* Payment made form header fields.
|
* Payment made form header fields.
|
||||||
*/
|
*/
|
||||||
function PaymentMadeFormHeaderFields({ baseCurrency }) {
|
function PaymentMadeFormHeaderFields({ baseCurrency }) {
|
||||||
const { vendors, accounts, isNewMode } = usePaymentMadeFormContext();
|
const {
|
||||||
|
vendors,
|
||||||
|
accounts,
|
||||||
|
isNewMode,
|
||||||
|
setPaymentVendorId,
|
||||||
|
} = usePaymentMadeFormContext();
|
||||||
|
|
||||||
const payableFullAmount = 0;
|
const payableFullAmount = 0;
|
||||||
|
|
||||||
@@ -57,6 +62,7 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) {
|
|||||||
defaultSelectText={<T id={'select_vender_account'} />}
|
defaultSelectText={<T id={'select_vender_account'} />}
|
||||||
onContactSelected={(contact) => {
|
onContactSelected={(contact) => {
|
||||||
form.setFieldValue('vendor_id', contact.id);
|
form.setFieldValue('vendor_id', contact.id);
|
||||||
|
setPaymentVendorId(contact.id);
|
||||||
}}
|
}}
|
||||||
disabled={!isNewMode}
|
disabled={!isNewMode}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
usePaymentMade,
|
usePaymentMade,
|
||||||
useSettings,
|
useSettings,
|
||||||
useCreatePaymentMade,
|
useCreatePaymentMade,
|
||||||
useEditPaymentMade
|
useEditPaymentMade,
|
||||||
|
useDueBills,
|
||||||
} from 'hooks/query';
|
} from 'hooks/query';
|
||||||
import { DashboardInsider } from 'components';
|
import { DashboardInsider } from 'components';
|
||||||
|
|
||||||
@@ -17,6 +18,9 @@ const PaymentMadeFormContext = createContext();
|
|||||||
* Payment made form provider.
|
* Payment made form provider.
|
||||||
*/
|
*/
|
||||||
function PaymentMadeFormProvider({ paymentMadeId, ...props }) {
|
function PaymentMadeFormProvider({ paymentMadeId, ...props }) {
|
||||||
|
const [submitPayload, setSubmitPayload] = React.useState({});
|
||||||
|
const [paymentVendorId, setPaymentVendorId] = React.useState(null);
|
||||||
|
|
||||||
// Handle fetch accounts data.
|
// Handle fetch accounts data.
|
||||||
const { data: accounts, isFetching: isAccountsFetching } = useAccounts();
|
const { data: accounts, isFetching: isAccountsFetching } = useAccounts();
|
||||||
|
|
||||||
@@ -42,6 +46,13 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) {
|
|||||||
enabled: !!paymentMadeId,
|
enabled: !!paymentMadeId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Retrieve the due bills of the given vendor.
|
||||||
|
const {
|
||||||
|
data: dueBills,
|
||||||
|
isLoading: isDueBillsLoading,
|
||||||
|
isFetching: isDueBillsFetching,
|
||||||
|
} = useDueBills(paymentVendorId, { enabled: !!paymentVendorId });
|
||||||
|
|
||||||
// Fetch payment made settings.
|
// Fetch payment made settings.
|
||||||
useSettings();
|
useSettings();
|
||||||
|
|
||||||
@@ -49,6 +60,8 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) {
|
|||||||
const { mutateAsync: createPaymentMadeMutate } = useCreatePaymentMade();
|
const { mutateAsync: createPaymentMadeMutate } = useCreatePaymentMade();
|
||||||
const { mutateAsync: editPaymentMadeMutate } = useEditPaymentMade();
|
const { mutateAsync: editPaymentMadeMutate } = useEditPaymentMade();
|
||||||
|
|
||||||
|
const isNewMode = !paymentMadeId;
|
||||||
|
|
||||||
// Provider payload.
|
// Provider payload.
|
||||||
const provider = {
|
const provider = {
|
||||||
paymentMadeId,
|
paymentMadeId,
|
||||||
@@ -58,16 +71,25 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) {
|
|||||||
paymentBills,
|
paymentBills,
|
||||||
vendors,
|
vendors,
|
||||||
items,
|
items,
|
||||||
|
dueBills,
|
||||||
|
submitPayload,
|
||||||
|
paymentVendorId,
|
||||||
|
|
||||||
|
isNewMode,
|
||||||
isAccountsFetching,
|
isAccountsFetching,
|
||||||
isItemsFetching,
|
isItemsFetching,
|
||||||
isItemsLoading,
|
isItemsLoading,
|
||||||
isVendorsFetching,
|
isVendorsFetching,
|
||||||
isPaymentFetching,
|
isPaymentFetching,
|
||||||
isPaymentLoading,
|
isPaymentLoading,
|
||||||
|
isDueBillsLoading,
|
||||||
|
isDueBillsFetching,
|
||||||
|
|
||||||
createPaymentMadeMutate,
|
createPaymentMadeMutate,
|
||||||
editPaymentMadeMutate,
|
editPaymentMadeMutate,
|
||||||
|
|
||||||
|
setSubmitPayload,
|
||||||
|
setPaymentVendorId,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,153 +1,70 @@
|
|||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import { CloudLoadingIndicator } from 'components';
|
import { CloudLoadingIndicator } from 'components';
|
||||||
import PaymentMadeItemsTableEditor from './PaymentMadeItemsTableEditor';
|
import { Button } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
import { CLASSES } from 'common/classes';
|
||||||
import withBillActions from '../Bill/withBillActions';
|
import { DataTableEditable } from 'components';
|
||||||
import withBills from '../Bill/withBills';
|
import { usePaymentMadeEntriesTableColumns } from './components';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { usePaymentMadeFormContext } from './PaymentMadeFormProvider';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Payment made items table.
|
* Payment made items table.
|
||||||
*/
|
*/
|
||||||
function PaymentMadeItemsTable({
|
export default function PaymentMadeItemsTable() {
|
||||||
// #ownProps
|
const {
|
||||||
paymentMadeId,
|
paymentVendorId,
|
||||||
vendorId,
|
dueBills,
|
||||||
fullAmount,
|
isDueBillsFetching,
|
||||||
onUpdateData,
|
isNewMode,
|
||||||
paymentEntries = [], // { bill_id: number, payment_amount: number, id?: number }
|
} = usePaymentMadeFormContext();
|
||||||
onClickClearAllLines,
|
|
||||||
errors,
|
|
||||||
onFetchEntriesSuccess,
|
|
||||||
|
|
||||||
// #withBillActions
|
const columns = usePaymentMadeEntriesTableColumns();
|
||||||
requestFetchDueBills,
|
|
||||||
|
|
||||||
// #withBills
|
|
||||||
vendorPayableBillsEntries,
|
|
||||||
}) {
|
|
||||||
const isNewMode = !paymentMadeId;
|
|
||||||
|
|
||||||
// Detarmines takes vendor payable bills entries in create mode
|
// Detarmines takes vendor payable bills entries in create mode
|
||||||
// or payment made entries in edit mode.
|
// or payment made entries in edit mode.
|
||||||
const computedTableEntries = useMemo(
|
const computedTableEntries = useMemo(() => [], []);
|
||||||
() =>
|
|
||||||
!isEmpty(paymentEntries)
|
|
||||||
? paymentEntries
|
|
||||||
: (vendorPayableBillsEntries || []),
|
|
||||||
[vendorPayableBillsEntries, paymentEntries],
|
|
||||||
);
|
|
||||||
const [tableData, setTableData] = useState(computedTableEntries);
|
|
||||||
const [localEntries, setLocalEntries] = useState(computedTableEntries);
|
|
||||||
|
|
||||||
const [localAmount, setLocalAmount] = useState(fullAmount);
|
|
||||||
|
|
||||||
// Triggers `onUpdateData` event that passes changed entries.
|
// Triggers `onUpdateData` event that passes changed entries.
|
||||||
const triggerUpdateData = useCallback(
|
const triggerUpdateData = useCallback((entries) => {}, []);
|
||||||
(entries) => {
|
|
||||||
onUpdateData && onUpdateData(entries);
|
|
||||||
},
|
|
||||||
[onUpdateData],
|
|
||||||
);
|
|
||||||
|
|
||||||
const triggerOnFetchBillsSuccess = useCallback(
|
const triggerOnFetchBillsSuccess = useCallback((bills) => {}, []);
|
||||||
(bills) => {
|
|
||||||
onFetchEntriesSuccess && onFetchEntriesSuccess(bills);
|
|
||||||
},
|
|
||||||
[onFetchEntriesSuccess],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (computedTableEntries !== localEntries) {
|
|
||||||
setTableData(computedTableEntries);
|
|
||||||
setLocalEntries(computedTableEntries);
|
|
||||||
}
|
|
||||||
}, [computedTableEntries, localEntries]);
|
|
||||||
|
|
||||||
// Handle mapping `fullAmount` prop to `localAmount` state.
|
|
||||||
useEffect(() => {
|
|
||||||
if (localAmount !== fullAmount) {
|
|
||||||
let _fullAmount = fullAmount;
|
|
||||||
const newTableData = tableData.map((data) => {
|
|
||||||
const amount = Math.min(data.due_amount, _fullAmount);
|
|
||||||
_fullAmount -= Math.max(amount, 0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...data,
|
|
||||||
payment_amount: amount,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setTableData(newTableData);
|
|
||||||
setLocalAmount(fullAmount);
|
|
||||||
triggerUpdateData(newTableData);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
tableData,
|
|
||||||
setTableData,
|
|
||||||
setLocalAmount,
|
|
||||||
triggerUpdateData,
|
|
||||||
localAmount,
|
|
||||||
fullAmount,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Fetches vendor due bills.
|
|
||||||
const fetchVendorDueBills = useQuery(
|
|
||||||
['vendor-due-bills', vendorId],
|
|
||||||
(key, _vendorId) => requestFetchDueBills(_vendorId),
|
|
||||||
{ enabled: isNewMode && vendorId },
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const enabled = isNewMode && vendorId;
|
|
||||||
|
|
||||||
if (!fetchVendorDueBills.isFetching && enabled) {
|
|
||||||
triggerOnFetchBillsSuccess(computedTableEntries);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
vendorId,
|
|
||||||
isNewMode,
|
|
||||||
fetchVendorDueBills.isFetching,
|
|
||||||
computedTableEntries,
|
|
||||||
triggerOnFetchBillsSuccess,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Handle update data.
|
// Handle update data.
|
||||||
const handleUpdateData = useCallback(
|
const handleUpdateData = useCallback((rows) => {}, []);
|
||||||
(rows) => {
|
|
||||||
setTableData(rows);
|
|
||||||
triggerUpdateData(rows);
|
|
||||||
},
|
|
||||||
[setTableData, triggerUpdateData],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Detarmines the right no results message before selecting vendor and aftering
|
// Detarmines the right no results message before selecting vendor and aftering
|
||||||
// selecting vendor id.
|
// selecting vendor id.
|
||||||
const noResultsMessage = vendorId
|
const noResultsMessage = paymentVendorId
|
||||||
? 'There is no payable bills for this vendor that can be applied for this payment'
|
? 'There is no payable bills for this vendor that can be applied for this payment'
|
||||||
: 'Please select a vendor to display all open bills for it.';
|
: 'Please select a vendor to display all open bills for it.';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CloudLoadingIndicator isLoading={fetchVendorDueBills.isFetching}>
|
<CloudLoadingIndicator isLoading={isDueBillsFetching}>
|
||||||
<PaymentMadeItemsTableEditor
|
<DataTableEditable
|
||||||
noResultsMessage={noResultsMessage}
|
progressBarLoading={isDueBillsFetching}
|
||||||
data={tableData}
|
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
|
||||||
errors={errors}
|
columns={columns}
|
||||||
onUpdateData={handleUpdateData}
|
data={[]}
|
||||||
onClickClearAllLines={onClickClearAllLines}
|
spinnerProps={false}
|
||||||
|
payload={{
|
||||||
|
errors: [],
|
||||||
|
updateData: handleUpdateData,
|
||||||
|
}}
|
||||||
|
noResults={noResultsMessage}
|
||||||
|
actions={
|
||||||
|
<Button
|
||||||
|
small={true}
|
||||||
|
className={'button--secondary button--clear-lines'}
|
||||||
|
// onClick={handleClickClearAllLines}
|
||||||
|
>
|
||||||
|
<T id={'clear_all_lines'} />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
totalRow={true}
|
||||||
/>
|
/>
|
||||||
</CloudLoadingIndicator>
|
</CloudLoadingIndicator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withPaymentMadeActions,
|
|
||||||
withBillActions,
|
|
||||||
withBills(({ paymentMadePayableBills, vendorPayableBillsEntries }) => ({
|
|
||||||
paymentMadePayableBills,
|
|
||||||
vendorPayableBillsEntries,
|
|
||||||
})),
|
|
||||||
)(PaymentMadeItemsTable);
|
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
||||||
import { Button } from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { sumBy } from 'lodash';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
import { DataTableEditable, Money } from 'components';
|
|
||||||
import { transformUpdatedRows } from 'utils';
|
|
||||||
import {
|
|
||||||
MoneyFieldCell,
|
|
||||||
DivFieldCell,
|
|
||||||
EmptyDiv,
|
|
||||||
} from 'components/DataTableCells';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cell renderer guard.
|
|
||||||
*/
|
|
||||||
const CellRenderer = (content, type) => (props) => {
|
|
||||||
if (props.data.length === props.row.index + 1) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return content(props);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TotalCellRederer = (content, type) => (props) => {
|
|
||||||
if (props.data.length === props.row.index + 1) {
|
|
||||||
return <Money amount={props.cell.row.original[type]} currency={'USD'} />;
|
|
||||||
}
|
|
||||||
return content(props);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Payment made items editor table.
|
|
||||||
*/
|
|
||||||
export default function PaymentMadeItemsTableEditor({
|
|
||||||
//#ownProps
|
|
||||||
onClickClearAllLines,
|
|
||||||
onUpdateData,
|
|
||||||
data,
|
|
||||||
errors,
|
|
||||||
noResultsMessage,
|
|
||||||
}) {
|
|
||||||
const transformedData = useMemo(() => {
|
|
||||||
const rows = [...data];
|
|
||||||
const totalRow = {
|
|
||||||
due_amount: sumBy(data, 'due_amount'),
|
|
||||||
payment_amount: sumBy(data, 'payment_amount'),
|
|
||||||
};
|
|
||||||
if (rows.length > 0) {
|
|
||||||
rows.push(totalRow);
|
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const [localData, setLocalData] = useState(transformedData);
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (localData !== transformedData) {
|
|
||||||
setLocalData(transformedData);
|
|
||||||
}
|
|
||||||
}, [setLocalData, localData, transformedData]);
|
|
||||||
|
|
||||||
const columns = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
Header: '#',
|
|
||||||
accessor: 'index',
|
|
||||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
|
||||||
width: 40,
|
|
||||||
disableResizing: true,
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'Date' }),
|
|
||||||
id: 'bill_date',
|
|
||||||
accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
|
|
||||||
Cell: CellRenderer(EmptyDiv, 'bill_date'),
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'bill_number' }),
|
|
||||||
accessor: (row) => `#${row?.bill_number || ''}`,
|
|
||||||
Cell: CellRenderer(EmptyDiv, 'bill_number'),
|
|
||||||
disableSortBy: true,
|
|
||||||
className: 'bill_number',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'bill_amount' }),
|
|
||||||
accessor: 'amount',
|
|
||||||
Cell: CellRenderer(DivFieldCell, 'amount'),
|
|
||||||
disableSortBy: true,
|
|
||||||
className: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'amount_due' }),
|
|
||||||
accessor: 'due_amount',
|
|
||||||
Cell: TotalCellRederer(DivFieldCell, 'due_amount'),
|
|
||||||
disableSortBy: true,
|
|
||||||
className: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'payment_amount' }),
|
|
||||||
accessor: 'payment_amount',
|
|
||||||
Cell: TotalCellRederer(MoneyFieldCell, 'payment_amount'),
|
|
||||||
disableSortBy: true,
|
|
||||||
className: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[formatMessage],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle click clear all lines button.
|
|
||||||
const handleClickClearAllLines = () => {
|
|
||||||
onClickClearAllLines && onClickClearAllLines();
|
|
||||||
};
|
|
||||||
|
|
||||||
const rowClassNames = useCallback(
|
|
||||||
(row) => ({ 'row--total': localData.length === row.index + 1 }),
|
|
||||||
[localData],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle update data.
|
|
||||||
const handleUpdateData = useCallback(
|
|
||||||
(rowIndex, columnId, value) => {
|
|
||||||
const newRows = transformUpdatedRows(
|
|
||||||
localData,
|
|
||||||
rowIndex,
|
|
||||||
columnId,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
newRows.splice(-1, 1); // removes the total row.
|
|
||||||
|
|
||||||
setLocalData(newRows);
|
|
||||||
onUpdateData && onUpdateData(newRows);
|
|
||||||
},
|
|
||||||
[localData, setLocalData, onUpdateData],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DataTableEditable
|
|
||||||
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
|
|
||||||
columns={columns}
|
|
||||||
data={localData}
|
|
||||||
rowClassNames={rowClassNames}
|
|
||||||
spinnerProps={false}
|
|
||||||
payload={{
|
|
||||||
errors,
|
|
||||||
updateData: handleUpdateData,
|
|
||||||
}}
|
|
||||||
noResults={noResultsMessage}
|
|
||||||
actions={
|
|
||||||
<Button
|
|
||||||
small={true}
|
|
||||||
className={'button--secondary button--clear-lines'}
|
|
||||||
onClick={handleClickClearAllLines}
|
|
||||||
>
|
|
||||||
<T id={'clear_all_lines'} />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
totalRow={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useIntl } from "react-intl";
|
||||||
|
import moment from 'moment';
|
||||||
|
import { Money } from 'components';
|
||||||
|
import { safeSumBy, formattedAmount } from 'utils';
|
||||||
|
|
||||||
|
function BillNumberAccessor(row) {
|
||||||
|
return `#${row?.bill_number || ''}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function IndexTableCell({ row: { index } }) {
|
||||||
|
return (<span>{index + 1}</span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BillDateTableCell({ value }) {
|
||||||
|
return moment(value).format('YYYY MMM DD');
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Balance footer cell.
|
||||||
|
*/
|
||||||
|
function AmountFooterCell({ rows }) {
|
||||||
|
const total = safeSumBy(rows, 'original.amount');
|
||||||
|
return <span>{ formattedAmount(total, 'USD') }</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due amount footer cell.
|
||||||
|
*/
|
||||||
|
function DueAmountFooterCell({ rows }) {
|
||||||
|
const totalDueAmount = safeSumBy(rows, 'original.due_amount');
|
||||||
|
return <span>{ formattedAmount(totalDueAmount, 'USD') }</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment amount footer cell.
|
||||||
|
*/
|
||||||
|
function PaymentAmountFooterCell({ rows }) {
|
||||||
|
const totalPaymentAmount = safeSumBy(rows, 'original.payment_amount');
|
||||||
|
return <span>{ formattedAmount(totalPaymentAmount, 'USD') }</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment made entries table columns
|
||||||
|
*/
|
||||||
|
export function usePaymentMadeEntriesTableColumns() {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return React.useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: '#',
|
||||||
|
accessor: 'index',
|
||||||
|
Cell: IndexTableCell,
|
||||||
|
width: 40,
|
||||||
|
disableResizing: true,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'Date' }),
|
||||||
|
id: 'bill_date',
|
||||||
|
accessor: 'bill_date',
|
||||||
|
Cell: BillDateTableCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'bill_number' }),
|
||||||
|
accessor: BillNumberAccessor,
|
||||||
|
disableSortBy: true,
|
||||||
|
className: 'bill_number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'bill_amount' }),
|
||||||
|
accessor: 'amount',
|
||||||
|
Footer: AmountFooterCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'amount_due' }),
|
||||||
|
accessor: 'due_amount',
|
||||||
|
Footer: DueAmountFooterCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'payment_amount' }),
|
||||||
|
accessor: 'payment_amount',
|
||||||
|
Footer: PaymentAmountFooterCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[formatMessage],
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import { useHistory } from 'react-router-dom';
|
|||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
import { CLASSES } from 'common/classes';
|
||||||
import PaymentReceiveHeader from './PaymentReceiveFormHeader';
|
import PaymentReceiveHeader from './PaymentReceiveFormHeader';
|
||||||
// import PaymentReceiveItemsTable from './PaymentReceiveItemsTable';
|
import PaymentReceiveItemsTable from './PaymentReceiveItemsTable';
|
||||||
import PaymentReceiveFloatingActions from './PaymentReceiveFloatingActions';
|
import PaymentReceiveFloatingActions from './PaymentReceiveFloatingActions';
|
||||||
import PaymentReceiveFormFooter from './PaymentReceiveFormFooter';
|
import PaymentReceiveFormFooter from './PaymentReceiveFormFooter';
|
||||||
|
|
||||||
@@ -167,48 +167,16 @@ function PaymentReceiveForm({
|
|||||||
>
|
>
|
||||||
<Form>
|
<Form>
|
||||||
<PaymentReceiveHeader />
|
<PaymentReceiveHeader />
|
||||||
|
|
||||||
|
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
||||||
|
<PaymentReceiveItemsTable />
|
||||||
|
</div>
|
||||||
|
|
||||||
<PaymentReceiveFormFooter />
|
<PaymentReceiveFormFooter />
|
||||||
<PaymentReceiveFloatingActions />
|
<PaymentReceiveFloatingActions />
|
||||||
</Form>
|
</Form>
|
||||||
</Formik>
|
</Formik>
|
||||||
|
|
||||||
{/* <form onSubmit={handleSubmit}> */}
|
|
||||||
{/* <PaymentReceiveHeader
|
|
||||||
errors={errors}
|
|
||||||
touched={touched}
|
|
||||||
setFieldValue={setFieldValue}
|
|
||||||
getFieldProps={getFieldProps}
|
|
||||||
values={values}
|
|
||||||
paymentReceiveId={paymentReceiveId}
|
|
||||||
customerId={values.customer_id}
|
|
||||||
onFullAmountChanged={handleFullAmountChange}
|
|
||||||
receivableFullAmount={receivableFullAmount}
|
|
||||||
amountReceived={fullAmountReceived}
|
|
||||||
onPaymentReceiveNumberChanged={handlePaymentReceiveNumberChanged}
|
|
||||||
/>
|
|
||||||
<div className={classNames(CLASSES.PAGE_FORM_BODY)}>
|
|
||||||
<PaymentReceiveItemsTable
|
|
||||||
paymentReceiveId={paymentReceiveId}
|
|
||||||
customerId={values.customer_id}
|
|
||||||
fullAmount={fullAmount}
|
|
||||||
onUpdateData={handleUpdataData}
|
|
||||||
paymentReceiveEntries={localPaymentEntries}
|
|
||||||
errors={errors?.entries}
|
|
||||||
onClickClearAllLines={handleClearAllLines}
|
|
||||||
onFetchEntriesSuccess={handleFetchEntriesSuccess}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<PaymentReceiveFloatingActions
|
|
||||||
isSubmitting={isSubmitting}
|
|
||||||
paymentReceiveId={paymentReceiveId}
|
|
||||||
onClearClick={handleClearBtnClick}
|
|
||||||
onSubmitClick={handleSubmitClick}
|
|
||||||
onCancelClick={handleCancelClick}
|
|
||||||
onSubmitForm={submitForm}
|
|
||||||
/> */}
|
|
||||||
{/* </form> */}
|
{/* </form> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
useAccounts,
|
useAccounts,
|
||||||
useCustomers,
|
useCustomers,
|
||||||
useCreatePaymentReceive,
|
useCreatePaymentReceive,
|
||||||
useEditPaymentReceive
|
useEditPaymentReceive,
|
||||||
|
useDueInvoices,
|
||||||
} from 'hooks/query';
|
} from 'hooks/query';
|
||||||
|
|
||||||
// Payment receive form context.
|
// Payment receive form context.
|
||||||
@@ -16,14 +17,16 @@ const PaymentReceiveFormContext = createContext();
|
|||||||
* Payment receive form provider.
|
* Payment receive form provider.
|
||||||
*/
|
*/
|
||||||
function PaymentReceiveFormProvider({ paymentReceiveId, ...props }) {
|
function PaymentReceiveFormProvider({ paymentReceiveId, ...props }) {
|
||||||
|
// Form state.
|
||||||
|
const [paymentCustomerId, setPaymentCustomerId] = React.useState(null);
|
||||||
|
const [submitPayload, setSubmitPayload] = React.useState({});
|
||||||
|
|
||||||
// Fetches payment recevie details.
|
// Fetches payment recevie details.
|
||||||
const {
|
const {
|
||||||
data: paymentReceive,
|
data: paymentReceive,
|
||||||
isLoading: isPaymentLoading,
|
isLoading: isPaymentLoading,
|
||||||
isFetching: isPaymentFetching,
|
isFetching: isPaymentFetching,
|
||||||
} = usePaymentReceive(paymentReceiveId, {
|
} = usePaymentReceive(paymentReceiveId, { enabled: !!paymentReceiveId });
|
||||||
enabled: !!paymentReceiveId,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle fetch accounts data.
|
// Handle fetch accounts data.
|
||||||
const { data: accounts, isFetching: isAccountsFetching } = useAccounts();
|
const { data: accounts, isFetching: isAccountsFetching } = useAccounts();
|
||||||
@@ -37,7 +40,16 @@ function PaymentReceiveFormProvider({ paymentReceiveId, ...props }) {
|
|||||||
isFetching: isCustomersFetching,
|
isFetching: isCustomersFetching,
|
||||||
} = useCustomers();
|
} = useCustomers();
|
||||||
|
|
||||||
const [submitPayload, setSubmitPayload] = React.useState({});
|
// Fetches customer receivable invoices.
|
||||||
|
const {
|
||||||
|
data: dueInvoices,
|
||||||
|
isLoading: isDueInvoicesLoading,
|
||||||
|
isFetching: isDueInvoicesFetching,
|
||||||
|
} = useDueInvoices(paymentCustomerId, {
|
||||||
|
enabled: !!paymentCustomerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Detarmines whether the new mode.
|
||||||
const isNewMode = !paymentReceiveId;
|
const isNewMode = !paymentReceiveId;
|
||||||
|
|
||||||
// Create and edit payment receive mutations.
|
// Create and edit payment receive mutations.
|
||||||
@@ -49,27 +61,29 @@ function PaymentReceiveFormProvider({ paymentReceiveId, ...props }) {
|
|||||||
paymentReceive,
|
paymentReceive,
|
||||||
accounts,
|
accounts,
|
||||||
customers,
|
customers,
|
||||||
|
dueInvoices,
|
||||||
|
|
||||||
isPaymentLoading,
|
isPaymentLoading,
|
||||||
isPaymentFetching,
|
isPaymentFetching,
|
||||||
isAccountsFetching,
|
isAccountsFetching,
|
||||||
isCustomersFetching,
|
isCustomersFetching,
|
||||||
|
isDueInvoicesLoading,
|
||||||
|
isDueInvoicesFetching,
|
||||||
|
|
||||||
|
paymentCustomerId,
|
||||||
submitPayload,
|
submitPayload,
|
||||||
setSubmitPayload,
|
|
||||||
isNewMode,
|
isNewMode,
|
||||||
|
|
||||||
|
setSubmitPayload,
|
||||||
|
setPaymentCustomerId,
|
||||||
|
|
||||||
editPaymentReceiveMutate,
|
editPaymentReceiveMutate,
|
||||||
createPaymentReceiveMutate
|
createPaymentReceiveMutate,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={
|
loading={isPaymentLoading || isAccountsFetching || isCustomersFetching}
|
||||||
isPaymentLoading ||
|
|
||||||
isAccountsFetching ||
|
|
||||||
isCustomersFetching
|
|
||||||
}
|
|
||||||
name={'payment-receive-form'}
|
name={'payment-receive-form'}
|
||||||
>
|
>
|
||||||
<PaymentReceiveFormContext.Provider value={provider} {...props} />
|
<PaymentReceiveFormContext.Provider value={provider} {...props} />
|
||||||
|
|||||||
@@ -37,7 +37,12 @@ import { compose } from 'utils';
|
|||||||
*/
|
*/
|
||||||
function PaymentReceiveHeaderFields({ baseCurrency }) {
|
function PaymentReceiveHeaderFields({ baseCurrency }) {
|
||||||
// Payment receive form context.
|
// Payment receive form context.
|
||||||
const { customers, accounts, isNewMode } = usePaymentReceiveFormContext();
|
const {
|
||||||
|
customers,
|
||||||
|
accounts,
|
||||||
|
isNewMode,
|
||||||
|
setPaymentCustomerId,
|
||||||
|
} = usePaymentReceiveFormContext();
|
||||||
|
|
||||||
// Formik form context.
|
// Formik form context.
|
||||||
const { values } = useFormikContext();
|
const { values } = useFormikContext();
|
||||||
@@ -66,8 +71,9 @@ function PaymentReceiveHeaderFields({ baseCurrency }) {
|
|||||||
contactsList={customers}
|
contactsList={customers}
|
||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={<T id={'select_customer_account'} />}
|
defaultSelectText={<T id={'select_customer_account'} />}
|
||||||
onContactSelected={(value) => {
|
onContactSelected={(customer) => {
|
||||||
form.setFieldValue('customer_id', value);
|
form.setFieldValue('customer_id', customer);
|
||||||
|
setPaymentCustomerId(customer.id);
|
||||||
}}
|
}}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
disabled={!isNewMode}
|
disabled={!isNewMode}
|
||||||
|
|||||||
@@ -1,137 +1,76 @@
|
|||||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
|
import { Button } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { CloudLoadingIndicator } from 'components';
|
import { CloudLoadingIndicator } from 'components';
|
||||||
import { useQuery } from 'react-query';
|
import classNames from 'classnames';
|
||||||
import { omit } from 'lodash';
|
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider';
|
||||||
|
import { DataTableEditable } from 'components';
|
||||||
|
import { usePaymentReceiveEntriesColumns } from './components';
|
||||||
|
|
||||||
import withInvoices from '../Invoice/withInvoices';
|
/**
|
||||||
import PaymentReceiveItemsTableEditor from './PaymentReceiveItemsTableEditor';
|
* Payment receive items table.
|
||||||
import withInvoiceActions from 'containers/Sales/Invoice/withInvoiceActions';
|
*/
|
||||||
|
export default function PaymentReceiveItemsTable() {
|
||||||
|
// Payment receive form context.
|
||||||
|
const {
|
||||||
|
isNewMode,
|
||||||
|
isDueInvoicesFetching,
|
||||||
|
paymentCustomerId,
|
||||||
|
dueInvoices,
|
||||||
|
} = usePaymentReceiveFormContext();
|
||||||
|
|
||||||
|
// Payment receive entries form context.
|
||||||
function PaymentReceiveItemsTable({
|
const columns = usePaymentReceiveEntriesColumns();
|
||||||
// #ownProps
|
|
||||||
paymentReceiveId,
|
|
||||||
customerId,
|
|
||||||
fullAmount,
|
|
||||||
onUpdateData,
|
|
||||||
paymentReceiveEntries = [],
|
|
||||||
errors,
|
|
||||||
onClickClearAllLines,
|
|
||||||
onFetchEntriesSuccess,
|
|
||||||
|
|
||||||
// #withInvoices
|
|
||||||
customerInvoiceEntries,
|
|
||||||
|
|
||||||
// #withPaymentReceiveActions
|
|
||||||
requestFetchDueInvoices
|
|
||||||
}) {
|
|
||||||
const isNewMode = !paymentReceiveId;
|
|
||||||
|
|
||||||
// Detarmines takes payment receive invoices entries from customer receivable
|
// Detarmines takes payment receive invoices entries from customer receivable
|
||||||
// invoices or associated payment receive invoices.
|
// invoices or associated payment receive invoices.
|
||||||
const computedTableData = useMemo(() => [
|
const computedTableData = useMemo(
|
||||||
...(!isNewMode ? (paymentReceiveEntries || []) : []),
|
() => [
|
||||||
...(isNewMode ? (customerInvoiceEntries || []) : []),
|
...(!isNewMode ? [] || [] : []),
|
||||||
], [
|
...(isNewMode ? dueInvoices || [] : []),
|
||||||
isNewMode,
|
],
|
||||||
paymentReceiveEntries,
|
[isNewMode, dueInvoices],
|
||||||
customerInvoiceEntries,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [tableData, setTableData] = useState(computedTableData);
|
|
||||||
const [localEntries, setLocalEntries] = useState(computedTableData);
|
|
||||||
|
|
||||||
const [localAmount, setLocalAmount] = useState(fullAmount);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (computedTableData !== localEntries) {
|
|
||||||
setTableData(computedTableData);
|
|
||||||
setLocalEntries(computedTableData);
|
|
||||||
}
|
|
||||||
}, [computedTableData, localEntries]);
|
|
||||||
|
|
||||||
// Triggers `onUpdateData` prop event.
|
|
||||||
const triggerUpdateData = useCallback((entries) => {
|
|
||||||
onUpdateData && onUpdateData(entries);
|
|
||||||
}, [onUpdateData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (localAmount !== fullAmount) {
|
|
||||||
let _fullAmount = fullAmount;
|
|
||||||
|
|
||||||
const newTableData = tableData.map((data) => {
|
|
||||||
const amount = Math.min(data?.due_amount, _fullAmount);
|
|
||||||
_fullAmount -= Math.max(amount, 0);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...data,
|
|
||||||
payment_amount: amount,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setTableData(newTableData);
|
|
||||||
setLocalAmount(fullAmount);
|
|
||||||
triggerUpdateData(newTableData);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
fullAmount,
|
|
||||||
localAmount,
|
|
||||||
tableData,
|
|
||||||
triggerUpdateData,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Fetches customer receivable invoices.
|
|
||||||
const fetchCustomerDueInvoices = useQuery(
|
|
||||||
['customer-due-invoices', customerId],
|
|
||||||
(key, _customerId) => requestFetchDueInvoices(_customerId),
|
|
||||||
{ enabled: isNewMode && customerId },
|
|
||||||
);
|
);
|
||||||
// No results message.
|
|
||||||
const noResultsMessage = (customerId) ?
|
|
||||||
'There is no receivable invoices for this customer that can be applied for this payment' :
|
|
||||||
'Please select a customer to display all open invoices for it.';
|
|
||||||
|
|
||||||
const triggerOnFetchInvoicesSuccess = useCallback((bills) => {
|
// No results message.
|
||||||
onFetchEntriesSuccess && onFetchEntriesSuccess(bills);
|
const noResultsMessage = paymentCustomerId
|
||||||
}, [onFetchEntriesSuccess])
|
? 'There is no receivable invoices for this customer that can be applied for this payment'
|
||||||
|
: 'Please select a customer to display all open invoices for it.';
|
||||||
|
|
||||||
// Handle update data.
|
// Handle update data.
|
||||||
const handleUpdateData = useCallback((rows) => {
|
const handleUpdateData = useCallback((rows) => {}, []);
|
||||||
triggerUpdateData(rows);
|
|
||||||
setTableData(rows);
|
|
||||||
}, [triggerUpdateData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
// Handle click clear all lines button.
|
||||||
const enabled = isNewMode && customerId;
|
const handleClickClearAllLines = () => {
|
||||||
|
|
||||||
if (!fetchCustomerDueInvoices.isFetching && enabled) {
|
};
|
||||||
triggerOnFetchInvoicesSuccess(computedTableData);
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
isNewMode,
|
|
||||||
customerId,
|
|
||||||
fetchCustomerDueInvoices.isFetching,
|
|
||||||
computedTableData,
|
|
||||||
triggerOnFetchInvoicesSuccess,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CloudLoadingIndicator isLoading={fetchCustomerDueInvoices.isFetching}>
|
<CloudLoadingIndicator isLoading={isDueInvoicesFetching}>
|
||||||
<PaymentReceiveItemsTableEditor
|
<DataTableEditable
|
||||||
noResultsMessage={noResultsMessage}
|
progressBarLoading={isDueInvoicesFetching}
|
||||||
data={tableData}
|
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
|
||||||
errors={errors}
|
columns={columns}
|
||||||
onUpdateData={handleUpdateData}
|
data={[]}
|
||||||
onClickClearAllLines={onClickClearAllLines}
|
spinnerProps={false}
|
||||||
|
payload={{
|
||||||
|
errors: [],
|
||||||
|
updateData: handleUpdateData,
|
||||||
|
}}
|
||||||
|
noResults={noResultsMessage}
|
||||||
|
actions={
|
||||||
|
<Button
|
||||||
|
small={true}
|
||||||
|
className={'button--secondary button--clear-lines'}
|
||||||
|
onClick={handleClickClearAllLines}
|
||||||
|
>
|
||||||
|
<T id={'clear_all_lines'} />
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
totalRow={true}
|
||||||
/>
|
/>
|
||||||
</CloudLoadingIndicator>
|
</CloudLoadingIndicator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
|
||||||
withInvoices(({ customerInvoiceEntries }) => ({
|
|
||||||
customerInvoiceEntries,
|
|
||||||
})),
|
|
||||||
withInvoiceActions,
|
|
||||||
)(PaymentReceiveItemsTable);
|
|
||||||
|
|||||||
@@ -1,172 +0,0 @@
|
|||||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
|
||||||
import { Button } from '@blueprintjs/core';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { sumBy } from 'lodash';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
|
|
||||||
import { CLASSES } from 'common/classes';
|
|
||||||
import { DataTableEditable, Money } from 'components';
|
|
||||||
import { transformUpdatedRows } from 'utils';
|
|
||||||
import {
|
|
||||||
MoneyFieldCell,
|
|
||||||
DivFieldCell,
|
|
||||||
EmptyDiv,
|
|
||||||
} from 'components/DataTableCells';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cell renderer guard.
|
|
||||||
*/
|
|
||||||
const CellRenderer = (content, type) => (props) => {
|
|
||||||
if (props.data.length === props.row.index + 1) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return content(props);
|
|
||||||
};
|
|
||||||
|
|
||||||
const TotalCellRederer = (content, type) => (props) => {
|
|
||||||
if (props.data.length === props.row.index + 1) {
|
|
||||||
return <Money amount={props.cell.row.original[type]} currency={'USD'} />;
|
|
||||||
}
|
|
||||||
return content(props);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function PaymentReceiveItemsTableEditor({
|
|
||||||
onClickClearAllLines,
|
|
||||||
onUpdateData,
|
|
||||||
data,
|
|
||||||
errors,
|
|
||||||
noResultsMessage,
|
|
||||||
}) {
|
|
||||||
const transformedData = useMemo(() => {
|
|
||||||
const rows = [...data];
|
|
||||||
const totalRow = {
|
|
||||||
due_amount: sumBy(data, 'due_amount'),
|
|
||||||
payment_amount: sumBy(data, 'payment_amount'),
|
|
||||||
};
|
|
||||||
if (rows.length > 0) {
|
|
||||||
rows.push(totalRow);
|
|
||||||
}
|
|
||||||
return rows;
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const [localData, setLocalData] = useState(transformedData);
|
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (localData !== transformedData) {
|
|
||||||
setLocalData(transformedData);
|
|
||||||
}
|
|
||||||
}, [setLocalData, localData, transformedData]);
|
|
||||||
|
|
||||||
const columns = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
Header: '#',
|
|
||||||
accessor: 'index',
|
|
||||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
|
||||||
width: 40,
|
|
||||||
disableResizing: true,
|
|
||||||
disableSortBy: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'Date' }),
|
|
||||||
id: 'invoice_date',
|
|
||||||
accessor: (r) => moment(r.invoice_date).format('YYYY MMM DD'),
|
|
||||||
Cell: CellRenderer(EmptyDiv, 'invoice_date'),
|
|
||||||
disableSortBy: true,
|
|
||||||
disableResizing: true,
|
|
||||||
width: 250,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'invocie_number' }),
|
|
||||||
accessor: (row) => {
|
|
||||||
const invNumber = row?.invoice_no || row?.id;
|
|
||||||
return `#INV-${invNumber || ''}`;
|
|
||||||
},
|
|
||||||
Cell: CellRenderer(EmptyDiv, 'invoice_no'),
|
|
||||||
disableSortBy: true,
|
|
||||||
className: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'invoice_amount' }),
|
|
||||||
accessor: 'balance',
|
|
||||||
Cell: CellRenderer(DivFieldCell, 'balance'),
|
|
||||||
disableSortBy: true,
|
|
||||||
width: 100,
|
|
||||||
className: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'amount_due' }),
|
|
||||||
accessor: 'due_amount',
|
|
||||||
Cell: TotalCellRederer(DivFieldCell, 'due_amount'),
|
|
||||||
disableSortBy: true,
|
|
||||||
width: 150,
|
|
||||||
className: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: formatMessage({ id: 'payment_amount' }),
|
|
||||||
accessor: 'payment_amount',
|
|
||||||
Cell: TotalCellRederer(MoneyFieldCell, 'payment_amount'),
|
|
||||||
disableSortBy: true,
|
|
||||||
width: 150,
|
|
||||||
className: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[formatMessage],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle click clear all lines button.
|
|
||||||
const handleClickClearAllLines = () => {
|
|
||||||
onClickClearAllLines && onClickClearAllLines();
|
|
||||||
};
|
|
||||||
|
|
||||||
const rowClassNames = useCallback(
|
|
||||||
(row) => ({ 'row--total': localData.length === row.index + 1 }),
|
|
||||||
[localData],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle update data.
|
|
||||||
const handleUpdateData = useCallback(
|
|
||||||
(rowIndex, columnId, value) => {
|
|
||||||
const newRows = transformUpdatedRows(
|
|
||||||
localData,
|
|
||||||
rowIndex,
|
|
||||||
columnId,
|
|
||||||
value,
|
|
||||||
);
|
|
||||||
if (newRows.length > 0) {
|
|
||||||
newRows.splice(-1, 1);
|
|
||||||
}
|
|
||||||
setLocalData(newRows);
|
|
||||||
onUpdateData && onUpdateData(newRows);
|
|
||||||
},
|
|
||||||
[localData, setLocalData, onUpdateData],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DataTableEditable
|
|
||||||
className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)}
|
|
||||||
columns={columns}
|
|
||||||
data={localData}
|
|
||||||
rowClassNames={rowClassNames}
|
|
||||||
spinnerProps={false}
|
|
||||||
payload={{
|
|
||||||
errors,
|
|
||||||
updateData: handleUpdateData,
|
|
||||||
}}
|
|
||||||
noResults={noResultsMessage}
|
|
||||||
actions={
|
|
||||||
<Button
|
|
||||||
small={true}
|
|
||||||
className={'button--secondary button--clear-lines'}
|
|
||||||
onClick={handleClickClearAllLines}
|
|
||||||
>
|
|
||||||
<T id={'clear_all_lines'} />
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
totalRow={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { safeSumBy, formattedAmount } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice date cell.
|
||||||
|
*/
|
||||||
|
function InvoiceDateCell({ value }) {
|
||||||
|
return <span>{ moment(value).format('YYYY MMM DD') }</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Index table cell.
|
||||||
|
*/
|
||||||
|
function IndexCell({ row: { index } }) {
|
||||||
|
return (<span>{index + 1}</span>);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoice number table cell accessor.
|
||||||
|
*/
|
||||||
|
function InvNumberCellAccessor(row) {
|
||||||
|
const invNumber = row?.invoice_no || row?.id;
|
||||||
|
return `#INV-${invNumber || ''}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Balance footer cell.
|
||||||
|
*/
|
||||||
|
function BalanceFooterCell({ rows }) {
|
||||||
|
const total = safeSumBy(rows, 'original.balance');
|
||||||
|
return <span>{ formattedAmount(total, 'USD') }</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due amount footer cell.
|
||||||
|
*/
|
||||||
|
function DueAmountFooterCell({ rows }) {
|
||||||
|
const totalDueAmount = safeSumBy(rows, 'original.due_amount');
|
||||||
|
return <span>{ formattedAmount(totalDueAmount, 'USD') }</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment amount footer cell.
|
||||||
|
*/
|
||||||
|
function PaymentAmountFooterCell({ rows }) {
|
||||||
|
const totalPaymentAmount = safeSumBy(rows, 'original.payment_amount');
|
||||||
|
return <span>{ formattedAmount(totalPaymentAmount, 'USD') }</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve payment receive form entries columns.
|
||||||
|
*/
|
||||||
|
export const usePaymentReceiveEntriesColumns = () => {
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
return React.useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: '#',
|
||||||
|
accessor: 'index',
|
||||||
|
Cell: IndexCell,
|
||||||
|
width: 40,
|
||||||
|
disableResizing: true,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'Date' }),
|
||||||
|
id: 'invoice_date',
|
||||||
|
accessor: 'invoice_date',
|
||||||
|
Cell: InvoiceDateCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
disableResizing: true,
|
||||||
|
width: 250,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'invocie_number' }),
|
||||||
|
accessor: InvNumberCellAccessor,
|
||||||
|
Cell: 'invoice_no',
|
||||||
|
disableSortBy: true,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'invoice_amount' }),
|
||||||
|
accessor: 'balance',
|
||||||
|
Footer: BalanceFooterCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
width: 100,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'amount_due' }),
|
||||||
|
accessor: 'due_amount',
|
||||||
|
Footer: DueAmountFooterCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
width: 150,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'payment_amount' }),
|
||||||
|
accessor: 'payment_amount',
|
||||||
|
Footer: PaymentAmountFooterCell,
|
||||||
|
disableSortBy: true,
|
||||||
|
width: 150,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[formatMessage],
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -119,3 +119,26 @@ export function useOpenBill(props) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the due bills of the given vendor id.
|
||||||
|
* @param {number} vendorId -
|
||||||
|
*/
|
||||||
|
export function useDueBills(vendorId, props) {
|
||||||
|
const states = useQuery(
|
||||||
|
['BILLS_DUE', vendorId],
|
||||||
|
() =>
|
||||||
|
ApiService.get(`purchases/bills/due`, {
|
||||||
|
params: { vendor_id: vendorId },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
select: (res) => res.data.bills,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...states,
|
||||||
|
data: defaultTo(states.data, []),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ export function useInvoices(query, props) {
|
|||||||
total: 0,
|
total: 0,
|
||||||
},
|
},
|
||||||
filterMeta: {},
|
filterMeta: {},
|
||||||
})
|
}),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,27 +87,25 @@ export function useInvoices(query, props) {
|
|||||||
export function useDeliverInvoice(props) {
|
export function useDeliverInvoice(props) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation(
|
return useMutation((id) => ApiService.post(`sales/invoices/${id}/deliver`), {
|
||||||
(id) => ApiService.post(`sales/invoices/${id}/deliver`),
|
onSuccess: (res, id) => {
|
||||||
{
|
queryClient.invalidateQueries('SALE_INVOICES');
|
||||||
onSuccess: (res, id) => {
|
queryClient.invalidateQueries(['SALE_INVOICE', id]);
|
||||||
queryClient.invalidateQueries('SALE_INVOICES');
|
|
||||||
queryClient.invalidateQueries(['SALE_INVOICE', id]);
|
|
||||||
},
|
|
||||||
...props,
|
|
||||||
},
|
},
|
||||||
);
|
...props,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the sale invoice details.
|
* Retrieve the sale invoice details.
|
||||||
*/
|
*/
|
||||||
export function useInvoice(id, props) {
|
export function useInvoice(id, props) {
|
||||||
const states = useQuery(['SALE_INVOICE', id], () =>
|
const states = useQuery(
|
||||||
ApiService.get(`sales/invoices/${id}`),
|
['SALE_INVOICE', id],
|
||||||
|
() => ApiService.get(`sales/invoices/${id}`),
|
||||||
{
|
{
|
||||||
select: (res) => res.data.sale_invoice,
|
select: (res) => res.data.sale_invoice,
|
||||||
...props
|
...props,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -116,3 +114,26 @@ export function useInvoice(id, props) {
|
|||||||
data: defaultTo(states.data, {}),
|
data: defaultTo(states.data, {}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve due invoices of the given customer id.
|
||||||
|
* @param {number} customerId - Customer id.
|
||||||
|
*/
|
||||||
|
export function useDueInvoices(customerId, props) {
|
||||||
|
const states = useQuery(
|
||||||
|
['SALE_INVOICE_DUE', customerId],
|
||||||
|
() =>
|
||||||
|
ApiService.get(`sales/invoices/payable`, {
|
||||||
|
params: { customer_id: customerId },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
select: (res) => res.data.sales_invoices,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...states,
|
||||||
|
data: defaultTo(states.data, []),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ export default [
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
breadcrumb: 'New Payment Made',
|
breadcrumb: 'New Payment Made',
|
||||||
pageTitle: formatMessage({ id: 'edit_payment_made' }),
|
pageTitle: formatMessage({ id: 'new_payment_made' }),
|
||||||
sidebarShrink: true,
|
sidebarShrink: true,
|
||||||
backLink: true,
|
backLink: true,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user