mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 07:10:33 +00:00
Merge branch 'master' of https://github.com/abouolia/Bigcapital
This commit is contained in:
@@ -9,12 +9,16 @@ const CLASSES = {
|
|||||||
PAGE_FORM_HEADER: 'page-form__header',
|
PAGE_FORM_HEADER: 'page-form__header',
|
||||||
PAGE_FORM_HEADER_PRIMARY: 'page-form__primary-section',
|
PAGE_FORM_HEADER_PRIMARY: 'page-form__primary-section',
|
||||||
PAGE_FORM_FOOTER: 'page-form__footer',
|
PAGE_FORM_FOOTER: 'page-form__footer',
|
||||||
PAGE_FORM_FLOATING_ACTIONS: 'page-form__floating-action',
|
PAGE_FORM_FLOATING_ACTIONS: 'page-form__floating-actions',
|
||||||
|
|
||||||
PAGE_FORM_BILL: 'page-form--bill',
|
PAGE_FORM_BILL: 'page-form--bill',
|
||||||
PAGE_FORM_ESTIMATE: 'page-form--estimate',
|
PAGE_FORM_ESTIMATE: 'page-form--estimate',
|
||||||
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',
|
||||||
|
|
||||||
|
CLOUD_SPINNER: 'cloud-spinner',
|
||||||
|
IS_LOADING: 'is-loading',
|
||||||
...Classes,
|
...Classes,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
23
client/src/components/CloudLoadingIndicator.js
Normal file
23
client/src/components/CloudLoadingIndicator.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { Spinner } from '@blueprintjs/core';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import If from './Utils/If';
|
||||||
|
|
||||||
|
export default function CloudLoadingIndicator({
|
||||||
|
isLoading,
|
||||||
|
children,
|
||||||
|
}) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames(
|
||||||
|
CLASSES.CLOUD_SPINNER,
|
||||||
|
{ [CLASSES.IS_LOADING]: isLoading },
|
||||||
|
)}>
|
||||||
|
<If condition={isLoading}>
|
||||||
|
<Spinner size={30} value={null} />
|
||||||
|
</If>
|
||||||
|
{ children }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import InputPrependButton from './Forms/InputPrependButton';
|
|||||||
import CategoriesSelectList from './CategoriesSelectList';
|
import CategoriesSelectList from './CategoriesSelectList';
|
||||||
import Row from './Grid/Row';
|
import Row from './Grid/Row';
|
||||||
import Col from './Grid/Col';
|
import Col from './Grid/Col';
|
||||||
|
import CloudLoadingIndicator from './CloudLoadingIndicator';
|
||||||
|
|
||||||
const Hint = FieldHint;
|
const Hint = FieldHint;
|
||||||
|
|
||||||
@@ -63,4 +64,5 @@ export {
|
|||||||
CategoriesSelectList,
|
CategoriesSelectList,
|
||||||
Col,
|
Col,
|
||||||
Row,
|
Row,
|
||||||
|
CloudLoadingIndicator,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
editBill,
|
editBill,
|
||||||
fetchBillsTable,
|
fetchBillsTable,
|
||||||
fetchBill,
|
fetchBill,
|
||||||
|
fetchDueBills,
|
||||||
} from 'store/Bills/bills.actions';
|
} from 'store/Bills/bills.actions';
|
||||||
import t from 'store/types';
|
import t from 'store/types';
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
requestDeleteBill: (id) => dispatch(deleteBill({ id })),
|
requestDeleteBill: (id) => dispatch(deleteBill({ id })),
|
||||||
requestFetchBillsTable: (query = {}) =>
|
requestFetchBillsTable: (query = {}) =>
|
||||||
dispatch(fetchBillsTable({ query: { ...query } })),
|
dispatch(fetchBillsTable({ query: { ...query } })),
|
||||||
|
requestFetchDueBills: (vendorId) => dispatch(fetchDueBills({ vendorId })),
|
||||||
|
|
||||||
changeBillView: (id) =>
|
changeBillView: (id) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ import {
|
|||||||
getBillCurrentPageFactory,
|
getBillCurrentPageFactory,
|
||||||
getBillPaginationMetaFactory,
|
getBillPaginationMetaFactory,
|
||||||
getBillTableQueryFactory,
|
getBillTableQueryFactory,
|
||||||
getVendorDueBillsFactory
|
getVendorPayableBillsFactory,
|
||||||
|
getPayableBillsByPaymentMadeFactory
|
||||||
} from 'store/Bills/bills.selectors';
|
} from 'store/Bills/bills.selectors';
|
||||||
|
|
||||||
export default (mapState) => {
|
export default (mapState) => {
|
||||||
const getBillsItems = getBillCurrentPageFactory();
|
const getBillsItems = getBillCurrentPageFactory();
|
||||||
const getBillsPaginationMeta = getBillPaginationMetaFactory();
|
const getBillsPaginationMeta = getBillPaginationMetaFactory();
|
||||||
const getBillTableQuery = getBillTableQueryFactory();
|
const getBillTableQuery = getBillTableQueryFactory();
|
||||||
const getVendorDueBills = getVendorDueBillsFactory();
|
const getVendorPayableBills = getVendorPayableBillsFactory();
|
||||||
|
const getPayableBillsByPaymentMade = getPayableBillsByPaymentMadeFactory();
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => {
|
const mapStateToProps = (state, props) => {
|
||||||
const tableQuery = getBillTableQuery(state, props);
|
const tableQuery = getBillTableQuery(state, props);
|
||||||
@@ -26,7 +28,8 @@ export default (mapState) => {
|
|||||||
billsLoading: state.bills.loading,
|
billsLoading: state.bills.loading,
|
||||||
nextBillNumberChanged: state.bills.nextBillNumberChanged,
|
nextBillNumberChanged: state.bills.nextBillNumberChanged,
|
||||||
|
|
||||||
vendorDueBills: getVendorDueBills(state, props),
|
vendorPayableBills: getVendorPayableBills(state, props),
|
||||||
|
paymentMadePayableBills: getPayableBillsByPaymentMade(state, props),
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import React, { useCallback, useState, useEffect, useMemo } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useParams, useHistory } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
|
import { useIntl } from 'react-intl';
|
||||||
|
|
||||||
import PaymentMadeForm from './PaymentMadeForm';
|
import PaymentMadeForm from './PaymentMadeForm';
|
||||||
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
import DashboardInsider from 'components/Dashboard/DashboardInsider';
|
||||||
|
|
||||||
|
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||||
import withVenderActions from 'containers/Vendors/withVendorActions';
|
import withVenderActions from 'containers/Vendors/withVendorActions';
|
||||||
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
import withAccountsActions from 'containers/Accounts/withAccountsActions';
|
||||||
import withItemsActions from 'containers/Items/withItemsActions';
|
import withItemsActions from 'containers/Items/withItemsActions';
|
||||||
@@ -15,7 +17,7 @@ import withSettingsActions from 'containers/Settings/withSettingsActions';
|
|||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
function PaymentMade({
|
function PaymentMade({
|
||||||
//#withwithAccountsActions
|
//#withAccountsActions
|
||||||
requestFetchAccounts,
|
requestFetchAccounts,
|
||||||
|
|
||||||
//#withVenderActions
|
//#withVenderActions
|
||||||
@@ -27,64 +29,60 @@ function PaymentMade({
|
|||||||
//#withPaymentMadesActions
|
//#withPaymentMadesActions
|
||||||
requestFetchPaymentMade,
|
requestFetchPaymentMade,
|
||||||
|
|
||||||
//#withBillActions
|
|
||||||
|
|
||||||
// #withSettingsActions
|
// #withSettingsActions
|
||||||
requestFetchOptions,
|
requestFetchOptions,
|
||||||
}) {
|
|
||||||
const history = useHistory();
|
|
||||||
const { id } = useParams();
|
|
||||||
const [venderId, setVenderId] = useState(null);
|
|
||||||
|
|
||||||
// Handle fetch accounts data
|
// #withDashboardActions
|
||||||
|
changePageTitle,
|
||||||
|
}) {
|
||||||
|
const { id: paymentMadeId } = useParams();
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
// Handle page title change in new and edit mode.
|
||||||
|
useEffect(() => {
|
||||||
|
if (paymentMadeId) {
|
||||||
|
changePageTitle(formatMessage({ id: 'edit_payment_made' }));
|
||||||
|
} else {
|
||||||
|
changePageTitle(formatMessage({ id: 'payment_made' }));
|
||||||
|
}
|
||||||
|
}, [changePageTitle, paymentMadeId, formatMessage]);
|
||||||
|
|
||||||
|
// Handle fetch accounts data.
|
||||||
const fetchAccounts = useQuery('accounts-list', (key) =>
|
const fetchAccounts = useQuery('accounts-list', (key) =>
|
||||||
requestFetchAccounts(),
|
requestFetchAccounts(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle fetch Items data table or list
|
// Handle fetch Items data table or list.
|
||||||
const fetchItems = useQuery('items-list', () => requestFetchItems({}));
|
const fetchItems = useQuery('items-list', () => requestFetchItems({}));
|
||||||
|
|
||||||
// Handle fetch venders data table or list
|
// Handle fetch venders data table or list.
|
||||||
const fetchVenders = useQuery('venders-list', () =>
|
const fetchVenders = useQuery('venders-list', () =>
|
||||||
requestFetchVendorsTable({}),
|
requestFetchVendorsTable({}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle fetch specific payment made details.
|
||||||
const fetchPaymentMade = useQuery(
|
const fetchPaymentMade = useQuery(
|
||||||
['payment-made', id],
|
['payment-made', paymentMadeId],
|
||||||
(key, _id) => requestFetchPaymentMade(_id),
|
(key, _id) => requestFetchPaymentMade(_id),
|
||||||
{ enabled: !!id },
|
{ enabled: !!paymentMadeId },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Fetch payment made settings.
|
||||||
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
|
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
|
||||||
|
|
||||||
const handleFormSubmit = useCallback(
|
|
||||||
(payload) => {
|
|
||||||
payload.redirect && history.push('/payment-mades');
|
|
||||||
},
|
|
||||||
[history],
|
|
||||||
);
|
|
||||||
const handleCancel = useCallback(() => {
|
|
||||||
history.goBack();
|
|
||||||
}, [history]);
|
|
||||||
|
|
||||||
const handleVenderChange = (venderId) => {
|
|
||||||
setVenderId(venderId);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider
|
||||||
loading={
|
loading={
|
||||||
fetchVenders.isFetching ||
|
fetchVenders.isFetching ||
|
||||||
fetchItems.isFetching ||
|
fetchItems.isFetching ||
|
||||||
fetchAccounts.isFetching ||
|
fetchAccounts.isFetching ||
|
||||||
fetchPaymentMade.isFetching
|
fetchPaymentMade.isFetching ||
|
||||||
|
fetchSettings.isFetching
|
||||||
}
|
}
|
||||||
name={'payment-made'}
|
name={'payment-made'}
|
||||||
>
|
>
|
||||||
<PaymentMadeForm
|
<PaymentMadeForm
|
||||||
onFormSubmit={handleFormSubmit}
|
paymentMadeId={paymentMadeId}
|
||||||
paymentMadeId={id}
|
|
||||||
onCancelForm={handleCancel}
|
|
||||||
onVenderChange={handleVenderChange}
|
|
||||||
/>
|
/>
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
@@ -96,5 +94,7 @@ export default compose(
|
|||||||
withAccountsActions,
|
withAccountsActions,
|
||||||
withBillActions,
|
withBillActions,
|
||||||
withPaymentMadeActions,
|
withPaymentMadeActions,
|
||||||
withSettingsActions
|
withSettingsActions,
|
||||||
|
withDashboardActions,
|
||||||
)(PaymentMade);
|
)(PaymentMade);
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
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 PaymentMadeFormFooter({
|
import { CLASSES } from 'common/classes';
|
||||||
formik: { isSubmitting, resetForm },
|
|
||||||
|
/**
|
||||||
|
* Payment made floating actions bar.
|
||||||
|
*/
|
||||||
|
export default function PaymentMadeFloatingActions({
|
||||||
|
isSubmitting,
|
||||||
onSubmitClick,
|
onSubmitClick,
|
||||||
onCancelClick,
|
onCancelClick,
|
||||||
paymentMade,
|
onClearBtnClick,
|
||||||
}) {
|
}) {
|
||||||
|
const handleClearBtnClick = (event) => {
|
||||||
|
onClearBtnClick && onClearBtnClick(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmitClick = (event) => {
|
||||||
|
onSubmitClick && onSubmitClick(event, { redirect: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelClick = (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 });
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<T id={'save_send'} />
|
<T id={'save_send'} />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -27,23 +43,23 @@ export default function PaymentMadeFormFooter({
|
|||||||
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>
|
||||||
|
|
||||||
<Button className={'ml1'} disabled={isSubmitting} s>
|
<Button
|
||||||
|
className={'ml1'}
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={handleClearBtnClick}
|
||||||
|
>
|
||||||
<T id={'clear'} />
|
<T id={'clear'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={'ml1'}
|
className={'ml1'}
|
||||||
type="submit"
|
type="submit"
|
||||||
onClick={() => {
|
onClick={handleCancelClick}
|
||||||
onCancelClick && onCancelClick();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<T id={'close'} />
|
<T id={'close'} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,96 +1,56 @@
|
|||||||
import React, {
|
import React, { useMemo, useState, useCallback } from 'react';
|
||||||
useMemo,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
useRef,
|
|
||||||
} from '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 { Intent, Alert } 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 classNames from 'classnames';
|
||||||
|
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
import PaymentMadeHeader from './PaymentMadeFormHeader';
|
import PaymentMadeHeader from './PaymentMadeFormHeader';
|
||||||
import PaymentMadeItemsTable from './PaymentMadeItemsTable';
|
import PaymentMadeItemsTable from './PaymentMadeItemsTable';
|
||||||
import PaymentMadeFloatingActions from './PaymentMadeFloatingActions';
|
import PaymentMadeFloatingActions from './PaymentMadeFloatingActions';
|
||||||
|
|
||||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
|
||||||
import withMediaActions from 'containers/Media/withMediaActions';
|
import withMediaActions from 'containers/Media/withMediaActions';
|
||||||
import withPaymentMadeActions from './withPaymentMadeActions';
|
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||||
import withPaymentMadeDetail from './withPaymentMadeDetail';
|
import withPaymentMadeDetail from './withPaymentMadeDetail';
|
||||||
import withPaymentMade from './withPaymentMade';
|
import withPaymentMade from './withPaymentMade';
|
||||||
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, orderingLinesIndexes } from 'utils';
|
||||||
import withSettings from 'containers/Settings/withSettings';
|
import withSettings from 'containers/Settings/withSettings';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
const MIN_LINES_NUMBER = 5;
|
const ERRORS = {
|
||||||
|
PAYMENT_NUMBER_NOT_UNIQUE: 'PAYMENT.NUMBER.NOT.UNIQUE',
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Payment made form component.
|
||||||
|
*/
|
||||||
function PaymentMadeForm({
|
function PaymentMadeForm({
|
||||||
//#withMedia
|
// #withMedia
|
||||||
requestSubmitMedia,
|
requestSubmitMedia,
|
||||||
requestDeleteMedia,
|
requestDeleteMedia,
|
||||||
|
|
||||||
//#withPaymentMadesActions
|
// #withPaymentMadesActions
|
||||||
requestSubmitPaymentMade,
|
requestSubmitPaymentMade,
|
||||||
requestEditPaymentMade,
|
requestEditPaymentMade,
|
||||||
setPaymentNumberChange,
|
|
||||||
|
|
||||||
// #withPaymentMade
|
// #withPaymentMadeDetail
|
||||||
nextPaymentNumberChanged,
|
|
||||||
|
|
||||||
// #withSettings
|
|
||||||
paymentNextNumber,
|
|
||||||
paymentNumberPrefix,
|
|
||||||
|
|
||||||
//#withDashboard
|
|
||||||
changePageTitle,
|
|
||||||
changePageSubtitle,
|
|
||||||
|
|
||||||
//#withPaymentMadeDetail
|
|
||||||
paymentMade,
|
paymentMade,
|
||||||
|
|
||||||
onFormSubmit,
|
paymentMadeId,
|
||||||
onCancelForm,
|
|
||||||
onVenderChange,
|
|
||||||
}) {
|
}) {
|
||||||
|
const history = useHistory();
|
||||||
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 [amountChangeAlert, setAmountChangeAlert] = useState(false);
|
||||||
const clearSavedMediaIds = () => {
|
const [clearLinesAlert, setClearLinesAlert] = useState(false);
|
||||||
savedMediaIds.current = [];
|
const [clearFormAlert, setClearFormAlert] = useState(false);
|
||||||
};
|
const [fullAmount, setFullAmount] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onVenderChange && onVenderChange(formik.values.vendor_id);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (paymentMade && paymentMade.id) {
|
|
||||||
changePageTitle(formatMessage({ id: 'edit_payment_made' }));
|
|
||||||
} else {
|
|
||||||
changePageTitle(formatMessage({ id: 'payment_made' }));
|
|
||||||
}
|
|
||||||
}, [changePageTitle, paymentMade, formatMessage]);
|
|
||||||
|
|
||||||
|
// Yup validation schema.
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
vendor_id: Yup.string()
|
vendor_id: Yup.string()
|
||||||
.label(formatMessage({ id: 'vendor_name_' }))
|
.label(formatMessage({ id: 'vendor_name_' }))
|
||||||
@@ -108,11 +68,9 @@ function PaymentMadeForm({
|
|||||||
description: Yup.string(),
|
description: Yup.string(),
|
||||||
entries: Yup.array().of(
|
entries: Yup.array().of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
payment_amount: Yup.number().nullable(),
|
id: Yup.number().nullable(),
|
||||||
bill_number: Yup.number().nullable(),
|
|
||||||
amount: Yup.number().nullable(),
|
|
||||||
due_amount: Yup.number().nullable(),
|
due_amount: Yup.number().nullable(),
|
||||||
bill_date: Yup.date(),
|
payment_amount: Yup.number().nullable().max(Yup.ref('due_amount')),
|
||||||
bill_id: Yup.number()
|
bill_id: Yup.number()
|
||||||
.nullable()
|
.nullable()
|
||||||
.when(['payment_amount'], {
|
.when(['payment_amount'], {
|
||||||
@@ -122,194 +80,260 @@ function PaymentMadeForm({
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const handleDropFiles = useCallback((_files) => {
|
|
||||||
setFiles(_files.filter((file) => file.uploaded === false));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const savePaymentMadeSubmit = useCallback((payload) => {
|
// Default payment made entry values.
|
||||||
onFormSubmit && onFormSubmit(payload);
|
const defaultPaymentMadeEntry = useMemo(
|
||||||
});
|
() => ({ bill_id: '', payment_amount: '', id: null }),
|
||||||
|
|
||||||
const defaultPaymentMade = useMemo(
|
|
||||||
() => ({
|
|
||||||
bill_id: '',
|
|
||||||
bill_date: moment(new Date()).format('YYYY-MM-DD'),
|
|
||||||
bill_number: '',
|
|
||||||
amount: '',
|
|
||||||
due_amount: '',
|
|
||||||
payment_amount: '',
|
|
||||||
}),
|
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const paymentNumber = paymentNumberPrefix
|
// Default initial values.
|
||||||
? `${paymentNumberPrefix}-${paymentNextNumber}`
|
|
||||||
: paymentNextNumber;
|
|
||||||
|
|
||||||
const defaultInitialValues = useMemo(
|
const defaultInitialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
|
full_amount: '',
|
||||||
vendor_id: '',
|
vendor_id: '',
|
||||||
payment_account_id: '',
|
payment_account_id: '',
|
||||||
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
payment_date: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
reference: '',
|
reference: '',
|
||||||
payment_number: paymentNumber,
|
payment_number: '',
|
||||||
// receive_amount: '',
|
|
||||||
description: '',
|
description: '',
|
||||||
entries: [...repeatValue(defaultPaymentMade, MIN_LINES_NUMBER)],
|
entries: [],
|
||||||
}),
|
}),
|
||||||
[defaultPaymentMade],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const orderingIndex = (_entries) => {
|
// Form initial values.
|
||||||
return _entries.map((item, index) => ({
|
|
||||||
...item,
|
|
||||||
index: index + 1,
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialValues = useMemo(
|
const initialValues = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
...(paymentMade
|
...(paymentMade
|
||||||
? {
|
? {
|
||||||
...pick(paymentMade, Object.keys(defaultInitialValues)),
|
...pick(paymentMade, Object.keys(defaultInitialValues)),
|
||||||
|
full_amount: sumBy(paymentMade.entries, 'payment_amount'),
|
||||||
entries: [
|
entries: [
|
||||||
...paymentMade.entries.map((paymentMade) => ({
|
...paymentMade.entries.map((paymentMadeEntry) => ({
|
||||||
...pick(paymentMade, Object.keys(defaultPaymentMade)),
|
...pick(paymentMadeEntry, Object.keys(defaultPaymentMadeEntry)),
|
||||||
})),
|
})),
|
||||||
...repeatValue(
|
|
||||||
defaultPaymentMade,
|
|
||||||
Math.max(MIN_LINES_NUMBER - paymentMade.entries.length, 0),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...defaultInitialValues,
|
...defaultInitialValues,
|
||||||
entries: orderingIndex(defaultInitialValues.entries),
|
entries: orderingLinesIndexes(defaultInitialValues.entries),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
[paymentMade, defaultInitialValues, defaultPaymentMade],
|
[paymentMade, defaultInitialValues, defaultPaymentMadeEntry],
|
||||||
);
|
);
|
||||||
|
|
||||||
const initialAttachmentFiles = useMemo(() => {
|
const handleSubmitForm = (
|
||||||
return paymentMade && paymentMade.media
|
values,
|
||||||
? paymentMade.media.map((attach) => ({
|
{ setSubmitting, resetForm, setFieldError },
|
||||||
preview: attach.attachment_file,
|
) => {
|
||||||
uploaded: true,
|
|
||||||
metadata: { ...attach },
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
}, [paymentMade]);
|
|
||||||
|
|
||||||
const formik = useFormik({
|
|
||||||
validationSchema,
|
|
||||||
initialValues: {
|
|
||||||
...initialValues,
|
|
||||||
},
|
|
||||||
onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
|
|
||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
const entries = formik.values.entries.filter((item) => {
|
|
||||||
if (item.bill_id !== undefined) {
|
// Filters entries that have no `bill_id` or `payment_amount`.
|
||||||
return { ...item };
|
const entries = values.entries.filter((item) => {
|
||||||
}
|
return !item.bill_id || item.payment_amount;
|
||||||
});
|
});
|
||||||
const form = {
|
// Total payment amount of entries.
|
||||||
...values,
|
const totalPaymentAmount = sumBy(entries, 'payment_amount');
|
||||||
entries,
|
|
||||||
|
if (totalPaymentAmount <= 0) {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: 'you_cannot_make_payment_with_zero_total_amount',
|
||||||
|
intent: Intent.WARNING,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const form = { ...values, entries };
|
||||||
|
|
||||||
|
// Triggers once the save request success.
|
||||||
|
const onSaved = (response) => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: formatMessage({
|
||||||
|
id: paymentMadeId
|
||||||
|
? 'the_payment_made_has_been_successfully_edited'
|
||||||
|
: 'the_payment_made_has_been_successfully_created',
|
||||||
|
}),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
setSubmitting(false);
|
||||||
|
resetForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestForm = { ...form };
|
const onError = (errors) => {
|
||||||
|
const getError = (errorType) => errors.find((e) => e.type === errorType);
|
||||||
|
|
||||||
|
if (getError(ERRORS.PAYMENT_NUMBER_NOT_UNIQUE)) {
|
||||||
|
setFieldError(
|
||||||
|
'payment_number',
|
||||||
|
formatMessage({ id: 'payment_number_is_not_unique' }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
|
||||||
if (paymentMade && paymentMade.id) {
|
if (paymentMade && paymentMade.id) {
|
||||||
requestEditPaymentMade(paymentMade.id, requestForm)
|
requestEditPaymentMade(paymentMade.id, form).then(onSaved).catch(onError);
|
||||||
.then((response) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_payment_made_has_been_successfully_edited',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
setSubmitting(false);
|
|
||||||
savePaymentMadeSubmit({ action: 'update', ...payload });
|
|
||||||
resetForm();
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setSubmitting(false);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
requestSubmitPaymentMade(requestForm)
|
requestSubmitPaymentMade(form).then(onSaved).catch(onError);
|
||||||
.then((response) => {
|
|
||||||
AppToaster.show({
|
|
||||||
message: formatMessage({
|
|
||||||
id: 'the_payment_made_has_been_successfully_created',
|
|
||||||
}),
|
|
||||||
intent: Intent.SUCCESS,
|
|
||||||
});
|
|
||||||
setSubmitting(false);
|
|
||||||
resetForm();
|
|
||||||
savePaymentMadeSubmit({ action: 'new', ...payload });
|
|
||||||
})
|
|
||||||
.catch((errors) => {
|
|
||||||
setSubmitting(false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDeleteFile = useCallback(
|
|
||||||
(_deletedFiles) => {
|
|
||||||
_deletedFiles.forEach((deletedFile) => {
|
|
||||||
if (deletedFile.upload && deletedFile.metadata.id) {
|
|
||||||
setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[setDeletedFiles, deletedFiles],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmitClick = useCallback(
|
|
||||||
(payload) => {
|
|
||||||
setPayload(payload);
|
|
||||||
formik.submitForm();
|
|
||||||
},
|
|
||||||
[setPayload, formik],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleCancelClick = useCallback(
|
|
||||||
(payload) => {
|
|
||||||
onCancelForm && onCancelForm(payload);
|
|
||||||
},
|
|
||||||
[onCancelForm],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClickAddNewRow = () => {
|
|
||||||
formik.setFieldValue(
|
|
||||||
'entries',
|
|
||||||
orderingIndex([...formik.values.entries, defaultPaymentMade]),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
setFieldValue,
|
||||||
|
getFieldProps,
|
||||||
|
setValues,
|
||||||
|
values,
|
||||||
|
handleSubmit,
|
||||||
|
isSubmitting,
|
||||||
|
} = useFormik({
|
||||||
|
validationSchema,
|
||||||
|
initialValues,
|
||||||
|
onSubmit: handleSubmitForm,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFullAmountChange = useCallback(
|
||||||
|
(value) => {
|
||||||
|
if (value !== fullAmount) {
|
||||||
|
setAmountChangeAlert(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[fullAmount, setAmountChangeAlert],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle cancel button of amount change alert.
|
||||||
|
const handleCancelAmountChangeAlert = () => {
|
||||||
|
setAmountChangeAlert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle confirm button of amount change alert.
|
||||||
|
const handleConfirmAmountChangeAlert = () => {
|
||||||
|
setFullAmount(amountChangeAlert);
|
||||||
|
setAmountChangeAlert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle update data.
|
||||||
|
const handleUpdataData = useCallback(
|
||||||
|
(entries) => {
|
||||||
|
setFieldValue('entries', entries);
|
||||||
|
},
|
||||||
|
[setFieldValue],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle cancel button click.
|
||||||
|
const handleCancelClick = useCallback(() => {
|
||||||
|
history.push('/payment-mades');
|
||||||
|
}, [history]);
|
||||||
|
|
||||||
|
// Handle clear all lines button click.
|
||||||
const handleClearAllLines = () => {
|
const handleClearAllLines = () => {
|
||||||
formik.setFieldValue(
|
setClearLinesAlert(true);
|
||||||
'entries',
|
|
||||||
orderingIndex([...repeatValue(defaultPaymentMade, MIN_LINES_NUMBER)]),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const handleCancelClearLines = useCallback(() => {
|
||||||
formik.setFieldValue('payment_number', paymentNumber);
|
setClearLinesAlert(false);
|
||||||
setPaymentNumberChange(false);
|
}, [setClearLinesAlert]);
|
||||||
}, [nextPaymentNumberChanged, paymentNumber]);
|
|
||||||
|
const handleConfirmClearLines = useCallback(() => {
|
||||||
|
setFieldValue(
|
||||||
|
'entries',
|
||||||
|
values.entries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
payment_amount: 0,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
setClearLinesAlert(false);
|
||||||
|
}, [setFieldValue, setClearLinesAlert, values.entries]);
|
||||||
|
|
||||||
|
// Handle clear button click.
|
||||||
|
const handleClearBtnClick = useCallback(() => {
|
||||||
|
setClearFormAlert(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//
|
||||||
|
const handleCancelClearFormAlert = () => {
|
||||||
|
setClearFormAlert(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfirmCancelClearFormAlert = () => {
|
||||||
|
setValues({
|
||||||
|
...defaultInitialValues,
|
||||||
|
...(paymentMadeId
|
||||||
|
? {
|
||||||
|
vendor_id: values.vendor_id,
|
||||||
|
payment_number: values.payment_number,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
});
|
||||||
|
setClearFormAlert(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'payment_made_form'}>
|
<div
|
||||||
<form onSubmit={formik.handleSubmit}>
|
className={classNames(CLASSES.PAGE_FORM, CLASSES.PAGE_FORM_PAYMENT_MADE)}
|
||||||
<PaymentMadeHeader formik={formik} />
|
>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<PaymentMadeHeader
|
||||||
|
paymentMadeId={paymentMadeId}
|
||||||
|
vendorId={values.vendor_id}
|
||||||
|
errors={errors}
|
||||||
|
touched={touched}
|
||||||
|
setFieldValue={setFieldValue}
|
||||||
|
getFieldProps={getFieldProps}
|
||||||
|
values={values}
|
||||||
|
onFullAmountChanged={handleFullAmountChange}
|
||||||
|
/>
|
||||||
|
|
||||||
<PaymentMadeItemsTable
|
<PaymentMadeItemsTable
|
||||||
formik={formik}
|
fullAmount={fullAmount}
|
||||||
entries={formik.values.entries}
|
paymentEntries={values.entries}
|
||||||
vendor_id={formik.values.vendor_id}
|
vendorId={values.vendor_id}
|
||||||
onClickAddNewRow={handleClickAddNewRow}
|
paymentMadeId={paymentMadeId}
|
||||||
|
onUpdateData={handleUpdataData}
|
||||||
onClickClearAllLines={handleClearAllLines}
|
onClickClearAllLines={handleClearAllLines}
|
||||||
|
errors={errors?.entries}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'ok'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={clearFormAlert}
|
||||||
|
onCancel={handleCancelClearFormAlert}
|
||||||
|
onConfirm={handleConfirmCancelClearFormAlert}
|
||||||
|
>
|
||||||
|
<p>Are you sure to clear form data.</p>
|
||||||
|
</Alert>
|
||||||
|
|
||||||
|
<PaymentMadeFloatingActions
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
onCancelClick={handleCancelClick}
|
||||||
|
onClearBtnClick={handleClearBtnClick}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <Dragzone
|
{/* <Dragzone
|
||||||
@@ -319,18 +343,12 @@ function PaymentMadeForm({
|
|||||||
hint={'Attachments: Maxiumum size: 20MB'}
|
hint={'Attachments: Maxiumum size: 20MB'}
|
||||||
/> */}
|
/> */}
|
||||||
</form>
|
</form>
|
||||||
<PaymentMadeFloatingActions
|
|
||||||
formik={formik}
|
|
||||||
onSubmitClick={handleSubmitClick}
|
|
||||||
onCancel={handleCancelClick}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withPaymentMadeActions,
|
withPaymentMadeActions,
|
||||||
withDashboardActions,
|
|
||||||
withMediaActions,
|
withMediaActions,
|
||||||
withPaymentMadeDetail(),
|
withPaymentMadeDetail(),
|
||||||
withPaymentMade(({ nextPaymentNumberChanged }) => ({
|
withPaymentMade(({ nextPaymentNumberChanged }) => ({
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo, useCallback, useState } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
@@ -7,38 +7,59 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Classes,
|
Classes,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
|
import { sumBy } from 'lodash';
|
||||||
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 moment from 'moment';
|
import moment from 'moment';
|
||||||
import { momentFormatter, compose, tansformDateValue } from 'utils';
|
|
||||||
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,
|
||||||
Icon,
|
Money,
|
||||||
InputPrependButton,
|
Hint,
|
||||||
} from 'components';
|
} from 'components';
|
||||||
|
|
||||||
|
import withBills from '../Bill/withBills';
|
||||||
import withVender from 'containers/Vendors/withVendors';
|
import withVender from 'containers/Vendors/withVendors';
|
||||||
import withAccounts from 'containers/Accounts/withAccounts';
|
import withAccounts from 'containers/Accounts/withAccounts';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment made header form.
|
||||||
|
*/
|
||||||
function PaymentMadeFormHeader({
|
function PaymentMadeFormHeader({
|
||||||
formik: { errors, touched, setFieldValue, getFieldProps, values },
|
paymentMadeId,
|
||||||
|
vendorId,
|
||||||
|
|
||||||
|
// #useFormik
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
setFieldValue,
|
||||||
|
getFieldProps,
|
||||||
|
values,
|
||||||
|
|
||||||
|
onFullAmountChanged,
|
||||||
|
|
||||||
//#withVender
|
//#withVender
|
||||||
vendorsCurrentPage,
|
vendorsCurrentPage,
|
||||||
vendorItems,
|
vendorItems,
|
||||||
|
|
||||||
//#withAccouts
|
//#withAccouts
|
||||||
accountsList,
|
accountsList,
|
||||||
// #withDialogActions
|
|
||||||
openDialog,
|
// #withBills
|
||||||
|
vendorPayableBills,
|
||||||
}) {
|
}) {
|
||||||
|
const isNewMode = !paymentMadeId;
|
||||||
|
|
||||||
|
const payableFullAmount = useMemo(
|
||||||
|
() => sumBy(vendorPayableBills, 'due_amount'),
|
||||||
|
[vendorPayableBills],
|
||||||
|
);
|
||||||
|
|
||||||
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');
|
||||||
@@ -58,6 +79,14 @@ function PaymentMadeFormHeader({
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const triggerFullAmountChanged = (value) => {
|
||||||
|
onFullAmountChanged && onFullAmountChanged(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFullAmountBlur = (event) => {
|
||||||
|
triggerFullAmountChanged(event.currentTarget.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleFilterVender = (query, vender, index, exactMatch) => {
|
const handleFilterVender = (query, vender, index, exactMatch) => {
|
||||||
const normalizedTitle = vender.display_name.toLowerCase();
|
const normalizedTitle = vender.display_name.toLowerCase();
|
||||||
const normalizedQuery = query.toLowerCase();
|
const normalizedQuery = query.toLowerCase();
|
||||||
@@ -85,14 +114,15 @@ function PaymentMadeFormHeader({
|
|||||||
[accountsList],
|
[accountsList],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePaymentNumberChange = useCallback(() => {
|
const handleReceiveFullAmountClick = () => {
|
||||||
openDialog('payment-number-form', {});
|
setFieldValue('full_amount', payableFullAmount);
|
||||||
}, [openDialog]);
|
triggerFullAmountChanged(payableFullAmount);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER)}>
|
||||||
<div>
|
<div className={classNames(CLASSES.PAGE_FORM_HEADER_PRIMARY)}>
|
||||||
{/* Vendor name */}
|
{/* ------------ Vendor name ------------ */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'vendor_name'} />}
|
label={<T id={'vendor_name'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
@@ -114,10 +144,12 @@ function PaymentMadeFormHeader({
|
|||||||
selectedItemProp={'id'}
|
selectedItemProp={'id'}
|
||||||
defaultText={<T id={'select_vender_account'} />}
|
defaultText={<T id={'select_vender_account'} />}
|
||||||
labelProp={'display_name'}
|
labelProp={'display_name'}
|
||||||
|
buttonProps={{ disabled: !isNewMode }}
|
||||||
|
disabled={!isNewMode}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{/* Payment date */}
|
{/* ------------ Payment date ------------ */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'payment_date'} />}
|
label={<T id={'payment_date'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
@@ -136,7 +168,35 @@ function PaymentMadeFormHeader({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{/* payment number */}
|
{/* ------------ 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={payableFullAmount} currency={'USD'} />)
|
||||||
|
</a>
|
||||||
|
</FormGroup>
|
||||||
|
|
||||||
|
{/* ------------ Payment number ------------ */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'payment_no'} />}
|
label={<T id={'payment_no'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
@@ -154,25 +214,11 @@ function PaymentMadeFormHeader({
|
|||||||
errors.payment_number && touched.payment_number && Intent.DANGER
|
errors.payment_number && touched.payment_number && Intent.DANGER
|
||||||
}
|
}
|
||||||
minimal={true}
|
minimal={true}
|
||||||
rightElement={
|
|
||||||
<InputPrependButton
|
|
||||||
buttonProps={{
|
|
||||||
onClick: handlePaymentNumberChange,
|
|
||||||
icon: <Icon icon={'settings-18'} />,
|
|
||||||
}}
|
|
||||||
tooltip={true}
|
|
||||||
tooltipProps={{
|
|
||||||
content: 'Setting your auto-generated payment number',
|
|
||||||
position: Position.BOTTOM_LEFT,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
minimal={true}
|
|
||||||
{...getFieldProps('payment_number')}
|
{...getFieldProps('payment_number')}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
{/* payment account */}
|
{/* ------------ Payment account ------------ */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'payment_account'} />}
|
label={<T id={'payment_account'} />}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@@ -202,9 +248,8 @@ function PaymentMadeFormHeader({
|
|||||||
selectedAccountId={values.payment_account_id}
|
selectedAccountId={values.payment_account_id}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* reference */}
|
{/* ------------ Reference ------------ */}
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={<T id={'reference'} />}
|
label={<T id={'reference'} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
@@ -219,6 +264,7 @@ function PaymentMadeFormHeader({
|
|||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,5 +276,7 @@ export default compose(
|
|||||||
withAccounts(({ accountsList }) => ({
|
withAccounts(({ accountsList }) => ({
|
||||||
accountsList,
|
accountsList,
|
||||||
})),
|
})),
|
||||||
withDialogActions,
|
withBills(({ vendorPayableBills }) => ({
|
||||||
|
vendorPayableBills,
|
||||||
|
})),
|
||||||
)(PaymentMadeFormHeader);
|
)(PaymentMadeFormHeader);
|
||||||
|
|||||||
@@ -1,234 +1,137 @@
|
|||||||
import React, { useState, useMemo, useEffect, useCallback } from 'react';
|
import React, { useState, useEffect, useMemo, useCallback } from 'react';
|
||||||
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
|
import { useQuery } from 'react-query';
|
||||||
import { FormattedMessage as T, useIntl } from 'react-intl';
|
import { CloudLoadingIndicator } from 'components'
|
||||||
import { Icon, DataTable } from 'components';
|
import PaymentMadeItemsTableEditor from './PaymentMadeItemsTableEditor';
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
import { compose, formattedAmount, transformUpdatedRows } from 'utils';
|
|
||||||
import {
|
|
||||||
MoneyFieldCell,
|
|
||||||
DivFieldCell,
|
|
||||||
EmptyDiv,
|
|
||||||
} from 'components/DataTableCells';
|
|
||||||
|
|
||||||
|
import withPaymentMadeActions from './withPaymentMadeActions';
|
||||||
|
import withBillActions from '../Bill/withBillActions';
|
||||||
import withBills from '../Bill/withBills';
|
import withBills from '../Bill/withBills';
|
||||||
import { omit, pick } from 'lodash';
|
|
||||||
|
|
||||||
const ActionsCellRenderer = ({
|
import { compose } from 'utils';
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment made items table.
|
||||||
|
*/
|
||||||
function PaymentMadeItemsTable({
|
function PaymentMadeItemsTable({
|
||||||
//#ownProps
|
// #ownProps
|
||||||
onClickRemoveRow,
|
paymentMadeId,
|
||||||
onClickAddNewRow,
|
vendorId,
|
||||||
|
fullAmount,
|
||||||
|
onUpdateData,
|
||||||
|
paymentEntries = [], // { bill_id: number, payment_amount: number, id?: number }
|
||||||
onClickClearAllLines,
|
onClickClearAllLines,
|
||||||
entries,
|
errors,
|
||||||
formik: { errors, setFieldValue, values },
|
|
||||||
|
// #withBillActions
|
||||||
|
requestFetchDueBills,
|
||||||
|
|
||||||
|
// #withBills
|
||||||
|
vendorPayableBills,
|
||||||
|
paymentMadePayableBills,
|
||||||
|
|
||||||
|
// #withPaymentMadeDetail
|
||||||
|
paymentMade,
|
||||||
}) {
|
}) {
|
||||||
const [rows, setRows] = useState([]);
|
const [tableData, setTableData] = useState([]);
|
||||||
const [entrie, setEntrie] = useState([]);
|
const [localAmount, setLocalAmount] = useState(fullAmount);
|
||||||
const { formatMessage } = useIntl();
|
|
||||||
|
// 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 triggerUpdateData = useCallback((data) => {
|
||||||
|
onUpdateData && onUpdateData(data);
|
||||||
|
}, [onUpdateData]);
|
||||||
|
|
||||||
|
// Merges payment entries with payable bills.
|
||||||
|
const computedTableData = useMemo(() => {
|
||||||
|
const entriesTable = new Map(
|
||||||
|
paymentEntries.map((e) => [e.bill_id, e]),
|
||||||
|
);
|
||||||
|
return payableBills.map((bill) => {
|
||||||
|
const entry = entriesTable.get(bill.id);
|
||||||
|
return {
|
||||||
|
...bill,
|
||||||
|
bill_id: bill.id,
|
||||||
|
bill_payment_amount: bill.payment_amount,
|
||||||
|
payment_amount: entry ? entry.payment_amount : 0,
|
||||||
|
id: entry ? entry.id : null,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, [paymentEntries, payableBills]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRows([...entries.map((e) => ({ ...e }))]);
|
setTableData(computedTableData);
|
||||||
}, [entries]);
|
}, [computedTableData]);
|
||||||
|
|
||||||
const columns = useMemo(
|
// Handle mapping `fullAmount` prop to `localAmount` state.
|
||||||
() => [
|
useEffect(() => {
|
||||||
{
|
if (localAmount !== fullAmount) {
|
||||||
Header: '#',
|
let _fullAmount = fullAmount;
|
||||||
accessor: 'index',
|
const newTableData = tableData.map((data) => {
|
||||||
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
const amount = Math.min(data.due_amount, _fullAmount);
|
||||||
width: 40,
|
_fullAmount -= Math.max(amount, 0);
|
||||||
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,
|
|
||||||
disableResizing: true,
|
|
||||||
width: 250,
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
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,
|
|
||||||
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);
|
|
||||||
},
|
|
||||||
[entrie, 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 {
|
return {
|
||||||
...rows[rowIndex],
|
...data,
|
||||||
[columnId]: value,
|
payment_amount: amount,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
return row;
|
|
||||||
});
|
});
|
||||||
// setRows(newRows);
|
setTableData(newTableData);
|
||||||
setFieldValue(
|
setLocalAmount(fullAmount);
|
||||||
'entries',
|
triggerUpdateData(newTableData);
|
||||||
newRows,
|
}
|
||||||
// .map((row) => ({
|
}, [
|
||||||
// ...pick(row, ['payment_amount']),
|
tableData,
|
||||||
// invoice_id: row.id,
|
setTableData,
|
||||||
// })),
|
setLocalAmount,
|
||||||
);
|
triggerUpdateData,
|
||||||
},
|
localAmount,
|
||||||
[rows, setFieldValue, setRows],
|
fullAmount,
|
||||||
);
|
]);
|
||||||
return (
|
|
||||||
<div className={'estimate-form__table'}>
|
|
||||||
<DataTable
|
|
||||||
columns={columns}
|
|
||||||
data={rows}
|
|
||||||
rowClassNames={rowClassNames}
|
|
||||||
spinnerProps={false}
|
|
||||||
payload={{
|
|
||||||
errors: errors.entries || [],
|
|
||||||
updateData: handleUpdateData,
|
|
||||||
removeRow: handleRemoveRow,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className={'mt1'}>
|
|
||||||
<Button
|
|
||||||
small={true}
|
|
||||||
className={'button--secondary button--new-line'}
|
|
||||||
onClick={onClickNewRow}
|
|
||||||
>
|
|
||||||
<T id={'new_lines'} />
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
// Fetches vendor due bills.
|
||||||
small={true}
|
const fetchVendorDueBills = useQuery(
|
||||||
className={'button--secondary button--clear-lines ml1'}
|
['vendor-due-bills', vendorId],
|
||||||
onClick={handleClickClearAllLines}
|
(key, _vendorId) => requestFetchDueBills(_vendorId),
|
||||||
>
|
{ enabled: isNewMode && vendorId },
|
||||||
<T id={'clear_all_lines'} />
|
);
|
||||||
</Button>
|
|
||||||
</div>
|
// Handle update data.
|
||||||
</div>
|
const handleUpdateData = (rows) => {
|
||||||
|
triggerUpdateData(rows);
|
||||||
|
};
|
||||||
|
|
||||||
|
const noResultsMessage = (vendorId) ?
|
||||||
|
'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 (
|
||||||
|
<CloudLoadingIndicator isLoading={fetchVendorDueBills.isFetching}>
|
||||||
|
<PaymentMadeItemsTableEditor
|
||||||
|
noResultsMessage={noResultsMessage}
|
||||||
|
data={tableData}
|
||||||
|
errors={errors}
|
||||||
|
onUpdateData={handleUpdateData}
|
||||||
|
onClickClearAllLines={onClickClearAllLines}
|
||||||
|
/>
|
||||||
|
</CloudLoadingIndicator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose()(PaymentMadeItemsTable);
|
export default compose(
|
||||||
// withBills(({}) => ({}))
|
withPaymentMadeActions,
|
||||||
|
withBillActions,
|
||||||
|
withBills(({ vendorPayableBills, paymentMadePayableBills }) => ({
|
||||||
|
vendorPayableBills,
|
||||||
|
paymentMadePayableBills,
|
||||||
|
})),
|
||||||
|
)(PaymentMadeItemsTable);
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
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 { 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payment made items editor table.
|
||||||
|
*/
|
||||||
|
export default function PaymentMadeItemsTableEditor({
|
||||||
|
//#ownProps
|
||||||
|
onClickClearAllLines,
|
||||||
|
onUpdateData,
|
||||||
|
data,
|
||||||
|
errors,
|
||||||
|
noResultsMessage
|
||||||
|
}) {
|
||||||
|
const transformedData = useMemo(() => {
|
||||||
|
const rows = data;
|
||||||
|
const totalRow = {
|
||||||
|
due_amount: sumBy(data, 'due_amount'),
|
||||||
|
payment_amount: sumBy(data, 'payment_amount'),
|
||||||
|
};
|
||||||
|
if (rows.length > 0) { rows.push(totalRow) }
|
||||||
|
return rows;
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const [localData, setLocalData] = useState(transformedData);
|
||||||
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (localData !== transformedData) {
|
||||||
|
setLocalData(transformedData);
|
||||||
|
}
|
||||||
|
}, [setLocalData, localData, transformedData]);
|
||||||
|
|
||||||
|
const columns = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
Header: '#',
|
||||||
|
accessor: 'index',
|
||||||
|
Cell: ({ row: { index } }) => <span>{index + 1}</span>,
|
||||||
|
width: 40,
|
||||||
|
disableResizing: true,
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'Date' }),
|
||||||
|
id: 'bill_date',
|
||||||
|
accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
|
||||||
|
Cell: CellRenderer(EmptyDiv, 'bill_date'),
|
||||||
|
disableSortBy: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'bill_number' }),
|
||||||
|
accessor: (row) => `#${row.bill_number}`,
|
||||||
|
Cell: CellRenderer(EmptyDiv, 'bill_number'),
|
||||||
|
disableSortBy: true,
|
||||||
|
className: 'bill_number',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'bill_amount' }),
|
||||||
|
accessor: 'amount',
|
||||||
|
Cell: CellRenderer(DivFieldCell, 'amount'),
|
||||||
|
disableSortBy: true,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'amount_due' }),
|
||||||
|
accessor: 'due_amount',
|
||||||
|
Cell: TotalCellRederer(DivFieldCell, 'due_amount'),
|
||||||
|
disableSortBy: true,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Header: formatMessage({ id: 'payment_amount' }),
|
||||||
|
accessor: 'payment_amount',
|
||||||
|
Cell: TotalCellRederer(MoneyFieldCell, 'payment_amount'),
|
||||||
|
disableSortBy: true,
|
||||||
|
className: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[formatMessage],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle click clear all lines button.
|
||||||
|
const handleClickClearAllLines = () => {
|
||||||
|
onClickClearAllLines && onClickClearAllLines();
|
||||||
|
};
|
||||||
|
|
||||||
|
const rowClassNames = useCallback(
|
||||||
|
(row) => ({ 'row--total': localData.length === row.index + 1 }),
|
||||||
|
[localData],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle update data.
|
||||||
|
const handleUpdateData = useCallback(
|
||||||
|
(rowIndex, columnId, value) => {
|
||||||
|
const newRows = transformUpdatedRows(
|
||||||
|
localData,
|
||||||
|
rowIndex,
|
||||||
|
columnId,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
deletePaymentMade,
|
deletePaymentMade,
|
||||||
fetchPaymentMadesTable,
|
fetchPaymentMadesTable,
|
||||||
fetchPaymentMade,
|
fetchPaymentMade,
|
||||||
|
fetchPaymentMadeBills,
|
||||||
} from 'store/PaymentMades/paymentMade.actions';
|
} from 'store/PaymentMades/paymentMade.actions';
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@@ -16,6 +17,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
requestFetchPaymentMadesTable: (query = {}) =>
|
requestFetchPaymentMadesTable: (query = {}) =>
|
||||||
dispatch(fetchPaymentMadesTable({ query: { ...query } })),
|
dispatch(fetchPaymentMadesTable({ query: { ...query } })),
|
||||||
|
|
||||||
|
requestFetchPaymentMadeBills: (paymentMadeId) => dispatch(fetchPaymentMadeBills({ paymentMadeId })),
|
||||||
|
|
||||||
changePaymentMadeView: (id) =>
|
changePaymentMadeView: (id) =>
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.PAYMENT_MADE_SET_CURRENT_VIEW,
|
type: t.PAYMENT_MADE_SET_CURRENT_VIEW,
|
||||||
|
|||||||
@@ -123,11 +123,19 @@ export const fetchDueBills = ({ vendorId }) => (dispatch) => new Promise((resolv
|
|||||||
|
|
||||||
ApiService.get(`purchases/bills/due`, { params }).then((response) => {
|
ApiService.get(`purchases/bills/due`, { params }).then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.DUE_BILLS_SET,
|
type: t.BILLS_ITEMS_SET,
|
||||||
|
payload: {
|
||||||
|
bills: response.data.bills,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if ( vendorId ) {
|
||||||
|
dispatch({
|
||||||
|
type: t.BILLS_PAYABLE_BY_VENDOR_ID,
|
||||||
payload: {
|
payload: {
|
||||||
bills: response.data.bills,
|
bills: response.data.bills,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
resolve(response);
|
resolve(response);
|
||||||
}).catch(error => { reject(error) });
|
}).catch(error => { reject(error) });
|
||||||
});
|
});
|
||||||
@@ -13,7 +13,10 @@ const initialState = {
|
|||||||
page: 1,
|
page: 1,
|
||||||
},
|
},
|
||||||
nextBillNumberChanged: false,
|
nextBillNumberChanged: false,
|
||||||
dueBills: {},
|
payable: {
|
||||||
|
byVendorId: [],
|
||||||
|
byBillPayamentId: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultBill = {
|
const defaultBill = {
|
||||||
@@ -105,23 +108,31 @@ const reducer = createReducer(initialState, {
|
|||||||
state.nextBillNumberChanged = isChanged;
|
state.nextBillNumberChanged = isChanged;
|
||||||
},
|
},
|
||||||
|
|
||||||
[t.DUE_BILLS_SET]: (state, action) => {
|
[t.BILLS_PAYABLE_BY_VENDOR_ID]: (state, action) => {
|
||||||
const { bills } = action.payload;
|
const { bills } = action.payload;
|
||||||
|
const _data = {};
|
||||||
const _dueBills = { ...state.dueBills };
|
|
||||||
const _bills = { ...state.items };
|
|
||||||
|
|
||||||
bills.forEach((bill) => {
|
bills.forEach((bill) => {
|
||||||
_bills[bill.id] = { ...bill };
|
if (!_data[bill.vendor_id]) {
|
||||||
|
_data[bill.vendor_id] = [];
|
||||||
if (!_dueBills[bill.vendor_id]) {
|
|
||||||
_dueBills[bill.vendor_id] = []
|
|
||||||
}
|
}
|
||||||
_dueBills[bill.vendor_id].push(bill.id);
|
_data[bill.vendor_id].push(bill.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
state.items = { ..._bills };
|
state.payable.byVendorId = {
|
||||||
state.dueBills = { ..._dueBills };
|
...state.payable.byVendorId,
|
||||||
|
..._data,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
[t.BILLS_PAYABLE_BY_PAYMENT_ID]: (state, action) => {
|
||||||
|
const { bills, billPaymentId } = action.payload;
|
||||||
|
const _data = [];
|
||||||
|
|
||||||
|
bills.forEach((bill) => {
|
||||||
|
_data.push(bill.id);
|
||||||
|
});
|
||||||
|
state.payable.byBillPayamentId[billPaymentId] = _data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ const billByIdSelector = (state, props) => state.bills.items[props.billId];
|
|||||||
* Retrieve vendor due bills ids.
|
* Retrieve vendor due bills ids.
|
||||||
* @return {number[]}
|
* @return {number[]}
|
||||||
*/
|
*/
|
||||||
const billsDueVendorSelector = (state, props) => state.bills.dueBills[props.vendorId];
|
const billsPayableVendorSelector = (state, props) => state.bills.payable.byVendorId[props.vendorId];
|
||||||
|
const billsPayableByPaymentMadeSelector = (state, props) => {
|
||||||
|
return state.bills.payable.byBillPayamentId[props.paymentMadeId];
|
||||||
|
}
|
||||||
|
|
||||||
const billPaginationSelector = (state, props) => {
|
const billPaginationSelector = (state, props) => {
|
||||||
const viewId = state.bills.currentViewId;
|
const viewId = state.bills.currentViewId;
|
||||||
@@ -62,16 +65,26 @@ export const getBillPaginationMetaFactory = () =>
|
|||||||
return billPage?.paginationMeta || {};
|
return billPage?.paginationMeta || {};
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve due bills of specific vendor.
|
export const getVendorPayableBillsFactory = () =>
|
||||||
*/
|
|
||||||
export const getVendorDueBillsFactory = () =>
|
|
||||||
createSelector(
|
createSelector(
|
||||||
billItemsSelector,
|
billItemsSelector,
|
||||||
billsDueVendorSelector,
|
billsPayableVendorSelector,
|
||||||
(billsItems, dueBillsIds) => {
|
(billsItems, payableBillsIds) => {
|
||||||
return Array.isArray(dueBillsIds)
|
return Array.isArray(payableBillsIds)
|
||||||
? pickItemsFromIds(billsItems, dueBillsIds) || []
|
? pickItemsFromIds(billsItems, payableBillsIds) || []
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export const getPayableBillsByPaymentMadeFactory = () =>
|
||||||
|
createSelector(
|
||||||
|
billItemsSelector,
|
||||||
|
billsPayableByPaymentMadeSelector,
|
||||||
|
(billsItems, payableBillsIds) => {
|
||||||
|
return Array.isArray(payableBillsIds)
|
||||||
|
? pickItemsFromIds(billsItems, payableBillsIds) || []
|
||||||
: [];
|
: [];
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -10,5 +10,7 @@ export default {
|
|||||||
BILLS_PAGE_SET: 'BILLS_PAGE_SET',
|
BILLS_PAGE_SET: 'BILLS_PAGE_SET',
|
||||||
BILLS_ITEMS_SET: 'BILLS_ITEMS_SET',
|
BILLS_ITEMS_SET: 'BILLS_ITEMS_SET',
|
||||||
BILL_NUMBER_CHANGED: 'BILL_NUMBER_CHANGED',
|
BILL_NUMBER_CHANGED: 'BILL_NUMBER_CHANGED',
|
||||||
DUE_BILLS_SET: 'DUE_BILLS_SET'
|
|
||||||
|
BILLS_PAYABLE_BY_PAYMENT_ID: 'BILLS_PAYABLE_BY_PAYMENT_ID',
|
||||||
|
BILLS_PAYABLE_BY_VENDOR_ID: 'BILLS_PAYABLE_BY_VENDOR_ID',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -106,7 +106,20 @@ export const fetchPaymentMade = ({ id }) => {
|
|||||||
type: t.PAYMENT_MADE_SET,
|
type: t.PAYMENT_MADE_SET,
|
||||||
payload: {
|
payload: {
|
||||||
id,
|
id,
|
||||||
bill_payment: response.data.bill_payment,
|
paymentMade: response.data.bill_payment,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: t.BILLS_PAYABLE_BY_PAYMENT_ID,
|
||||||
|
payload: {
|
||||||
|
billPaymentId: id,
|
||||||
|
bills: response.data.bill_payment.payable_bills,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dispatch({
|
||||||
|
type: t.BILLS_ITEMS_SET,
|
||||||
|
payload: {
|
||||||
|
bills: response.data.bill_payment.payable_bills,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
resovle(response);
|
resovle(response);
|
||||||
@@ -118,3 +131,17 @@ export const fetchPaymentMade = ({ id }) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchPaymentMadeBills = ({ paymentMadeId }) => (dispatch) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ApiService.get(`purchases/bill_payments/${paymentMadeId}/bills`).then((response) => {
|
||||||
|
dispatch({
|
||||||
|
type: t.BILLS_ITEMS_SET,
|
||||||
|
payload: {
|
||||||
|
bills: response.data.bills,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
resolve(response);
|
||||||
|
}).catch((error) => { reject(error) });
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -39,6 +39,17 @@ const reducer = createReducer(initialState, {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[t.PAYMENT_MADE_SET]: (state, action) => {
|
||||||
|
const { id, paymentMade } = action.payload;
|
||||||
|
const _oldPaymentMade = (state.items[id] || {});
|
||||||
|
|
||||||
|
state.items[id] = {
|
||||||
|
...defaultPaymentMade,
|
||||||
|
..._oldPaymentMade,
|
||||||
|
...paymentMade,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
[t.PAYMENT_MADE_DELETE]: (state, action) => {
|
[t.PAYMENT_MADE_DELETE]: (state, action) => {
|
||||||
const { id } = action.payload;
|
const { id } = action.payload;
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ const paymentMadesIds = (state, props) => {
|
|||||||
return state.paymentMades.items[props.paymentMadeId];
|
return state.paymentMades.items[props.paymentMadeId];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const paymentMadeEntries = (state, props) => props.paymentMadeEntries;
|
||||||
|
const billsItemsSelector = (state, props) => state.bills.items;
|
||||||
|
|
||||||
export const getPaymentMadeCurrentPageFactory = () =>
|
export const getPaymentMadeCurrentPageFactory = () =>
|
||||||
createSelector(
|
createSelector(
|
||||||
paymentMadesPageSelector,
|
paymentMadesPageSelector,
|
||||||
@@ -54,3 +57,15 @@ export const getPaymentMadeByIdFactory = () =>
|
|||||||
createSelector(paymentMadesIds, (payment_Made) => {
|
createSelector(paymentMadesIds, (payment_Made) => {
|
||||||
return payment_Made;
|
return payment_Made;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getPaymentMadeEntriesDataFactory = () =>
|
||||||
|
createSelector(
|
||||||
|
billsItemsSelector,
|
||||||
|
paymentMadeEntries,
|
||||||
|
(billsItems, paymentEntries) => {
|
||||||
|
return Array.isArray(paymentEntries) ?
|
||||||
|
paymentEntries.map((entry) => ({
|
||||||
|
...entry, ...(billsItems[entry.bill_id] || {}),
|
||||||
|
})) : [];
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -70,6 +70,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
|||||||
@import 'pages/estimates';
|
@import 'pages/estimates';
|
||||||
@import 'pages/invoice-form';
|
@import 'pages/invoice-form';
|
||||||
@import 'pages/receipt-form';
|
@import 'pages/receipt-form';
|
||||||
|
@import 'pages/payment-made';
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
@import 'views/filter-dropdown';
|
@import 'views/filter-dropdown';
|
||||||
@@ -219,8 +220,16 @@ body.authentication {
|
|||||||
padding: 15px;
|
padding: 15px;
|
||||||
margin: 15px 0 0 0;
|
margin: 15px 0 0 0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
&__floating-actions{
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: #fff;
|
||||||
|
padding: 14px 18px;
|
||||||
|
border-top: 1px solid #ececec;
|
||||||
|
}
|
||||||
|
}
|
||||||
.datatable-editor{
|
.datatable-editor{
|
||||||
|
|
||||||
padding: 15px 15px 0;
|
padding: 15px 15px 0;
|
||||||
@@ -391,3 +400,32 @@ body.authentication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.cloud-spinner{
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.is-loading:before{
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp3-spinner{
|
||||||
|
position: absolute;
|
||||||
|
z-index: 999999;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -20px;
|
||||||
|
margin-left: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.is-loading) .bp3-spinner{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
61
client/src/style/pages/payment-made.scss
Normal file
61
client/src/style/pages/payment-made.scss
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
|
||||||
|
|
||||||
|
.page-form--payment-made {
|
||||||
|
$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{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -243,3 +243,7 @@ export const flatToNestedArray = (
|
|||||||
});
|
});
|
||||||
return nestedArray;
|
return nestedArray;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const orderingLinesIndexes = (lines, attribute = 'index') => {
|
||||||
|
return lines.map((line, index) => ({ ...line, [attribute]: index + 1 }));
|
||||||
|
};
|
||||||
@@ -151,7 +151,8 @@ export default class BillsController extends BaseController {
|
|||||||
get dueBillsListingValidationSchema() {
|
get dueBillsListingValidationSchema() {
|
||||||
return [
|
return [
|
||||||
query('vendor_id').optional().trim().escape(),
|
query('vendor_id').optional().trim().escape(),
|
||||||
]
|
query('payment_made_id').optional().trim().escape(),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -331,7 +332,13 @@ export default class BillsController extends BaseController {
|
|||||||
errors: [{ type: 'BILL_ENTRIES_IDS_NOT_FOUND', code: 900 }],
|
errors: [{ type: 'BILL_ENTRIES_IDS_NOT_FOUND', code: 900 }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (error.errorType === 'ITEMS_NOT_FOUND') {
|
||||||
|
return res.boom.badRequest(null, {
|
||||||
|
errors: [{ type: 'ITEMS_NOT_FOUND', code: 1000 }],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
console.log(error.errorType);
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,8 +198,14 @@ export default class BillsPayments extends BaseController {
|
|||||||
const { id: billPaymentId } = req.params;
|
const { id: billPaymentId } = req.params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const billPayment = await this.billPaymentService.getBillPayment(tenantId, billPaymentId);
|
const { billPayment, payableBills } = await this.billPaymentService.getBillPayment(tenantId, billPaymentId);
|
||||||
return res.status(200).send({ bill_payment: billPayment });
|
|
||||||
|
return res.status(200).send({
|
||||||
|
bill_payment: {
|
||||||
|
...this.transfromToResponse({ ...billPayment }),
|
||||||
|
payable_bills: payableBills,
|
||||||
|
},
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Router, Request, Response, NextFunction } from 'express';
|
import { Router, Request, Response, NextFunction } from 'express';
|
||||||
import { check, param, query } from 'express-validator';
|
import { check, param, query } from 'express-validator';
|
||||||
import { raw } from 'objection';
|
|
||||||
import { Service, Inject } from 'typedi';
|
import { Service, Inject } from 'typedi';
|
||||||
import BaseController from '../BaseController';
|
import BaseController from '../BaseController';
|
||||||
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
import asyncMiddleware from 'api/middleware/asyncMiddleware';
|
||||||
@@ -52,11 +51,11 @@ export default class SaleInvoicesController extends BaseController{
|
|||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
'/due', [
|
'/payable', [
|
||||||
...this.dueSalesInvoicesListValidationSchema,
|
...this.dueSalesInvoicesListValidationSchema,
|
||||||
],
|
],
|
||||||
this.validationResult,
|
this.validationResult,
|
||||||
asyncMiddleware(this.getDueInvoices.bind(this)),
|
asyncMiddleware(this.getPayableInvoices.bind(this)),
|
||||||
this.handleServiceErrors,
|
this.handleServiceErrors,
|
||||||
);
|
);
|
||||||
router.get(
|
router.get(
|
||||||
@@ -251,12 +250,12 @@ export default class SaleInvoicesController extends BaseController{
|
|||||||
* @param {NextFunction} next -
|
* @param {NextFunction} next -
|
||||||
* @return {Response|void}
|
* @return {Response|void}
|
||||||
*/
|
*/
|
||||||
public async getDueInvoices(req: Request, res: Response, next: NextFunction) {
|
public async getPayableInvoices(req: Request, res: Response, next: NextFunction) {
|
||||||
const { tenantId } = req;
|
const { tenantId } = req;
|
||||||
const { customerId } = this.matchedQueryData(req);
|
const { customerId } = this.matchedQueryData(req);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const salesInvoices = await this.saleInvoiceService.getDueInvoices(tenantId, customerId);
|
const salesInvoices = await this.saleInvoiceService.getPayableInvoices(tenantId, customerId);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
sales_invoices: this.transfromToResponse(salesInvoices),
|
sales_invoices: this.transfromToResponse(salesInvoices),
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default class BillPayment extends TenantModel {
|
|||||||
filter(builder) {
|
filter(builder) {
|
||||||
builder.where('reference_type', 'BillPayment');
|
builder.where('reference_type', 'BillPayment');
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ export default class BillPaymentsService {
|
|||||||
* @return {object}
|
* @return {object}
|
||||||
*/
|
*/
|
||||||
public async getBillPayment(tenantId: number, billPaymentId: number) {
|
public async getBillPayment(tenantId: number, billPaymentId: number) {
|
||||||
const { BillPayment } = this.tenancy.models(tenantId);
|
const { BillPayment, Bill } = this.tenancy.models(tenantId);
|
||||||
const billPayment = await BillPayment.query()
|
const billPayment = await BillPayment.query()
|
||||||
.findById(billPaymentId)
|
.findById(billPaymentId)
|
||||||
.withGraphFetched('entries')
|
.withGraphFetched('entries')
|
||||||
@@ -499,7 +499,16 @@ export default class BillPaymentsService {
|
|||||||
if (!billPayment) {
|
if (!billPayment) {
|
||||||
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
throw new ServiceError(ERRORS.PAYMENT_MADE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
return billPayment;
|
|
||||||
|
const payableBills = await Bill.query().onBuild((builder) => {
|
||||||
|
const billsIds = billPayment.entries.map((entry) => entry.billId);
|
||||||
|
|
||||||
|
builder.where('vendor_id', billPayment.vendorId);
|
||||||
|
builder.orWhereIn('id', billsIds);
|
||||||
|
builder.orderByRaw(`FIELD(id, ${billsIds.join(', ')}) DESC`);
|
||||||
|
builder.orderBy('bill_date', 'ASC');
|
||||||
|
})
|
||||||
|
return { billPayment, payableBills };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { omit, sumBy, pick, difference } from 'lodash';
|
import { omit, sumBy, pick, difference, assignWith } from 'lodash';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Inject, Service } from 'typedi';
|
import { Inject, Service } from 'typedi';
|
||||||
import {
|
import {
|
||||||
@@ -23,11 +23,13 @@ import {
|
|||||||
IPaginationMeta,
|
IPaginationMeta,
|
||||||
IFilterMeta,
|
IFilterMeta,
|
||||||
IBillsFilter,
|
IBillsFilter,
|
||||||
|
IBillPaymentEntry,
|
||||||
} from 'interfaces';
|
} from 'interfaces';
|
||||||
import { ServiceError } from 'exceptions';
|
import { ServiceError } from 'exceptions';
|
||||||
import ItemsService from 'services/Items/ItemsService';
|
import ItemsService from 'services/Items/ItemsService';
|
||||||
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
|
||||||
import { Bill } from 'models';
|
import { Bill } from 'models';
|
||||||
|
import PaymentMadesSubscriber from 'subscribers/paymentMades';
|
||||||
|
|
||||||
const ERRORS = {
|
const ERRORS = {
|
||||||
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
|
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
|
||||||
@@ -428,6 +430,7 @@ export default class BillsService extends SalesInvoicesCost {
|
|||||||
const { Bill } = this.tenancy.models(tenantId);
|
const { Bill } = this.tenancy.models(tenantId);
|
||||||
|
|
||||||
const dueBills = await Bill.query().onBuild((query) => {
|
const dueBills = await Bill.query().onBuild((query) => {
|
||||||
|
query.orderBy('bill_date', 'DESC');
|
||||||
query.modify('dueBills');
|
query.modify('dueBills');
|
||||||
|
|
||||||
if (vendorId) {
|
if (vendorId) {
|
||||||
|
|||||||
@@ -413,7 +413,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
|
|||||||
* @param {number} tenantId
|
* @param {number} tenantId
|
||||||
* @param {number} customerId
|
* @param {number} customerId
|
||||||
*/
|
*/
|
||||||
public async getDueInvoices(
|
public async getPayableInvoices(
|
||||||
tenantId: number,
|
tenantId: number,
|
||||||
customerId?: number,
|
customerId?: number,
|
||||||
): Promise<ISaleInvoice> {
|
): Promise<ISaleInvoice> {
|
||||||
|
|||||||
Reference in New Issue
Block a user