diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFloatingActions.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFloatingActions.js index f03e6a45a..06943211f 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFloatingActions.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFloatingActions.js @@ -15,7 +15,6 @@ import { useFormikContext } from 'formik'; import { usePaymentMadeFormContext } from './PaymentMadeFormProvider'; import { CLASSES } from 'common/classes'; - import { Icon } from 'components'; /** diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFooter.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFooter.js index 515baea79..10d1a6328 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFooter.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFooter.js @@ -9,7 +9,7 @@ import { CLASSES } from 'common/classes'; /** * Payment made form footer. */ -export default function PaymentMadeFooter({ getFieldProps }) { +export default function PaymentMadeFooter() { return (
diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.js index 682222fb0..3f5e11a85 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeForm.js @@ -11,6 +11,7 @@ import { AppToaster } from 'components'; import PaymentMadeHeader from './PaymentMadeFormHeader'; import PaymentMadeFloatingActions from './PaymentMadeFloatingActions'; import PaymentMadeFooter from './PaymentMadeFooter'; +import PaymentMadeItemsTable from './PaymentMadeItemsTable'; import withSettings from 'containers/Settings/withSettings'; import { @@ -135,7 +136,7 @@ function PaymentMadeForm() {
- +
diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.js index cf07eb753..a64da7c51 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormHeaderFields.js @@ -34,7 +34,12 @@ import { * Payment made form header fields. */ function PaymentMadeFormHeaderFields({ baseCurrency }) { - const { vendors, accounts, isNewMode } = usePaymentMadeFormContext(); + const { + vendors, + accounts, + isNewMode, + setPaymentVendorId, + } = usePaymentMadeFormContext(); const payableFullAmount = 0; @@ -57,6 +62,7 @@ function PaymentMadeFormHeaderFields({ baseCurrency }) { defaultSelectText={} onContactSelected={(contact) => { form.setFieldValue('vendor_id', contact.id); + setPaymentVendorId(contact.id); }} disabled={!isNewMode} popoverFill={true} diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.js index 302600de3..c0af6177c 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeFormProvider.js @@ -6,7 +6,8 @@ import { usePaymentMade, useSettings, useCreatePaymentMade, - useEditPaymentMade + useEditPaymentMade, + useDueBills, } from 'hooks/query'; import { DashboardInsider } from 'components'; @@ -17,6 +18,9 @@ const PaymentMadeFormContext = createContext(); * Payment made form provider. */ function PaymentMadeFormProvider({ paymentMadeId, ...props }) { + const [submitPayload, setSubmitPayload] = React.useState({}); + const [paymentVendorId, setPaymentVendorId] = React.useState(null); + // Handle fetch accounts data. const { data: accounts, isFetching: isAccountsFetching } = useAccounts(); @@ -42,6 +46,13 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) { 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. useSettings(); @@ -49,6 +60,8 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) { const { mutateAsync: createPaymentMadeMutate } = useCreatePaymentMade(); const { mutateAsync: editPaymentMadeMutate } = useEditPaymentMade(); + const isNewMode = !paymentMadeId; + // Provider payload. const provider = { paymentMadeId, @@ -58,16 +71,25 @@ function PaymentMadeFormProvider({ paymentMadeId, ...props }) { paymentBills, vendors, items, + dueBills, + submitPayload, + paymentVendorId, + isNewMode, isAccountsFetching, isItemsFetching, isItemsLoading, isVendorsFetching, isPaymentFetching, isPaymentLoading, + isDueBillsLoading, + isDueBillsFetching, createPaymentMadeMutate, editPaymentMadeMutate, + + setSubmitPayload, + setPaymentVendorId, }; return ( diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeItemsTable.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeItemsTable.js index 5c3b6c8b0..d9850583f 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeItemsTable.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeItemsTable.js @@ -1,153 +1,70 @@ -import React, { useState, useEffect, useMemo, useCallback } from 'react'; -import { useQuery } from 'react-query'; -import { isEmpty } from 'lodash'; +import React, { useMemo, useCallback } from 'react'; 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 withBillActions from '../Bill/withBillActions'; -import withBills from '../Bill/withBills'; +import { CLASSES } from 'common/classes'; +import { DataTableEditable } from 'components'; +import { usePaymentMadeEntriesTableColumns } from './components'; -import { compose } from 'utils'; +import { usePaymentMadeFormContext } from './PaymentMadeFormProvider'; /** * Payment made items table. */ -function PaymentMadeItemsTable({ - // #ownProps - paymentMadeId, - vendorId, - fullAmount, - onUpdateData, - paymentEntries = [], // { bill_id: number, payment_amount: number, id?: number } - onClickClearAllLines, - errors, - onFetchEntriesSuccess, +export default function PaymentMadeItemsTable() { + const { + paymentVendorId, + dueBills, + isDueBillsFetching, + isNewMode, + } = usePaymentMadeFormContext(); - // #withBillActions - requestFetchDueBills, - - // #withBills - vendorPayableBillsEntries, -}) { - const isNewMode = !paymentMadeId; + const columns = usePaymentMadeEntriesTableColumns(); // Detarmines takes vendor payable bills entries in create mode // or payment made entries in edit mode. - const computedTableEntries = useMemo( - () => - !isEmpty(paymentEntries) - ? paymentEntries - : (vendorPayableBillsEntries || []), - [vendorPayableBillsEntries, paymentEntries], - ); - const [tableData, setTableData] = useState(computedTableEntries); - const [localEntries, setLocalEntries] = useState(computedTableEntries); - - const [localAmount, setLocalAmount] = useState(fullAmount); + const computedTableEntries = useMemo(() => [], []); // Triggers `onUpdateData` event that passes changed entries. - const triggerUpdateData = useCallback( - (entries) => { - onUpdateData && onUpdateData(entries); - }, - [onUpdateData], - ); + const triggerUpdateData = useCallback((entries) => {}, []); - const triggerOnFetchBillsSuccess = useCallback( - (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, - ]); + const triggerOnFetchBillsSuccess = useCallback((bills) => {}, []); // Handle update data. - const handleUpdateData = useCallback( - (rows) => { - setTableData(rows); - triggerUpdateData(rows); - }, - [setTableData, triggerUpdateData], - ); + const handleUpdateData = useCallback((rows) => {}, []); // Detarmines the right no results message before selecting vendor and aftering // selecting vendor id. - const noResultsMessage = vendorId + const noResultsMessage = paymentVendorId ? '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.'; return ( - - + + + + } + totalRow={true} /> ); } - -export default compose( - withPaymentMadeActions, - withBillActions, - withBills(({ paymentMadePayableBills, vendorPayableBillsEntries }) => ({ - paymentMadePayableBills, - vendorPayableBillsEntries, - })), -)(PaymentMadeItemsTable); diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeItemsTableEditor.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeItemsTableEditor.js deleted file mode 100644 index b4a2cf22f..000000000 --- a/client/src/containers/Purchases/PaymentMades/PaymentForm/PaymentMadeItemsTableEditor.js +++ /dev/null @@ -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 ; - } - 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 } }) => {index + 1}, - 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 ( - - - - } - totalRow={true} - /> - ); -} diff --git a/client/src/containers/Purchases/PaymentMades/PaymentForm/components.js b/client/src/containers/Purchases/PaymentMades/PaymentForm/components.js new file mode 100644 index 000000000..9e6383341 --- /dev/null +++ b/client/src/containers/Purchases/PaymentMades/PaymentForm/components.js @@ -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 ({index + 1}); +} + +function BillDateTableCell({ value }) { + return moment(value).format('YYYY MMM DD'); +} +/** + * Balance footer cell. + */ +function AmountFooterCell({ rows }) { + const total = safeSumBy(rows, 'original.amount'); + return { formattedAmount(total, 'USD') }; +} + +/** + * Due amount footer cell. + */ +function DueAmountFooterCell({ rows }) { + const totalDueAmount = safeSumBy(rows, 'original.due_amount'); + return { formattedAmount(totalDueAmount, 'USD') }; +} + +/** + * Payment amount footer cell. + */ +function PaymentAmountFooterCell({ rows }) { + const totalPaymentAmount = safeSumBy(rows, 'original.payment_amount'); + return { formattedAmount(totalPaymentAmount, 'USD') }; +} + +/** + * 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], + ) +} \ No newline at end of file diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.js index 029bf2af3..30cdb99dd 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveForm.js @@ -9,7 +9,7 @@ import { useHistory } from 'react-router-dom'; import { CLASSES } from 'common/classes'; import PaymentReceiveHeader from './PaymentReceiveFormHeader'; -// import PaymentReceiveItemsTable from './PaymentReceiveItemsTable'; +import PaymentReceiveItemsTable from './PaymentReceiveItemsTable'; import PaymentReceiveFloatingActions from './PaymentReceiveFloatingActions'; import PaymentReceiveFormFooter from './PaymentReceiveFormFooter'; @@ -167,48 +167,16 @@ function PaymentReceiveForm({ >
+ +
+ +
+ - {/*
*/} - {/* -
- -
- - - - */} {/* */}
); diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormProvider.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormProvider.js index a68299d11..a4c7cd8f3 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormProvider.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormProvider.js @@ -6,7 +6,8 @@ import { useAccounts, useCustomers, useCreatePaymentReceive, - useEditPaymentReceive + useEditPaymentReceive, + useDueInvoices, } from 'hooks/query'; // Payment receive form context. @@ -16,14 +17,16 @@ const PaymentReceiveFormContext = createContext(); * Payment receive form provider. */ function PaymentReceiveFormProvider({ paymentReceiveId, ...props }) { + // Form state. + const [paymentCustomerId, setPaymentCustomerId] = React.useState(null); + const [submitPayload, setSubmitPayload] = React.useState({}); + // Fetches payment recevie details. const { data: paymentReceive, isLoading: isPaymentLoading, isFetching: isPaymentFetching, - } = usePaymentReceive(paymentReceiveId, { - enabled: !!paymentReceiveId, - }); + } = usePaymentReceive(paymentReceiveId, { enabled: !!paymentReceiveId }); // Handle fetch accounts data. const { data: accounts, isFetching: isAccountsFetching } = useAccounts(); @@ -37,7 +40,16 @@ function PaymentReceiveFormProvider({ paymentReceiveId, ...props }) { isFetching: isCustomersFetching, } = 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; // Create and edit payment receive mutations. @@ -49,27 +61,29 @@ function PaymentReceiveFormProvider({ paymentReceiveId, ...props }) { paymentReceive, accounts, customers, + dueInvoices, isPaymentLoading, isPaymentFetching, isAccountsFetching, isCustomersFetching, + isDueInvoicesLoading, + isDueInvoicesFetching, + paymentCustomerId, submitPayload, - setSubmitPayload, isNewMode, + + setSubmitPayload, + setPaymentCustomerId, editPaymentReceiveMutate, - createPaymentReceiveMutate + createPaymentReceiveMutate, }; return ( diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.js index e1f6d5fab..fca90d310 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveHeaderFields.js @@ -37,7 +37,12 @@ import { compose } from 'utils'; */ function PaymentReceiveHeaderFields({ baseCurrency }) { // Payment receive form context. - const { customers, accounts, isNewMode } = usePaymentReceiveFormContext(); + const { + customers, + accounts, + isNewMode, + setPaymentCustomerId, + } = usePaymentReceiveFormContext(); // Formik form context. const { values } = useFormikContext(); @@ -66,8 +71,9 @@ function PaymentReceiveHeaderFields({ baseCurrency }) { contactsList={customers} selectedContactId={value} defaultSelectText={} - onContactSelected={(value) => { - form.setFieldValue('customer_id', value); + onContactSelected={(customer) => { + form.setFieldValue('customer_id', customer); + setPaymentCustomerId(customer.id); }} popoverFill={true} disabled={!isNewMode} diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js index 25ff66360..182fd64e8 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js @@ -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 { useQuery } from 'react-query'; -import { omit } from 'lodash'; +import classNames from 'classnames'; -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'; -import withInvoiceActions from 'containers/Sales/Invoice/withInvoiceActions'; +/** + * Payment receive items table. + */ +export default function PaymentReceiveItemsTable() { + // Payment receive form context. + const { + isNewMode, + isDueInvoicesFetching, + paymentCustomerId, + dueInvoices, + } = usePaymentReceiveFormContext(); - -function PaymentReceiveItemsTable({ - // #ownProps - paymentReceiveId, - customerId, - fullAmount, - onUpdateData, - paymentReceiveEntries = [], - errors, - onClickClearAllLines, - onFetchEntriesSuccess, - - // #withInvoices - customerInvoiceEntries, - - // #withPaymentReceiveActions - requestFetchDueInvoices -}) { - const isNewMode = !paymentReceiveId; + // Payment receive entries form context. + const columns = usePaymentReceiveEntriesColumns(); // Detarmines takes payment receive invoices entries from customer receivable // invoices or associated payment receive invoices. - const computedTableData = useMemo(() => [ - ...(!isNewMode ? (paymentReceiveEntries || []) : []), - ...(isNewMode ? (customerInvoiceEntries || []) : []), - ], [ - isNewMode, - paymentReceiveEntries, - 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 }, + const computedTableData = useMemo( + () => [ + ...(!isNewMode ? [] || [] : []), + ...(isNewMode ? dueInvoices || [] : []), + ], + [isNewMode, dueInvoices], ); - // 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) => { - onFetchEntriesSuccess && onFetchEntriesSuccess(bills); - }, [onFetchEntriesSuccess]) + // No results message. + const noResultsMessage = paymentCustomerId + ? '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. - const handleUpdateData = useCallback((rows) => { - triggerUpdateData(rows); - setTableData(rows); - }, [triggerUpdateData]); + const handleUpdateData = useCallback((rows) => {}, []); - useEffect(() => { - const enabled = isNewMode && customerId; - - if (!fetchCustomerDueInvoices.isFetching && enabled) { - triggerOnFetchInvoicesSuccess(computedTableData); - } - }, [ - isNewMode, - customerId, - fetchCustomerDueInvoices.isFetching, - computedTableData, - triggerOnFetchInvoicesSuccess, - ]); + // Handle click clear all lines button. + const handleClickClearAllLines = () => { + + }; return ( - - + + + + } + totalRow={true} /> ); } - -export default compose( - withInvoices(({ customerInvoiceEntries }) => ({ - customerInvoiceEntries, - })), - withInvoiceActions, -)(PaymentReceiveItemsTable); diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTableEditor.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTableEditor.js deleted file mode 100644 index 579dd01e1..000000000 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTableEditor.js +++ /dev/null @@ -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 ; - } - 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 } }) => {index + 1}, - 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 ( - - - - } - totalRow={true} - /> - ); -} diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js new file mode 100644 index 000000000..e37d9d5d4 --- /dev/null +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js @@ -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 { moment(value).format('YYYY MMM DD') } +} + +/** + * Index table cell. + */ +function IndexCell({ row: { index } }) { + return ({index + 1}); +} + +/** + * 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 { formattedAmount(total, 'USD') }; +} + +/** + * Due amount footer cell. + */ +function DueAmountFooterCell({ rows }) { + const totalDueAmount = safeSumBy(rows, 'original.due_amount'); + return { formattedAmount(totalDueAmount, 'USD') }; +} + +/** + * Payment amount footer cell. + */ +function PaymentAmountFooterCell({ rows }) { + const totalPaymentAmount = safeSumBy(rows, 'original.payment_amount'); + return { formattedAmount(totalPaymentAmount, 'USD') }; +} + +/** + * 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], + ) +} \ No newline at end of file diff --git a/client/src/hooks/query/bills.js b/client/src/hooks/query/bills.js index e0971cb26..3697eeb00 100644 --- a/client/src/hooks/query/bills.js +++ b/client/src/hooks/query/bills.js @@ -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, []), + }; +} diff --git a/client/src/hooks/query/invoices.js b/client/src/hooks/query/invoices.js index ac206095d..75200b557 100644 --- a/client/src/hooks/query/invoices.js +++ b/client/src/hooks/query/invoices.js @@ -77,8 +77,8 @@ export function useInvoices(query, props) { total: 0, }, filterMeta: {}, - }) - } + }), + }; } /** @@ -87,27 +87,25 @@ export function useInvoices(query, props) { export function useDeliverInvoice(props) { const queryClient = useQueryClient(); - return useMutation( - (id) => ApiService.post(`sales/invoices/${id}/deliver`), - { - onSuccess: (res, id) => { - queryClient.invalidateQueries('SALE_INVOICES'); - queryClient.invalidateQueries(['SALE_INVOICE', id]); - }, - ...props, + return useMutation((id) => ApiService.post(`sales/invoices/${id}/deliver`), { + onSuccess: (res, id) => { + queryClient.invalidateQueries('SALE_INVOICES'); + queryClient.invalidateQueries(['SALE_INVOICE', id]); }, - ); + ...props, + }); } /** * Retrieve the sale invoice details. */ export function useInvoice(id, props) { - const states = useQuery(['SALE_INVOICE', id], () => - ApiService.get(`sales/invoices/${id}`), - { + const states = useQuery( + ['SALE_INVOICE', id], + () => ApiService.get(`sales/invoices/${id}`), + { select: (res) => res.data.sale_invoice, - ...props + ...props, }, ); @@ -116,3 +114,26 @@ export function useInvoice(id, props) { 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, []), + }; +} diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js index 00e736b57..98528683b 100644 --- a/client/src/routes/dashboard.js +++ b/client/src/routes/dashboard.js @@ -450,7 +450,7 @@ export default [ ), ), breadcrumb: 'New Payment Made', - pageTitle: formatMessage({ id: 'edit_payment_made' }), + pageTitle: formatMessage({ id: 'new_payment_made' }), sidebarShrink: true, backLink: true, },