Compare commits

...

2 Commits

15 changed files with 337 additions and 34 deletions

View File

@ -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 (
<MessageDialogView
title={'Are you sure you want to authorise this payment?'}
description={`Authorising payment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to authorised.`}
onOk={handleAuthorise}
okText='Authorise'
okLoading={authoriseLoading}
/>
)
}
AuthorisePayment.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default AuthorisePayment

View File

@ -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 (
<MessageDialogView
title={'Are you sure you want to cancel this payment?'}
description={`Cancelling payment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to cancelled.`}
onOk={handleCancel}
okText='Cancel'
okLoading={cancelLoading}
/>
)
}
CancelPayment.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default CancelPayment

View File

@ -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 (
<MessageDialogView
title={'Are you sure you want to decline this payment?'}
description={`Declining payment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to declined.`}
onOk={handleDecline}
okText='Decline'
okLoading={declineLoading}
/>
)
}
DeclinePayment.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default DeclinePayment

View File

@ -53,6 +53,8 @@ const NewPayment = ({ onOk, reset, defaultValues }) => {
updatedAt: false, updatedAt: false,
_reference: false, _reference: false,
postedAt: false, postedAt: false,
authorisedAt: false,
declinedAt: false,
cancelledAt: false cancelledAt: false
}} }}
isEditing={false} isEditing={false}

View File

@ -24,6 +24,9 @@ import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelByName } from '../../../../database/ObjectModels.js'
import PostPayment from './PostPayment.jsx' 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') const log = loglevel.getLogger('PaymentInfo')
log.setLevel(config.logLevel) log.setLevel(config.logLevel)
@ -48,6 +51,9 @@ const PaymentInfo = () => {
objectData: {} objectData: {}
}) })
const [postPaymentOpen, setPostPaymentOpen] = useState(false) const [postPaymentOpen, setPostPaymentOpen] = useState(false)
const [authorisePaymentOpen, setAuthorisePaymentOpen] = useState(false)
const [declinePaymentOpen, setDeclinePaymentOpen] = useState(false)
const [cancelPaymentOpen, setCancelPaymentOpen] = useState(false)
const actions = { const actions = {
edit: () => { edit: () => {
@ -69,6 +75,18 @@ const PaymentInfo = () => {
post: () => { post: () => {
setPostPaymentOpen(true) setPostPaymentOpen(true)
return 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} objectData={objectFormState.objectData}
/> />
</Modal> </Modal>
<Modal
open={authorisePaymentOpen}
onCancel={() => {
setAuthorisePaymentOpen(false)
}}
width={515}
footer={null}
destroyOnHidden={true}
centered={true}
>
<AuthorisePayment
onOk={() => {
setAuthorisePaymentOpen(false)
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
</Modal>
<Modal
open={declinePaymentOpen}
onCancel={() => {
setDeclinePaymentOpen(false)
}}
width={515}
footer={null}
destroyOnHidden={true}
centered={true}
>
<DeclinePayment
onOk={() => {
setDeclinePaymentOpen(false)
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
</Modal>
<Modal
open={cancelPaymentOpen}
onCancel={() => {
setCancelPaymentOpen(false)
}}
width={515}
footer={null}
destroyOnHidden={true}
centered={true}
>
<CancelPayment
onOk={() => {
setCancelPaymentOpen(false)
objectFormRef?.current.handleFetchObject()
}}
objectData={objectFormState.objectData}
/>
</Modal>
</> </>
) )
} }

View File

@ -140,6 +140,14 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
status = 'magenta' status = 'magenta'
text = 'Posted' text = 'Posted'
break break
case 'authorised':
status = 'success'
text = 'Authorised'
break
case 'declined':
status = 'error'
text = 'Declined'
break
case 'received': case 'received':
status = 'success' status = 'success'
text = 'Received' text = 'Received'

View File

@ -82,6 +82,35 @@ export const Payment = {
return objectData?.state?.type == 'draft' 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', name: 'cancel',
label: 'Cancel', label: 'Cancel',
@ -91,13 +120,10 @@ export const Payment = {
url: (_id) => url: (_id) =>
`/dashboard/finance/payments/info?paymentId=${_id}&action=cancel`, `/dashboard/finance/payments/info?paymentId=${_id}&action=cancel`,
disabled: (objectData) => { disabled: (objectData) => {
return objectData?.state?.type == 'cancelled' return objectData?.state?.type != 'posted'
}, },
visible: (objectData) => { visible: (objectData) => {
return ( return objectData?.state?.type == 'posted'
objectData?.state?.type == 'draft' ||
objectData?.state?.type == 'posted'
)
} }
} }
], ],
@ -197,6 +223,20 @@ export const Payment = {
readOnly: true, readOnly: true,
columnWidth: 175 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', name: 'cancelledAt',
label: 'Cancelled At', label: 'Cancelled At',
@ -256,6 +296,34 @@ export const Payment = {
prefix: '£', prefix: '£',
roundNumber: 2, roundNumber: 2,
color: 'cyan' 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'
} }
] ]
} }

View File

