From 70269d382a87fc3444457c4147320668f945a4b2 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 1 Nov 2020 17:40:40 +0200 Subject: [PATCH] feat: payment made form in new and edit mode. --- .../Purchases/Bill/withBillActions.js | 2 + .../containers/Purchases/Bill/withBills.js | 9 +- .../Purchases/PaymentMades/PaymentMade.js | 70 +-- .../PaymentMadeFloatingActions.js | 36 +- .../Purchases/PaymentMades/PaymentMadeForm.js | 433 +++++++++--------- .../PaymentMades/PaymentMadeFormHeader.js | 116 +++-- .../PaymentMades/PaymentMadeItemsTable.js | 341 +++++--------- .../PaymentMadeItemsTableEditor.js | 162 +++++++ .../PaymentMades/withPaymentMadeActions.js | 3 + client/src/store/Bills/bills.actions.js | 12 +- client/src/store/Bills/bills.reducer.js | 35 +- client/src/store/Bills/bills.selectors.js | 31 +- client/src/store/Bills/bills.type.js | 4 +- .../store/PaymentMades/paymentMade.actions.js | 29 +- .../store/PaymentMades/paymentMade.reducer.js | 11 + .../PaymentMades/paymentMade.selector.js | 15 + client/src/utils.js | 4 + server/src/api/controllers/Purchases/Bills.ts | 9 +- .../controllers/Purchases/BillsPayments.ts | 10 +- .../api/controllers/Sales/SalesInvoices.ts | 9 +- server/src/models/BillPayment.js | 2 +- server/src/services/Purchases/BillPayments.ts | 13 +- server/src/services/Purchases/Bills.ts | 5 +- server/src/services/Sales/SalesInvoices.ts | 2 +- 24 files changed, 812 insertions(+), 551 deletions(-) create mode 100644 client/src/containers/Purchases/PaymentMades/PaymentMadeItemsTableEditor.js diff --git a/client/src/containers/Purchases/Bill/withBillActions.js b/client/src/containers/Purchases/Bill/withBillActions.js index d733ba20f..2fb0c9710 100644 --- a/client/src/containers/Purchases/Bill/withBillActions.js +++ b/client/src/containers/Purchases/Bill/withBillActions.js @@ -5,6 +5,7 @@ import { editBill, fetchBillsTable, fetchBill, + fetchDueBills, } from 'store/Bills/bills.actions'; import t from 'store/types'; @@ -15,6 +16,7 @@ const mapDispatchToProps = (dispatch) => ({ requestDeleteBill: (id) => dispatch(deleteBill({ id })), requestFetchBillsTable: (query = {}) => dispatch(fetchBillsTable({ query: { ...query } })), + requestFetchDueBills: (vendorId) => dispatch(fetchDueBills({ vendorId })), changeBillView: (id) => dispatch({ diff --git a/client/src/containers/Purchases/Bill/withBills.js b/client/src/containers/Purchases/Bill/withBills.js index 4c8a15547..1182f2b1c 100644 --- a/client/src/containers/Purchases/Bill/withBills.js +++ b/client/src/containers/Purchases/Bill/withBills.js @@ -4,14 +4,16 @@ import { getBillCurrentPageFactory, getBillPaginationMetaFactory, getBillTableQueryFactory, - getVendorDueBillsFactory + getVendorPayableBillsFactory, + getPayableBillsByPaymentMadeFactory } from 'store/Bills/bills.selectors'; export default (mapState) => { const getBillsItems = getBillCurrentPageFactory(); const getBillsPaginationMeta = getBillPaginationMetaFactory(); const getBillTableQuery = getBillTableQueryFactory(); - const getVendorDueBills = getVendorDueBillsFactory(); + const getVendorPayableBills = getVendorPayableBillsFactory(); + const getPayableBillsByPaymentMade = getPayableBillsByPaymentMadeFactory(); const mapStateToProps = (state, props) => { const tableQuery = getBillTableQuery(state, props); @@ -26,7 +28,8 @@ export default (mapState) => { billsLoading: state.bills.loading, nextBillNumberChanged: state.bills.nextBillNumberChanged, - vendorDueBills: getVendorDueBills(state, props), + vendorPayableBills: getVendorPayableBills(state, props), + paymentMadePayableBills: getPayableBillsByPaymentMade(state, props), }; return mapState ? mapState(mapped, state, props) : mapped; }; diff --git a/client/src/containers/Purchases/PaymentMades/PaymentMade.js b/client/src/containers/Purchases/PaymentMades/PaymentMade.js index a20e55947..7eee9a8fb 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentMade.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentMade.js @@ -1,10 +1,12 @@ -import React, { useCallback, useState, useEffect, useMemo } from 'react'; -import { useParams, useHistory } from 'react-router-dom'; +import React, { useEffect } from 'react'; +import { useParams } from 'react-router-dom'; import { useQuery } from 'react-query'; +import { useIntl } from 'react-intl'; import PaymentMadeForm from './PaymentMadeForm'; import DashboardInsider from 'components/Dashboard/DashboardInsider'; +import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withVenderActions from 'containers/Vendors/withVendorActions'; import withAccountsActions from 'containers/Accounts/withAccountsActions'; import withItemsActions from 'containers/Items/withItemsActions'; @@ -15,7 +17,7 @@ import withSettingsActions from 'containers/Settings/withSettingsActions'; import { compose } from 'utils'; function PaymentMade({ - //#withwithAccountsActions + //#withAccountsActions requestFetchAccounts, //#withVenderActions @@ -27,65 +29,61 @@ function PaymentMade({ //#withPaymentMadesActions requestFetchPaymentMade, - //#withBillActions - // #withSettingsActions 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) => requestFetchAccounts(), ); - // Handle fetch Items data table or list + // Handle fetch Items data table or list. 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', () => requestFetchVendorsTable({}), ); + // Handle fetch specific payment made details. const fetchPaymentMade = useQuery( - ['payment-made', id], + ['payment-made', paymentMadeId], (key, _id) => requestFetchPaymentMade(_id), - { enabled: !!id }, + { enabled: !!paymentMadeId }, ); + // Fetch payment made settings. 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 ( + paymentMadeId={paymentMadeId} + /> ); } @@ -96,5 +94,7 @@ export default compose( withAccountsActions, withBillActions, withPaymentMadeActions, - withSettingsActions + withSettingsActions, + withDashboardActions, )(PaymentMade); + diff --git a/client/src/containers/Purchases/PaymentMades/PaymentMadeFloatingActions.js b/client/src/containers/Purchases/PaymentMades/PaymentMadeFloatingActions.js index eecfbdd4f..cb750a9e8 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentMadeFloatingActions.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentMadeFloatingActions.js @@ -2,21 +2,31 @@ import React from 'react'; import { Intent, Button } from '@blueprintjs/core'; import { FormattedMessage as T } from 'react-intl'; -export default function PaymentMadeFormFooter({ - formik: { isSubmitting, resetForm }, +export default function PaymentMadeFloatingActions({ + isSubmitting, onSubmitClick, onCancelClick, - paymentMade, + onClearBtnClick, }) { + const handleClearBtnClick = (event) => { + onClearBtnClick && onClearBtnClick(event); + }; + + const handleSubmitClick = (event) => { + onSubmitClick && onSubmitClick(event, { redirect: true }); + }; + + const handleCancelClick = (event) => { + onCancelClick && onCancelClick(event); + }; + return (
@@ -27,23 +37,23 @@ export default function PaymentMadeFormFooter({ className={'ml1'} name={'save'} type="submit" - onClick={() => { - onSubmitClick({ redirect: false }); - }} + onClick={handleSubmitClick} > - diff --git a/client/src/containers/Purchases/PaymentMades/PaymentMadeForm.js b/client/src/containers/Purchases/PaymentMades/PaymentMadeForm.js index 3206c6ab5..5318e45e1 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentMadeForm.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentMadeForm.js @@ -1,96 +1,56 @@ -import React, { - useMemo, - useCallback, - useEffect, - useState, - useRef, -} from 'react'; - +import React, { useMemo, useState, useCallback, useEffect } from '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 { Intent, Alert } from '@blueprintjs/core'; import { FormattedMessage as T, useIntl } from 'react-intl'; -import { pick, values } from 'lodash'; +import { pick, sumBy } from 'lodash'; import PaymentMadeHeader from './PaymentMadeFormHeader'; import PaymentMadeItemsTable from './PaymentMadeItemsTable'; import PaymentMadeFloatingActions from './PaymentMadeFloatingActions'; -import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withMediaActions from 'containers/Media/withMediaActions'; import withPaymentMadeActions from './withPaymentMadeActions'; import withPaymentMadeDetail from './withPaymentMadeDetail'; import withPaymentMade from './withPaymentMade'; import { AppToaster } from 'components'; -import Dragzone from 'components/Dragzone'; -import useMedia from 'hooks/useMedia'; -import { compose, repeatValue } from 'utils'; +import { compose, repeatValue, orderingLinesIndexes } from 'utils'; 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({ - //#withMedia + // #withMedia requestSubmitMedia, requestDeleteMedia, - //#withPaymentMadesActions + // #withPaymentMadesActions requestSubmitPaymentMade, requestEditPaymentMade, - setPaymentNumberChange, - // #withPaymentMade - nextPaymentNumberChanged, - - // #withSettings - paymentNextNumber, - paymentNumberPrefix, - - //#withDashboard - changePageTitle, - changePageSubtitle, - - //#withPaymentMadeDetail + // #withPaymentMadeDetail paymentMade, - onFormSubmit, - onCancelForm, - onVenderChange, + paymentMadeId, }) { + const history = useHistory(); 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(() => { - 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]); + const [amountChangeAlert, setAmountChangeAlert] = useState(false); + const [clearLinesAlert, setClearLinesAlert] = useState(false); + const [clearFormAlert, setClearFormAlert] = useState(false); + const [fullAmount, setFullAmount] = useState(null); + // Yup validation schema. const validationSchema = Yup.object().shape({ vendor_id: Yup.string() .label(formatMessage({ id: 'vendor_name_' })) @@ -108,11 +68,10 @@ function PaymentMadeForm({ description: Yup.string(), entries: Yup.array().of( Yup.object().shape({ - payment_amount: Yup.number().nullable(), - bill_number: Yup.number().nullable(), - amount: Yup.number().nullable(), + id: 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() .nullable() .when(['payment_amount'], { @@ -122,194 +81,254 @@ function PaymentMadeForm({ }), ), }); - const handleDropFiles = useCallback((_files) => { - setFiles(_files.filter((file) => file.uploaded === false)); - }, []); - const savePaymentMadeSubmit = useCallback((payload) => { - onFormSubmit && onFormSubmit(payload); - }); - - const defaultPaymentMade = useMemo( - () => ({ - bill_id: '', - bill_date: moment(new Date()).format('YYYY-MM-DD'), - bill_number: '', - amount: '', - due_amount: '', - payment_amount: '', - }), + // Default payment made entry values. + const defaultPaymentMadeEntry = useMemo( + () => ({ bill_id: '', payment_amount: '', id: null }), [], ); - const paymentNumber = paymentNumberPrefix - ? `${paymentNumberPrefix}-${paymentNextNumber}` - : paymentNextNumber; - + // Default initial values. const defaultInitialValues = useMemo( () => ({ + full_amount: '', vendor_id: '', payment_account_id: '', payment_date: moment(new Date()).format('YYYY-MM-DD'), reference: '', - payment_number: paymentNumber, - // receive_amount: '', + payment_number: '', description: '', - entries: [...repeatValue(defaultPaymentMade, MIN_LINES_NUMBER)], + entries: [], }), - [defaultPaymentMade], + [], ); - const orderingIndex = (_entries) => { - return _entries.map((item, index) => ({ - ...item, - index: index + 1, - })); - }; - + // Form initial values. const initialValues = useMemo( () => ({ ...(paymentMade ? { ...pick(paymentMade, Object.keys(defaultInitialValues)), + full_amount: sumBy(paymentMade.entries, 'payment_amount'), entries: [ - ...paymentMade.entries.map((paymentMade) => ({ - ...pick(paymentMade, Object.keys(defaultPaymentMade)), + ...paymentMade.entries.map((paymentMadeEntry) => ({ + ...pick(paymentMadeEntry, Object.keys(defaultPaymentMadeEntry)), })), - ...repeatValue( - defaultPaymentMade, - Math.max(MIN_LINES_NUMBER - paymentMade.entries.length, 0), - ), ], } : { ...defaultInitialValues, - entries: orderingIndex(defaultInitialValues.entries), + entries: orderingLinesIndexes(defaultInitialValues.entries), }), }), - [paymentMade, defaultInitialValues, defaultPaymentMade], + [paymentMade, defaultInitialValues, defaultPaymentMadeEntry], ); - const initialAttachmentFiles = useMemo(() => { - return paymentMade && paymentMade.media - ? paymentMade.media.map((attach) => ({ - preview: attach.attachment_file, - uploaded: true, - metadata: { ...attach }, - })) - : []; - }, [paymentMade]); + const handleSubmitForm = (values, { setSubmitting, resetForm, setFieldError }) => { + setSubmitting(true); - const formik = useFormik({ - validationSchema, - initialValues: { - ...initialValues, - }, - onSubmit: async (values, { setSubmitting, setErrors, resetForm }) => { - setSubmitting(true); - const entries = formik.values.entries.filter((item) => { - if (item.bill_id !== undefined) { - return { ...item }; - } + // Filters entries that have no `bill_id` or `payment_amount`. + const entries = values.entries.filter((item) => { + return !item.bill_id || item.payment_amount; + }); + // 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, + }), }); - const form = { - ...values, - entries, - }; + return; + } + const form = { ...values, entries }; - const requestForm = { ...form }; + // 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(); + }; - if (paymentMade && paymentMade.id) { - requestEditPaymentMade(paymentMade.id, requestForm) - .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 { - requestSubmitPaymentMade(requestForm) - .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 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) { + requestEditPaymentMade(paymentMade.id, form).then(onSaved).catch(onError); + } else { + requestSubmitPaymentMade(form).then(onSaved).catch(onError); + } + }; + + const { + errors, + touched, + setFieldValue, + getFieldProps, + setValues, + values, + handleSubmit, + isSubmitting, + } = useFormik({ + validationSchema, + initialValues, + onSubmit: handleSubmitForm, }); - const handleDeleteFile = useCallback( - (_deletedFiles) => { - _deletedFiles.forEach((deletedFile) => { - if (deletedFile.upload && deletedFile.metadata.id) { - setDeletedFiles([...deletedFiles, deletedFile.metadata.id]); - } - }); + const handleFullAmountChange = useCallback( + (value) => { + if (value !== fullAmount) { + setAmountChangeAlert(value); + } }, - [setDeletedFiles, deletedFiles], + [fullAmount, setAmountChangeAlert], ); - 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]), - ); + // 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 = () => { - formik.setFieldValue( - 'entries', - orderingIndex([...repeatValue(defaultPaymentMade, MIN_LINES_NUMBER)]), - ); + setClearLinesAlert(true); }; - useEffect(() => { - formik.setFieldValue('payment_number', paymentNumber); - setPaymentNumberChange(false); - }, [nextPaymentNumberChanged, paymentNumber]); + const handleCancelClearLines = useCallback(() => { + setClearLinesAlert(false); + }, [setClearLinesAlert]); + + 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 (
-
- + + + + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={amountChangeAlert} + onCancel={handleCancelAmountChangeAlert} + onConfirm={handleConfirmAmountChangeAlert} + > +

Are you sure to discard full amount?

+
+ + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={clearLinesAlert} + onCancel={handleCancelClearLines} + onConfirm={handleConfirmClearLines} + > +

Are you sure to discard full amount?

+
+ + } + confirmButtonText={} + intent={Intent.WARNING} + isOpen={clearFormAlert} + onCancel={handleCancelClearFormAlert} + onConfirm={handleConfirmCancelClearFormAlert} + > +

Are you sure to clear form data.

+
+ + {/* */} -
); } export default compose( withPaymentMadeActions, - withDashboardActions, withMediaActions, withPaymentMadeDetail(), withPaymentMade(({ nextPaymentNumberChanged }) => ({ diff --git a/client/src/containers/Purchases/PaymentMades/PaymentMadeFormHeader.js b/client/src/containers/Purchases/PaymentMades/PaymentMadeFormHeader.js index 66d726587..217eda93d 100644 --- a/client/src/containers/Purchases/PaymentMades/PaymentMadeFormHeader.js +++ b/client/src/containers/Purchases/PaymentMades/PaymentMadeFormHeader.js @@ -1,4 +1,4 @@ -import React, { useMemo, useCallback, useState } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { FormGroup, InputGroup, @@ -7,38 +7,57 @@ import { MenuItem, Classes, } from '@blueprintjs/core'; - +import { sumBy } from 'lodash'; import { DateInput } from '@blueprintjs/datetime'; import { FormattedMessage as T } from 'react-intl'; -import { useParams, useHistory } from 'react-router-dom'; - import moment from 'moment'; -import { momentFormatter, compose, tansformDateValue } from 'utils'; import classNames from 'classnames'; +import { momentFormatter, compose, tansformDateValue } from 'utils'; import { AccountsSelectList, ListSelect, ErrorMessage, FieldRequiredHint, - Icon, - InputPrependButton, + Money, } from 'components'; +import withBills from '../Bill/withBills'; import withVender from 'containers/Vendors/withVendors'; import withAccounts from 'containers/Accounts/withAccounts'; -import withDialogActions from 'containers/Dialog/withDialogActions'; +/** + * Payment made header form. + */ function PaymentMadeFormHeader({ - formik: { errors, touched, setFieldValue, getFieldProps, values }, + paymentMadeId, + vendorId, + + // #useFormik + errors, + touched, + setFieldValue, + getFieldProps, + values, + + onFullAmountChanged, //#withVender vendorsCurrentPage, vendorItems, + //#withAccouts accountsList, - // #withDialogActions - openDialog, + + // #withBills + vendorPayableBills, }) { + const isNewMode = !paymentMadeId; + + const payableFullAmount = useMemo( + () => sumBy(vendorPayableBills, 'due_amount'), + [vendorPayableBills], + ); + const handleDateChange = useCallback( (date_filed) => (date) => { const formatted = moment(date).format('YYYY-MM-DD'); @@ -58,6 +77,14 @@ function PaymentMadeFormHeader({ [], ); + const triggerFullAmountChanged = (value) => { + onFullAmountChanged && onFullAmountChanged(value); + } + + const handleFullAmountBlur = (event) => { + triggerFullAmountChanged(event.currentTarget.value); + }; + const handleFilterVender = (query, vender, index, exactMatch) => { const normalizedTitle = vender.display_name.toLowerCase(); const normalizedQuery = query.toLowerCase(); @@ -85,14 +112,15 @@ function PaymentMadeFormHeader({ [accountsList], ); - const handlePaymentNumberChange = useCallback(() => { - openDialog('payment-number-form', {}); - }, [openDialog]); + const handleReceiveFullAmountClick = () => { + setFieldValue('full_amount', payableFullAmount); + triggerFullAmountChanged(payableFullAmount); + }; return (
- {/* 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}> - + 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 {