From e36817cb885f1946c1b42b2d0c0152c6dc0e6221 Mon Sep 17 00:00:00 2001 From: "a.bouhuolia" Date: Wed, 17 Feb 2021 19:45:18 +0200 Subject: [PATCH] refactoring: payment receive form. --- client/package.json | 6 -- client/src/components/ContactSelecetList.js | 2 + client/src/components/Sidebar/SidebarMenu.js | 2 +- .../Alerts/Invoices/InvoiceDeleteAlert.js | 2 +- .../PaymentMades/ChangingFullAmountAlert.js | 45 ++++++++- .../PaymentMades/ClearTransactionAlert.js | 62 +++++++++---- .../PaymentMades/ClearningAllLinesAlert.js | 65 +++++++++---- .../ChangingFullAmountAlert.js | 22 ----- .../PaymentReceives/ClearTransactionAlert.js | 21 ----- .../PaymentReceives/ClearingAllLinesAlert.js | 66 ++++++++++---- .../src/containers/Items/ItemForm.schema.js | 3 +- .../EstimatesLanding/EstimatesList.js | 2 + .../Estimates/EstimatesLanding/components.js | 1 + .../Sales/Invoices/InvoiceForm/InvoiceForm.js | 2 +- .../Invoices/InvoicesLanding/components.js | 27 +++--- .../PaymentReceiveFloatingActions.js | 17 ++-- .../PaymentReceiveForm/PaymentReceiveForm.js | 58 +++++------- .../PaymentReceiveFormAlerts.js | 26 ++++++ .../PaymentReceiveFormBody.js | 25 +++++ .../PaymentReceiveFormHeader.js | 8 +- .../PaymentReceiveFormProvider.js | 36 +++----- .../PaymentReceiveHeaderFields.js | 91 ++++++++++++------- .../PaymentReceiveInnerProvider.js | 50 ++++++++++ .../PaymentReceiveItemsTable.js | 43 +++++---- .../PaymentReceiveForm/components.js | 27 +++++- .../PaymentReceiveForm/utils.js | 72 ++++++++++++--- .../PaymentReceiptsListProvider.js | 2 +- .../PaymentsLanding/PaymentReceivesList.js | 17 +--- .../PaymentReceivesListProvider.js | 2 +- .../PaymentsLanding/PaymentReceivesTable.js | 6 +- .../PaymentsLanding/components.js | 16 ++-- .../Receipts/ReceiptsLanding/ReceiptsList.js | 2 + client/src/hooks/query/paymentReceives.js | 63 +++++++++++-- client/src/lang/en/index.js | 6 +- client/src/routes/dashboard.js | 1 + client/src/static/json/icons.js | 6 ++ client/src/style/App.scss | 5 + .../src/style/pages/PaymentReceive/List.scss | 18 ++++ .../style/pages/PaymentReceive/PageForm.scss | 14 ++- client/src/style/pages/SaleEstimate/List.scss | 18 ++++ client/src/style/pages/SaleInvoice/List.scss | 13 ++- client/src/style/pages/SaleReceipt/List.scss | 18 ++++ .../api/controllers/Sales/PaymentReceives.ts | 14 ++- server/src/interfaces/PaymentReceive.ts | 20 +++- server/src/services/Sales/PaymentsReceives.ts | 76 +++++++++++----- server/src/services/Sales/SalesInvoices.ts | 2 + 46 files changed, 775 insertions(+), 325 deletions(-) delete mode 100644 client/src/containers/Alerts/PaymentReceives/ChangingFullAmountAlert.js delete mode 100644 client/src/containers/Alerts/PaymentReceives/ClearTransactionAlert.js create mode 100644 client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormAlerts.js create mode 100644 client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFormBody.js create mode 100644 client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveInnerProvider.js create mode 100644 client/src/style/pages/PaymentReceive/List.scss create mode 100644 client/src/style/pages/SaleEstimate/List.scss create mode 100644 client/src/style/pages/SaleReceipt/List.scss diff --git a/client/package.json b/client/package.json index b8345e974..e5d3a7e97 100644 --- a/client/package.json +++ b/client/package.json @@ -11,7 +11,6 @@ "@blueprintjs/timezone": "^3.6.2", "@reduxjs/toolkit": "^1.2.5", "@svgr/webpack": "4.3.3", - "@syncfusion/ej2-react-grids": "^17.4.50", "@tanem/react-nprogress": "^3.0.24", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.4.0", @@ -57,7 +56,6 @@ "moment": "^2.24.0", "node-sass": "^4.13.1", "optimize-css-assets-webpack-plugin": "5.0.3", - "p-progress": "^0.4.2", "pnp-webpack-plugin": "1.6.0", "postcss-flexbugs-fixes": "4.1.0", "postcss-loader": "3.0.0", @@ -72,8 +70,6 @@ "react-dom": "^16.12.0", "react-dropzone": "^11.0.1", "react-error-boundary": "^3.0.2", - "react-grid-system": "^6.2.3", - "react-hook-form": "^4.9.4", "react-hotkeys-hook": "^3.0.3", "react-intl": "^3.12.0", "react-loadable": "^5.5.0", @@ -98,12 +94,10 @@ "resolve-url-loader": "3.1.1", "sass-loader": "8.0.2", "semver": "6.3.0", - "sortablejs": "^1.10.2", "style-loader": "0.23.1", "terser-webpack-plugin": "2.3.4", "ts-pnp": "1.1.5", "url-loader": "2.3.0", - "use-named-routes": "^0.3.2", "webpack": "4.41.5", "webpack-dev-server": "3.10.2", "webpack-manifest-plugin": "2.2.0", diff --git a/client/src/components/ContactSelecetList.js b/client/src/components/ContactSelecetList.js index ed10398c2..d19d4943c 100644 --- a/client/src/components/ContactSelecetList.js +++ b/client/src/components/ContactSelecetList.js @@ -14,6 +14,7 @@ export default function ContactSelecetList({ onContactSelected, popoverFill = false, disabled = false, + buttonProps }) { const contacts = useMemo( () => @@ -94,6 +95,7 @@ export default function ContactSelecetList({ text={ selecetedContact ? selecetedContact.display_name : defaultSelectText } + {...buttonProps} /> ); diff --git a/client/src/components/Sidebar/SidebarMenu.js b/client/src/components/Sidebar/SidebarMenu.js index 5e5bf3f2c..31275978c 100644 --- a/client/src/components/Sidebar/SidebarMenu.js +++ b/client/src/components/Sidebar/SidebarMenu.js @@ -59,7 +59,7 @@ export default function SidebarMenu() { disabled={item.disabled} children={children} dropdownType={item.dropdownType || 'collapse'} - caretIconSize={15} + caretIconSize={16} onClick={handleItemClick} callapseActive={!!isActive} itemClassName={classNames({ diff --git a/client/src/containers/Alerts/Invoices/InvoiceDeleteAlert.js b/client/src/containers/Alerts/Invoices/InvoiceDeleteAlert.js index a93147048..e3a8e9969 100644 --- a/client/src/containers/Alerts/Invoices/InvoiceDeleteAlert.js +++ b/client/src/containers/Alerts/Invoices/InvoiceDeleteAlert.js @@ -47,7 +47,7 @@ function InvoiceDeleteAlert({ intent: Intent.SUCCESS, }); }) - .catch((errors) => { + .catch(({ response: { data: { errors } } }) => { handleDeleteErrors(errors); }) .finally(() => { diff --git a/client/src/containers/Alerts/PaymentMades/ChangingFullAmountAlert.js b/client/src/containers/Alerts/PaymentMades/ChangingFullAmountAlert.js index 30f3c470f..94b86d689 100644 --- a/client/src/containers/Alerts/PaymentMades/ChangingFullAmountAlert.js +++ b/client/src/containers/Alerts/PaymentMades/ChangingFullAmountAlert.js @@ -1,15 +1,45 @@ import React from 'react'; -import { Alert } from '@blueprintjs/core'; +import { Intent, Alert } from '@blueprintjs/core'; + +import withAlertActions from 'containers/Alert/withAlertActions'; +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; + +import { compose } from 'utils'; +import { saveInvoke } from '../../../utils'; + +/** + * Changing full-amount alert in payment made form. + */ +function ChangingFullAmountAlert({ + name, + onConfirm, + + // #withAlertStoreConnect + isOpen, + payload: { }, + + // #withAlertActions + closeAlert, +}) { + // Handle the alert cancel. + const handleCancel = () => { + closeAlert(name); + }; + + // Handle confirm delete manual journal. + const handleConfirm = (event) => { + closeAlert(name); + saveInvoke(onConfirm, event) + }; -function ChangingFullAmountAlert() { return ( } confirmButtonText={} intent={Intent.DANGER} - isOpen={amountChangeAlert} - onCancel={handleCancelAmountChangeAlert} - onConfirm={handleConfirmAmountChangeAlert} + isOpen={isOpen} + onCancel={handleCancel} + onConfirm={handleConfirm} >