@ -17,7 +17,7 @@ export const TaxRecord = {
default: true, default: true,
row: true, row: true,
icon: InfoCircleIcon, icon: InfoCircleIcon,
url: (_id) => `/dashboard/management/taxrecords/info?taxRecordId=${_id}` url: (_id) => `/dashboard/finance/taxrecords/info?taxRecordId=${_id}`
}, },
{ {
name: 'edit', name: 'edit',
@ -25,7 +25,7 @@ export const TaxRecord = {
row: true, row: true,
icon: EditIcon, icon: EditIcon,
url: (_id) => url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=edit`, `/dashboard/finance/taxrecords/info?taxRecordId=${_id}&action=edit`,
visible: (objectData) => { visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true) return !(objectData?._isEditing && objectData?._isEditing == true)
} }
@ -35,7 +35,7 @@ export const TaxRecord = {
label: 'Save Edits', label: 'Save Edits',
icon: CheckIcon, icon: CheckIcon,
url: (_id) => url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=finishEdit`, `/dashboard/finance/taxrecords/info?taxRecordId=${_id}&action=finishEdit`,
visible: (objectData) => { visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true return objectData?._isEditing && objectData?._isEditing == true
} }
@ -45,7 +45,7 @@ export const TaxRecord = {
label: 'Cancel Edits', label: 'Cancel Edits',
icon: XMarkIcon, icon: XMarkIcon,
url: (_id) => url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=cancelEdit`, `/dashboard/finance/taxrecords/info?taxRecordId=${_id}&action=cancelEdit`,
visible: (objectData) => { visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true return objectData?._isEditing && objectData?._isEditing == true
} }
@ -57,7 +57,7 @@ export const TaxRecord = {
icon: BinIcon, icon: BinIcon,
danger: true, danger: true,
url: (_id) => url: (_id) =>
`/dashboard/management/taxrecords/info?taxRecordId=${_id}&action=delete` `/dashboard/finance/taxrecords/info?taxRecordId=${_id}&action=delete`
} }
], ],
columns: [ columns: [

View File

@ -17,6 +17,12 @@ const financeSidebarItems = [
label: 'Payments', label: 'Payments',
iconKey: 'payment', iconKey: 'payment',
path: '/dashboard/finance/payments' path: '/dashboard/finance/payments'
},
{
key: 'taxRecords',
iconKey: 'taxRecord',
label: 'Tax Records',
path: '/dashboard/finance/taxrecords'
} }
] ]

View File

@ -73,12 +73,6 @@ const managementSidebarItems = [
label: 'Tax Rates', label: 'Tax Rates',
path: '/dashboard/management/taxrates' path: '/dashboard/management/taxrates'
}, },
{
key: 'taxRecords',
iconKey: 'taxRecord',
label: 'Tax Records',
path: '/dashboard/management/taxrecords'
},
{ type: 'divider' }, { type: 'divider' },
{ {
key: 'noteTypes', key: 'noteTypes',

View File

@ -1,5 +1,15 @@
import { lazy } from 'react' import { lazy } from 'react'
import { Route } from 'react-router-dom' import { Navigate, Route, useLocation } from 'react-router-dom'
const TaxRecordsInfoRedirect = () => {
const location = useLocation()
return (
<Navigate
to={`/dashboard/finance/taxrecords/info${location.search}`}
replace
/>
)
}
const Invoices = lazy( const Invoices = lazy(
() => import('../components/Dashboard/Finance/Invoices.jsx') () => import('../components/Dashboard/Finance/Invoices.jsx')
@ -16,6 +26,12 @@ const PaymentInfo = lazy(
const FinanceOverview = lazy( const FinanceOverview = lazy(
() => import('../components/Dashboard/Finance/FinanceOverview.jsx') () => import('../components/Dashboard/Finance/FinanceOverview.jsx')
) )
const TaxRecords = lazy(
() => import('../components/Dashboard/Finance/TaxRecords.jsx')
)
const TaxRecordInfo = lazy(
() => import('../components/Dashboard/Finance/TaxRecords/TaxRecordInfo.jsx')
)
const FinanceRoutes = [ const FinanceRoutes = [
<Route <Route
@ -34,6 +50,22 @@ const FinanceRoutes = [
key='payments-info' key='payments-info'
path='finance/payments/info' path='finance/payments/info'
element={<PaymentInfo />} element={<PaymentInfo />}
/>,
<Route key='taxrecords' path='finance/taxrecords' element={<TaxRecords />} />,
<Route
key='taxrecords-info'
path='finance/taxrecords/info'
element={<TaxRecordInfo />}
/>,
<Route
key='taxrecords-redirect'
path='management/taxrecords'
element={<Navigate to='/dashboard/finance/taxrecords' replace />}
/>,
<Route
key='taxrecords-info-redirect'
path='management/taxrecords/info'
element={<TaxRecordsInfoRedirect />}
/> />
] ]

View File

@ -139,13 +139,6 @@ const TaxRates = lazy(
const TaxRateInfo = lazy( const TaxRateInfo = lazy(
() => import('../components/Dashboard/Management/TaxRates/TaxRateInfo.jsx') () => import('../components/Dashboard/Management/TaxRates/TaxRateInfo.jsx')
) )
const TaxRecords = lazy(
() => import('../components/Dashboard/Management/TaxRecords.jsx')
)
const TaxRecordInfo = lazy(
() =>
import('../components/Dashboard/Management/TaxRecords/TaxRecordInfo.jsx')
)
const About = lazy(() => import('../components/Dashboard/Management/About.jsx')) const About = lazy(() => import('../components/Dashboard/Management/About.jsx'))
const ManagementRoutes = [ const ManagementRoutes = [
@ -319,16 +312,6 @@ const ManagementRoutes = [
key='taxrates-info' key='taxrates-info'
path='management/taxrates/info' path='management/taxrates/info'
element={<TaxRateInfo />} element={<TaxRateInfo />}
/>,
<Route
key='taxrecords'
path='management/taxrecords'
element={<TaxRecords />}
/>,
<Route
key='taxrecords-info'
path='management/taxrecords/info'
element={<TaxRecordInfo />}
/> />
] ]