From 457c427928608c39fdb528a1f301e7da2d929534 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 21 Jun 2026 22:32:51 +0100 Subject: [PATCH] Add payment management features to Finance dashboard, including AuthorisePayment, DeclinePayment, and CancelPayment components for handling payment actions. Update PaymentInfo to integrate new modals for these actions and enhance Payment model to support new states and attributes, improving overall payment processing functionality. --- .../Finance/Payments/AuthorisePayment.jsx | 46 +++++++++++ .../Finance/Payments/CancelPayment.jsx | 46 +++++++++++ .../Finance/Payments/DeclinePayment.jsx | 46 +++++++++++ .../Dashboard/Finance/Payments/NewPayment.jsx | 2 + .../Finance/Payments/PaymentInfo.jsx | 72 +++++++++++++++++ src/components/Dashboard/common/StateTag.jsx | 8 ++ src/database/models/Payment.js | 78 +++++++++++++++++-- 7 files changed, 293 insertions(+), 5 deletions(-) create mode 100644 src/components/Dashboard/Finance/Payments/AuthorisePayment.jsx create mode 100644 src/components/Dashboard/Finance/Payments/CancelPayment.jsx create mode 100644 src/components/Dashboard/Finance/Payments/DeclinePayment.jsx diff --git a/src/components/Dashboard/Finance/Payments/AuthorisePayment.jsx b/src/components/Dashboard/Finance/Payments/AuthorisePayment.jsx new file mode 100644 index 0000000..c7822d0 --- /dev/null +++ b/src/components/Dashboard/Finance/Payments/AuthorisePayment.jsx @@ -0,0 +1,46 @@ +import { useState, useContext } from 'react' +import PropTypes from 'prop-types' +import { ApiServerContext } from '../../context/ApiServerContext' +import { message } from 'antd' +import MessageDialogView from '../../common/MessageDialogView.jsx' + +const AuthorisePayment = ({ onOk, objectData }) => { + const [authoriseLoading, setAuthoriseLoading] = useState(false) + const { sendObjectFunction } = useContext(ApiServerContext) + + const handleAuthorise = async () => { + setAuthoriseLoading(true) + try { + const result = await sendObjectFunction( + objectData._id, + 'Payment', + 'authorise' + ) + if (result) { + message.success('Payment authorised successfully') + onOk(result) + } + } catch (error) { + console.error('Error authorising payment:', error) + } finally { + setAuthoriseLoading(false) + } + } + + return ( + + ) +} + +AuthorisePayment.propTypes = { + onOk: PropTypes.func.isRequired, + objectData: PropTypes.object +} + +export default AuthorisePayment diff --git a/src/components/Dashboard/Finance/Payments/CancelPayment.jsx b/src/components/Dashboard/Finance/Payments/CancelPayment.jsx new file mode 100644 index 0000000..b98083a --- /dev/null +++ b/src/components/Dashboard/Finance/Payments/CancelPayment.jsx @@ -0,0 +1,46 @@ +import { useState, useContext } from 'react' +import PropTypes from 'prop-types' +import { ApiServerContext } from '../../context/ApiServerContext' +import { message } from 'antd' +import MessageDialogView from '../../common/MessageDialogView.jsx' + +const CancelPayment = ({ onOk, objectData }) => { + const [cancelLoading, setCancelLoading] = useState(false) + const { sendObjectFunction } = useContext(ApiServerContext) + + const handleCancel = async () => { + setCancelLoading(true) + try { + const result = await sendObjectFunction( + objectData._id, + 'Payment', + 'cancel' + ) + if (result) { + message.success('Payment cancelled successfully') + onOk(result) + } + } catch (error) { + console.error('Error cancelling payment:', error) + } finally { + setCancelLoading(false) + } + } + + return ( + + ) +} + +CancelPayment.propTypes = { + onOk: PropTypes.func.isRequired, + objectData: PropTypes.object +} + +export default CancelPayment diff --git a/src/components/Dashboard/Finance/Payments/DeclinePayment.jsx b/src/components/Dashboard/Finance/Payments/DeclinePayment.jsx new file mode 100644 index 0000000..ca5014e --- /dev/null +++ b/src/components/Dashboard/Finance/Payments/DeclinePayment.jsx @@ -0,0 +1,46 @@ +import { useState, useContext } from 'react' +import PropTypes from 'prop-types' +import { ApiServerContext } from '../../context/ApiServerContext' +import { message } from 'antd' +import MessageDialogView from '../../common/MessageDialogView.jsx' + +const DeclinePayment = ({ onOk, objectData }) => { + const [declineLoading, setDeclineLoading] = useState(false) + const { sendObjectFunction } = useContext(ApiServerContext) + + const handleDecline = async () => { + setDeclineLoading(true) + try { + const result = await sendObjectFunction( + objectData._id, + 'Payment', + 'decline' + ) + if (result) { + message.success('Payment declined successfully') + onOk(result) + } + } catch (error) { + console.error('Error declining payment:', error) + } finally { + setDeclineLoading(false) + } + } + + return ( + + ) +} + +DeclinePayment.propTypes = { + onOk: PropTypes.func.isRequired, + objectData: PropTypes.object +} + +export default DeclinePayment diff --git a/src/components/Dashboard/Finance/Payments/NewPayment.jsx b/src/components/Dashboard/Finance/Payments/NewPayment.jsx index 71c5663..840e638 100644 --- a/src/components/Dashboard/Finance/Payments/NewPayment.jsx +++ b/src/components/Dashboard/Finance/Payments/NewPayment.jsx @@ -53,6 +53,8 @@ const NewPayment = ({ onOk, reset, defaultValues }) => { updatedAt: false, _reference: false, postedAt: false, + authorisedAt: false, + declinedAt: false, cancelledAt: false }} isEditing={false} diff --git a/src/components/Dashboard/Finance/Payments/PaymentInfo.jsx b/src/components/Dashboard/Finance/Payments/PaymentInfo.jsx index b574b36..8bb6430 100644 --- a/src/components/Dashboard/Finance/Payments/PaymentInfo.jsx +++ b/src/components/Dashboard/Finance/Payments/PaymentInfo.jsx @@ -24,6 +24,9 @@ import UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import ScrollBox from '../../common/ScrollBox.jsx' import { getModelByName } from '../../../../database/ObjectModels.js' import PostPayment from './PostPayment.jsx' +import AuthorisePayment from './AuthorisePayment.jsx' +import DeclinePayment from './DeclinePayment.jsx' +import CancelPayment from './CancelPayment.jsx' const log = loglevel.getLogger('PaymentInfo') log.setLevel(config.logLevel) @@ -48,6 +51,9 @@ const PaymentInfo = () => { objectData: {} }) const [postPaymentOpen, setPostPaymentOpen] = useState(false) + const [authorisePaymentOpen, setAuthorisePaymentOpen] = useState(false) + const [declinePaymentOpen, setDeclinePaymentOpen] = useState(false) + const [cancelPaymentOpen, setCancelPaymentOpen] = useState(false) const actions = { edit: () => { @@ -69,6 +75,18 @@ const PaymentInfo = () => { post: () => { setPostPaymentOpen(true) return true + }, + authorise: () => { + setAuthorisePaymentOpen(true) + return true + }, + decline: () => { + setDeclinePaymentOpen(true) + return true + }, + cancel: () => { + setCancelPaymentOpen(true) + return true } } @@ -234,6 +252,60 @@ const PaymentInfo = () => { objectData={objectFormState.objectData} /> + { + setAuthorisePaymentOpen(false) + }} + width={515} + footer={null} + destroyOnHidden={true} + centered={true} + > + { + setAuthorisePaymentOpen(false) + objectFormRef?.current.handleFetchObject() + }} + objectData={objectFormState.objectData} + /> + + { + setDeclinePaymentOpen(false) + }} + width={515} + footer={null} + destroyOnHidden={true} + centered={true} + > + { + setDeclinePaymentOpen(false) + objectFormRef?.current.handleFetchObject() + }} + objectData={objectFormState.objectData} + /> + + { + setCancelPaymentOpen(false) + }} + width={515} + footer={null} + destroyOnHidden={true} + centered={true} + > + { + setCancelPaymentOpen(false) + objectFormRef?.current.handleFetchObject() + }} + objectData={objectFormState.objectData} + /> + ) } diff --git a/src/components/Dashboard/common/StateTag.jsx b/src/components/Dashboard/common/StateTag.jsx index 89220a7..7c9c1b9 100644 --- a/src/components/Dashboard/common/StateTag.jsx +++ b/src/components/Dashboard/common/StateTag.jsx @@ -140,6 +140,14 @@ const StateTag = ({ state, showBadge = true, style = {} }) => { status = 'magenta' text = 'Posted' break + case 'authorised': + status = 'success' + text = 'Authorised' + break + case 'declined': + status = 'error' + text = 'Declined' + break case 'received': status = 'success' text = 'Received' diff --git a/src/database/models/Payment.js b/src/database/models/Payment.js index ce74451..0de2816 100644 --- a/src/database/models/Payment.js +++ b/src/database/models/Payment.js @@ -82,6 +82,35 @@ export const Payment = { return objectData?.state?.type == 'draft' } }, + { + name: 'authorise', + label: 'Authorise', + type: 'button', + icon: CheckIcon, + url: (_id) => + `/dashboard/finance/payments/info?paymentId=${_id}&action=authorise`, + disabled: (objectData) => { + return objectData?.state?.type != 'posted' + }, + visible: (objectData) => { + return objectData?.state?.type == 'posted' + } + }, + { + name: 'decline', + label: 'Decline', + type: 'button', + icon: XMarkIcon, + danger: true, + url: (_id) => + `/dashboard/finance/payments/info?paymentId=${_id}&action=decline`, + disabled: (objectData) => { + return objectData?.state?.type != 'posted' + }, + visible: (objectData) => { + return objectData?.state?.type == 'posted' + } + }, { name: 'cancel', label: 'Cancel', @@ -91,13 +120,10 @@ export const Payment = { url: (_id) => `/dashboard/finance/payments/info?paymentId=${_id}&action=cancel`, disabled: (objectData) => { - return objectData?.state?.type == 'cancelled' + return objectData?.state?.type != 'posted' }, visible: (objectData) => { - return ( - objectData?.state?.type == 'draft' || - objectData?.state?.type == 'posted' - ) + return objectData?.state?.type == 'posted' } } ], @@ -197,6 +223,20 @@ export const Payment = { readOnly: true, columnWidth: 175 }, + { + name: 'authorisedAt', + label: 'Authorised At', + type: 'dateTime', + readOnly: true, + columnWidth: 175 + }, + { + name: 'declinedAt', + label: 'Declined At', + type: 'dateTime', + readOnly: true, + columnWidth: 175 + }, { name: 'cancelledAt', label: 'Cancelled At', @@ -256,6 +296,34 @@ export const Payment = { prefix: '£', roundNumber: 2, color: 'cyan' + }, + { + name: 'authorised.authorisedCount.count', + label: 'Authorised', + type: 'number', + color: 'green' + }, + { + name: 'authorised.authorisedAmount.sum', + label: 'Authorised Amount', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'green' + }, + { + name: 'declined.declinedCount.count', + label: 'Declined', + type: 'number', + color: 'red' + }, + { + name: 'declined.declinedAmount.sum', + label: 'Declined Amount', + type: 'number', + prefix: '£', + roundNumber: 2, + color: 'red' } ] }