Changing full amount will change all credit and payment were applied, Is @@ -18,3 +48,8 @@ function ChangingFullAmountAlert() { ); } + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(ChangingFullAmountAlert); diff --git a/client/src/containers/Alerts/PaymentMades/ClearTransactionAlert.js b/client/src/containers/Alerts/PaymentMades/ClearTransactionAlert.js index 535417f08..9adb0f22d 100644 --- a/client/src/containers/Alerts/PaymentMades/ClearTransactionAlert.js +++ b/client/src/containers/Alerts/PaymentMades/ClearTransactionAlert.js @@ -1,24 +1,54 @@ +import React from 'react'; +import { Intent, Alert } from '@blueprintjs/core'; +import { FormattedMessage as T } from 'react-intl'; +import withAlertActions from 'containers/Alert/withAlertActions'; +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import { compose } from 'utils'; +/** + * Alert description. + */ +function ClearPaymentTransactionAlert({ + name, -export default function ClearTransactionAlert() { + // #withAlertStoreConnect + isOpen, + payload: { }, + // #withAlertActions + closeAlert, +}) { + // Handle the alert cancel. + const handleCancel = () => { + closeAlert(name); + }; + // Handle confirm delete manual journal. + const handleConfirm = () => { + + }; + return ( + } + confirmButtonText={} + icon="trash" + intent={Intent.DANGER} + isOpen={isOpen} + onCancel={handleCancel} + onConfirm={handleConfirm} + loading={false} + > +

+ +

+
+ ); +} - return ( - } - confirmButtonText={} - intent={Intent.WARNING} - isOpen={clearFormAlert} - onCancel={handleCancelClearFormAlert} - onConfirm={handleConfirmCancelClearFormAlert} - > -

- -

-
- ) -} \ No newline at end of file +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(ClearPaymentTransactionAlert); \ No newline at end of file diff --git a/client/src/containers/Alerts/PaymentMades/ClearningAllLinesAlert.js b/client/src/containers/Alerts/PaymentMades/ClearningAllLinesAlert.js index c9ccc1a70..e9c3a670c 100644 --- a/client/src/containers/Alerts/PaymentMades/ClearningAllLinesAlert.js +++ b/client/src/containers/Alerts/PaymentMades/ClearningAllLinesAlert.js @@ -1,23 +1,54 @@ +import React from 'react'; +import { Intent, Alert } from '@blueprintjs/core'; +import { FormattedMessage as T } from 'react-intl'; +import withAlertActions from 'containers/Alert/withAlertActions'; +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import { compose } from 'utils'; +/** + * Clearning all lines alert. + */ +function ClearAllLinesAlert({ + name, -function ClearningAllLinesAlert() { + // #withAlertStoreConnect + isOpen, + payload: {}, - return ( + // #withAlertActions + closeAlert, +}) { - } - confirmButtonText={} - intent={Intent.DANGER} - isOpen={clearLinesAlert} - onCancel={handleCancelClearLines} - onConfirm={handleConfirmClearLines} - > -

- Clearing the table lines will delete all credits and payments were - applied. Is this okay? -

-
- ) -} \ No newline at end of file + // Handle the alert cancel. + const handleCancel = () => { + closeAlert(name); + }; + + // Handle confirm delete manual journal. + const handleConfirm = () => {}; + + return ( + } + confirmButtonText={} + icon="trash" + intent={Intent.DANGER} + isOpen={isOpen} + onCancel={handleCancel} + onConfirm={handleConfirm} + loading={false} + > +

+ Clearing the table lines will delete all credits and payments were + applied. Is this okay? +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(ClearAllLinesAlert); diff --git a/client/src/containers/Alerts/PaymentReceives/ChangingFullAmountAlert.js b/client/src/containers/Alerts/PaymentReceives/ChangingFullAmountAlert.js deleted file mode 100644 index 47f8676d4..000000000 --- a/client/src/containers/Alerts/PaymentReceives/ChangingFullAmountAlert.js +++ /dev/null @@ -1,22 +0,0 @@ - - - -function ChangingFullAmountAlert() { - - return ( - } - confirmButtonText={} - intent={Intent.WARNING} - isOpen={amountChangeAlert} - onCancel={handleCancelAmountChangeAlert} - onConfirm={handleConfirmAmountChangeAlert} - > -

- -

-
- ) -} \ No newline at end of file diff --git a/client/src/containers/Alerts/PaymentReceives/ClearTransactionAlert.js b/client/src/containers/Alerts/PaymentReceives/ClearTransactionAlert.js deleted file mode 100644 index a8873302f..000000000 --- a/client/src/containers/Alerts/PaymentReceives/ClearTransactionAlert.js +++ /dev/null @@ -1,21 +0,0 @@ - - - - -function ClearTransactionAlert() { - - return ( - } - confirmButtonText={} - intent={Intent.WARNING} - isOpen={clearFormAlert} - onCancel={handleCancelClearFormAlert} - onConfirm={handleConfirmCancelClearFormAlert} - > -

- -

-
- ) -} \ No newline at end of file diff --git a/client/src/containers/Alerts/PaymentReceives/ClearingAllLinesAlert.js b/client/src/containers/Alerts/PaymentReceives/ClearingAllLinesAlert.js index 856a5aeb7..9143d98aa 100644 --- a/client/src/containers/Alerts/PaymentReceives/ClearingAllLinesAlert.js +++ b/client/src/containers/Alerts/PaymentReceives/ClearingAllLinesAlert.js @@ -1,20 +1,54 @@ +import React from 'react'; +import { Intent, Alert } from '@blueprintjs/core'; +import { FormattedMessage as T } from 'react-intl'; +import withAlertActions from 'containers/Alert/withAlertActions'; +import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect'; +import { saveInvoke, compose } from 'utils'; -function ClearingAllLinesAlert() { +/** + * Clearning all lines alert. + */ +function ClearningAllLinesAlert({ + name, + onConfirm, - return ( - } - confirmButtonText={} - intent={Intent.WARNING} - isOpen={clearLinesAlert} - onCancel={handleCancelClearLines} - onConfirm={handleConfirmClearLines} - > -

