- {/* Vendor name */}
+ {/* ------------ Vendor name ------------ */}
}
inline={true}
@@ -114,10 +142,12 @@ function PaymentMadeFormHeader({
selectedItemProp={'id'}
defaultText={
}
labelProp={'display_name'}
+ buttonProps={{ disabled: !isNewMode }}
+ disabled={!isNewMode}
/>
- {/* Payment date */}
+ {/* ------------ Payment date ------------ */}
}
inline={true}
@@ -136,7 +166,35 @@ function PaymentMadeFormHeader({
/>
- {/* payment number */}
+ {/* ------------ Full amount ------------ */}
+
}
+ inline={true}
+ className={('form-group--full-amount', Classes.FILL)}
+ labelInfo={
}
+ intent={
+ errors.full_amount && touched.full_amount && Intent.DANGER
+ }
+ helperText={
+
+ }
+ >
+
+
+
+ Receive full amount ()
+
+
+
+ {/* ------------ Payment number ------------ */}
}
inline={true}
@@ -154,25 +212,11 @@ function PaymentMadeFormHeader({
errors.payment_number && touched.payment_number && Intent.DANGER
}
minimal={true}
- rightElement={
-
,
- }}
- tooltip={true}
- tooltipProps={{
- content: 'Setting your auto-generated payment number',
- position: Position.BOTTOM_LEFT,
- }}
- />
- }
- minimal={true}
{...getFieldProps('payment_number')}
/>
- {/* payment account */}
+ {/* ------------ Payment account ------------ */}
}
className={classNames(
@@ -192,7 +236,7 @@ function PaymentMadeFormHeader({
name={'payment_account_id'}
{...{ errors, touched }}
/>
- }
+ }
>
- {/* reference */}
+ {/* ------------ Reference ------------ */}
}
inline={true}
@@ -230,5 +274,7 @@ export default compose(
withAccounts(({ accountsList }) => ({
accountsList,
})),
- withDialogActions,
+ withBills(({ vendorPayableBills }) => ({
+ vendorPayableBills,
+ })),
)(PaymentMadeFormHeader);
diff --git a/client/src/containers/Purchases/PaymentMades/PaymentMadeItemsTable.js b/client/src/containers/Purchases/PaymentMades/PaymentMadeItemsTable.js
index d751f86d2..d0eb68aa2 100644
--- a/client/src/containers/Purchases/PaymentMades/PaymentMadeItemsTable.js
+++ b/client/src/containers/Purchases/PaymentMades/PaymentMadeItemsTable.js
@@ -1,234 +1,141 @@
-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 { compose, formattedAmount, transformUpdatedRows } from 'utils';
-import {
- MoneyFieldCell,
- DivFieldCell,
- EmptyDiv,
-} from 'components/DataTableCells';
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
+import { useQuery } from 'react-query';
+import { pick } from 'lodash';
+import { LoadingIndicator, Choose } from 'components';
+import PaymentMadeItemsTableEditor from './PaymentMadeItemsTableEditor';
+import withPaymentMadeActions from './withPaymentMadeActions';
+import withBillActions from '../Bill/withBillActions';
import withBills from '../Bill/withBills';
-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);
-};
+import { compose } from 'utils';
+/**
+ * Payment made items table.
+ */
function PaymentMadeItemsTable({
- //#ownProps
- onClickRemoveRow,
- onClickAddNewRow,
+ // #ownProps
+ paymentMadeId,
+ vendorId,
+ fullAmount,
+ onUpdateData,
+ paymentEntries = [], // { bill_id: number, payment_amount: number, id?: number }
onClickClearAllLines,
- entries,
- formik: { errors, setFieldValue, values },
+ errors,
+
+ // #withBillActions
+ requestFetchDueBills,
+
+ // #withBills
+ vendorPayableBills,
+ paymentMadePayableBills,
+
+ // #withPaymentMadeDetail
+ paymentMade,
}) {
- const [rows, setRows] = useState([]);
- const [entrie, setEntrie] = useState([]);
- const { formatMessage } = useIntl();
+ const [tableData, setTableData] = useState([]);
+ const [localAmount, setLocalAmount] = useState(fullAmount);
+
+ // Payable bills based on selected vendor or specific payment made.
+ const payableBills = useMemo(
+ () =>
+ paymentMadeId
+ ? paymentMadePayableBills
+ : vendorId
+ ? vendorPayableBills
+ : [],
+ [paymentMadeId, paymentMadePayableBills, vendorId, vendorPayableBills],
+ );
+ const isNewMode = !paymentMadeId;
+
+ const 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(() => {
- setRows([...entries.map((e) => ({ ...e }))]);
- }, [entries]);
+ setTableData(computedTableData);
+ }, [computedTableData]);
- const columns = useMemo(
- () => [
- {
- Header: '#',
- accessor: 'index',
- Cell: ({ row: { index } }) =>
{index + 1},
- width: 40,
- disableResizing: true,
- disableSortBy: true,
- },
- {
- Header: formatMessage({ id: 'Date' }),
- id: 'bill_date',
- accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
- Cell: CellRenderer(EmptyDiv, 'bill_date'),
- disableSortBy: true,
- disableResizing: true,
- width: 250,
- },
+ // Handle
+ useEffect(() => {
+ if (localAmount !== fullAmount) {
+ let _fullAmount = fullAmount;
+ const newTableData = tableData.map((data) => {
+ const amount = Math.min(data.due_amount, _fullAmount);
+ _fullAmount -= Math.max(amount, 0);
- {
- 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 {
- ...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],
- );
- return (
-
-
-
-
+ setTableData(newTableData);
+ setLocalAmount(fullAmount);
+ triggerUpdateData(newTableData);
+ }
+ }, [
+ tableData,
+ setTableData,
+ setLocalAmount,
+ triggerUpdateData,
+ localAmount,
+ fullAmount,
+ ]);
-
-
+ // Fetches vendor due bills.
+ const fetchVendorDueBills = useQuery(
+ ['vendor-due-bills', vendorId],
+ (key, _vendorId) => requestFetchDueBills(_vendorId),
+ { enabled: isNewMode && vendorId },
+ );
+
+ // Handle update data.
+ const handleUpdateData = (rows) => {
+ triggerUpdateData(rows);
+ };
+
+ return (
+
+
+
+ 0}>
+
+
+
+ The vendor has no due invoices.
+
+
);
}
-export default compose()(PaymentMadeItemsTable);
-// withBills(({}) => ({}))
+export default compose(
+ withPaymentMadeActions,
+ withBillActions,
+ withBills(({ vendorPayableBills, paymentMadePayableBills }) => ({
+ vendorPayableBills,
+ paymentMadePayableBills,
+ })),
+)(PaymentMadeItemsTable);
diff --git a/client/src/containers/Purchases/PaymentMades/PaymentMadeItemsTableEditor.js b/client/src/containers/Purchases/PaymentMades/PaymentMadeItemsTableEditor.js
new file mode 100644
index 000000000..6e6909ac2
--- /dev/null
+++ b/client/src/containers/Purchases/PaymentMades/PaymentMadeItemsTableEditor.js
@@ -0,0 +1,162 @@
+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 { 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);
+};
+
+/**
+ * Payment made items editor table.
+ */
+export default function PaymentMadeItemsTableEditor({
+ //#ownProps
+ onClickClearAllLines,
+ onUpdateData,
+ data,
+ errors
+}) {
+ const transformedData = useMemo(() => {
+ return [ ...data, {
+ due_amount: sumBy(data, 'due_amount'),
+ payment_amount: sumBy(data, 'payment_amount'),
+ }];
+ }, [data]);
+
+ const [localData, setLocalData] = useState(transformedData);
+ const { formatMessage } = useIntl();
+
+ useEffect(() => {
+ if (localData !== transformedData) {
+ setLocalData(transformedData);
+ }
+ }, [setLocalData, localData, transformedData]);
+
+ const columns = useMemo(
+ () => [
+ {
+ Header: '#',
+ accessor: 'index',
+ Cell: ({ row: { index } }) =>
{index + 1},
+ width: 40,
+ disableResizing: true,
+ disableSortBy: true,
+ },
+ {
+ Header: formatMessage({ id: 'Date' }),
+ id: 'bill_date',
+ accessor: (r) => moment(r.bill_date).format('YYYY MMM DD'),
+ Cell: CellRenderer(EmptyDiv, 'bill_date'),
+ disableSortBy: true,
+ 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: '',
+ },
+ ],
+ [formatMessage],
+ );
+
+ const handleClickClearAllLines = () => {
+ onClickClearAllLines && onClickClearAllLines();
+ };
+
+ const rowClassNames = useCallback(
+ (row) => {
+ return { '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 (
+
+ );
+}
diff --git a/client/src/containers/Purchases/PaymentMades/withPaymentMadeActions.js b/client/src/containers/Purchases/PaymentMades/withPaymentMadeActions.js
index 867f486f3..626161f3e 100644
--- a/client/src/containers/Purchases/PaymentMades/withPaymentMadeActions.js
+++ b/client/src/containers/Purchases/PaymentMades/withPaymentMadeActions.js
@@ -6,6 +6,7 @@ import {
deletePaymentMade,
fetchPaymentMadesTable,
fetchPaymentMade,
+ fetchPaymentMadeBills,
} from 'store/PaymentMades/paymentMade.actions';
const mapDispatchToProps = (dispatch) => ({
@@ -16,6 +17,8 @@ const mapDispatchToProps = (dispatch) => ({
requestFetchPaymentMadesTable: (query = {}) =>
dispatch(fetchPaymentMadesTable({ query: { ...query } })),
+ requestFetchPaymentMadeBills: (paymentMadeId) => dispatch(fetchPaymentMadeBills({ paymentMadeId })),
+
changePaymentMadeView: (id) =>
dispatch({
type: t.PAYMENT_MADE_SET_CURRENT_VIEW,
diff --git a/client/src/store/Bills/bills.actions.js b/client/src/store/Bills/bills.actions.js
index 8e57fa4df..84afb8a81 100644
--- a/client/src/store/Bills/bills.actions.js
+++ b/client/src/store/Bills/bills.actions.js
@@ -123,11 +123,19 @@ export const fetchDueBills = ({ vendorId }) => (dispatch) => new Promise((resolv
ApiService.get(`purchases/bills/due`, { params }).then((response) => {
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: {
+ bills: response.data.bills,
+ }
+ });
+ }
resolve(response);
}).catch(error => { reject(error) });
});
\ No newline at end of file
diff --git a/client/src/store/Bills/bills.reducer.js b/client/src/store/Bills/bills.reducer.js
index 6f7f438e0..cdcc8b5b5 100644
--- a/client/src/store/Bills/bills.reducer.js
+++ b/client/src/store/Bills/bills.reducer.js
@@ -13,7 +13,10 @@ const initialState = {
page: 1,
},
nextBillNumberChanged: false,
- dueBills: {},
+ payable: {
+ byVendorId: [],
+ byBillPayamentId: [],
+ },
};
const defaultBill = {
@@ -105,23 +108,31 @@ const reducer = createReducer(initialState, {
state.nextBillNumberChanged = isChanged;
},
- [t.DUE_BILLS_SET]: (state, action) => {
+ [t.BILLS_PAYABLE_BY_VENDOR_ID]: (state, action) => {
const { bills } = action.payload;
-
- const _dueBills = { ...state.dueBills };
- const _bills = { ...state.items };
+ const _data = {};
bills.forEach((bill) => {
- _bills[bill.id] = { ...bill };
-
- if (!_dueBills[bill.vendor_id]) {
- _dueBills[bill.vendor_id] = []
+ if (!_data[bill.vendor_id]) {
+ _data[bill.vendor_id] = [];
}
- _dueBills[bill.vendor_id].push(bill.id);
+ _data[bill.vendor_id].push(bill.id);
});
- state.items = { ..._bills };
- state.dueBills = { ..._dueBills };
+ state.payable.byVendorId = {
+ ...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;
}
});
diff --git a/client/src/store/Bills/bills.selectors.js b/client/src/store/Bills/bills.selectors.js
index b2840dc6d..c461be448 100644
--- a/client/src/store/Bills/bills.selectors.js
+++ b/client/src/store/Bills/bills.selectors.js
@@ -19,7 +19,10 @@ const billByIdSelector = (state, props) => state.bills.items[props.billId];
* Retrieve vendor due bills ids.
* @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 viewId = state.bills.currentViewId;
@@ -62,16 +65,26 @@ export const getBillPaginationMetaFactory = () =>
return billPage?.paginationMeta || {};
});
-/**
- * Retrieve due bills of specific vendor.
- */
-export const getVendorDueBillsFactory = () =>
+
+export const getVendorPayableBillsFactory = () =>
createSelector(
billItemsSelector,
- billsDueVendorSelector,
- (billsItems, dueBillsIds) => {
- return Array.isArray(dueBillsIds)
- ? pickItemsFromIds(billsItems, dueBillsIds) || []
+ billsPayableVendorSelector,
+ (billsItems, payableBillsIds) => {
+ return Array.isArray(payableBillsIds)
+ ? pickItemsFromIds(billsItems, payableBillsIds) || []
: [];
},
+ );
+
+
+export const getPayableBillsByPaymentMadeFactory = () =>
+ createSelector(
+ billItemsSelector,
+ billsPayableByPaymentMadeSelector,
+ (billsItems, payableBillsIds) => {
+ return Array.isArray(payableBillsIds)
+ ? pickItemsFromIds(billsItems, payableBillsIds) || []
+ : [];
+ },
);
\ No newline at end of file
diff --git a/client/src/store/Bills/bills.type.js b/client/src/store/Bills/bills.type.js
index c8d3fae9d..465935aee 100644
--- a/client/src/store/Bills/bills.type.js
+++ b/client/src/store/Bills/bills.type.js
@@ -10,5 +10,7 @@ export default {
BILLS_PAGE_SET: 'BILLS_PAGE_SET',
BILLS_ITEMS_SET: 'BILLS_ITEMS_SET',
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',
};
diff --git a/client/src/store/PaymentMades/paymentMade.actions.js b/client/src/store/PaymentMades/paymentMade.actions.js
index 4dff01f37..a4e0f8214 100644
--- a/client/src/store/PaymentMades/paymentMade.actions.js
+++ b/client/src/store/PaymentMades/paymentMade.actions.js
@@ -106,7 +106,20 @@ export const fetchPaymentMade = ({ id }) => {
type: t.PAYMENT_MADE_SET,
payload: {
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);
@@ -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) });
+ });
+}
\ No newline at end of file
diff --git a/client/src/store/PaymentMades/paymentMade.reducer.js b/client/src/store/PaymentMades/paymentMade.reducer.js
index e39d95819..4ce3b8c95 100644
--- a/client/src/store/PaymentMades/paymentMade.reducer.js
+++ b/client/src/store/PaymentMades/paymentMade.reducer.js
@@ -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) => {
const { id } = action.payload;
diff --git a/client/src/store/PaymentMades/paymentMade.selector.js b/client/src/store/PaymentMades/paymentMade.selector.js
index b795160c7..08726aca7 100644
--- a/client/src/store/PaymentMades/paymentMade.selector.js
+++ b/client/src/store/PaymentMades/paymentMade.selector.js
@@ -23,6 +23,9 @@ const paymentMadesIds = (state, props) => {
return state.paymentMades.items[props.paymentMadeId];
};
+const paymentMadeEntries = (state, props) => props.paymentMadeEntries;
+const billsItemsSelector = (state, props) => state.bills.items;
+
export const getPaymentMadeCurrentPageFactory = () =>
createSelector(
paymentMadesPageSelector,
@@ -54,3 +57,15 @@ export const getPaymentMadeByIdFactory = () =>
createSelector(paymentMadesIds, (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] || {}),
+ })) : [];
+ }
+ )
\ No newline at end of file
diff --git a/client/src/utils.js b/client/src/utils.js
index 021ef3a7a..e30558cca 100644
--- a/client/src/utils.js
+++ b/client/src/utils.js
@@ -243,3 +243,7 @@ export const flatToNestedArray = (
});
return nestedArray;
};
+
+export const orderingLinesIndexes = (lines, attribute = 'index') => {
+ return lines.map((line, index) => ({ ...line, [attribute]: index + 1 }));
+};
\ No newline at end of file
diff --git a/server/src/api/controllers/Purchases/Bills.ts b/server/src/api/controllers/Purchases/Bills.ts
index 26af691c2..c147809d2 100644
--- a/server/src/api/controllers/Purchases/Bills.ts
+++ b/server/src/api/controllers/Purchases/Bills.ts
@@ -151,7 +151,8 @@ export default class BillsController extends BaseController {
get dueBillsListingValidationSchema() {
return [
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 }],
});
}
+ if (error.errorType === 'ITEMS_NOT_FOUND') {
+ return res.boom.badRequest(null, {
+ errors: [{ type: 'ITEMS_NOT_FOUND', code: 1000 }],
+ });
+ }
}
+ console.log(error.errorType);
next(error);
}
}
diff --git a/server/src/api/controllers/Purchases/BillsPayments.ts b/server/src/api/controllers/Purchases/BillsPayments.ts
index b4a81285c..cfa954993 100644
--- a/server/src/api/controllers/Purchases/BillsPayments.ts
+++ b/server/src/api/controllers/Purchases/BillsPayments.ts
@@ -198,8 +198,14 @@ export default class BillsPayments extends BaseController {
const { id: billPaymentId } = req.params;
try {
- const billPayment = await this.billPaymentService.getBillPayment(tenantId, billPaymentId);
- return res.status(200).send({ bill_payment: billPayment });
+ const { billPayment, payableBills } = await this.billPaymentService.getBillPayment(tenantId, billPaymentId);
+
+ return res.status(200).send({
+ bill_payment: {
+ ...this.transfromToResponse({ ...billPayment }),
+ payable_bills: payableBills,
+ },
+ });
} catch (error) {
next(error);
}
diff --git a/server/src/api/controllers/Sales/SalesInvoices.ts b/server/src/api/controllers/Sales/SalesInvoices.ts
index a4b07bd97..ae1a1aa60 100644
--- a/server/src/api/controllers/Sales/SalesInvoices.ts
+++ b/server/src/api/controllers/Sales/SalesInvoices.ts
@@ -1,6 +1,5 @@
import { Router, Request, Response, NextFunction } from 'express';
import { check, param, query } from 'express-validator';
-import { raw } from 'objection';
import { Service, Inject } from 'typedi';
import BaseController from '../BaseController';
import asyncMiddleware from 'api/middleware/asyncMiddleware';
@@ -52,11 +51,11 @@ export default class SaleInvoicesController extends BaseController{
this.handleServiceErrors,
);
router.get(
- '/due', [
+ '/payable', [
...this.dueSalesInvoicesListValidationSchema,
],
this.validationResult,
- asyncMiddleware(this.getDueInvoices.bind(this)),
+ asyncMiddleware(this.getPayableInvoices.bind(this)),
this.handleServiceErrors,
);
router.get(
@@ -251,12 +250,12 @@ export default class SaleInvoicesController extends BaseController{
* @param {NextFunction} next -
* @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 { customerId } = this.matchedQueryData(req);
try {
- const salesInvoices = await this.saleInvoiceService.getDueInvoices(tenantId, customerId);
+ const salesInvoices = await this.saleInvoiceService.getPayableInvoices(tenantId, customerId);
return res.status(200).send({
sales_invoices: this.transfromToResponse(salesInvoices),
diff --git a/server/src/models/BillPayment.js b/server/src/models/BillPayment.js
index 11638bfbd..2ac029278 100644
--- a/server/src/models/BillPayment.js
+++ b/server/src/models/BillPayment.js
@@ -70,7 +70,7 @@ export default class BillPayment extends TenantModel {
filter(builder) {
builder.where('reference_type', 'BillPayment');
},
- }
+ },
};
}
diff --git a/server/src/services/Purchases/BillPayments.ts b/server/src/services/Purchases/BillPayments.ts
index 7b3b35194..c6f8dba45 100644
--- a/server/src/services/Purchases/BillPayments.ts
+++ b/server/src/services/Purchases/BillPayments.ts
@@ -489,7 +489,7 @@ export default class BillPaymentsService {
* @return {object}
*/
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()
.findById(billPaymentId)
.withGraphFetched('entries')
@@ -499,7 +499,16 @@ export default class BillPaymentsService {
if (!billPayment) {
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 };
}
/**
diff --git a/server/src/services/Purchases/Bills.ts b/server/src/services/Purchases/Bills.ts
index 6aebb2a02..c2a1aedea 100644
--- a/server/src/services/Purchases/Bills.ts
+++ b/server/src/services/Purchases/Bills.ts
@@ -1,4 +1,4 @@
-import { omit, sumBy, pick, difference } from 'lodash';
+import { omit, sumBy, pick, difference, assignWith } from 'lodash';
import moment from 'moment';
import { Inject, Service } from 'typedi';
import {
@@ -23,11 +23,13 @@ import {
IPaginationMeta,
IFilterMeta,
IBillsFilter,
+ IBillPaymentEntry,
} from 'interfaces';
import { ServiceError } from 'exceptions';
import ItemsService from 'services/Items/ItemsService';
import ItemsEntriesService from 'services/Items/ItemsEntriesService';
import { Bill } from 'models';
+import PaymentMadesSubscriber from 'subscribers/paymentMades';
const ERRORS = {
BILL_NOT_FOUND: 'BILL_NOT_FOUND',
@@ -428,6 +430,7 @@ export default class BillsService extends SalesInvoicesCost {
const { Bill } = this.tenancy.models(tenantId);
const dueBills = await Bill.query().onBuild((query) => {
+ query.orderBy('bill_date', 'DESC');
query.modify('dueBills');
if (vendorId) {
diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts
index 99267a2df..1324ea271 100644
--- a/server/src/services/Sales/SalesInvoices.ts
+++ b/server/src/services/Sales/SalesInvoices.ts
@@ -413,7 +413,7 @@ export default class SaleInvoicesService extends SalesInvoicesCost {
* @param {number} tenantId
* @param {number} customerId
*/
- public async getDueInvoices(
+ public async getPayableInvoices(
tenantId: number,
customerId?: number,
): Promise
{