mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 14:50:32 +00:00
feat: payment receive of customers invoices.
This commit is contained in:
@@ -16,6 +16,7 @@ const CLASSES = {
|
|||||||
PAGE_FORM_INVOICE: 'page-form--invoice',
|
PAGE_FORM_INVOICE: 'page-form--invoice',
|
||||||
PAGE_FORM_RECEIPT: 'page-form--receipt',
|
PAGE_FORM_RECEIPT: 'page-form--receipt',
|
||||||
PAGE_FORM_PAYMENT_MADE: 'page-form--payment-made',
|
PAGE_FORM_PAYMENT_MADE: 'page-form--payment-made',
|
||||||
|
PAGE_FORM_PAYMENT_RECEIVE: 'page-form--payment-receive',
|
||||||
|
|
||||||
CLOUD_SPINNER: 'cloud-spinner',
|
CLOUD_SPINNER: 'cloud-spinner',
|
||||||
IS_LOADING: 'is-loading',
|
IS_LOADING: 'is-loading',
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import {
|
|||||||
getBillPaginationMetaFactory,
|
getBillPaginationMetaFactory,
|
||||||
getBillTableQueryFactory,
|
getBillTableQueryFactory,
|
||||||
getVendorPayableBillsFactory,
|
getVendorPayableBillsFactory,
|
||||||
getPayableBillsByPaymentMadeFactory
|
getPayableBillsByPaymentMadeFactory,
|
||||||
|
getPaymentMadeFormPayableBillsFactory
|
||||||
} from 'store/Bills/bills.selectors';
|
} from 'store/Bills/bills.selectors';
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
@@ -14,6 +15,7 @@ export default (mapState) => {
|
|||||||
const getBillTableQuery = getBillTableQueryFactory();
|
const getBillTableQuery = getBillTableQueryFactory();
|
||||||
const getVendorPayableBills = getVendorPayableBillsFactory();
|
const getVendorPayableBills = getVendorPayableBillsFactory();
|
||||||
const getPayableBillsByPaymentMade = getPayableBillsByPaymentMadeFactory();
|
const getPayableBillsByPaymentMade = getPayableBillsByPaymentMadeFactory();
|
||||||
|
const getPaymentMadeFormPayableBills = getPaymentMadeFormPayableBillsFactory();
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const tableQuery = getBillTableQuery(state, props);
|
const tableQuery = getBillTableQuery(state, props);
|
||||||
@@ -28,8 +30,8 @@ export default (mapState) => {
|
|||||||
billsLoading: state.bills.loading,
|
billsLoading: state.bills.loading,
|
||||||
nextBillNumberChanged: state.bills.nextBillNumberChanged,
|
nextBillNumberChanged: state.bills.nextBillNumberChanged,
|
||||||
|
|
||||||
vendorPayableBills: getVendorPayableBills(state, props),
|
// vendorPayableBills: getVendorPayableBills(state, props),
|
||||||
paymentMadePayableBills: getPayableBillsByPaymentMade(state, props),
|
paymentMadePayableBills: getPaymentMadeFormPayableBills(state, props),
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -203,12 +203,10 @@ function PaymentMadeForm({
|
|||||||
},
|
},
|
||||||
[fullAmount, setAmountChangeAlert],
|
[fullAmount, setAmountChangeAlert],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle cancel button of amount change alert.
|
// Handle cancel button of amount change alert.
|
||||||
const handleCancelAmountChangeAlert = () => {
|
const handleCancelAmountChangeAlert = () => {
|
||||||
setAmountChangeAlert(false);
|
setAmountChangeAlert(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle confirm button of amount change alert.
|
// Handle confirm button of amount change alert.
|
||||||
const handleConfirmAmountChangeAlert = () => {
|
const handleConfirmAmountChangeAlert = () => {
|
||||||
setFullAmount(amountChangeAlert);
|
setFullAmount(amountChangeAlert);
|
||||||
@@ -286,7 +284,6 @@ function PaymentMadeForm({
|
|||||||
values={values}
|
values={values}
|
||||||
onFullAmountChanged={handleFullAmountChange}
|
onFullAmountChanged={handleFullAmountChange}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<PaymentMadeItemsTable
|
<PaymentMadeItemsTable
|
||||||
fullAmount={fullAmount}
|
fullAmount={fullAmount}
|
||||||
paymentEntries={values.entries}
|
paymentEntries={values.entries}
|
||||||
@@ -296,7 +293,6 @@ function PaymentMadeForm({
|
|||||||
onClickClearAllLines={handleClearAllLines}
|
onClickClearAllLines={handleClearAllLines}
|
||||||
errors={errors?.entries}
|
errors={errors?.entries}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Alert
|
<Alert
|
||||||
cancelButtonText={<T id={'cancel'} />}
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
confirmButtonText={<T id={'ok'} />}
|
confirmButtonText={<T id={'ok'} />}
|
||||||
|
|||||||
@@ -51,13 +51,13 @@ function PaymentMadeFormHeader({
|
|||||||
accountsList,
|
accountsList,
|
||||||
|
|
||||||
// #withBills
|
// #withBills
|
||||||
vendorPayableBills,
|
paymentMadePayableBills,
|
||||||
}) {
|
}) {
|
||||||
const isNewMode = !paymentMadeId;
|
const isNewMode = !paymentMadeId;
|
||||||
|
|
||||||
const payableFullAmount = useMemo(
|
const payableFullAmount = useMemo(
|
||||||
() => sumBy(vendorPayableBills, 'due_amount'),
|
() => sumBy(paymentMadePayableBills, 'due_amount'),
|
||||||
[vendorPayableBills],
|
[paymentMadePayableBills],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleDateChange = useCallback(
|
const handleDateChange = useCallback(
|
||||||
@@ -276,7 +276,7 @@ export default compose(
|
|||||||
withAccounts(({ accountsList }) => ({
|
withAccounts(({ accountsList }) => ({
|
||||||
accountsList,
|
accountsList,
|
||||||
})),
|
})),
|
||||||
withBills(({ vendorPayableBills }) => ({
|
withBills(({ paymentMadePayableBills }) => ({
|
||||||
vendorPayableBills,
|
paymentMadePayableBills,
|
||||||
})),
|
})),
|
||||||
)(PaymentMadeFormHeader);
|
)(PaymentMadeFormHeader);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
import { omit } from 'lodash';
|
||||||
import { CloudLoadingIndicator } from 'components'
|
import { CloudLoadingIndicator } from 'components'
|
||||||
import PaymentMadeItemsTableEditor from './PaymentMadeItemsTableEditor';
|
import PaymentMadeItemsTableEditor from './PaymentMadeItemsTableEditor';
|
||||||
|
|
||||||
@@ -26,7 +27,6 @@ function PaymentMadeItemsTable({
|
|||||||
requestFetchDueBills,
|
requestFetchDueBills,
|
||||||
|
|
||||||
// #withBills
|
// #withBills
|
||||||
vendorPayableBills,
|
|
||||||
paymentMadePayableBills,
|
paymentMadePayableBills,
|
||||||
|
|
||||||
// #withPaymentMadeDetail
|
// #withPaymentMadeDetail
|
||||||
@@ -35,20 +35,14 @@ function PaymentMadeItemsTable({
|
|||||||
const [tableData, setTableData] = useState([]);
|
const [tableData, setTableData] = useState([]);
|
||||||
const [localAmount, setLocalAmount] = useState(fullAmount);
|
const [localAmount, setLocalAmount] = useState(fullAmount);
|
||||||
|
|
||||||
// Payable bills based on selected vendor or specific payment made.
|
|
||||||
const payableBills = useMemo(
|
|
||||||
() =>
|
|
||||||
paymentMadeId
|
|
||||||
? paymentMadePayableBills
|
|
||||||
: vendorId
|
|
||||||
? vendorPayableBills
|
|
||||||
: [],
|
|
||||||
[paymentMadeId, paymentMadePayableBills, vendorId, vendorPayableBills],
|
|
||||||
);
|
|
||||||
const isNewMode = !paymentMadeId;
|
const isNewMode = !paymentMadeId;
|
||||||
|
|
||||||
const triggerUpdateData = useCallback((data) => {
|
const triggerUpdateData = useCallback((entries) => {
|
||||||
onUpdateData && onUpdateData(data);
|
const _data = entries.map((entry) => ({
|
||||||
|
bill_id: entry?.bill?.id,
|
||||||
|
...omit(entry, ['bill']),
|
||||||
|
}))
|
||||||
|
onUpdateData && onUpdateData(_data);
|
||||||
}, [onUpdateData]);
|
}, [onUpdateData]);
|
||||||
|
|
||||||
// Merges payment entries with payable bills.
|
// Merges payment entries with payable bills.
|
||||||
@@ -56,17 +50,16 @@ function PaymentMadeItemsTable({
|
|||||||
const entriesTable = new Map(
|
const entriesTable = new Map(
|
||||||
paymentEntries.map((e) => [e.bill_id, e]),
|
paymentEntries.map((e) => [e.bill_id, e]),
|
||||||
);
|
);
|
||||||
return payableBills.map((bill) => {
|
return paymentMadePayableBills.map((bill) => {
|
||||||
const entry = entriesTable.get(bill.id);
|
const entry = entriesTable.get(bill.id);
|
||||||
return {
|
return {
|
||||||
...bill,
|
bill,
|
||||||
bill_id: bill.id,
|
id: null,
|
||||||
bill_payment_amount: bill.payment_amount,
|
payment_number: 0,
|
||||||
payment_amount: entry ? entry.payment_amount : 0,
|
...(entry || {}),
|
||||||
id: entry ? entry.id : null,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [paymentEntries, payableBills]);
|
}, [paymentEntries, paymentMadePayableBills]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTableData(computedTableData);
|
setTableData(computedTableData);
|
||||||
@@ -127,11 +120,10 @@ function PaymentMadeItemsTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withPaymentMadeActions,
|
withPaymentMadeActions,
|
||||||
withBillActions,
|
withBillActions,
|
||||||
withBills(({ vendorPayableBills, paymentMadePayableBills }) => ({
|
withBills(({ paymentMadePayableBills }) => ({
|
||||||
vendorPayableBills,
|
|
||||||
paymentMadePayableBills,
|
paymentMadePayableBills,
|
||||||
})),
|
})),
|
||||||
)(PaymentMadeItemsTable);
|
)(PaymentMadeItemsTable);
|
||||||
|
|||||||
@@ -74,27 +74,27 @@ export default function PaymentMadeItemsTableEditor({
|
|||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'Date' }),
|
Header: formatMessage({ id: 'Date' }),
|
||||||
id: 'bill_date',
|
id: 'bill_date',
|
||||||
accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
|
accessor: (r) => moment(r.bill?.bill_date).format('YYYY MMM DD'),
|
||||||
Cell: CellRenderer(EmptyDiv, 'bill_date'),
|
Cell: CellRenderer(EmptyDiv, 'bill_date'),
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'bill_number' }),
|
Header: formatMessage({ id: 'bill_number' }),
|
||||||
accessor: (row) => `#${row.bill_number}`,
|
accessor: (row) => `#${row.bill?.bill_number}`,
|
||||||
Cell: CellRenderer(EmptyDiv, 'bill_number'),
|
Cell: CellRenderer(EmptyDiv, 'bill_number'),
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
className: 'bill_number',
|
className: 'bill_number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'bill_amount' }),
|
Header: formatMessage({ id: 'bill_amount' }),
|
||||||
accessor: 'amount',
|
accessor: r => r.bill?.amount,
|
||||||
Cell: CellRenderer(DivFieldCell, 'amount'),
|
Cell: CellRenderer(DivFieldCell, 'amount'),
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
className: '',
|
className: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: formatMessage({ id: 'amount_due' }),
|
Header: formatMessage({ id: 'amount_due' }),
|
||||||
accessor: 'due_amount',
|
accessor: r => r.bill?.due_amount,
|
||||||
Cell: TotalCellRederer(DivFieldCell, 'due_amount'),
|
Cell: TotalCellRederer(DivFieldCell, 'due_amount'),
|
||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
className: '',
|
className: '',
|
||||||
@@ -129,6 +129,8 @@ export default function PaymentMadeItemsTableEditor({
|
|||||||
columnId,
|
columnId,
|
||||||
value,
|
value,
|
||||||
);
|
);
|
||||||
|
newRows.splice(-1,1); // removes the total row.
|
||||||
|
|
||||||
setLocalData(newRows);
|
setLocalData(newRows);
|
||||||
onUpdateData && onUpdateData(newRows);
|
onUpdateData && onUpdateData(newRows);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
deleteInvoice,
|
deleteInvoice,
|
||||||
fetchInvoice,
|
fetchInvoice,
|
||||||
fetchInvoicesTable,
|
fetchInvoicesTable,
|
||||||
dueInvoices,
|
fetchDueInvoices,
|
||||||
} from 'store/Invoice/invoices.actions';
|
} from 'store/Invoice/invoices.actions';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ const mapDipatchToProps = (dispatch) => ({
|
|||||||
requestFetchInvoiceTable: (query = {}) =>
|
requestFetchInvoiceTable: (query = {}) =>
|
||||||
dispatch(fetchInvoicesTable({ query: { ...query } })),
|
dispatch(fetchInvoicesTable({ query: { ...query } })),
|
||||||
requestDeleteInvoice: (id) => dispatch(deleteInvoice({ id })),
|
requestDeleteInvoice: (id) => dispatch(deleteInvoice({ id })),
|
||||||
requestFetchDueInvoices: (id) => dispatch(dueInvoices({ id })),
|
requestFetchDueInvoices: (customerId) => dispatch(fetchDueInvoices({ customerId })),
|
||||||
changeInvoiceView: (id) =>
|
changeInvoiceView: (id) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.INVOICES_SET_CURRENT_VIEW,
|
type: t.INVOICES_SET_CURRENT_VIEW,
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import {
|
|||||||
getInvoiceCurrentPageFactory,
|
getInvoiceCurrentPageFactory,
|
||||||
getInvoicePaginationMetaFactory,
|
getInvoicePaginationMetaFactory,
|
||||||
getInvoiceTableQueryFactory,
|
getInvoiceTableQueryFactory,
|
||||||
getInvoiceTableQuery,
|
getCustomerReceivableInvoicesFactory,
|
||||||
getdueInvoices,
|
getPaymentReceivableInvoicesFactory,
|
||||||
|
getPaymentReceiveReceivableInvoicesFactory
|
||||||
} from 'store/Invoice/invoices.selector';
|
} from 'store/Invoice/invoices.selector';
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
@@ -14,6 +15,11 @@ export default (mapState) => {
|
|||||||
const getInvoicesPaginationMeta = getInvoicePaginationMetaFactory();
|
const getInvoicesPaginationMeta = getInvoicePaginationMetaFactory();
|
||||||
const getInvoiceTableQuery = getInvoiceTableQueryFactory();
|
const getInvoiceTableQuery = getInvoiceTableQueryFactory();
|
||||||
|
|
||||||
|
// const getPaymentReceivableInvoices = getPaymentReceivableInvoicesFactory();
|
||||||
|
// const getCustomerReceivableInvoices = getCustomerReceivableInvoicesFactory();
|
||||||
|
|
||||||
|
const getPaymentReceiveReceivableInvoices = getPaymentReceiveReceivableInvoicesFactory();
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const query = getInvoiceTableQuery(state, props);
|
const query = getInvoiceTableQuery(state, props);
|
||||||
|
|
||||||
@@ -24,7 +30,8 @@ export default (mapState) => {
|
|||||||
invoicesTableQuery: query,
|
invoicesTableQuery: query,
|
||||||
invoicesPageination: getInvoicesPaginationMeta(state, props, query),
|
invoicesPageination: getInvoicesPaginationMeta(state, props, query),
|
||||||
invoicesLoading: state.salesInvoices.loading,
|
invoicesLoading: state.salesInvoices.loading,
|
||||||
dueInvoices: getdueInvoices(state, props),
|
|
||||||
|
paymentReceiveReceivableInvoices: getPaymentReceiveReceivableInvoices(state, props),
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,29 +1,41 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Intent, Button } from '@blueprintjs/core';
|
import { Intent, Button } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
export default function PaymentReceiveFormFooter({
|
import { CLASSES } from 'common/classes';
|
||||||
formik: { isSubmitting, resetForm },
|
|
||||||
|
/**
|
||||||
|
* Payment receive floating actions bar.
|
||||||
|
*/
|
||||||
|
export default function PaymentReceiveFormFloatingActions({
|
||||||
|
isSubmitting,
|
||||||
onSubmitClick,
|
onSubmitClick,
|
||||||
onCancelClick,
|
onCancelClick,
|
||||||
onClearClick,
|
onClearClick,
|
||||||
paymentReceive,
|
paymentReceiveId,
|
||||||
}) {
|
}) {
|
||||||
|
const handleSubmitClick = (event) => {
|
||||||
|
onSubmitClick && onSubmitClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearBtnClick = (event) => {
|
||||||
|
onClearClick && onClearClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseBtnClick = (event) => {
|
||||||
|
onCancelClick && onCancelClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'estimate-form__floating-footer'}>
|
<div className={classNames(CLASSES.PAGE_FORM_FLOATING_ACTIONS)}>
|
||||||
<Button
|
<Button
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => {
|
onClick={handleSubmitClick}
|
||||||
onSubmitClick({ redirect: true });
|
>
|
||||||
}}
|
{paymentReceiveId ? <T id={'edit'} /> : <T id={'save_send'} />}
|
||||||
>
|
|
||||||
{paymentReceive && paymentReceive.id ? (
|
|
||||||
<T id={'edit'} />
|
|
||||||
) : (
|
|
||||||
<T id={'save_send'} />
|
|
||||||
)}
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -32,9 +44,7 @@ export default function PaymentReceiveFormFooter({
|
|||||||
className={'ml1'}
|
className={'ml1'}
|
||||||
name={'save'}
|
name={'save'}
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => {
|
onClick={handleSubmitClick}
|
||||||
onSubmitClick({ redirect: false });
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<T id={'save'} />
|
<T id={'save'} />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -42,18 +52,12 @@ export default function PaymentReceiveFormFooter({
|
|||||||
<Button
|
<Button
|
||||||
className={'ml1'}
|
className={'ml1'}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onClick={() => onClearClick && onClearClick()}
|
onClick={handleClearBtnClick}
|
||||||
>
|
>
|
||||||
<T id={'clear'} />
|
<T id={'clear'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button className={'ml1'} type="submit" onClick={handleCloseBtnClick}>
|
||||||
className={'ml1'}
|
|
||||||
type="submit"
|
|
||||||
onClick={() => {
|
|
||||||
onCancelClick && onCancelClick();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<T id={'close'} />
|
<T id={'close'} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,89 +9,41 @@ import React, {
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Intent, FormGroup, TextArea } from '@blueprintjs/core';
|
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T, useIntl } from 'react-intl';
|
||||||
import { pick, values } from 'lodash';
|
import { pick, sumBy } from 'lodash';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
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 withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
|
||||||
import withMediaActions from 'containers/Media/withMediaActions';
|
import withMediaActions from 'containers/Media/withMediaActions';
|
||||||
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||||
import withInvoices from '../Invoice/withInvoices';
|
|
||||||
import withPaymentReceiveDetail from './withPaymentReceiveDetail';
|
import withPaymentReceiveDetail from './withPaymentReceiveDetail';
|
||||||
import withPaymentReceives from './withPaymentReceives';
|
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import Dragzone from 'components/Dragzone';
|
|
||||||
import useMedia from 'hooks/useMedia';
|
|
||||||
|
|
||||||
import { compose, repeatValue } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
const MIN_LINES_NUMBER = 5;
|
|
||||||
|
|
||||||
function PaymentReceiveForm({
|
function PaymentReceiveForm({
|
||||||
//#withMedia
|
// #ownProps
|
||||||
requestSubmitMedia,
|
paymentReceiveId,
|
||||||
requestDeleteMedia,
|
|
||||||
|
|
||||||
//#WithPaymentReceiveActions
|
//#WithPaymentReceiveActions
|
||||||
requestSubmitPaymentReceive,
|
requestSubmitPaymentReceive,
|
||||||
requestEditPaymentReceive,
|
requestEditPaymentReceive,
|
||||||
|
|
||||||
//#withDashboard
|
// #withPaymentReceive
|
||||||
changePageTitle,
|
|
||||||
changePageSubtitle,
|
|
||||||
|
|
||||||
//#withPaymentReceiveDetail
|
|
||||||
paymentReceive,
|
paymentReceive,
|
||||||
paymentReceiveInvoices,
|
|
||||||
paymentReceivesItems,
|
|
||||||
|
|
||||||
//#OWn Props
|
|
||||||
// payment_receive,
|
|
||||||
onFormSubmit,
|
|
||||||
onCancelForm,
|
|
||||||
dueInvoiceLength,
|
|
||||||
onCustomerChange,
|
|
||||||
}) {
|
}) {
|
||||||
|
const [amountChangeAlert, setAmountChangeAlert] = useState(false);
|
||||||
|
const [clearLinesAlert, setClearLinesAlert] = useState(false);
|
||||||
|
const [fullAmount, setFullAmount] = useState(null);
|
||||||
|
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [payload, setPayload] = useState({});
|
|
||||||
const { id } = useParams();
|
|
||||||
const {
|
|
||||||
setFiles,
|
|
||||||
saveMedia,
|
|
||||||
deletedFiles,
|
|
||||||
setDeletedFiles,
|
|
||||||
deleteMedia,
|
|
||||||
} = useMedia({
|
|
||||||
saveCallback: requestSubmitMedia,
|
|
||||||
deleteCallback: requestDeleteMedia,
|
|
||||||
});
|
|
||||||
|
|
||||||
const savedMediaIds = useRef([]);
|
|
||||||
const clearSavedMediaIds = () => {
|
|
||||||
savedMediaIds.current = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (paymentReceive && paymentReceive.id) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
onCustomerChange && onCustomerChange(formik.values.customer_id);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (paymentReceive && paymentReceive.id) {
|
|
||||||
changePageTitle(formatMessage({ id: 'edit_payment_receive' }));
|
|
||||||
} else {
|
|
||||||
changePageTitle(formatMessage({ id: 'payment_receive' }));
|
|
||||||
}
|
|
||||||
}, [changePageTitle, paymentReceive, formatMessage]);
|
|
||||||
|
|
||||||
|
// Form validation schema.
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
customer_id: Yup.string()
|
customer_id: Yup.string()
|
||||||
.label(formatMessage({ id: 'customer_name_' }))
|
.label(formatMessage({ id: 'customer_name_' }))
|
||||||
@@ -102,9 +54,7 @@ function PaymentReceiveForm({
|
|||||||
deposit_account_id: Yup.number()
|
deposit_account_id: Yup.number()
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'deposit_account_' })),
|
.label(formatMessage({ id: 'deposit_account_' })),
|
||||||
// receive_amount: Yup.number()
|
full_amount: Yup.number().nullable(),
|
||||||
// .required()
|
|
||||||
// .label(formatMessage({ id: 'receive_amount_' })),
|
|
||||||
payment_receive_no: Yup.number()
|
payment_receive_no: Yup.number()
|
||||||
.required()
|
.required()
|
||||||
.label(formatMessage({ id: 'payment_receive_no_' })),
|
.label(formatMessage({ id: 'payment_receive_no_' })),
|
||||||
@@ -112,11 +62,9 @@ function PaymentReceiveForm({
|
|||||||
description: Yup.string().nullable(),
|
description: Yup.string().nullable(),
|
||||||
entries: Yup.array().of(
|
entries: Yup.array().of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
payment_amount: Yup.number().nullable(),
|
id: Yup.number().nullable(),
|
||||||
invoice_no: Yup.number().nullable(),
|
|
||||||
balance: Yup.number().nullable(),
|
|
||||||
due_amount: Yup.number().nullable(),
|
due_amount: Yup.number().nullable(),
|
||||||
invoice_date: Yup.date(),
|
payment_amount: Yup.number().nullable().max(Yup.ref('due_amount')),
|
||||||
invoice_id: Yup.number()
|
invoice_id: Yup.number()
|
||||||
.nullable()
|
.nullable()
|
||||||
.when(['payment_amount'], {
|
.when(['payment_amount'], {
|
||||||
@@ -126,15 +74,7 @@ function PaymentReceiveForm({
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
// Default payment receive.
|
||||||
const handleDropFiles = useCallback((_files) => {
|
|
||||||
setFiles(_files.filter((file) => file.uploaded === false));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const savePaymentReceiveSubmit = useCallback((payload) => {
|
|
||||||
onFormSubmit && onFormSubmit(payload);
|
|
||||||
});
|
|
||||||
|
|
||||||
const defaultPaymentReceive = useMemo(
|
const defaultPaymentReceive = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
invoice_id: '',
|
invoice_id: '',
|
||||||
@@ -146,6 +86,12 @@ function PaymentReceiveForm({
|
|||||||
}),
|
}),
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
const defaultPaymentReceiveEntry = {
|
||||||
|
id: null,
|
||||||
|
payment_amount: null,
|
||||||
|
invoice_id: null,
|
||||||
|
};
|
||||||
|
// Form initial values.
|
||||||
const defaultInitialValues = useMemo(
|
const defaultInitialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
customer_id: '',
|
customer_id: '',
|
||||||
@@ -153,20 +99,13 @@ function PaymentReceiveForm({
|
|||||||
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
reference_no: '',
|
reference_no: '',
|
||||||
payment_receive_no: '',
|
payment_receive_no: '',
|
||||||
// receive_amount: '',
|
|
||||||
description: '',
|
description: '',
|
||||||
entries: [...repeatValue(defaultPaymentReceive, MIN_LINES_NUMBER)],
|
entries: [],
|
||||||
}),
|
}),
|
||||||
[defaultPaymentReceive],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const orderingIndex = (_entries) => {
|
// Form initial values.
|
||||||
return _entries.map((item, index) => ({
|
|
||||||
...item,
|
|
||||||
index: index + 1,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues = useMemo(
|
const initialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...(paymentReceive
|
...(paymentReceive
|
||||||
@@ -176,167 +115,205 @@ function PaymentReceiveForm({
|
|||||||
...paymentReceive.entries.map((paymentReceive) => ({
|
...paymentReceive.entries.map((paymentReceive) => ({
|
||||||
...pick(paymentReceive, Object.keys(defaultPaymentReceive)),
|
...pick(paymentReceive, Object.keys(defaultPaymentReceive)),
|
||||||
})),
|
})),
|
||||||
...repeatValue(
|
|
||||||
defaultPaymentReceive,
|
|
||||||
Math.max(MIN_LINES_NUMBER - paymentReceive.entries.length, 0),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...defaultInitialValues,
|
...defaultInitialValues,
|
||||||
entries: orderingIndex(defaultInitialValues.entries),
|
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[paymentReceive, defaultInitialValues, defaultPaymentReceive],
|
[paymentReceive, defaultInitialValues, defaultPaymentReceive],
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialAttachmentFiles = useMemo(() => {
|
// Handle form submit.
|
||||||
return paymentReceive && paymentReceive.media
|
const handleSubmitForm = (
|
||||||
? paymentReceive.media.map((attach) => ({
|
values,
|
||||||
preview: attach.attachment_file,
|
{ setSubmitting, resetForm, setFieldError },
|
||||||
uploaded: true,
|
) => {
|
||||||
metadata: { ...attach },
|
setSubmitting(true);
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
}, [paymentReceive]);
|
|
||||||
|
|
||||||
const formik = useFormik({
|
// Filters entries that have no `invoice_id` and `payment_amount`.
|
||||||
|
const entries = values.entries
|
||||||
|
.filter((entry) => entry.invoice_id && entry.payment_amount)
|
||||||
|
.map((entry) => ({
|
||||||
|
...pick(entry, Object.keys(defaultPaymentReceiveEntry)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Calculates the total payment amount of entries.
|
||||||
|
const totalPaymentAmount = sumBy(entries, 'payment_amount');
|
||||||
|
|
||||||
|
if (totalPaymentAmount <= 0) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'you_cannot_make_payment_with_zero_total_amount',
|
||||||
|
intent: Intent.WARNING,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const form = { ...values, entries };
|
||||||
|
|
||||||
|
// Handle request response success.
|
||||||
|
const onSaved = (response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: paymentReceiveId
|
||||||
|
? 'the_payment_has_been_received_successfully_edited'
|
||||||
|
: 'the_payment_has_been_received_successfully_created',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
|
resetForm();
|
||||||
|
};
|
||||||
|
// Handle request response errors.
|
||||||
|
const onError = (errors) => {
|
||||||
|
const getError = (errorType) => errors.find((e) => e.type === errorType);
|
||||||
|
|
||||||
|
if (getError('PAYMENT_RECEIVE_NO_EXISTS')) {
|
||||||
|
setFieldError(
|
||||||
|
'payment_receive_no',
|
||||||
|
formatMessage({ id: 'payment_number_is_not_unique' }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (paymentReceiveId) {
|
||||||
|
requestEditPaymentReceive(paymentReceiveId, form)
|
||||||
|
.then(onSaved)
|
||||||
|
.catch(onError);
|
||||||
|
} else {
|
||||||
|
requestSubmitPaymentReceive(form).then(onSaved).catch(onError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
errors,
|
||||||
|
values,
|
||||||
|
setFieldValue,
|
||||||
|
getFieldProps,
|
||||||
|
setValues,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
touched,
|
||||||
|
} = useFormik({
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
initialValues: {
|
initialValues: {
|
||||||
...initialValues,
|
...initialValues,
|
||||||
},
|
},
|
||||||
onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
|
onSubmit: handleSubmitForm,
|
||||||
setSubmitting(true);
|
|
||||||
const entries = formik.values.entries.filter((item) => {
|
|
||||||
if (item.invoice_id !== undefined) {
|
|
||||||
return { ...item };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const form = {
|
|
||||||
...values,
|
|
||||||
entries,
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestForm = { ...form };
|
|
||||||
|
|
||||||
if (paymentReceive && paymentReceive.id) {
|
|
||||||
requestEditPaymentReceive(paymentReceive.id, requestForm)
|
|
||||||
.then((response) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_payment_receive_has_been_successfully_edited',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
setSubmitting(false);
|
|
||||||
savePaymentReceiveSubmit({ action: 'update', ...payload });
|
|
||||||
resetForm();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setSubmitting(false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
requestSubmitPaymentReceive(requestForm)
|
|
||||||
.then((response) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_payment_receive_has_been_successfully_created',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
setSubmitting(false);
|
|
||||||
resetForm();
|
|
||||||
savePaymentReceiveSubmit({ action: 'new', ...payload });
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
setSubmitting(false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDeleteFile = useCallback(
|
// Handle update data.
|
||||||
(_deletedFiles) => {
|
const handleUpdataData = useCallback(
|
||||||
_deletedFiles.forEach((deletedFile) => {
|
(entries) => {
|
||||||
if (deletedFile.upload && deletedFile.metadata.id) {
|
setFieldValue('entries', entries);
|
||||||
setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
[setDeletedFiles, deletedFiles],
|
[setFieldValue],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSubmitClick = useCallback(
|
const handleFullAmountChange = useCallback(
|
||||||
(payload) => {
|
(value) => {
|
||||||
setPayload(payload);
|
if (value !== fullAmount) {
|
||||||
formik.submitForm();
|
setAmountChangeAlert(value);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[setPayload, formik],
|
[fullAmount, setAmountChangeAlert],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCancelClick = useCallback(
|
// Handle clear all lines button click.
|
||||||
(payload) => {
|
const handleClearAllLines = useCallback(() => {
|
||||||
onCancelForm && onCancelForm(payload);
|
setClearLinesAlert(true);
|
||||||
},
|
}, [setClearLinesAlert]);
|
||||||
[onCancelForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClearClick = () => {
|
// Handle cancel button click of clear lines alert
|
||||||
formik.resetForm();
|
const handleCancelClearLines = useCallback(() => {
|
||||||
|
setClearLinesAlert(false);
|
||||||
|
}, [setClearLinesAlert]);
|
||||||
|
|
||||||
|
// Handle cancel button of amount change alert.
|
||||||
|
const handleCancelAmountChangeAlert = () => {
|
||||||
|
setAmountChangeAlert(false);
|
||||||
};
|
};
|
||||||
|
// Handle confirm button of amount change alert.
|
||||||
const handleClickAddNewRow = () => {
|
const handleConfirmAmountChangeAlert = () => {
|
||||||
formik.setFieldValue(
|
setFullAmount(amountChangeAlert);
|
||||||
|
setAmountChangeAlert(false);
|
||||||
|
};
|
||||||
|
// Handle confirm clear all lines.
|
||||||
|
const handleConfirmClearLines = useCallback(() => {
|
||||||
|
setFieldValue(
|
||||||
'entries',
|
'entries',
|
||||||
orderingIndex([...formik.values.entries, defaultPaymentReceive]),
|
values.entries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
payment_amount: 0,
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
};
|
setClearLinesAlert(false);
|
||||||
|
}, [setFieldValue, setClearLinesAlert, values.entries]);
|
||||||
|
|
||||||
const handleClearAllLines = () => {
|
|
||||||
formik.setFieldValue(
|
|
||||||
'entries',
|
|
||||||
orderingIndex([...repeatValue(defaultPaymentReceive, MIN_LINES_NUMBER)]),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'payment_receive_form'}>
|
<div className={classNames(
|
||||||
<form onSubmit={formik.handleSubmit}>
|
CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_PAYMENT_RECEIVE
|
||||||
<PaymentReceiveHeader formik={formik} />
|
)}>
|
||||||
<PaymentReceiveItemsTable
|
<form onSubmit={handleSubmit}>
|
||||||
entries={formik.values.entries}
|
<PaymentReceiveHeader
|
||||||
customer_id={formik.values.customer_id}
|
errors={errors}
|
||||||
onClickAddNewRow={handleClickAddNewRow}
|
touched={touched}
|
||||||
onClickClearAllLines={handleClearAllLines}
|
setFieldValue={setFieldValue}
|
||||||
formik={formik}
|
getFieldProps={getFieldProps}
|
||||||
invoices={paymentReceiveInvoices}
|
values={values}
|
||||||
|
paymentReceiveId={paymentReceiveId}
|
||||||
|
customerId={values.customer_id}
|
||||||
|
onFullAmountChanged={handleFullAmountChange}
|
||||||
/>
|
/>
|
||||||
{/* <Dragzone
|
<PaymentReceiveItemsTable
|
||||||
initialFiles={initialAttachmentFiles}
|
paymentReceiveId={paymentReceiveId}
|
||||||
onDrop={handleDropFiles}
|
customerId={values.customer_id}
|
||||||
onDeleteFile={handleDeleteFile}
|
fullAmount={fullAmount}
|
||||||
hint={'Attachments: Maxiumum size: 20MB'}
|
onUpdateData={handleUpdataData}
|
||||||
/> */}
|
paymentEntries={values.entries}
|
||||||
</form>
|
errors={errors?.entries}
|
||||||
|
onClickClearAllLines={handleClearAllLines}
|
||||||
|
/>
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'ok'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={amountChangeAlert}
|
||||||
|
onCancel={handleCancelAmountChangeAlert}
|
||||||
|
onConfirm={handleConfirmAmountChangeAlert}
|
||||||
|
>
|
||||||
|
<p>Are you sure to discard full amount?</p>
|
||||||
|
</Alert>
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'ok'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={clearLinesAlert}
|
||||||
|
onCancel={handleCancelClearLines}
|
||||||
|
onConfirm={handleConfirmClearLines}
|
||||||
|
>
|
||||||
|
<p>Are you sure to discard full amount?</p>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
<PaymentReceiveFloatingActions
|
<PaymentReceiveFloatingActions
|
||||||
formik={formik}
|
isSubmitting={isSubmitting}
|
||||||
onSubmitClick={handleSubmitClick}
|
paymentReceiveId={paymentReceiveId}
|
||||||
paymentReceive={paymentReceive}
|
/>
|
||||||
onCancel={handleCancelClick}
|
</form>
|
||||||
onClearClick={handleClearClick}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withPaymentReceivesActions,
|
withPaymentReceivesActions,
|
||||||
withDashboardActions,
|
|
||||||
withMediaActions,
|
withMediaActions,
|
||||||
withPaymentReceives(({ paymentReceivesItems }) => ({
|
// withPaymentReceives(({ paymentReceivesItems }) => ({
|
||||||
paymentReceivesItems,
|
// paymentReceivesItems,
|
||||||
|
// })),
|
||||||
|
withPaymentReceiveDetail(({ paymentReceive }) => ({
|
||||||
|
paymentReceive,
|
||||||
})),
|
})),
|
||||||
withPaymentReceiveDetail(),
|
|
||||||
)(PaymentReceiveForm);
|
)(PaymentReceiveForm);
|
||||||
|
|||||||
@@ -5,35 +5,54 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
Position,
|
Position,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Classes,
|
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
|
|
||||||
import { DateInput } from '@blueprintjs/datetime';
|
import { DateInput } from '@blueprintjs/datetime';
|
||||||
import { FormattedMessage as T } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
import { sumBy } from 'lodash';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes'
|
||||||
|
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
||||||
import {
|
import {
|
||||||
AccountsSelectList,
|
AccountsSelectList,
|
||||||
ListSelect,
|
ListSelect,
|
||||||
ErrorMessage,
|
ErrorMessage,
|
||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
|
Hint,
|
||||||
|
Money,
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
|
||||||
|
import withInvoices from 'containers/Sales/Invoice/withInvoices';
|
||||||
import withCustomers from 'containers/Customers/withCustomers';
|
import withCustomers from 'containers/Customers/withCustomers';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
|
|
||||||
function PaymentReceiveFormHeader({
|
function PaymentReceiveFormHeader({
|
||||||
formik: { errors, touched, setFieldValue, getFieldProps, values },
|
// #useFormik
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
setFieldValue,
|
||||||
|
getFieldProps,
|
||||||
|
values,
|
||||||
|
onFullAmountChanged,
|
||||||
|
paymentReceiveId,
|
||||||
|
customerId,
|
||||||
|
|
||||||
//#withCustomers
|
//#withCustomers
|
||||||
customers,
|
customers,
|
||||||
|
|
||||||
//#withAccouts
|
//#withAccouts
|
||||||
accountsList,
|
accountsList,
|
||||||
|
|
||||||
|
// #withInvoices
|
||||||
|
receivableInvoices,
|
||||||
}) {
|
}) {
|
||||||
|
// Compute the total receivable amount.
|
||||||
|
const receivableFullAmount = useMemo(
|
||||||
|
() => sumBy(receivableInvoices, 'due_amount'),
|
||||||
|
[receivableInvoices],
|
||||||
|
);
|
||||||
const handleDateChange = useCallback(
|
const handleDateChange = useCallback(
|
||||||
(date_filed) => (date) => {
|
(date_filed) => (date) => {
|
||||||
const formatted = moment(date).format('YYYY-MM-DD');
|
const formatted = moment(date).format('YYYY-MM-DD');
|
||||||
@@ -82,14 +101,28 @@ function PaymentReceiveFormHeader({
|
|||||||
[accountsList],
|
[accountsList],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const triggerFullAmountChanged = (value) => {
|
||||||
|
onFullAmountChanged && onFullAmountChanged(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle full amount changed event.
|
||||||
|
const handleFullAmountBlur = (event) => {
|
||||||
|
triggerFullAmountChanged(event.currentTarget.value);
|
||||||
|
};
|
||||||
|
// Handle link click of receive full amount.
|
||||||
|
const handleReceiveFullAmountClick = () => {
|
||||||
|
setFieldValue('full_amount', receivableFullAmount);
|
||||||
|
triggerFullAmountChanged(receivableFullAmount);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
|
||||||
<div>
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
||||||
{/* Customer name */}
|
{/* ------------- Customer name ------------- */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'customer_name'} />}
|
label={<T id={'customer_name'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
className={classNames('form-group--select-list', CLASSES.FILL)}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={errors.customer_id && touched.customer_id && Intent.DANGER}
|
intent={errors.customer_id && touched.customer_id && Intent.DANGER}
|
||||||
helperText={
|
helperText={
|
||||||
@@ -110,12 +143,12 @@ function PaymentReceiveFormHeader({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{/* Payment date */}
|
{/* ------------- Payment date ------------- */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'payment_date'} />}
|
label={<T id={'payment_date'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
className={classNames('form-group--select-list', CLASSES.FILL)}
|
||||||
intent={errors.payment_date && touched.payment_date && Intent.DANGER}
|
intent={errors.payment_date && touched.payment_date && Intent.DANGER}
|
||||||
helperText={
|
helperText={
|
||||||
<ErrorMessage name="payment_date" {...{ errors, touched }} />
|
<ErrorMessage name="payment_date" {...{ errors, touched }} />
|
||||||
@@ -129,11 +162,39 @@ function PaymentReceiveFormHeader({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{/* payment receive no */}
|
{/* ------------ Full amount ------------ */}
|
||||||
|
<FormGroup
|
||||||
|
label={<T id={'full_amount'} />}
|
||||||
|
inline={true}
|
||||||
|
className={('form-group--full-amount', CLASSES.FILL)}
|
||||||
|
intent={
|
||||||
|
errors.full_amount && touched.full_amount && Intent.DANGER
|
||||||
|
}
|
||||||
|
labelInfo={<Hint />}
|
||||||
|
helperText={
|
||||||
|
<ErrorMessage name="full_amount" {...{ errors, touched }} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<InputGroup
|
||||||
|
intent={
|
||||||
|
errors.full_amount && touched.full_amount && Intent.DANGER
|
||||||
|
}
|
||||||
|
minimal={true}
|
||||||
|
value={values.full_amount}
|
||||||
|
{...getFieldProps('full_amount')}
|
||||||
|
onBlur={handleFullAmountBlur}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<a onClick={handleReceiveFullAmountClick} href="#" className={'receive-full-amount'}>
|
||||||
|
Receive full amount (<Money amount={receivableFullAmount} currency={'USD'} />)
|
||||||
|
</a>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{/* ------------ Payment receive no. ------------ */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'payment_receive_no'} />}
|
label={<T id={'payment_receive_no'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
className={('form-group--payment_receive_no', Classes.FILL)}
|
className={('form-group--payment_receive_no', CLASSES.FILL)}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
intent={
|
intent={
|
||||||
errors.payment_receive_no &&
|
errors.payment_receive_no &&
|
||||||
@@ -155,13 +216,13 @@ function PaymentReceiveFormHeader({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{/* deposit account */}
|
{/* ------------ Deposit account ------------ */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'deposit_to'} />}
|
label={<T id={'deposit_to'} />}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'form-group--deposit_account_id',
|
'form-group--deposit_account_id',
|
||||||
'form-group--select-list',
|
'form-group--select-list',
|
||||||
Classes.FILL,
|
CLASSES.FILL,
|
||||||
)}
|
)}
|
||||||
inline={true}
|
inline={true}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
@@ -185,45 +246,22 @@ function PaymentReceiveFormHeader({
|
|||||||
selectedAccountId={values.deposit_account_id}
|
selectedAccountId={values.deposit_account_id}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Receive amount */}
|
{/* ------------ Reference No. ------------ */}
|
||||||
|
<FormGroup
|
||||||
{/* <FormGroup
|
label={<T id={'reference'} />}
|
||||||
label={<T id={'receive_amount'} />}
|
inline={true}
|
||||||
inline={true}
|
className={classNames('form-group--reference', CLASSES.FILL)}
|
||||||
labelInfo={<FieldRequiredHint />}
|
|
||||||
className={classNames('form-group--', Classes.FILL)}
|
|
||||||
intent={
|
|
||||||
errors.receive_amount && touched.receive_amount && Intent.DANGER
|
|
||||||
}
|
|
||||||
helperText={
|
|
||||||
<ErrorMessage name="receive_amount" {...{ errors, touched }} />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
intent={
|
|
||||||
errors.receive_amount && touched.receive_amount && Intent.DANGER
|
|
||||||
}
|
|
||||||
minimal={true}
|
|
||||||
{...getFieldProps('receive_amount')}
|
|
||||||
/>
|
|
||||||
</FormGroup> */}
|
|
||||||
|
|
||||||
{/* reference_no */}
|
|
||||||
<FormGroup
|
|
||||||
label={<T id={'reference'} />}
|
|
||||||
inline={true}
|
|
||||||
className={classNames('form-group--reference', Classes.FILL)}
|
|
||||||
intent={errors.reference_no && touched.reference_no && Intent.DANGER}
|
|
||||||
helperText={<ErrorMessage name="reference" {...{ errors, touched }} />}
|
|
||||||
>
|
|
||||||
<InputGroup
|
|
||||||
intent={errors.reference_no && touched.reference_no && Intent.DANGER}
|
intent={errors.reference_no && touched.reference_no && Intent.DANGER}
|
||||||
minimal={true}
|
helperText={<ErrorMessage name="reference" {...{ errors, touched }} />}
|
||||||
{...getFieldProps('reference_no')}
|
>
|
||||||
/>
|
<InputGroup
|
||||||
</FormGroup>
|
intent={errors.reference_no && touched.reference_no && Intent.DANGER}
|
||||||
|
minimal={true}
|
||||||
|
{...getFieldProps('reference_no')}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -235,4 +273,7 @@ export default compose(
|
|||||||
withAccounts(({ accountsList }) => ({
|
withAccounts(({ accountsList }) => ({
|
||||||
accountsList,
|
accountsList,
|
||||||
})),
|
})),
|
||||||
|
withInvoices(({ paymentReceiveReceivableInvoices }) => ({
|
||||||
|
receivableInvoices: paymentReceiveReceivableInvoices,
|
||||||
|
})),
|
||||||
)(PaymentReceiveFormHeader);
|
)(PaymentReceiveFormHeader);
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
import { useQuery } from 'react-query';
|
||||||
|
|
||||||
|
import { DashboardInsider } from 'components'
|
||||||
|
|
||||||
|
// import PaymentReceiveForm from './PaymentReceiveForm';
|
||||||
|
import withDashboardActions from "containers/Dashboard/withDashboardActions";
|
||||||
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
|
import withSettingsActions from 'containers/Settings/withSettingsActions';
|
||||||
|
import withPaymentReceivesActions from './withPaymentReceivesActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment receive form page.
|
||||||
|
*/
|
||||||
|
function PaymentReceiveFormPage({
|
||||||
|
|
||||||
|
// #withDashboardAction
|
||||||
|
changePageTitle,
|
||||||
|
|
||||||
|
// #withAccountsActions
|
||||||
|
requestFetchAccounts,
|
||||||
|
|
||||||
|
// #withSettingsActions
|
||||||
|
requestFetchOptions,
|
||||||
|
|
||||||
|
// #withPaymentReceivesActions
|
||||||
|
requestFetchPaymentReceive
|
||||||
|
|
||||||
|
// #withCustomersActions
|
||||||
|
requestFetchCustomers
|
||||||
|
}) {
|
||||||
|
const { id: paymentReceiveId } = useParams();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (paymentReceiveId) {
|
||||||
|
changePageTitle(formatMessage({ id: 'edit_payment_receive' }));
|
||||||
|
} else {
|
||||||
|
changePageTitle(formatMessage({ id: 'payment_receive' }));
|
||||||
|
}
|
||||||
|
}, [changePageTitle, paymentReceiveId, formatMessage]);
|
||||||
|
|
||||||
|
// Fetches payment recevie details.
|
||||||
|
const fetchPaymentReceive = useQuery(
|
||||||
|
['payment-receive', paymentReceiveId],
|
||||||
|
(key, _id) => requestFetchPaymentReceive(_id),
|
||||||
|
{ enabled: paymentReceiveId },
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handle fetch accounts data.
|
||||||
|
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||||
|
requestFetchAccounts(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fetch payment made settings.
|
||||||
|
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
|
||||||
|
|
||||||
|
// Fetches customers list.
|
||||||
|
const fetchCustomers = useQuery(
|
||||||
|
['customers-list'], () => requestFetchCustomers(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DashboardInsider
|
||||||
|
loading={
|
||||||
|
fetchPaymentReceive.isFetching ||
|
||||||
|
fetchAccounts.isFetching ||
|
||||||
|
fetchSettings.isFetching ||
|
||||||
|
fetchCustomers.isFetching
|
||||||
|
}>
|
||||||
|
{/* <PaymentReceiveForm
|
||||||
|
paymentReceiveId={paymentReceiveId}
|
||||||
|
/> */}
|
||||||
|
</DashboardInsider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withDashboardActions,
|
||||||
|
withAccountsActions,
|
||||||
|
withSettingsActions,
|
||||||
|
withPaymentReceivesActions,
|
||||||
|
withCustomersActions,
|
||||||
|
)(PaymentReceiveFormPage);
|
||||||
@@ -1,258 +1,132 @@
|
|||||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
||||||
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
|
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { FormattedMessage as T } from 'react-intl';
|
||||||
import { Icon, DataTable } from 'components';
|
import { Icon, CloudLoadingIndicator } from 'components';
|
||||||
import moment from 'moment';
|
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
import { omit } from 'lodash';
|
||||||
|
|
||||||
|
import { compose, formattedAmount } from 'utils';
|
||||||
|
|
||||||
import { compose, formattedAmount, transformUpdatedRows } from 'utils';
|
|
||||||
import {
|
|
||||||
InputGroupCell,
|
|
||||||
MoneyFieldCell,
|
|
||||||
ItemsListCell,
|
|
||||||
DivFieldCell,
|
|
||||||
EmptyDiv,
|
|
||||||
} from 'components/DataTableCells';
|
|
||||||
import withInvoices from '../Invoice/withInvoices';
|
import withInvoices from '../Invoice/withInvoices';
|
||||||
import withInvoiceActions from '../Invoice/withInvoiceActions';
|
import PaymentReceiveItemsTableEditor from './PaymentReceiveItemsTableEditor';
|
||||||
|
import withInvoiceActions from 'containers/Sales/Invoice/withInvoiceActions';
|
||||||
|
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
|
||||||
import { useUpdateEffect } from 'hooks';
|
|
||||||
|
|
||||||
import { omit, pick } from 'lodash';
|
|
||||||
|
|
||||||
const ActionsCellRenderer = ({
|
|
||||||
row: { index },
|
|
||||||
column: { id },
|
|
||||||
cell: { value },
|
|
||||||
data,
|
|
||||||
payload,
|
|
||||||
}) => {
|
|
||||||
if (data.length <= index + 1) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const onRemoveRole = () => {
|
|
||||||
payload.removeRow(index);
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<Tooltip content={<T id={'remove_the_line'} />} position={Position.LEFT}>
|
|
||||||
<Button
|
|
||||||
icon={<Icon icon={'times-circle'} iconSize={14} />}
|
|
||||||
iconSize={14}
|
|
||||||
className="m12"
|
|
||||||
intent={Intent.DANGER}
|
|
||||||
onClick={onRemoveRole}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
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) {
|
|
||||||
const total = props.data.reduce((total, entry) => {
|
|
||||||
const amount = parseInt(entry[type], 10);
|
|
||||||
const computed = amount ? total + amount : total;
|
|
||||||
|
|
||||||
return computed;
|
|
||||||
}, 0);
|
|
||||||
return <span>{formattedAmount(total, 'USD')}</span>;
|
|
||||||
}
|
|
||||||
return content(props);
|
|
||||||
};
|
|
||||||
|
|
||||||
function PaymentReceiveItemsTable({
|
function PaymentReceiveItemsTable({
|
||||||
//#ownProps
|
// #ownProps
|
||||||
onClickRemoveRow,
|
paymentReceiveId,
|
||||||
onClickAddNewRow,
|
customerId,
|
||||||
|
fullAmount,
|
||||||
|
onUpdateData,
|
||||||
|
paymentEntries = [],// { invoice_id: number, payment_amount: number, id?: number }
|
||||||
|
errors,
|
||||||
onClickClearAllLines,
|
onClickClearAllLines,
|
||||||
entries,
|
|
||||||
formik: { errors, setFieldValue, values },
|
|
||||||
|
|
||||||
dueInvoices,
|
// #withInvoices
|
||||||
customer_id,
|
paymentReceiveReceivableInvoices,
|
||||||
invoices,
|
|
||||||
|
// #withPaymentReceive
|
||||||
|
receivableInvoices,
|
||||||
|
|
||||||
|
// #withPaymentReceiveActions
|
||||||
|
requestFetchDueInvoices
|
||||||
}) {
|
}) {
|
||||||
const [rows, setRows] = useState([]);
|
const isNewMode = !paymentReceiveId;
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
const [tableData, setTableData] = useState([]);
|
||||||
|
const [localAmount, setLocalAmount] = useState(fullAmount);
|
||||||
|
|
||||||
|
const computedTableData = useMemo(() => {
|
||||||
|
const entriesTable = new Map(
|
||||||
|
paymentEntries.map((e) => [e.invoice_id, e]),
|
||||||
|
);
|
||||||
|
return receivableInvoices.map((invoice) => {
|
||||||
|
const entry = entriesTable.get(invoice.id);
|
||||||
|
|
||||||
|
return {
|
||||||
|
invoice,
|
||||||
|
id: null,
|
||||||
|
payment_amount: 0,
|
||||||
|
...(entry || {}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [
|
||||||
|
receivableInvoices,
|
||||||
|
paymentEntries,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRows([...dueInvoices.map((e) => ({ ...e })), ...invoices, {}]);
|
setTableData(computedTableData);
|
||||||
}, [invoices]);
|
}, [computedTableData]);
|
||||||
|
|
||||||
// useEffect(() => {
|
// Triggers `onUpdateData` prop event.
|
||||||
// setRows([...dueInvoices.map((e) => ({ ...e })), {}]);
|
const triggerUpdateData = useCallback((entries) => {
|
||||||
|
const _data = entries.map((entry) => ({
|
||||||
|
invoice_id: entry?.invoice?.id,
|
||||||
|
...omit(entry, ['invoice']),
|
||||||
|
due_amount: entry?.invoice?.due_amount,
|
||||||
|
}))
|
||||||
|
onUpdateData && onUpdateData(_data);
|
||||||
|
}, [onUpdateData]);
|
||||||
|
|
||||||
// setEntrie([
|
useEffect(() => {
|
||||||
// ...dueInvoices.map((e) => {
|
if (localAmount !== fullAmount) {
|
||||||
// return { id: e.id, payment_amount: e.payment_amount };
|
let _fullAmount = fullAmount;
|
||||||
// }),
|
|
||||||
// ]);
|
const newTableData = tableData.map((data) => {
|
||||||
// }, [dueInvoices]);
|
const amount = Math.min(data?.invoice?.due_amount, _fullAmount);
|
||||||
|
_fullAmount -= Math.max(amount, 0);
|
||||||
|
|
||||||
const columns = useMemo(
|
return {
|
||||||
() => [
|
...data,
|
||||||
{
|
payment_amount: amount,
|
||||||
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) => `#${row.invoice_no}`,
|
|
||||||
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: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Header: '',
|
|
||||||
accessor: 'action',
|
|
||||||
Cell: ActionsCellRenderer,
|
|
||||||
className: 'actions',
|
|
||||||
disableSortBy: true,
|
|
||||||
disableResizing: true,
|
|
||||||
width: 45,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[formatMessage],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleRemoveRow = useCallback(
|
|
||||||
(rowIndex) => {
|
|
||||||
if (rows.length <= 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const removeIndex = parseInt(rowIndex, 10);
|
|
||||||
const newRows = rows.filter((row, index) => index !== removeIndex);
|
|
||||||
|
|
||||||
setFieldValue(
|
|
||||||
'entries',
|
|
||||||
newRows.map((row, index) => ({
|
|
||||||
...omit(row),
|
|
||||||
index: index - 1,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
onClickRemoveRow && onClickRemoveRow(removeIndex);
|
|
||||||
},
|
|
||||||
[rows, setFieldValue, onClickRemoveRow],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onClickNewRow = () => {
|
|
||||||
onClickAddNewRow && onClickAddNewRow();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClickClearAllLines = () => {
|
|
||||||
onClickClearAllLines && onClickClearAllLines();
|
|
||||||
};
|
|
||||||
|
|
||||||
const rowClassNames = useCallback((row) => {
|
|
||||||
return { 'row--total': rows.length === row.index + 1 };
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleUpdateData = useCallback(
|
|
||||||
(rowIndex, columnId, value) => {
|
|
||||||
const newRows = rows.map((row, index) => {
|
|
||||||
if (index === rowIndex) {
|
|
||||||
return {
|
|
||||||
...rows[rowIndex],
|
|
||||||
[columnId]: value,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
setRows(newRows);
|
setTableData(newTableData);
|
||||||
setFieldValue(
|
setLocalAmount(fullAmount);
|
||||||
'entries',
|
triggerUpdateData(newTableData);
|
||||||
newRows.map((row) => ({
|
}
|
||||||
...pick(row, ['payment_amount']),
|
}, [
|
||||||
invoice_id: row.id,
|
fullAmount,
|
||||||
})),
|
localAmount,
|
||||||
);
|
tableData,
|
||||||
},
|
triggerUpdateData,
|
||||||
[rows, setFieldValue, setRows],
|
]);
|
||||||
|
|
||||||
|
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.';
|
||||||
|
|
||||||
|
// Handle update data.
|
||||||
|
const handleUpdateData = useCallback((rows) => {
|
||||||
|
triggerUpdateData(rows);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
console.log(tableData, 'XX');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'estimate-form__table'}>
|
<CloudLoadingIndicator isLoading={fetchCustomerDueInvoices.isFetching}>
|
||||||
<DataTable
|
<PaymentReceiveItemsTableEditor
|
||||||
columns={columns}
|
noResultsMessage={noResultsMessage}
|
||||||
data={rows}
|
data={tableData}
|
||||||
rowClassNames={rowClassNames}
|
errors={errors}
|
||||||
spinnerProps={false}
|
onUpdateData={handleUpdateData}
|
||||||
payload={{
|
onClickClearAllLines={onClickClearAllLines}
|
||||||
errors: errors.entries || [],
|
|
||||||
updateData: handleUpdateData,
|
|
||||||
removeRow: handleRemoveRow,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<div className={'mt1'}>
|
</CloudLoadingIndicator>
|
||||||
<Button
|
|
||||||
small={true}
|
|
||||||
className={'button--secondary button--new-line'}
|
|
||||||
onClick={onClickNewRow}
|
|
||||||
>
|
|
||||||
<T id={'new_lines'} />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
small={true}
|
|
||||||
className={'button--secondary button--clear-lines ml1'}
|
|
||||||
onClick={handleClickClearAllLines}
|
|
||||||
>
|
|
||||||
<T id={'clear_all_lines'} />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withInvoices(({ dueInvoices }) => ({
|
withInvoices(({ paymentReceiveReceivableInvoices }) => ({
|
||||||
dueInvoices,
|
receivableInvoices: paymentReceiveReceivableInvoices,
|
||||||
})),
|
})),
|
||||||
|
withInvoiceActions,
|
||||||
)(PaymentReceiveItemsTable);
|
)(PaymentReceiveItemsTable);
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
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 { DataTable, 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.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) => `#${row?.invoice?.invoice_no}`,
|
||||||
|
Cell: CellRenderer(EmptyDiv, 'invoice_no'),
|
||||||
|
disableSortBy: true,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'invoice_amount' }),
|
||||||
|
accessor: 'invoice.balance',
|
||||||
|
Cell: CellRenderer(DivFieldCell, 'balance'),
|
||||||
|
disableSortBy: true,
|
||||||
|
width: 100,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'amount_due' }),
|
||||||
|
accessor: 'invoice.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 (
|
||||||
|
<div className={classNames(
|
||||||
|
CLASSES.DATATABLE_EDITOR,
|
||||||
|
CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES,
|
||||||
|
)}>
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
data={localData}
|
||||||
|
rowClassNames={rowClassNames}
|
||||||
|
spinnerProps={false}
|
||||||
|
payload={{
|
||||||
|
errors,
|
||||||
|
updateData: handleUpdateData,
|
||||||
|
}}
|
||||||
|
noResults={noResultsMessage}
|
||||||
|
/>
|
||||||
|
<div className={classNames(CLASSES.DATATABLE_EDITOR_ACTIONS, 'mt1')}>
|
||||||
|
<Button
|
||||||
|
small={true}
|
||||||
|
className={'button--secondary button--clear-lines'}
|
||||||
|
onClick={handleClickClearAllLines}
|
||||||
|
>
|
||||||
|
<T id={'clear_all_lines'} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -87,4 +87,18 @@ export const getPayableBillsByPaymentMadeFactory = () =>
|
|||||||
? pickItemsFromIds(billsItems, payableBillsIds) || []
|
? pickItemsFromIds(billsItems, payableBillsIds) || []
|
||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getPaymentMadeFormPayableBillsFactory = () =>
|
||||||
|
createSelector(
|
||||||
|
billItemsSelector,
|
||||||
|
billsPayableVendorSelector,
|
||||||
|
billsPayableByPaymentMadeSelector,
|
||||||
|
(billsItems, vendorBillsIds, paymentMadeBillsIds) => {
|
||||||
|
const billsIds = [
|
||||||
|
...(vendorBillsIds || []),
|
||||||
|
...(paymentMadeBillsIds || [])
|
||||||
|
];
|
||||||
|
return pickItemsFromIds(billsItems, billsIds);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
@@ -121,26 +121,32 @@ export const fetchInvoice = ({ id }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
export const dueInvoices = ({ id }) => {
|
|
||||||
return (dispatch) =>
|
export const fetchDueInvoices = ({ customerId }) => (dispatch) => new Promise((resovle, reject) => {
|
||||||
new Promise((resovle, reject) => {
|
ApiService.get(`sales/invoices/payable`, {
|
||||||
ApiService.get(`sales/invoices/due_invoices`, {
|
params: { customer_id: customerId },
|
||||||
params: { customer_id: id },
|
})
|
||||||
})
|
.then((response) => {
|
||||||
.then((response) => {
|
dispatch({
|
||||||
dispatch({
|
type: t.INVOICES_ITEMS_SET,
|
||||||
type: t.DUE_INVOICES_SET,
|
payload: {
|
||||||
payload: {
|
sales_invoices: response.data.sales_invoices,
|
||||||
customer_id: id,
|
},
|
||||||
due_sales_invoices: response.data.due_sales_invoices,
|
});
|
||||||
},
|
if (customerId) {
|
||||||
});
|
dispatch({
|
||||||
resovle(response);
|
type: t.INVOICES_RECEIVABLE_BY_CUSTOMER_ID,
|
||||||
})
|
payload: {
|
||||||
.catch((error) => {
|
customerId,
|
||||||
const { response } = error;
|
saleInvoices: response.data.sales_invoices,
|
||||||
const { data } = response;
|
},
|
||||||
reject(data?.errors);
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
resovle(response);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
const { response } = error;
|
||||||
|
const { data } = response;
|
||||||
|
reject(data?.errors);
|
||||||
});
|
});
|
||||||
};
|
});
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ const initialState = {
|
|||||||
page: 1,
|
page: 1,
|
||||||
},
|
},
|
||||||
dueInvoices: {},
|
dueInvoices: {},
|
||||||
|
receivable: {
|
||||||
|
byCustomerId: [],
|
||||||
|
byPaymentReceiveId: [],
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultInvoice = {
|
const defaultInvoice = {
|
||||||
@@ -97,39 +101,19 @@ const reducer = createReducer(initialState, {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[t.DUE_INVOICES_SET]: (state, action) => {
|
|
||||||
const { customer_id, due_sales_invoices } = action.payload;
|
|
||||||
|
|
||||||
const _dueInvoices = [];
|
[t.INVOICES_RECEIVABLE_BY_PAYMENT_ID]: (state, action) => {
|
||||||
|
const { paymentReceiveId, saleInvoices } = action.payload;
|
||||||
|
const saleInvoicesIds = saleInvoices.map((saleInvoice) => saleInvoice.id);
|
||||||
|
|
||||||
state.dueInvoices[customer_id] = due_sales_invoices.map((due) => due.id);
|
state.receivable.byPaymentReceiveId[paymentReceiveId] = saleInvoicesIds;
|
||||||
const _invoices = {};
|
|
||||||
due_sales_invoices.forEach((invoice) => {
|
|
||||||
_invoices[invoice.id] = {
|
|
||||||
...invoice,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
state.items = {
|
|
||||||
...state.dueInvoices,
|
|
||||||
...state.items.dueInvoices,
|
|
||||||
..._invoices,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
[t.RELOAD_INVOICES]: (state, action) => {
|
|
||||||
const { sales_invoices } = action.payload;
|
|
||||||
|
|
||||||
const _sales_invoices = {};
|
[t.INVOICES_RECEIVABLE_BY_CUSTOMER_ID]: (state, action) => {
|
||||||
sales_invoices.forEach((invoice) => {
|
const { customerId, saleInvoices } = action.payload;
|
||||||
_sales_invoices[invoice.id] = {
|
const saleInvoiceIds = saleInvoices.map((saleInvoice) => saleInvoice.id);
|
||||||
...invoice,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
state.items = {
|
state.receivable.byCustomerId[customerId] = saleInvoiceIds
|
||||||
...state.items,
|
|
||||||
..._sales_invoices,
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@ const invoicesPageSelector = (state, props, query) => {
|
|||||||
|
|
||||||
const invoicesItemsSelector = (state) => state.salesInvoices.items;
|
const invoicesItemsSelector = (state) => state.salesInvoices.items;
|
||||||
|
|
||||||
|
const invoicesReceiableCustomerSelector = (state, props) => state.salesInvoices.receivable.byCustomerId[props.customerId];
|
||||||
|
const paymentReceivableInvoicesSelector = (state, props) => state.salesInvoices.receivable.byPaymentReceiveId[props.paymentReceiveId];
|
||||||
|
|
||||||
|
|
||||||
export const getInvoiceTableQueryFactory = () =>
|
export const getInvoiceTableQueryFactory = () =>
|
||||||
createSelector(
|
createSelector(
|
||||||
paginationLocationQuery,
|
paginationLocationQuery,
|
||||||
@@ -55,17 +59,39 @@ export const getInvoicePaginationMetaFactory = () =>
|
|||||||
return invoicePage?.paginationMeta || {};
|
return invoicePage?.paginationMeta || {};
|
||||||
});
|
});
|
||||||
|
|
||||||
const dueInvoicesSelector = (state, props) => {
|
// export const getCustomerReceivableInvoicesFactory = () =>
|
||||||
return state.salesInvoices.dueInvoices[props.customer_id] || [];
|
// createSelector(
|
||||||
};
|
// invoicesItemsSelector,
|
||||||
|
// invoicesReceiableCustomerSelector,
|
||||||
|
// (invoicesItems, invoicesIds) => {
|
||||||
|
// return Array.isArray(invoicesIds)
|
||||||
|
// ? (pickItemsFromIds(invoicesItems, invoicesIds) || [])
|
||||||
|
// : [];
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
|
||||||
export const getdueInvoices = createSelector(
|
// export const getPaymentReceivableInvoicesFactory = () =>
|
||||||
dueInvoicesSelector,
|
// createSelector(
|
||||||
invoicesItemsSelector,
|
// invoicesItemsSelector,
|
||||||
(customerIds, items) => {
|
// paymentReceivableInvoicesSelector,
|
||||||
|
// (invoicesItems, invoicesIds) => {
|
||||||
return typeof customerIds === 'object'
|
// return Array.isArray(invoicesIds)
|
||||||
? pickItemsFromIds(items, customerIds) || []
|
// ? (pickItemsFromIds(invoicesItems, invoicesIds) || [])
|
||||||
: [];
|
// : [];
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
|
|
||||||
|
|
||||||
|
export const getPaymentReceiveReceivableInvoicesFactory = () =>
|
||||||
|
createSelector(
|
||||||
|
invoicesItemsSelector,
|
||||||
|
invoicesReceiableCustomerSelector,
|
||||||
|
paymentReceivableInvoicesSelector,
|
||||||
|
(invoicesItems, customerInvoicesIds, paymentInvoicesIds) => {
|
||||||
|
const invoicesIds = [
|
||||||
|
...(customerInvoicesIds || []),
|
||||||
|
...(paymentInvoicesIds || []),
|
||||||
|
];
|
||||||
|
return pickItemsFromIds(invoicesItems, invoicesIds);
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -11,4 +11,7 @@ export default {
|
|||||||
INVOICES_ITEMS_SET: 'INVOICES_ITEMS_SET',
|
INVOICES_ITEMS_SET: 'INVOICES_ITEMS_SET',
|
||||||
DUE_INVOICES_SET: 'DUE_INVOICES_SET',
|
DUE_INVOICES_SET: 'DUE_INVOICES_SET',
|
||||||
RELOAD_INVOICES: 'RELOAD_INVOICES',
|
RELOAD_INVOICES: 'RELOAD_INVOICES',
|
||||||
|
|
||||||
|
INVOICES_RECEIVABLE_BY_PAYMENT_ID: 'INVOICES_RECEIVABLE_BY_PAYMENT_ID',
|
||||||
|
INVOICES_RECEIVABLE_BY_CUSTOMER_ID: 'INVOICES_RECEIVABLE_BY_CUSTOMER_ID'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,28 +52,29 @@ export const fetchPaymentReceive = ({ id }) => {
|
|||||||
new Promise((resovle, reject) => {
|
new Promise((resovle, reject) => {
|
||||||
ApiService.get(`sales/payment_receives/${id}`, {})
|
ApiService.get(`sales/payment_receives/${id}`, {})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
|
||||||
type: t.RELOAD_INVOICES,
|
|
||||||
payload: {
|
|
||||||
sales_invoices: response.data.paymentReceive.entries.map(
|
|
||||||
(e) => e.invoice,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.PAYMENT_RECEIVE_SET,
|
type: t.PAYMENT_RECEIVE_SET,
|
||||||
payload: {
|
payload: {
|
||||||
id,
|
id,
|
||||||
paymentReceive: response.data.paymentReceive,
|
paymentReceive: response.data.paymentReceive,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: t.INVOICES_ITEMS_SET,
|
||||||
|
payload: {
|
||||||
|
sales_invoices: response.data.sale_invoice.receivable_invoices,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: t.INVOICES_RECEIVABLE_BY_PAYMENT_ID,
|
||||||
|
payload: {
|
||||||
|
paymentReceiveid: response.data.id,
|
||||||
|
saleInvoices: response.data.sale_invoice.receivable_invoices,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
resovle(response);
|
resovle(response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
// const { response } = error;
|
|
||||||
// const { data } = response;
|
|
||||||
// reject(data?.errors);
|
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
|||||||
@import 'pages/invoice-form';
|
@import 'pages/invoice-form';
|
||||||
@import 'pages/receipt-form';
|
@import 'pages/receipt-form';
|
||||||
@import 'pages/payment-made';
|
@import 'pages/payment-made';
|
||||||
|
@import 'pages/payment-receive';
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
@import 'views/filter-dropdown';
|
@import 'views/filter-dropdown';
|
||||||
|
|||||||
61
client/src/style/pages/payment-receive.scss
Normal file
61
client/src/style/pages/payment-receive.scss
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
|
||||||
|
.page-form--payment-receive {
|
||||||
|
$self: '.page-form';
|
||||||
|
|
||||||
|
#{$self}__header{
|
||||||
|
.bp3-label{
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
.bp3-form-content{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-form-group{
|
||||||
|
margin-bottom: 18px;
|
||||||
|
|
||||||
|
&.bp3-inline{
|
||||||
|
max-width: 470px;
|
||||||
|
}
|
||||||
|
a.receive-full-amount{
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 6px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$self}__primary-section{
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.datatable-editor{
|
||||||
|
|
||||||
|
.table .tbody{
|
||||||
|
.tr.no-results{
|
||||||
|
.td{
|
||||||
|
border-bottom: 1px solid #e2e2e2;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 26px 0;
|
||||||
|
color: #5a5a77;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table{
|
||||||
|
.tr{
|
||||||
|
.td:first-of-type,
|
||||||
|
.th:first-of-type{
|
||||||
|
span, div{
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{$self}__footer{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,8 +34,9 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.post(
|
router.post(
|
||||||
'/',
|
'/', [
|
||||||
this.newPaymentReceiveValidation,
|
...this.newPaymentReceiveValidation,
|
||||||
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.newPaymentReceive.bind(this)),
|
asyncMiddleware(this.newPaymentReceive.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
@@ -87,6 +88,7 @@ export default class PaymentReceivesController extends BaseController {
|
|||||||
|
|
||||||
check('entries').isArray({ min: 1 }),
|
check('entries').isArray({ min: 1 }),
|
||||||
|
|
||||||
|
check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
|
||||||
check('entries.*.invoice_id').exists().isNumeric().toInt(),
|
check('entries.*.invoice_id').exists().isNumeric().toInt(),
|
||||||
check('entries.*.payment_amount').exists().isNumeric().toInt(),
|
check('entries.*.payment_amount').exists().isNumeric().toInt(),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { omit, sumBy, chain, difference } from 'lodash';
|
import { omit, sumBy, difference } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import {
|
import {
|
||||||
@@ -26,7 +26,6 @@ import { formatDateFields, entriesAmountDiff } from 'utils';
|
|||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import CustomersService from 'services/Contacts/CustomersService';
|
import CustomersService from 'services/Contacts/CustomersService';
|
||||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||||
import { SaleInvoice } from 'models';
|
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
|
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
|
||||||
@@ -327,16 +326,30 @@ export default class PaymentReceiveService {
|
|||||||
* @param {number} tenantId - Tenant id.
|
* @param {number} tenantId - Tenant id.
|
||||||
* @param {Integer} paymentReceiveId - Payment receive id.
|
* @param {Integer} paymentReceiveId - Payment receive id.
|
||||||
*/
|
*/
|
||||||
public async getPaymentReceive(tenantId: number, paymentReceiveId: number) {
|
public async getPaymentReceive(
|
||||||
const { PaymentReceive } = this.tenancy.models(tenantId);
|
tenantId: number,
|
||||||
|
paymentReceiveId: number
|
||||||
|
): Promise<{ paymentReceive: IPaymentReceive[], receivableInvoices: ISaleInvoice }> {
|
||||||
|
const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
|
||||||
const paymentReceive = await PaymentReceive.query()
|
const paymentReceive = await PaymentReceive.query()
|
||||||
.findById(paymentReceiveId)
|
.findById(paymentReceiveId)
|
||||||
.withGraphFetched('entries.invoice');
|
.withGraphFetched('entries')
|
||||||
|
.withGraphFetched('customer')
|
||||||
|
.withGraphFetched('depositAccount');
|
||||||
|
|
||||||
if (!paymentReceive) {
|
if (!paymentReceive) {
|
||||||
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
|
throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS);
|
||||||
}
|
}
|
||||||
return paymentReceive;
|
// Receivable open invoices.
|
||||||
|
const receivableInvoices = await SaleInvoice.query().onBuild((builder) => {
|
||||||
|
const invoicesIds = paymentReceive.entries.map((entry) => entry.invoiceId);
|
||||||
|
|
||||||
|
builder.where('customer_id', paymentReceive.customerId);
|
||||||
|
builder.orWhereIn('id', invoicesIds);
|
||||||
|
builder.orderByRaw(`FIELD(id, ${invoicesIds.join(', ')}) DESC`);
|
||||||
|
builder.orderBy('invoice_date', 'ASC');
|
||||||
|
});
|
||||||
|
return { paymentReceive, receivableInvoices };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user