- -

-
- ) -} \ No newline at end of file + // #withAlertStoreConnect + isOpen, + payload: {}, + + // #withAlertActions + closeAlert, +}) { + // Handle the alert cancel. + const handleCancel = () => { + closeAlert(name); + }; + + // Handle confirm delete manual journal. + const handleConfirm = (event) => { + closeAlert(name); + saveInvoke(onConfirm, event) + }; + + return ( + } + confirmButtonText={} + intent={Intent.DANGER} + isOpen={isOpen} + onCancel={handleCancel} + onConfirm={handleConfirm} + > +

+ +

+
+ ); +} + +export default compose( + withAlertStoreConnect(), + withAlertActions, +)(ClearningAllLinesAlert); diff --git a/client/src/containers/Items/ItemForm.schema.js b/client/src/containers/Items/ItemForm.schema.js index 8eade519d..283934c4f 100644 --- a/client/src/containers/Items/ItemForm.schema.js +++ b/client/src/containers/Items/ItemForm.schema.js @@ -2,7 +2,6 @@ import * as Yup from 'yup'; import { defaultTo } from 'lodash'; import { formatMessage } from 'services/intl'; import { DATATYPES_LENGTH } from 'common/dataTypes'; -import { isBlank } from 'utils'; const Schema = Yup.object().shape({ active: Yup.boolean(), @@ -20,6 +19,7 @@ const Schema = Yup.object().shape({ code: Yup.string().trim().min(0).max(DATATYPES_LENGTH.STRING), cost_price: Yup.number() .min(0) + .max(DATATYPES_LENGTH.DECIMAL_13_3) .when(['purchasable'], { is: true, then: Yup.number() @@ -29,6 +29,7 @@ const Schema = Yup.object().shape({ }), sell_price: Yup.number() .min(0) + .max(DATATYPES_LENGTH.DECIMAL_13_3) .when(['sellable'], { is: true, then: Yup.number() diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesList.js b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesList.js index 4b3ac6e39..016216290 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesList.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/EstimatesList.js @@ -1,6 +1,8 @@ import React from 'react'; import { DashboardContentTable, DashboardPageContent } from 'components'; +import 'style/pages/SaleEstimate/List.scss'; + import EstimatesActionsBar from './EstimatesActionsBar'; import EstimatesAlerts from '../EstimatesAlerts'; import EstimatesViewTabs from './EstimatesViewTabs'; diff --git a/client/src/containers/Sales/Estimates/EstimatesLanding/components.js b/client/src/containers/Sales/Estimates/EstimatesLanding/components.js index 87f8c11ea..ff5bec06f 100644 --- a/client/src/containers/Sales/Estimates/EstimatesLanding/components.js +++ b/client/src/containers/Sales/Estimates/EstimatesLanding/components.js @@ -75,6 +75,7 @@ export function ActionsMenu({ } text={formatMessage({ id: 'mark_as_rejected' })} onClick={safeCallback(onReject, original)} /> diff --git a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.js b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.js index 5d574c78f..1f4eaca01 100644 --- a/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.js +++ b/client/src/containers/Sales/Invoices/InvoiceForm/InvoiceForm.js @@ -122,7 +122,7 @@ function InvoiceForm({ }; // Handle the request error. - const onError = (errors) => { + const onError = ({ response: { data: { errors } } }) => { if (errors) { handleErrors(errors, { setErrors }); } diff --git a/client/src/containers/Sales/Invoices/InvoicesLanding/components.js b/client/src/containers/Sales/Invoices/InvoicesLanding/components.js index 30465bebf..043b673f9 100644 --- a/client/src/containers/Sales/Invoices/InvoicesLanding/components.js +++ b/client/src/containers/Sales/Invoices/InvoicesLanding/components.js @@ -10,15 +10,19 @@ import { Position, Button } from '@blueprintjs/core'; -import { Choose, If, Icon } from 'components'; import { FormattedMessage as T, useIntl } from 'react-intl'; import moment from 'moment'; +import { round } from 'lodash'; +import { Choose, If, Icon } from 'components'; import { Money, AppToaster } from 'components'; import { formatMessage } from 'services/intl'; import { safeCallback } from 'utils'; -const calculateStatus = (paymentAmount, balanceAmount) => - paymentAmount / balanceAmount; + +const calculateStatus = (paymentAmount, balanceAmount) => { + return round(paymentAmount / balanceAmount, 2); +} + export const statusAccessor = (row) => { return ( @@ -52,7 +56,7 @@ export const statusAccessor = (row) => { @@ -110,6 +114,7 @@ export function ActionsMenu({ /> } text={formatMessage({ id: 'mark_as_delivered' })} onClick={safeCallback(onDeliver, original)} /> @@ -157,13 +162,6 @@ export function useInvoicesTableColumns() { width: 180, className: 'customer_id', }, - { - id: 'invoice_no', - Header: formatMessage({ id: 'invoice_no__' }), - accessor: (row) => (row.invoice_no ? `#${row.invoice_no}` : null), - width: 100, - className: 'invoice_no', - }, { id: 'balance', Header: formatMessage({ id: 'balance' }), @@ -171,6 +169,13 @@ export function useInvoicesTableColumns() { width: 110, className: 'balance', }, + { + id: 'invoice_no', + Header: formatMessage({ id: 'invoice_no__' }), + accessor: (row) => (row.invoice_no ? `#${row.invoice_no}` : null), + width: 100, + className: 'invoice_no', + }, { id: 'status', Header: formatMessage({ id: 'status' }), diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFloatingActions.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFloatingActions.js index 24dcc0913..6cc28a56b 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFloatingActions.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveFloatingActions.js @@ -22,25 +22,23 @@ import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider'; * Payment receive floating actions bar. */ export default function PaymentReceiveFormFloatingActions() { - // Payment receive form context. const { setSubmitPayload, isNewMode } = usePaymentReceiveFormContext(); // Formik form context. - const { isSubmitting } = useFormikContext(); + const { isSubmitting, submitForm } = useFormikContext(); // History context. const history = useHistory(); // Handle submit button click. const handleSubmitBtnClick = (event) => { - setSubmitPayload({ redirect: true, }); + setSubmitPayload({ redirect: true }); + submitForm(); }; // Handle clear button click. - const handleClearBtnClick = (event) => { - - }; + const handleClearBtnClick = (event) => {}; // Handle cancel button click. const handleCancelBtnClick = (event) => { @@ -49,12 +47,14 @@ export default function PaymentReceiveFormFloatingActions() { // Handle submit & new button click. const handleSubmitAndNewClick = (event) => { - setSubmitPayload({ redirect: false, resetForm: true, }); + setSubmitPayload({ redirect: false, resetForm: true }); + submitForm(); }; // Handle submit & continue editing button click. const handleSubmitContinueEditingBtnClick = (event) => { setSubmitPayload({ redirect: false, publish: true }); + submitForm(); }; return ( @@ -63,6 +63,7 @@ export default function PaymentReceiveFormFloatingActions() { )} - + {/* ------------ Payment receive no. ------------ */} @@ -200,7 +228,7 @@ function PaymentReceiveHeaderFields({ baseCurrency }) { {/* ------------ Reference No. ------------ */} - + {({ form, field, meta: { error, touched } }) => ( } @@ -225,5 +253,4 @@ export default compose( withSettings(({ organizationSettings }) => ({ baseCurrency: organizationSettings?.baseCurrency, })), - withDialogActions, )(PaymentReceiveHeaderFields); diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveInnerProvider.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveInnerProvider.js new file mode 100644 index 000000000..c642911f6 --- /dev/null +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveInnerProvider.js @@ -0,0 +1,50 @@ +import React, { createContext, useContext, useEffect } from 'react'; +import { useFormikContext } from 'formik'; +import { useDueInvoices } from 'hooks/query'; +import { transformInvoicesNewPageEntries } from './utils'; +import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider'; +import { isEmpty } from 'lodash'; + +const PaymentReceiveInnerContext = createContext(); + +/** + * Payment receive inner form provider. + */ +function PaymentReceiveInnerProvider({ ...props }) { + const { isNewMode } = usePaymentReceiveFormContext(); + + // Formik context. + const { + values: { customer_id: customerId }, + setFieldValue, + } = useFormikContext(); + + // Fetches customer receivable invoices. + const { + data: dueInvoices, + isLoading: isDueInvoicesLoading, + isFetching: isDueInvoicesFetching, + } = useDueInvoices(customerId, { + enabled: !!customerId && isNewMode, + }); + + useEffect(() => { + if (!isDueInvoicesFetching && !isEmpty(dueInvoices)) { + setFieldValue('entries', transformInvoicesNewPageEntries(dueInvoices)); + } + }, [isDueInvoicesFetching, dueInvoices, setFieldValue]); + + // Provider payload. + const provider = { + dueInvoices, + isDueInvoicesLoading, + isDueInvoicesFetching, + }; + + return ; +} + +const usePaymentReceiveInnerContext = () => + useContext(PaymentReceiveInnerContext); + +export { PaymentReceiveInnerProvider, usePaymentReceiveInnerContext }; diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js index 182fd64e8..29fca29ee 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/PaymentReceiveItemsTable.js @@ -1,4 +1,4 @@ -import React, { useMemo, useCallback } from 'react'; +import React, { useCallback } from 'react'; import { Button } from '@blueprintjs/core'; import { FormattedMessage as T } from 'react-intl'; import { CloudLoadingIndicator } from 'components'; @@ -8,43 +8,50 @@ import { CLASSES } from 'common/classes'; import { usePaymentReceiveFormContext } from './PaymentReceiveFormProvider'; import { DataTableEditable } from 'components'; import { usePaymentReceiveEntriesColumns } from './components'; +import { compose, updateTableRow, safeSumBy } from 'utils'; +import withAlertActions from 'containers/Alert/withAlertActions'; + /** * Payment receive items table. */ -export default function PaymentReceiveItemsTable() { +function PaymentReceiveItemsTable({ + entries, + onUpdateData, + + // #withDialogActions + openAlert +}) { // Payment receive form context. const { - isNewMode, isDueInvoicesFetching, paymentCustomerId, - dueInvoices, } = usePaymentReceiveFormContext(); // Payment receive entries form context. const columns = usePaymentReceiveEntriesColumns(); - // Detarmines takes payment receive invoices entries from customer receivable - // invoices or associated payment receive invoices. - const computedTableData = useMemo( - () => [ - ...(!isNewMode ? [] || [] : []), - ...(isNewMode ? dueInvoices || [] : []), - ], - [isNewMode, dueInvoices], - ); - // No results message. const noResultsMessage = paymentCustomerId ? '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) => {}, []); + const handleUpdateData = useCallback((rowIndex, columnId, value) => { + const newRows = compose( + updateTableRow(rowIndex, columnId, value), + )(entries); + + onUpdateData(newRows); + }, [entries, onUpdateData]); // Handle click clear all lines button. const handleClickClearAllLines = () => { - + const fullAmount = safeSumBy(entries, 'payment_amount'); + + if (fullAmount > 0) { + openAlert('clear-all-lines-payment-receive'); + } }; return ( @@ -53,7 +60,7 @@ export default function PaymentReceiveItemsTable() { progressBarLoading={isDueInvoicesFetching} className={classNames(CLASSES.DATATABLE_EDITOR_ITEMS_ENTRIES)} columns={columns} - data={[]} + data={entries} spinnerProps={false} payload={{ errors: [], @@ -74,3 +81,5 @@ export default function PaymentReceiveItemsTable() { ); } + +export default compose(withAlertActions)(PaymentReceiveItemsTable); \ No newline at end of file diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js index e37d9d5d4..300538acd 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/components.js @@ -1,6 +1,8 @@ import React from 'react'; import moment from 'moment'; import { useIntl } from 'react-intl'; +import { Money } from 'components'; +import { MoneyFieldCell } from 'components/DataTableCells'; import { safeSumBy, formattedAmount } from 'utils'; /** @@ -21,15 +23,14 @@ function IndexCell({ row: { index } }) { * Invoice number table cell accessor. */ function InvNumberCellAccessor(row) { - const invNumber = row?.invoice_no || row?.id; - return `#INV-${invNumber || ''}`; + return row?.invoice_no ? `#${row?.invoice_no || ''}` : '-'; } /** * Balance footer cell. */ function BalanceFooterCell({ rows }) { - const total = safeSumBy(rows, 'original.balance'); + const total = safeSumBy(rows, 'original.amount'); return { formattedAmount(total, 'USD') }; } @@ -49,6 +50,18 @@ function PaymentAmountFooterCell({ rows }) { return { formattedAmount(totalPaymentAmount, 'USD') }; } + +/** + * Mobey table cell. + */ +function MoneyTableCell({ value }) { + return +} + +function DateFooterCell() { + return 'Total'; +} + /** * Retrieve payment receive form entries columns. */ @@ -64,12 +77,14 @@ export const usePaymentReceiveEntriesColumns = () => { width: 40, disableResizing: true, disableSortBy: true, + className: 'index' }, { Header: formatMessage({ id: 'Date' }), id: 'invoice_date', accessor: 'invoice_date', Cell: InvoiceDateCell, + Footer: DateFooterCell, disableSortBy: true, disableResizing: true, width: 250, @@ -77,14 +92,14 @@ export const usePaymentReceiveEntriesColumns = () => { { Header: formatMessage({ id: 'invocie_number' }), accessor: InvNumberCellAccessor, - Cell: 'invoice_no', disableSortBy: true, className: '', }, { Header: formatMessage({ id: 'invoice_amount' }), - accessor: 'balance', + accessor: 'amount', Footer: BalanceFooterCell, + Cell: MoneyTableCell, disableSortBy: true, width: 100, className: '', @@ -93,6 +108,7 @@ export const usePaymentReceiveEntriesColumns = () => { Header: formatMessage({ id: 'amount_due' }), accessor: 'due_amount', Footer: DueAmountFooterCell, + Cell: MoneyTableCell, disableSortBy: true, width: 150, className: '', @@ -100,6 +116,7 @@ export const usePaymentReceiveEntriesColumns = () => { { Header: formatMessage({ id: 'payment_amount' }), accessor: 'payment_amount', + Cell: MoneyFieldCell, Footer: PaymentAmountFooterCell, disableSortBy: true, width: 150, diff --git a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/utils.js b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/utils.js index 6a8555501..6388f0a7b 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/utils.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentReceiveForm/utils.js @@ -1,12 +1,14 @@ import moment from 'moment'; -import { transformToForm } from 'utils'; +import { transformToForm, safeSumBy } from 'utils'; // Default payment receive entry. export const defaultPaymentReceiveEntry = { - id: '', payment_amount: '', invoice_id: '', + invoice_no: '', due_amount: '', + date: '', + amount: '', }; // Form initial values. @@ -21,13 +23,61 @@ export const defaultPaymentReceive = { entries: [], }; -export const transformToEditForm = (paymentReceive, paymentReceiveEntries) => { - return { - ...transformToForm(paymentReceive, defaultPaymentReceive), - entries: [ - ...paymentReceiveEntries.map((paymentReceiveEntry) => ({ - ...transformToForm(paymentReceiveEntry, defaultPaymentReceiveEntry), - })), - ], - }; +/** + * + */ +export const transformToEditForm = (paymentReceive, paymentReceiveEntries) => ({ + ...transformToForm(paymentReceive, defaultPaymentReceive), + full_amount: safeSumBy(paymentReceiveEntries, 'payment_amount'), + entries: [ + ...paymentReceiveEntries.map((paymentReceiveEntry) => ({ + ...transformToForm(paymentReceiveEntry, defaultPaymentReceiveEntry), + })), + ], +}); + +/** + * Transformes the given invoices to the new page receivable entries. + */ +export const transformInvoicesNewPageEntries = (invoices) => [ + ...invoices.map((invoice, index) => ({ + index: index + 1, + invoice_id: invoice.id, + entry_type: 'invoice', + due_amount: invoice.due_amount, + date: invoice.invoice_date, + amount: invoice.balance, + payment_amount: 0, + invoice_no: invoice.invoice_no, + total_payment_amount: invoice.payment_amount, + })), +]; + +export const transformEntriesToEditForm = (receivableEntries) => [ + ...transformInvoicesNewPageEntries([...(receivableEntries || [])]), +]; + +export const clearAllPaymentEntries = (entries) => [ + ...entries.map((entry) => ({ ...entry, payment_amount: 0 })), +]; + +export const amountPaymentEntries = (amount, entries) => { + let total = amount; + + return entries.map((item) => { + const diff = Math.min(item.due_amount, total); + total -= Math.max(diff, 0); + + return { + ...item, + payment_amount: diff, + }; + }); }; + +export const fullAmountPaymentEntries = (entries) => { + return entries.map((item) => ({ + ...item, + payment_amount: item.due_amount, + })); +} \ No newline at end of file diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiptsListProvider.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiptsListProvider.js index aaff54857..51042b245 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiptsListProvider.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiptsListProvider.js @@ -57,7 +57,7 @@ function PaymentReceivesListProvider({ query, ...props }) { return ( diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList.js index 7c2483125..852dff346 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesList.js @@ -1,5 +1,6 @@ -import React, { useEffect } from 'react'; -import { useIntl } from 'react-intl'; +import React from 'react'; + +import 'style/pages/PaymentReceive/List.scss'; import { DashboardContentTable, DashboardPageContent } from 'components'; import PaymentReceiveActionsBar from './PaymentReceiveActionsBar'; @@ -8,7 +9,6 @@ import { PaymentReceivesListProvider } from './PaymentReceiptsListProvider'; import PaymentReceiveViewTabs from './PaymentReceiveViewTabs'; import PaymentReceivesTable from './PaymentReceivesTable'; -import withDashboardActions from 'containers/Dashboard/withDashboardActions'; import withPaymentReceives from './withPaymentReceives'; import { compose, transformTableStateToQuery } from 'utils'; @@ -17,19 +17,9 @@ import { compose, transformTableStateToQuery } from 'utils'; * Payment receives list. */ function PaymentReceiveList({ - // #withDashboardActions - changePageTitle, - // #withPaymentReceives paymentReceivesTableState, }) { - const { formatMessage } = useIntl(); - - // Changes the dashboard page title once the page mount. - useEffect(() => { - changePageTitle(formatMessage({ id: 'payment_Receives_list' })); - }, [changePageTitle, formatMessage]); - return ( ({ paymentReceivesTableState, })), diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesListProvider.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesListProvider.js index 2df4e069a..bb7fa2487 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesListProvider.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesListProvider.js @@ -29,7 +29,7 @@ function PaymentReceivesListProvider({ query, ...props }) { data: { paymentReceives, pagination, filterMeta }, isLoading: isPaymentReceivesLoading, isFetching: isPaymentReceivesFetching, - } = usePaymentReceives(query); + } = usePaymentReceives(query, { keepPreviousData: true }); // Provider payload. const provider = { diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesTable.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesTable.js index 46ac7120c..6bf775718 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesTable.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceivesTable.js @@ -1,10 +1,8 @@ import React, { useCallback } from 'react'; -import classNames from 'classnames'; - -import { compose } from 'utils'; import { useHistory } from 'react-router-dom'; -import { CLASSES } from 'common/classes'; +import { compose } from 'utils'; + import PaymentReceivesEmptyStatus from './PaymentReceivesEmptyStatus'; import { DataTable } from 'components'; import TableSkeletonRows from 'components/Datatable/TableSkeletonRows'; diff --git a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js index 3d2f18405..d97397766 100644 --- a/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js +++ b/client/src/containers/Sales/PaymentReceives/PaymentsLanding/components.js @@ -91,9 +91,16 @@ export function usePaymentReceivesColumns() { id: 'customer_id', Header: formatMessage({ id: 'customer_name' }), accessor: 'customer.display_name', - width: 140, + width: 160, className: 'customer_id', }, + { + id: 'amount', + Header: formatMessage({ id: 'amount' }), + accessor: AmountAccessor, + width: 120, + className: 'amount', + }, { id: 'payment_receive_no', Header: formatMessage({ id: 'payment_receive_no' }), @@ -102,13 +109,6 @@ export function usePaymentReceivesColumns() { width: 140, className: 'payment_receive_no', }, - { - id: 'amount', - Header: formatMessage({ id: 'amount' }), - accessor: AmountAccessor, - width: 140, - className: 'amount', - }, { id: 'reference_no', Header: formatMessage({ id: 'reference_no' }), diff --git a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsList.js b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsList.js index 9a63e3727..673c64268 100644 --- a/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsList.js +++ b/client/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptsList.js @@ -1,6 +1,8 @@ import React from 'react'; import { DashboardContentTable, DashboardPageContent } from 'components'; +import 'style/pages/SaleReceipt/List.scss'; + import ReceiptActionsBar from './ReceiptActionsBar'; import ReceiptViewTabs from './ReceiptViewTabs'; import ReceiptsAlerts from '../ReceiptsAlerts'; diff --git a/client/src/hooks/query/paymentReceives.js b/client/src/hooks/query/paymentReceives.js index b6027971c..eb42d0c6f 100644 --- a/client/src/hooks/query/paymentReceives.js +++ b/client/src/hooks/query/paymentReceives.js @@ -1,7 +1,7 @@ import { useMutation, useQuery, useQueryClient } from 'react-query'; import { defaultTo } from 'lodash'; import ApiService from 'services/ApiService'; -import { transformPagination } from 'utils'; +import { transformPagination, saveInvoke } from 'utils'; /** * Retrieve accounts list. @@ -43,8 +43,13 @@ export function useCreatePaymentReceive(props) { return useMutation( (values) => ApiService.post('sales/payment_receives', values), { - onSuccess: () => { + onSuccess: (data, values) => { client.invalidateQueries('PAYMENT_RECEIVES'); + client.invalidateQueries('SALE_INVOICE_DUE'); + client.invalidateQueries('SALE_INVOICES'); + client.invalidateQueries('SALE_INVOICE'); + + saveInvoke(props?.onSuccess, data); }, ...props, }, @@ -60,8 +65,13 @@ export function useEditPaymentReceive(props) { return useMutation( ([id, values]) => ApiService.post(`sales/payment_receives/${id}`, values), { - onSuccess: () => { + onSuccess: (data) => { client.invalidateQueries('PAYMENT_RECEIVES'); + client.invalidateQueries('SALE_INVOICE_DUE'); + client.invalidateQueries('SALE_INVOICES'); + client.invalidateQueries('SALE_INVOICE'); + + saveInvoke(props?.onSuccess, data); }, ...props, }, @@ -75,10 +85,15 @@ export function useDeletePaymentReceive(props) { const client = useQueryClient(); return useMutation( - (id, values) => ApiService.delete(`sales/payment_receives/${id}`, values), + (id) => ApiService.delete(`sales/payment_receives/${id}`), { - onSuccess: () => { + onSuccess: (data, [id]) => { client.invalidateQueries('PAYMENT_RECEIVES'); + client.invalidateQueries('SALE_INVOICE_DUE'); + client.invalidateQueries('SALE_INVOICES'); + client.invalidateQueries('SALE_INVOICE'); + + saveInvoke(props?.onSuccess, data); }, ...props, }, @@ -87,19 +102,53 @@ export function useDeletePaymentReceive(props) { /** * Retrieve specific payment receive. + * @param {number} id - Payment receive. */ export function usePaymentReceive(id, props) { const states = useQuery( ['PAYMENT_RECEIVE', id], () => ApiService.get(`sales/payment_receives/${id}`), { - select: (res) => res.data.payment_receive, + select: (res) => ({ + paymentReceive: res.data.payment_receive, + receivableEntries: res.data.receivable_entries, + }), ...props }, ); return { ...states, - data: defaultTo(states.data, {}), + data: defaultTo(states.data, { + paymentReceive: {}, + receivableInvoices: {}, + paymentInvoices: {} + }), + } +} + +/** + * Retrieve information of payment receive in edit page. + * @param {number} id - Payment receive id. + */ +export function usePaymentReceiveEditPage(id, props) { + const states = useQuery( + ['PAYMENT_RECEIVE_EDIT_PAGE', id], + () => ApiService.get(`sales/payment_receives/${id}/edit-page`), + { + select: (res) => ({ + paymentReceive: res.data.payment_receive, + entries: res.data.entries, + }), + ...props, + }, + ); + + return { + ...states, + data: defaultTo(states.data, { + paymentReceive: {}, + entries: [], + }) } } diff --git a/client/src/lang/en/index.js b/client/src/lang/en/index.js index c58470627..21e19ce97 100644 --- a/client/src/lang/en/index.js +++ b/client/src/lang/en/index.js @@ -678,7 +678,7 @@ export default { deposit_to: 'Deposit to', edit_payment_receive: 'Edit Payment Receive', delete_payment_receive: 'Delete Payment Receive', - payment_Receives_list: 'Payment Receives List', + payment_receives_list: 'Payment Receives List', payment_receive: 'Payment Receive', new_payment_receive: 'New Payment Receive', payment_receives: 'Payment Receives', @@ -965,5 +965,7 @@ export default { running_balance: 'Running balance', payment_via_voucher: 'Payment via voucher', voucher_number: 'Voucher number', - voucher: 'Voucher' + voucher: 'Voucher', + payment_number_is_not_unique: 'Payment number is not unique.', + change_full_amount: 'Change full amount' }; diff --git a/client/src/routes/dashboard.js b/client/src/routes/dashboard.js index 98528683b..f2362089c 100644 --- a/client/src/routes/dashboard.js +++ b/client/src/routes/dashboard.js @@ -389,6 +389,7 @@ export default [ ), ), breadcrumb: 'Payment Receives List', + pageTitle: formatMessage({ id: 'payment_receives_list' }), }, // Bills diff --git a/client/src/static/json/icons.js b/client/src/static/json/icons.js index 569aa06a3..f815f5389 100644 --- a/client/src/static/json/icons.js +++ b/client/src/static/json/icons.js @@ -386,4 +386,10 @@ export default { ], viewBox: '0 0 20 20', }, + "send": { + path: [ + 'M2.01 21L23 12 2.01 3 2 10l15 2-15 2z' + ], + viewBox: '0 0 24 24', + } }; diff --git a/client/src/style/App.scss b/client/src/style/App.scss index 75a6ca587..47dd9ff2e 100644 --- a/client/src/style/App.scss +++ b/client/src/style/App.scss @@ -95,3 +95,8 @@ body.hide-scrollbar .Pane2{ } + + +.bp3-progress-bar.bp3-intent-primary .bp3-progress-meter{ + background-color: #0066ff; +} \ No newline at end of file diff --git a/client/src/style/pages/PaymentReceive/List.scss b/client/src/style/pages/PaymentReceive/List.scss new file mode 100644 index 000000000..44e284fde --- /dev/null +++ b/client/src/style/pages/PaymentReceive/List.scss @@ -0,0 +1,18 @@ + +.dashboard__insider--payment-receives-list{ + + .bigcapital-datatable{ + + .tbody{ + + .td.amount { + + .cell-inner{ + > span{ + font-weight: 600; + } + } + } + } + } +} \ No newline at end of file diff --git a/client/src/style/pages/PaymentReceive/PageForm.scss b/client/src/style/pages/PaymentReceive/PageForm.scss index b555d4e6f..9dfd80667 100644 --- a/client/src/style/pages/PaymentReceive/PageForm.scss +++ b/client/src/style/pages/PaymentReceive/PageForm.scss @@ -20,10 +20,18 @@ &.bp3-inline{ max-width: 470px; } - a.receive-full-amount{ + button.receive-full-amount{ + width: auto; + padding: 0; + min-height: auto; font-size: 12px; - margin-top: 6px; - display: inline-block; + margin-top: 4px; + background-color: transparent; + color: #0052cc; + + &:hover{ + text-decoration: underline; + } } } } diff --git a/client/src/style/pages/SaleEstimate/List.scss b/client/src/style/pages/SaleEstimate/List.scss new file mode 100644 index 000000000..33688fad7 --- /dev/null +++ b/client/src/style/pages/SaleEstimate/List.scss @@ -0,0 +1,18 @@ + +.dashboard__insider--sale_estimate{ + + .bigcapital-datatable{ + + .tbody{ + + .td.amount { + + .cell-inner{ + > span{ + font-weight: 600; + } + } + } + } + } +} \ No newline at end of file diff --git a/client/src/style/pages/SaleInvoice/List.scss b/client/src/style/pages/SaleInvoice/List.scss index 75cc9216b..59a3db16a 100644 --- a/client/src/style/pages/SaleInvoice/List.scss +++ b/client/src/style/pages/SaleInvoice/List.scss @@ -7,6 +7,14 @@ .tbody{ + .balance.td{ + + .cell-inner{ + > span{ + font-weight: 600; + } + } + } .status.td{ .status-accessor{ @@ -33,7 +41,7 @@ line-height: 1; display: block; margin-bottom: 8px; - opacity: 0.65; + opacity: 0.7; } .fully-paid-icon{ width: 18px; @@ -49,10 +57,11 @@ } .bp3-progress-bar{ height: 4px; + max-width: 180px; &, .bp3-progress-meter{ - border-radius: 4px; + border-radius: 0; } } } diff --git a/client/src/style/pages/SaleReceipt/List.scss b/client/src/style/pages/SaleReceipt/List.scss new file mode 100644 index 000000000..7aec30dd2 --- /dev/null +++ b/client/src/style/pages/SaleReceipt/List.scss @@ -0,0 +1,18 @@ + +.dashboard__insider--sales_receipts{ + + .bigcapital-datatable{ + + .tbody{ + + .td.amount { + + .cell-inner{ + > span{ + font-weight: 600; + } + } + } + } + } +} \ 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 143c765d5..282a2888e 100644 --- a/server/src/api/controllers/Sales/PaymentReceives.ts +++ b/server/src/api/controllers/Sales/PaymentReceives.ts @@ -48,10 +48,10 @@ export default class PaymentReceivesController extends BaseController { this.handleServiceErrors ); router.get( - '/:id', + '/:id/edit-page', this.paymentReceiveValidation, this.validationResult, - asyncMiddleware(this.getPaymentReceive.bind(this)), + asyncMiddleware(this.getPaymentReceiveEditPage.bind(this)), this.handleServiceErrors ); router.get( @@ -215,16 +215,15 @@ export default class PaymentReceivesController extends BaseController { * @param {Request} req - * @param {Response} res - */ - async getPaymentReceive(req: Request, res: Response, next: NextFunction) { + async getPaymentReceiveEditPage(req: Request, res: Response, next: NextFunction) { const { tenantId, user } = req; const { id: paymentReceiveId } = req.params; try { const { paymentReceive, - receivableInvoices, - paymentReceiveInvoices, - } = await this.paymentReceiveService.getPaymentReceive( + entries + } = await this.paymentReceiveService.getPaymentReceiveEditPage( tenantId, paymentReceiveId, user @@ -232,8 +231,7 @@ export default class PaymentReceivesController extends BaseController { return res.status(200).send({ payment_receive: this.transfromToResponse({ ...paymentReceive }), - receivable_invoices: this.transfromToResponse([...receivableInvoices]), - payment_invoices: this.transfromToResponse([...paymentReceiveInvoices]), + entries: this.transfromToResponse([...entries]), }); } catch (error) { next(error); diff --git a/server/src/interfaces/PaymentReceive.ts b/server/src/interfaces/PaymentReceive.ts index 60c9c12d4..9c04496fa 100644 --- a/server/src/interfaces/PaymentReceive.ts +++ b/server/src/interfaces/PaymentReceive.ts @@ -1,5 +1,5 @@ -import { IDynamicListFilterDTO } from "./DynamicFilter"; +import { IDynamicListFilterDTO } from "./DynamicFilter"; export interface IPaymentReceive { id?: number, @@ -50,4 +50,20 @@ export interface IPaymentReceiveEntryDTO { export interface IPaymentReceivesFilter extends IDynamicListFilterDTO { stringifiedFilterRoles?: string, -} \ No newline at end of file +} + +export interface IPaymentReceiveEditPageEntry { + invoiceId: number, + entryType: string, + invoiceNo: string, + dueAmount: number, + amount: number, + totalPaymentAmount: number, + paymentAmount: number, + date: Date|string, +}; + +export interface IPaymentReceiveEditPage { + paymentReceive: IPaymentReceive, + entries: IPaymentReceiveEditPageEntry[]; +}; \ No newline at end of file diff --git a/server/src/services/Sales/PaymentsReceives.ts b/server/src/services/Sales/PaymentsReceives.ts index bf000906d..4a0616e41 100644 --- a/server/src/services/Sales/PaymentsReceives.ts +++ b/server/src/services/Sales/PaymentsReceives.ts @@ -1,5 +1,4 @@ import { omit, sumBy, difference } from 'lodash'; -import moment from 'moment'; import { Service, Inject } from 'typedi'; import { EventDispatcher, @@ -19,6 +18,7 @@ import { ISaleInvoice, ISystemService, ISystemUser, + IPaymentReceiveEditPageEntry, } from 'interfaces'; import AccountsService from 'services/Accounts/AccountsService'; import JournalPoster from 'services/Accounting/JournalPoster'; @@ -466,46 +466,80 @@ export default class PaymentReceiveService { }); } + /** + * Retrive edit page invoices entries from the given sale invoices models. + * @param {ISaleInvoice[]} invoices - Invoices. + * @return {IPaymentReceiveEditPageEntry} + */ + public invoicesToEditPageEntries( + invoice: ISaleInvoice + ): IPaymentReceiveEditPageEntry { + return { + entryType: 'invoice', + invoiceId: invoice.id, + dueAmount: invoice.dueAmount + invoice.paymentAmount, + amount: invoice.balance, + invoiceNo: invoice.invoiceNo, + totalPaymentAmount: invoice.paymentAmount, + paymentAmount: invoice.paymentAmount, + date: invoice.invoiceDate, + }; + } + /** * Retrieve the payment receive details of the given id. * @param {number} tenantId - Tenant id. * @param {Integer} paymentReceiveId - Payment receive id. */ - public async getPaymentReceive( + public async getPaymentReceiveEditPage( tenantId: number, paymentReceiveId: number, - authorizedUser: ISystemService ): Promise<{ paymentReceive: IPaymentReceive; - receivableInvoices: ISaleInvoice[]; - paymentReceiveInvoices: ISaleInvoice[]; + entries: IPaymentReceiveEditPageEntry[]; }> { const { PaymentReceive, SaleInvoice } = this.tenancy.models(tenantId); + + // Retrieve payment receive. const paymentReceive = await PaymentReceive.query() .findById(paymentReceiveId) - .withGraphFetched('entries.invoice') - .withGraphFetched('customer') - .withGraphFetched('depositAccount'); + .withGraphFetched('entries.invoice'); + // Throw not found the payment receive. if (!paymentReceive) { throw new ServiceError(ERRORS.PAYMENT_RECEIVE_NOT_EXISTS); } - const invoicesIds = paymentReceive.entries.map((entry) => entry.invoiceId); - // Retrieves all receivable bills that associated to the payment receive transaction. - const receivableInvoices = await SaleInvoice.query() - .modify('dueInvoices') - .where('customer_id', paymentReceive.customerId) - .whereNotIn('id', invoicesIds) - .orderBy('invoice_date', 'ASC'); + // Mapping the entries invoices. + const entriesInvoicesIds = paymentReceive.entries.map( + (entry) => entry.invoiceId + ); - // Retrieve all payment receive associated invoices. - const paymentReceiveInvoices = paymentReceive.entries.map((entry) => ({ - ...entry.invoice, - dueAmount: entry.invoice.dueAmount + entry.paymentAmount, + const paymentEntries = paymentReceive.entries.map((entry) => ({ + ...this.invoicesToEditPageEntries(entry.invoice), + paymentAmount: entry.paymentAmount, })); - return { paymentReceive, receivableInvoices, paymentReceiveInvoices }; + // Retrieves all receivable bills that associated to the payment receive transaction. + const restReceivableInvoices = await SaleInvoice.query() + .modify('dueInvoices') + .where('customer_id', paymentReceive.customerId) + .whereNotIn('id', entriesInvoicesIds) + .orderBy('invoice_date', 'ASC'); + + const restReceivableEntries = restReceivableInvoices.map( + this.invoicesToEditPageEntries + ); + + const entries = [ + ...paymentEntries, + ...restReceivableEntries, + ]; + + return { + paymentReceive: omit(paymentReceive, ['entries']), + entries, + }; } /** @@ -664,7 +698,7 @@ export default class PaymentReceiveService { */ async revertPaymentReceiveJournalEntries( tenantId: number, - paymentReceiveId: number, + paymentReceiveId: number ) { const { accountRepository } = this.tenancy.repositories(tenantId); diff --git a/server/src/services/Sales/SalesInvoices.ts b/server/src/services/Sales/SalesInvoices.ts index 0bbcb0dcf..b8c6a4f72 100644 --- a/server/src/services/Sales/SalesInvoices.ts +++ b/server/src/services/Sales/SalesInvoices.ts @@ -583,6 +583,8 @@ export default class SaleInvoicesService { query.where('customer_id', customerId); } }); + + return salesInvoices; } }