diff --git a/client/src/containers/Sales/PaymentReceive/PaymentReceiveForm.js b/client/src/containers/Sales/PaymentReceive/PaymentReceiveForm.js
index 02eb07d85..366555a59 100644
--- a/client/src/containers/Sales/PaymentReceive/PaymentReceiveForm.js
+++ b/client/src/containers/Sales/PaymentReceive/PaymentReceiveForm.js
@@ -9,89 +9,41 @@ import React, {
import * as Yup from 'yup';
import { useFormik } from 'formik';
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 { 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 PaymentReceiveItemsTable from './PaymentReceiveItemsTable';
import PaymentReceiveFloatingActions from './PaymentReceiveFloatingActions';
-import withDashboardActions from 'containers/Dashboard/withDashboardActions';
import withMediaActions from 'containers/Media/withMediaActions';
import withPaymentReceivesActions from './withPaymentReceivesActions';
-import withInvoices from '../Invoice/withInvoices';
import withPaymentReceiveDetail from './withPaymentReceiveDetail';
-import withPaymentReceives from './withPaymentReceives';
import { AppToaster } from 'components';
-import Dragzone from 'components/Dragzone';
-import useMedia from 'hooks/useMedia';
-import { compose, repeatValue } from 'utils';
-
-const MIN_LINES_NUMBER = 5;
+import { compose } from 'utils';
function PaymentReceiveForm({
- //#withMedia
- requestSubmitMedia,
- requestDeleteMedia,
+ // #ownProps
+ paymentReceiveId,
//#WithPaymentReceiveActions
requestSubmitPaymentReceive,
requestEditPaymentReceive,
- //#withDashboard
- changePageTitle,
- changePageSubtitle,
-
- //#withPaymentReceiveDetail
+ // #withPaymentReceive
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 [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({
customer_id: Yup.string()
.label(formatMessage({ id: 'customer_name_' }))
@@ -102,9 +54,7 @@ function PaymentReceiveForm({
deposit_account_id: Yup.number()
.required()
.label(formatMessage({ id: 'deposit_account_' })),
- // receive_amount: Yup.number()
- // .required()
- // .label(formatMessage({ id: 'receive_amount_' })),
+ full_amount: Yup.number().nullable(),
payment_receive_no: Yup.number()
.required()
.label(formatMessage({ id: 'payment_receive_no_' })),
@@ -112,11 +62,9 @@ function PaymentReceiveForm({
description: Yup.string().nullable(),
entries: Yup.array().of(
Yup.object().shape({
- payment_amount: Yup.number().nullable(),
- invoice_no: Yup.number().nullable(),
- balance: Yup.number().nullable(),
+ id: 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()
.nullable()
.when(['payment_amount'], {
@@ -126,15 +74,7 @@ function PaymentReceiveForm({
}),
),
});
-
- const handleDropFiles = useCallback((_files) => {
- setFiles(_files.filter((file) => file.uploaded === false));
- }, []);
-
- const savePaymentReceiveSubmit = useCallback((payload) => {
- onFormSubmit && onFormSubmit(payload);
- });
-
+ // Default payment receive.
const defaultPaymentReceive = useMemo(
() => ({
invoice_id: '',
@@ -146,6 +86,12 @@ function PaymentReceiveForm({
}),
[],
);
+ const defaultPaymentReceiveEntry = {
+ id: null,
+ payment_amount: null,
+ invoice_id: null,
+ };
+ // Form initial values.
const defaultInitialValues = useMemo(
() => ({
customer_id: '',
@@ -153,20 +99,13 @@ function PaymentReceiveForm({
payment_date: moment(new Date()).format('YYYY-MM-DD'),
reference_no: '',
payment_receive_no: '',
- // receive_amount: '',
description: '',
- entries: [...repeatValue(defaultPaymentReceive, MIN_LINES_NUMBER)],
+ entries: [],
}),
- [defaultPaymentReceive],
+ [],
);
- const orderingIndex = (_entries) => {
- return _entries.map((item, index) => ({
- ...item,
- index: index + 1,
- }));
- };
-
+ // Form initial values.
const initialValues = useMemo(
() => ({
...(paymentReceive
@@ -176,167 +115,205 @@ function PaymentReceiveForm({
...paymentReceive.entries.map((paymentReceive) => ({
...pick(paymentReceive, Object.keys(defaultPaymentReceive)),
})),
- ...repeatValue(
- defaultPaymentReceive,
- Math.max(MIN_LINES_NUMBER - paymentReceive.entries.length, 0),
- ),
],
}
: {
...defaultInitialValues,
- entries: orderingIndex(defaultInitialValues.entries),
}),
}),
[paymentReceive, defaultInitialValues, defaultPaymentReceive],
);
- const initialAttachmentFiles = useMemo(() => {
- return paymentReceive && paymentReceive.media
- ? paymentReceive.media.map((attach) => ({
- preview: attach.attachment_file,
- uploaded: true,
- metadata: { ...attach },
- }))
- : [];
- }, [paymentReceive]);
+ // Handle form submit.
+ const handleSubmitForm = (
+ values,
+ { setSubmitting, resetForm, setFieldError },
+ ) => {
+ setSubmitting(true);
- 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,
validationSchema,
initialValues: {
...initialValues,
},
- onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => {
- 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);
- });
- }
- },
+ onSubmit: handleSubmitForm,
});
- const handleDeleteFile = useCallback(
- (_deletedFiles) => {
- _deletedFiles.forEach((deletedFile) => {
- if (deletedFile.upload && deletedFile.metadata.id) {
- setDeletedFiles([...deletedFiles, deletedFile.metadata.id]);
- }
- });
+ // Handle update data.
+ const handleUpdataData = useCallback(
+ (entries) => {
+ setFieldValue('entries', entries);
},
- [setDeletedFiles, deletedFiles],
+ [setFieldValue],
);
- const handleSubmitClick = useCallback(
- (payload) => {
- setPayload(payload);
- formik.submitForm();
+ const handleFullAmountChange = useCallback(
+ (value) => {
+ if (value !== fullAmount) {
+ setAmountChangeAlert(value);
+ }
},
- [setPayload, formik],
+ [fullAmount, setAmountChangeAlert],
);
- const handleCancelClick = useCallback(
- (payload) => {
- onCancelForm && onCancelForm(payload);
- },
- [onCancelForm],
- );
+ // Handle clear all lines button click.
+ const handleClearAllLines = useCallback(() => {
+ setClearLinesAlert(true);
+ }, [setClearLinesAlert]);
- const handleClearClick = () => {
- formik.resetForm();
+ // Handle cancel button click of clear lines alert
+ const handleCancelClearLines = useCallback(() => {
+ setClearLinesAlert(false);
+ }, [setClearLinesAlert]);
+
+ // Handle cancel button of amount change alert.
+ const handleCancelAmountChangeAlert = () => {
+ setAmountChangeAlert(false);
};
-
- const handleClickAddNewRow = () => {
- formik.setFieldValue(
+ // Handle confirm button of amount change alert.
+ const handleConfirmAmountChangeAlert = () => {
+ setFullAmount(amountChangeAlert);
+ setAmountChangeAlert(false);
+ };
+ // Handle confirm clear all lines.
+ const handleConfirmClearLines = useCallback(() => {
+ setFieldValue(
'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 (
-
);
}
export default compose(
withPaymentReceivesActions,
- withDashboardActions,
withMediaActions,
- withPaymentReceives(({ paymentReceivesItems }) => ({
- paymentReceivesItems,
+ // withPaymentReceives(({ paymentReceivesItems }) => ({
+ // paymentReceivesItems,
+ // })),
+ withPaymentReceiveDetail(({ paymentReceive }) => ({
+ paymentReceive,
})),
- withPaymentReceiveDetail(),
)(PaymentReceiveForm);
diff --git a/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormHeader.js b/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormHeader.js
index 3b0e3812b..0396e55da 100644
--- a/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormHeader.js
+++ b/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormHeader.js
@@ -5,35 +5,54 @@ import {
Intent,
Position,
MenuItem,
- Classes,
} from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FormattedMessage as T } from 'react-intl';
-import { useParams, useHistory } from 'react-router-dom';
-import { useQuery } from 'react-query';
-
import moment from 'moment';
-import { momentFormatter, compose, tansformDateValue } from 'utils';
+import { sumBy } from 'lodash';
import classNames from 'classnames';
+
+import { CLASSES } from 'common/classes'
+import { momentFormatter, compose, tansformDateValue } from 'utils';
import {
AccountsSelectList,
ListSelect,
ErrorMessage,
FieldRequiredHint,
+ Hint,
+ Money,
} from 'components';
+import withInvoices from 'containers/Sales/Invoice/withInvoices';
import withCustomers from 'containers/Customers/withCustomers';
import withAccounts from 'containers/Accounts/withAccounts';
function PaymentReceiveFormHeader({
- formik: { errors, touched, setFieldValue, getFieldProps, values },
+ // #useFormik
+ errors,
+ touched,
+ setFieldValue,
+ getFieldProps,
+ values,
+ onFullAmountChanged,
+ paymentReceiveId,
+ customerId,
//#withCustomers
customers,
+
//#withAccouts
accountsList,
+
+ // #withInvoices
+ receivableInvoices,
}) {
+ // Compute the total receivable amount.
+ const receivableFullAmount = useMemo(
+ () => sumBy(receivableInvoices, 'due_amount'),
+ [receivableInvoices],
+ );
const handleDateChange = useCallback(
(date_filed) => (date) => {
const formatted = moment(date).format('YYYY-MM-DD');
@@ -82,14 +101,28 @@ function PaymentReceiveFormHeader({
[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 (
-
-
- {/* Customer name */}
+
+
+ {/* ------------- Customer name ------------- */}
}
inline={true}
- className={classNames('form-group--select-list', Classes.FILL)}
+ className={classNames('form-group--select-list', CLASSES.FILL)}
labelInfo={
}
intent={errors.customer_id && touched.customer_id && Intent.DANGER}
helperText={
@@ -110,12 +143,12 @@ function PaymentReceiveFormHeader({
/>
- {/* Payment date */}
+ {/* ------------- Payment date ------------- */}
}
inline={true}
labelInfo={
}
- 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}
helperText={
@@ -129,11 +162,39 @@ function PaymentReceiveFormHeader({
/>
- {/* payment receive no */}
+ {/* ------------ Full amount ------------ */}
+
}
+ inline={true}
+ className={('form-group--full-amount', CLASSES.FILL)}
+ intent={
+ errors.full_amount && touched.full_amount && Intent.DANGER
+ }
+ labelInfo={
}
+ helperText={
+
+ }
+ >
+
+
+
+ Receive full amount ()
+
+
+
+ {/* ------------ Payment receive no. ------------ */}
}
inline={true}
- className={('form-group--payment_receive_no', Classes.FILL)}
+ className={('form-group--payment_receive_no', CLASSES.FILL)}
labelInfo={
}
intent={
errors.payment_receive_no &&
@@ -155,13 +216,13 @@ function PaymentReceiveFormHeader({
/>
- {/* deposit account */}
+ {/* ------------ Deposit account ------------ */}
}
className={classNames(
'form-group--deposit_account_id',
'form-group--select-list',
- Classes.FILL,
+ CLASSES.FILL,
)}
inline={true}
labelInfo={
}
@@ -185,45 +246,22 @@ function PaymentReceiveFormHeader({
selectedAccountId={values.deposit_account_id}
/>
-
- {/* Receive amount */}
-
- {/*
}
- inline={true}
- labelInfo={
}
- className={classNames('form-group--', Classes.FILL)}
- intent={
- errors.receive_amount && touched.receive_amount && Intent.DANGER
- }
- helperText={
-
- }
- >
-
- */}
-
- {/* reference_no */}
-
}
- inline={true}
- className={classNames('form-group--reference', Classes.FILL)}
- intent={errors.reference_no && touched.reference_no && Intent.DANGER}
- helperText={
}
- >
-
}
+ inline={true}
+ className={classNames('form-group--reference', CLASSES.FILL)}
intent={errors.reference_no && touched.reference_no && Intent.DANGER}
- minimal={true}
- {...getFieldProps('reference_no')}
- />
-
+ helperText={
}
+ >
+
+
+
);
}
@@ -235,4 +273,7 @@ export default compose(
withAccounts(({ accountsList }) => ({
accountsList,
})),
+ withInvoices(({ paymentReceiveReceivableInvoices }) => ({
+ receivableInvoices: paymentReceiveReceivableInvoices,
+ })),
)(PaymentReceiveFormHeader);
diff --git a/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormPage.js b/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormPage.js
new file mode 100644
index 000000000..f9e2166fd
--- /dev/null
+++ b/client/src/containers/Sales/PaymentReceive/PaymentReceiveFormPage.js
@@ -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 (
+
+ {/* */}
+
+ )
+}
+
+export default compose(
+ withDashboardActions,
+ withAccountsActions,
+ withSettingsActions,
+ withPaymentReceivesActions,
+ withCustomersActions,
+)(PaymentReceiveFormPage);
\ No newline at end of file
diff --git a/client/src/containers/Sales/PaymentReceive/PaymentReceiveItemsTable.js b/client/src/containers/Sales/PaymentReceive/PaymentReceiveItemsTable.js
index 0777fb519..a372ccd3d 100644
--- a/client/src/containers/Sales/PaymentReceive/PaymentReceiveItemsTable.js
+++ b/client/src/containers/Sales/PaymentReceive/PaymentReceiveItemsTable.js
@@ -1,258 +1,132 @@
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import { Button, Intent, Position, Tooltip } from '@blueprintjs/core';
-import { FormattedMessage as T, useIntl } from 'react-intl';
-import { Icon, DataTable } from 'components';
-import moment from 'moment';
+import { FormattedMessage as T } from 'react-intl';
+import { Icon, CloudLoadingIndicator } from 'components';
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 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 (
-
} position={Position.LEFT}>
-
}
- iconSize={14}
- className="m12"
- intent={Intent.DANGER}
- onClick={onRemoveRole}
- />
-
- );
-};
-
-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
{formattedAmount(total, 'USD')};
- }
- return content(props);
-};
function PaymentReceiveItemsTable({
- //#ownProps
- onClickRemoveRow,
- onClickAddNewRow,
+ // #ownProps
+ paymentReceiveId,
+ customerId,
+ fullAmount,
+ onUpdateData,
+ paymentEntries = [],// { invoice_id: number, payment_amount: number, id?: number }
+ errors,
onClickClearAllLines,
- entries,
- formik: { errors, setFieldValue, values },
- dueInvoices,
- customer_id,
- invoices,
+ // #withInvoices
+ paymentReceiveReceivableInvoices,
+
+ // #withPaymentReceive
+ receivableInvoices,
+
+ // #withPaymentReceiveActions
+ requestFetchDueInvoices
}) {
- const [rows, setRows] = useState([]);
- const { formatMessage } = useIntl();
+ const isNewMode = !paymentReceiveId;
+
+ 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(() => {
- setRows([...dueInvoices.map((e) => ({ ...e })), ...invoices, {}]);
- }, [invoices]);
+ setTableData(computedTableData);
+ }, [computedTableData]);
- // useEffect(() => {
- // setRows([...dueInvoices.map((e) => ({ ...e })), {}]);
+ // Triggers `onUpdateData` prop event.
+ 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([
- // ...dueInvoices.map((e) => {
- // return { id: e.id, payment_amount: e.payment_amount };
- // }),
- // ]);
- // }, [dueInvoices]);
+ useEffect(() => {
+ if (localAmount !== fullAmount) {
+ let _fullAmount = fullAmount;
+
+ const newTableData = tableData.map((data) => {
+ const amount = Math.min(data?.invoice?.due_amount, _fullAmount);
+ _fullAmount -= Math.max(amount, 0);
- const columns = useMemo(
- () => [
- {
- Header: '#',
- accessor: 'index',
- Cell: ({ row: { index } }) =>
{index + 1},
- width: 40,
- disableResizing: true,
- disableSortBy: true,
- },
- {
- Header: formatMessage({ id: 'Date' }),
- id: 'invoice_date',
- accessor: (r) => moment(r.invoice_date).format('YYYY MMM DD'),
- Cell: CellRenderer(EmptyDiv, 'invoice_date'),
- disableSortBy: true,
- disableResizing: true,
- width: 250,
- },
-
- {
- Header: formatMessage({ id: 'invocie_number' }),
- accessor: (row) => `#${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;
+ return {
+ ...data,
+ payment_amount: amount,
+ };
});
- setRows(newRows);
- setFieldValue(
- 'entries',
- newRows.map((row) => ({
- ...pick(row, ['payment_amount']),
- invoice_id: row.id,
- })),
- );
- },
- [rows, setFieldValue, setRows],
+ setTableData(newTableData);
+ setLocalAmount(fullAmount);
+ triggerUpdateData(newTableData);
+ }
+ }, [
+ fullAmount,
+ localAmount,
+ tableData,
+ triggerUpdateData,
+ ]);
+
+ 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 (
-
+
);
}
export default compose(
- withInvoices(({ dueInvoices }) => ({
- dueInvoices,
+ withInvoices(({ paymentReceiveReceivableInvoices }) => ({
+ receivableInvoices: paymentReceiveReceivableInvoices,
})),
+ withInvoiceActions,
)(PaymentReceiveItemsTable);
diff --git a/client/src/containers/Sales/PaymentReceive/PaymentReceiveItemsTableEditor.js b/client/src/containers/Sales/PaymentReceive/PaymentReceiveItemsTableEditor.js
new file mode 100644
index 000000000..b67efd8c7
--- /dev/null
+++ b/client/src/containers/Sales/PaymentReceive/PaymentReceiveItemsTableEditor.js
@@ -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
+ }
+ return content(props);
+};
+
+export default function PaymentReceiveItemsTableEditor ({
+ onClickClearAllLines,
+ onUpdateData,
+ data,
+ errors,
+ noResultsMessage,
+}) {
+ const transformedData = useMemo(() => {
+ const rows = [ ...data ];
+ const totalRow = {
+ due_amount: sumBy(data, 'due_amount'),
+ payment_amount: sumBy(data, 'payment_amount'),
+ };
+ if (rows.length > 0) { rows.push(totalRow) }
+ return rows;
+ }, [data]);
+
+ const [localData, setLocalData] = useState(transformedData);
+ const { formatMessage } = useIntl();
+
+ useEffect(() => {
+ if (localData !== transformedData) {
+ setLocalData(transformedData);
+ }
+ }, [setLocalData, localData, transformedData]);
+
+ const columns = useMemo(
+ () => [
+ {
+ Header: '#',
+ accessor: 'index',
+ Cell: ({ row: { index } }) =>
{index + 1},
+ width: 40,
+ disableResizing: true,
+ disableSortBy: true,
+ },
+ {
+ Header: formatMessage({ id: 'Date' }),
+ id: 'invoice.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 (
+
+ );
+
+}
\ No newline at end of file
diff --git a/client/src/store/Bills/bills.selectors.js b/client/src/store/Bills/bills.selectors.js
index c461be448..cd3fab932 100644
--- a/client/src/store/Bills/bills.selectors.js
+++ b/client/src/store/Bills/bills.selectors.js
@@ -87,4 +87,18 @@ export const getPayableBillsByPaymentMadeFactory = () =>
? pickItemsFromIds(billsItems, payableBillsIds) || []
: [];
},
+ );
+
+export const getPaymentMadeFormPayableBillsFactory = () =>
+ createSelector(
+ billItemsSelector,
+ billsPayableVendorSelector,
+ billsPayableByPaymentMadeSelector,
+ (billsItems, vendorBillsIds, paymentMadeBillsIds) => {
+ const billsIds = [
+ ...(vendorBillsIds || []),
+ ...(paymentMadeBillsIds || [])
+ ];
+ return pickItemsFromIds(billsItems, billsIds);
+ },
);
\ No newline at end of file
diff --git a/client/src/store/Invoice/invoices.actions.js b/client/src/store/Invoice/invoices.actions.js
index 92787ad8c..ba9ad0b03 100644
--- a/client/src/store/Invoice/invoices.actions.js
+++ b/client/src/store/Invoice/invoices.actions.js
@@ -121,26 +121,32 @@ export const fetchInvoice = ({ id }) => {
});
});
};
-export const dueInvoices = ({ id }) => {
- return (dispatch) =>
- new Promise((resovle, reject) => {
- ApiService.get(`sales/invoices/due_invoices`, {
- params: { customer_id: id },
- })
- .then((response) => {
- dispatch({
- type: t.DUE_INVOICES_SET,
- payload: {
- customer_id: id,
- due_sales_invoices: response.data.due_sales_invoices,
- },
- });
- resovle(response);
- })
- .catch((error) => {
- const { response } = error;
- const { data } = response;
- reject(data?.errors);
+
+export const fetchDueInvoices = ({ customerId }) => (dispatch) => new Promise((resovle, reject) => {
+ ApiService.get(`sales/invoices/payable`, {
+ params: { customer_id: customerId },
+ })
+ .then((response) => {
+ dispatch({
+ type: t.INVOICES_ITEMS_SET,
+ payload: {
+ sales_invoices: response.data.sales_invoices,
+ },
+ });
+ if (customerId) {
+ dispatch({
+ type: t.INVOICES_RECEIVABLE_BY_CUSTOMER_ID,
+ payload: {
+ customerId,
+ saleInvoices: response.data.sales_invoices,
+ },
});
+ }
+ resovle(response);
+ })
+ .catch((error) => {
+ const { response } = error;
+ const { data } = response;
+ reject(data?.errors);
});
-};
+ });
diff --git a/client/src/store/Invoice/invoices.reducer.js b/client/src/store/Invoice/invoices.reducer.js
index 098f78db6..72d2b61c5 100644
--- a/client/src/store/Invoice/invoices.reducer.js
+++ b/client/src/store/Invoice/invoices.reducer.js
@@ -13,6 +13,10 @@ const initialState = {
page: 1,
},
dueInvoices: {},
+ receivable: {
+ byCustomerId: [],
+ byPaymentReceiveId: [],
+ }
};
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);
- const _invoices = {};
- due_sales_invoices.forEach((invoice) => {
- _invoices[invoice.id] = {
- ...invoice,
- };
- });
-
- state.items = {
- ...state.dueInvoices,
- ...state.items.dueInvoices,
- ..._invoices,
- };
+ state.receivable.byPaymentReceiveId[paymentReceiveId] = saleInvoicesIds;
},
- [t.RELOAD_INVOICES]: (state, action) => {
- const { sales_invoices } = action.payload;
- const _sales_invoices = {};
- sales_invoices.forEach((invoice) => {
- _sales_invoices[invoice.id] = {
- ...invoice,
- };
- });
+ [t.INVOICES_RECEIVABLE_BY_CUSTOMER_ID]: (state, action) => {
+ const { customerId, saleInvoices } = action.payload;
+ const saleInvoiceIds = saleInvoices.map((saleInvoice) => saleInvoice.id);
- state.items = {
- ...state.items,
- ..._sales_invoices,
- };
+ state.receivable.byCustomerId[customerId] = saleInvoiceIds
},
});
diff --git a/client/src/store/Invoice/invoices.selector.js b/client/src/store/Invoice/invoices.selector.js
index 700d19acc..f8c7f5076 100644
--- a/client/src/store/Invoice/invoices.selector.js
+++ b/client/src/store/Invoice/invoices.selector.js
@@ -22,6 +22,10 @@ const invoicesPageSelector = (state, props, query) => {
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 = () =>
createSelector(
paginationLocationQuery,
@@ -55,17 +59,39 @@ export const getInvoicePaginationMetaFactory = () =>
return invoicePage?.paginationMeta || {};
});
-const dueInvoicesSelector = (state, props) => {
- return state.salesInvoices.dueInvoices[props.customer_id] || [];
-};
+// export const getCustomerReceivableInvoicesFactory = () =>
+// createSelector(
+// invoicesItemsSelector,
+// invoicesReceiableCustomerSelector,
+// (invoicesItems, invoicesIds) => {
+// return Array.isArray(invoicesIds)
+// ? (pickItemsFromIds(invoicesItems, invoicesIds) || [])
+// : [];
+// },
+// );
-export const getdueInvoices = createSelector(
- dueInvoicesSelector,
- invoicesItemsSelector,
- (customerIds, items) => {
-
- return typeof customerIds === 'object'
- ? pickItemsFromIds(items, customerIds) || []
- : [];
- },
-);
+// export const getPaymentReceivableInvoicesFactory = () =>
+// createSelector(
+// invoicesItemsSelector,
+// paymentReceivableInvoicesSelector,
+// (invoicesItems, invoicesIds) => {
+// return Array.isArray(invoicesIds)
+// ? (pickItemsFromIds(invoicesItems, invoicesIds) || [])
+// : [];
+// },
+// );
+
+
+export const getPaymentReceiveReceivableInvoicesFactory = () =>
+ createSelector(
+ invoicesItemsSelector,
+ invoicesReceiableCustomerSelector,
+ paymentReceivableInvoicesSelector,
+ (invoicesItems, customerInvoicesIds, paymentInvoicesIds) => {
+ const invoicesIds = [
+ ...(customerInvoicesIds || []),
+ ...(paymentInvoicesIds || []),
+ ];
+ return pickItemsFromIds(invoicesItems, invoicesIds);
+ },
+ );
\ No newline at end of file
diff --git a/client/src/store/Invoice/invoices.types.js b/client/src/store/Invoice/invoices.types.js
index 5e59bfa0b..cb6b242c0 100644
--- a/client/src/store/Invoice/invoices.types.js
+++ b/client/src/store/Invoice/invoices.types.js
@@ -11,4 +11,7 @@ export default {
INVOICES_ITEMS_SET: 'INVOICES_ITEMS_SET',
DUE_INVOICES_SET: 'DUE_INVOICES_SET',
RELOAD_INVOICES: 'RELOAD_INVOICES',
+
+ INVOICES_RECEIVABLE_BY_PAYMENT_ID: 'INVOICES_RECEIVABLE_BY_PAYMENT_ID',
+ INVOICES_RECEIVABLE_BY_CUSTOMER_ID: 'INVOICES_RECEIVABLE_BY_CUSTOMER_ID'
};
diff --git a/client/src/store/PaymentReceive/paymentReceive.actions.js b/client/src/store/PaymentReceive/paymentReceive.actions.js
index f0a56e4f6..8ef6d11ee 100644
--- a/client/src/store/PaymentReceive/paymentReceive.actions.js
+++ b/client/src/store/PaymentReceive/paymentReceive.actions.js
@@ -52,28 +52,29 @@ export const fetchPaymentReceive = ({ id }) => {
new Promise((resovle, reject) => {
ApiService.get(`sales/payment_receives/${id}`, {})
.then((response) => {
- dispatch({
- type: t.RELOAD_INVOICES,
- payload: {
- sales_invoices: response.data.paymentReceive.entries.map(
- (e) => e.invoice,
- ),
- },
- });
dispatch({
type: t.PAYMENT_RECEIVE_SET,
payload: {
id,
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);
})
.catch((error) => {
- // const { response } = error;
- // const { data } = response;
- // reject(data?.errors);
reject(error);
});
});
diff --git a/client/src/style/App.scss b/client/src/style/App.scss
index a30b5bd74..bd82d0458 100644
--- a/client/src/style/App.scss
+++ b/client/src/style/App.scss
@@ -71,6 +71,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@import 'pages/invoice-form';
@import 'pages/receipt-form';
@import 'pages/payment-made';
+@import 'pages/payment-receive';
// Views
@import 'views/filter-dropdown';
diff --git a/client/src/style/pages/payment-receive.scss b/client/src/style/pages/payment-receive.scss
new file mode 100644
index 000000000..0d419d0cb
--- /dev/null
+++ b/client/src/style/pages/payment-receive.scss
@@ -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{
+
+ }
+}
\ No newline at end of file
diff --git a/server/src/api/controllers/Sales/PaymentReceives.ts b/server/src/api/controllers/Sales/PaymentReceives.ts
index 997ce1ee7..37d66dc56 100644
--- a/server/src/api/controllers/Sales/PaymentReceives.ts
+++ b/server/src/api/controllers/Sales/PaymentReceives.ts
@@ -34,8 +34,9 @@ export default class PaymentReceivesController extends BaseController {
this.handleServiceErrors,
);
router.post(
- '/',
- this.newPaymentReceiveValidation,
+ '/', [
+ ...this.newPaymentReceiveValidation,
+ ],
this.validationResult,
asyncMiddleware(this.newPaymentReceive.bind(this)),
this.handleServiceErrors,
@@ -87,6 +88,7 @@ export default class PaymentReceivesController extends BaseController {
check('entries').isArray({ min: 1 }),
+ check('entries.*.id').optional({ nullable: true }).isNumeric().toInt(),
check('entries.*.invoice_id').exists().isNumeric().toInt(),
check('entries.*.payment_amount').exists().isNumeric().toInt(),
];
diff --git a/server/src/services/Sales/PaymentsReceives.ts b/server/src/services/Sales/PaymentsReceives.ts
index ad9b61caa..f2c3112c3 100644
--- a/server/src/services/Sales/PaymentsReceives.ts
+++ b/server/src/services/Sales/PaymentsReceives.ts
@@ -1,4 +1,4 @@
-import { omit, sumBy, chain, difference } from 'lodash';
+import { omit, sumBy, difference } from 'lodash';
import moment from 'moment';
import { Service, Inject } from 'typedi';
import {
@@ -26,7 +26,6 @@ import { formatDateFields, entriesAmountDiff } from 'utils';
import { ServiceError } from 'exceptions';
import CustomersService from 'services/Contacts/CustomersService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
-import { SaleInvoice } from 'models';
const ERRORS = {
PAYMENT_RECEIVE_NO_EXISTS: 'PAYMENT_RECEIVE_NO_EXISTS',
@@ -327,16 +326,30 @@ export default class PaymentReceiveService {
* @param {number} tenantId - Tenant id.
* @param {Integer} paymentReceiveId - Payment receive id.
*/
- public async getPaymentReceive(tenantId: number, paymentReceiveId: number) {
- const { PaymentReceive } = this.tenancy.models(tenantId);
+ public async getPaymentReceive(
+ tenantId: number,
+ paymentReceiveId: number
+ ): Promise<{ paymentReceive: IPaymentReceive[], receivableInvoices: ISaleInvoice }> {
+ const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId);
const paymentReceive = await PaymentReceive.query()
.findById(paymentReceiveId)
- .withGraphFetched('entries.invoice');
+ .withGraphFetched('entries')
+ .withGraphFetched('customer')
+ .withGraphFetched('depositAccount');
if (!paymentReceive) {
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 };
}
/**