diff --git a/assets/icons/paymenticon.svg b/assets/icons/paymenticon.svg
new file mode 100644
index 0000000..7c0d9a0
--- /dev/null
+++ b/assets/icons/paymenticon.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/src/components/Dashboard/Finance/FinanceOverview.jsx b/src/components/Dashboard/Finance/FinanceOverview.jsx
index 5df8e4c..0e6be74 100644
--- a/src/components/Dashboard/Finance/FinanceOverview.jsx
+++ b/src/components/Dashboard/Finance/FinanceOverview.jsx
@@ -13,7 +13,8 @@ const FinanceOverview = () => {
const [collapseState, updateCollapseState] = useCollapseState(
'FinanceOverview',
{
- invoiceStats: true
+ invoiceStats: true,
+ paymentStats: true
}
)
@@ -41,6 +42,7 @@ const FinanceOverview = () => {
}
className='no-t-padding-collapse'
collapseKey='invoiceStats'
+ canCollapse={false}
>
{
+
+ updateCollapseState('paymentStats', isActive)
+ }
+ className='no-t-padding-collapse'
+ collapseKey='paymentStats'
+ canCollapse={false}
+ >
+
+
+
+
@@ -58,4 +80,3 @@ const FinanceOverview = () => {
}
export default FinanceOverview
-
diff --git a/src/components/Dashboard/Finance/FinanceSidebar.jsx b/src/components/Dashboard/Finance/FinanceSidebar.jsx
index 7f763da..1c4e032 100644
--- a/src/components/Dashboard/Finance/FinanceSidebar.jsx
+++ b/src/components/Dashboard/Finance/FinanceSidebar.jsx
@@ -1,6 +1,7 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import InvoiceIcon from '../../Icons/InvoiceIcon'
+import PaymentIcon from '../../Icons/PaymentIcon'
import FinanceIcon from '../../Icons/FinanceIcon'
const items = [
@@ -16,12 +17,19 @@ const items = [
label: 'Invoices',
icon: ,
path: '/dashboard/finance/invoices'
+ },
+ {
+ key: 'payments',
+ label: 'Payments',
+ icon: ,
+ path: '/dashboard/finance/payments'
}
]
const routeKeyMap = {
'/dashboard/finance/overview': 'overview',
- '/dashboard/finance/invoices': 'invoices'
+ '/dashboard/finance/invoices': 'invoices',
+ '/dashboard/finance/payments': 'payments'
}
const FinanceSidebar = (props) => {
@@ -43,4 +51,3 @@ const FinanceSidebar = (props) => {
}
export default FinanceSidebar
-
diff --git a/src/components/Dashboard/Finance/Invoices/AcknowledgeInvoice.jsx b/src/components/Dashboard/Finance/Invoices/AcknowledgeInvoice.jsx
new file mode 100644
index 0000000..362ceb4
--- /dev/null
+++ b/src/components/Dashboard/Finance/Invoices/AcknowledgeInvoice.jsx
@@ -0,0 +1,47 @@
+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 AcknowledgeInvoice = ({ onOk, objectData }) => {
+ const [acknowledgeLoading, setAcknowledgeLoading] = useState(false)
+ const { sendObjectFunction } = useContext(ApiServerContext)
+
+ const handleAcknowledge = async () => {
+ setAcknowledgeLoading(true)
+ try {
+ const result = await sendObjectFunction(
+ objectData._id,
+ 'Invoice',
+ 'acknowledge'
+ )
+ if (result) {
+ message.success('Invoice acknowledged successfully')
+ onOk(result)
+ }
+ } catch (error) {
+ console.error('Error acknowledging invoice:', error)
+ } finally {
+ setAcknowledgeLoading(false)
+ }
+ }
+
+ return (
+
+ )
+}
+
+AcknowledgeInvoice.propTypes = {
+ onOk: PropTypes.func.isRequired,
+ objectData: PropTypes.object
+}
+
+export default AcknowledgeInvoice
+
diff --git a/src/components/Dashboard/Finance/Invoices/InvoiceInfo.jsx b/src/components/Dashboard/Finance/Invoices/InvoiceInfo.jsx
index c66d4e0..b376e2e 100644
--- a/src/components/Dashboard/Finance/Invoices/InvoiceInfo.jsx
+++ b/src/components/Dashboard/Finance/Invoices/InvoiceInfo.jsx
@@ -29,6 +29,8 @@ import {
import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
import PostInvoice from './PostInvoice.jsx'
+import AcknowledgeInvoice from './AcknowledgeInvoice.jsx'
+import NewPayment from '../Payments/NewPayment.jsx'
const log = loglevel.getLogger('InvoiceInfo')
log.setLevel(config.logLevel)
@@ -42,6 +44,7 @@ const InvoiceInfo = () => {
info: true,
invoiceOrderItems: true,
invoiceShipments: true,
+ payments: true,
notes: true,
auditLogs: true
})
@@ -55,6 +58,8 @@ const InvoiceInfo = () => {
objectData: {}
})
const [postInvoiceOpen, setPostInvoiceOpen] = useState(false)
+ const [acknowledgeInvoiceOpen, setAcknowledgeInvoiceOpen] = useState(false)
+ const [newPaymentOpen, setNewPaymentOpen] = useState(false)
const actions = {
reload: () => {
@@ -80,6 +85,14 @@ const InvoiceInfo = () => {
post: () => {
setPostInvoiceOpen(true)
return true
+ },
+ acknowledge: () => {
+ setAcknowledgeInvoiceOpen(true)
+ return true
+ },
+ newPayment: () => {
+ setNewPaymentOpen(true)
+ return true
}
}
@@ -113,6 +126,7 @@ const InvoiceInfo = () => {
{ key: 'info', label: 'Invoice Information' },
{ key: 'invoiceOrderItems', label: 'Invoice Order Items' },
{ key: 'invoiceShipments', label: 'Invoice Shipments' },
+ { key: 'payments', label: 'Payments' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
@@ -205,6 +219,7 @@ const InvoiceInfo = () => {
isEditing={isEditing}
objectData={objectData}
loading={loading}
+ size='medium'
/>
{
isEditing={isEditing}
objectData={objectData}
loading={loading}
+ size='medium'
/>
)}
+ }
+ active={collapseState.payments}
+ onToggle={(expanded) => updateCollapseState('payments', expanded)}
+ collapseKey='payments'
+ >
+ {objectFormState.loading ? (
+
+ ) : (
+
+ )}
+
}
@@ -278,6 +311,46 @@ const InvoiceInfo = () => {
objectData={objectFormState.objectData}
/>
+ {
+ setAcknowledgeInvoiceOpen(false)
+ }}
+ width={515}
+ footer={null}
+ destroyOnHidden={true}
+ centered={true}
+ >
+ {
+ setAcknowledgeInvoiceOpen(false)
+ actions.reload()
+ }}
+ objectData={objectFormState.objectData}
+ />
+
+ {
+ setNewPaymentOpen(false)
+ }}
+ destroyOnHidden={true}
+ >
+ {
+ setNewPaymentOpen(false)
+ actions.reload()
+ }}
+ reset={newPaymentOpen}
+ defaultValues={{
+ invoice: { ...objectFormState.objectData },
+ amount: objectFormState.objectData?.grandTotalAmount
+ }}
+ />
+
>
)
}
diff --git a/src/components/Dashboard/Finance/Payments.jsx b/src/components/Dashboard/Finance/Payments.jsx
new file mode 100644
index 0000000..35fcad9
--- /dev/null
+++ b/src/components/Dashboard/Finance/Payments.jsx
@@ -0,0 +1,99 @@
+import { useState, useRef } from 'react'
+import { Button, Flex, Space, Dropdown, Modal } from 'antd'
+import NewPayment from './Payments/NewPayment'
+import ObjectTable from '../common/ObjectTable'
+import PlusIcon from '../../Icons/PlusIcon'
+import ReloadIcon from '../../Icons/ReloadIcon'
+import useColumnVisibility from '../hooks/useColumnVisibility'
+import GridIcon from '../../Icons/GridIcon'
+import ListIcon from '../../Icons/ListIcon'
+import useViewMode from '../hooks/useViewMode'
+import ColumnViewButton from '../common/ColumnViewButton'
+
+const Payments = () => {
+ const [newPaymentOpen, setNewPaymentOpen] = useState(false)
+ const tableRef = useRef()
+
+ const [viewMode, setViewMode] = useViewMode('payments')
+
+ const [columnVisibility, setColumnVisibility] =
+ useColumnVisibility('payments')
+
+ const actionItems = {
+ items: [
+ {
+ label: 'New Payment',
+ key: 'newPayment',
+ icon:
+ },
+ { type: 'divider' },
+ {
+ label: 'Reload List',
+ key: 'reloadList',
+ icon:
+ }
+ ],
+ onClick: ({ key }) => {
+ if (key === 'reloadList') {
+ tableRef.current?.reload()
+ } else if (key === 'newPayment') {
+ setNewPaymentOpen(true)
+ }
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ : }
+ onClick={() =>
+ setViewMode(viewMode === 'cards' ? 'list' : 'cards')
+ }
+ />
+
+
+
+
+ {
+ setNewPaymentOpen(false)
+ }}
+ destroyOnHidden={true}
+ >
+ {
+ setNewPaymentOpen(false)
+ tableRef.current?.reload()
+ }}
+ reset={newPaymentOpen}
+ />
+
+ >
+ )
+}
+
+export default Payments
+
diff --git a/src/components/Dashboard/Finance/Payments/NewPayment.jsx b/src/components/Dashboard/Finance/Payments/NewPayment.jsx
new file mode 100644
index 0000000..71c5663
--- /dev/null
+++ b/src/components/Dashboard/Finance/Payments/NewPayment.jsx
@@ -0,0 +1,89 @@
+import PropTypes from 'prop-types'
+import ObjectInfo from '../../common/ObjectInfo'
+import NewObjectForm from '../../common/NewObjectForm'
+import WizardView from '../../common/WizardView'
+
+const NewPayment = ({ onOk, reset, defaultValues }) => {
+ return (
+
+ {({ handleSubmit, submitLoading, objectData, formValid }) => {
+ const steps = [
+ {
+ title: 'Required',
+ key: 'required',
+ content: (
+
+ )
+ },
+ {
+ title: 'Summary',
+ key: 'summary',
+ content: (
+
+ )
+ }
+ ]
+ return (
+ {
+ const result = await handleSubmit()
+ if (result) {
+ onOk()
+ }
+ }}
+ />
+ )
+ }}
+
+ )
+}
+
+NewPayment.propTypes = {
+ onOk: PropTypes.func.isRequired,
+ reset: PropTypes.bool,
+ defaultValues: PropTypes.object
+}
+
+export default NewPayment
diff --git a/src/components/Dashboard/Finance/Payments/PaymentInfo.jsx b/src/components/Dashboard/Finance/Payments/PaymentInfo.jsx
new file mode 100644
index 0000000..15fda9d
--- /dev/null
+++ b/src/components/Dashboard/Finance/Payments/PaymentInfo.jsx
@@ -0,0 +1,242 @@
+import { useRef, useState } from 'react'
+import { useLocation } from 'react-router-dom'
+import { Space, Flex, Card, Modal } from 'antd'
+import { LoadingOutlined } from '@ant-design/icons'
+import loglevel from 'loglevel'
+import config from '../../../../config.js'
+import useCollapseState from '../../hooks/useCollapseState.js'
+import NotesPanel from '../../common/NotesPanel.jsx'
+import InfoCollapse from '../../common/InfoCollapse.jsx'
+import ObjectInfo from '../../common/ObjectInfo.jsx'
+import ViewButton from '../../common/ViewButton.jsx'
+import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
+import NoteIcon from '../../../Icons/NoteIcon.jsx'
+import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
+import ObjectForm from '../../common/ObjectForm.jsx'
+import EditButtons from '../../common/EditButtons.jsx'
+import LockIndicator from '../../common/LockIndicator.jsx'
+import ActionHandler from '../../common/ActionHandler.jsx'
+import ObjectActions from '../../common/ObjectActions.jsx'
+import ObjectTable from '../../common/ObjectTable.jsx'
+import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
+import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
+import ScrollBox from '../../common/ScrollBox.jsx'
+import {
+ getModelByName
+} from '../../../../database/ObjectModels.js'
+import PostPayment from './PostPayment.jsx'
+
+const log = loglevel.getLogger('PaymentInfo')
+log.setLevel(config.logLevel)
+
+const PaymentInfo = () => {
+ const location = useLocation()
+ const objectFormRef = useRef(null)
+ const actionHandlerRef = useRef(null)
+ const paymentId = new URLSearchParams(location.search).get('paymentId')
+ const [collapseState, updateCollapseState] = useCollapseState('PaymentInfo', {
+ info: true,
+ notes: true,
+ auditLogs: true
+ })
+
+ const [objectFormState, setEditFormState] = useState({
+ isEditing: false,
+ editLoading: false,
+ formValid: false,
+ lock: null,
+ loading: false,
+ objectData: {}
+ })
+ const [postPaymentOpen, setPostPaymentOpen] = useState(false)
+
+ const actions = {
+ reload: () => {
+ objectFormRef?.current?.handleFetchObject?.()
+ return true
+ },
+ edit: () => {
+ objectFormRef?.current?.startEditing?.()
+ return false
+ },
+ cancelEdit: () => {
+ objectFormRef?.current?.cancelEditing?.()
+ return true
+ },
+ finishEdit: () => {
+ objectFormRef?.current?.handleUpdate?.()
+ return true
+ },
+ delete: () => {
+ objectFormRef?.current?.handleDelete?.()
+ return true
+ },
+ post: () => {
+ setPostPaymentOpen(true)
+ return true
+ }
+ }
+
+ const editDisabled =
+ getModelByName('payment')
+ ?.actions?.find((action) => action.name === 'edit')
+ ?.disabled(objectFormState.objectData) ?? false
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ {
+ actionHandlerRef.current.callAction('finishEdit')
+ }}
+ cancelEditing={() => {
+ actionHandlerRef.current.callAction('cancelEdit')
+ }}
+ startEditing={() => {
+ actionHandlerRef.current.callAction('edit')
+ }}
+ editLoading={objectFormState.editLoading}
+ formValid={objectFormState.formValid}
+ disabled={
+ objectFormState.lock?.locked ||
+ objectFormState.loading ||
+ editDisabled
+ }
+ loading={objectFormState.editLoading}
+ />
+
+
+
+
+
+
+ {
+ setEditFormState((prev) => ({ ...prev, ...state }))
+ }}
+ >
+ {({ loading, isEditing, objectData }) => (
+
+ }
+ active={collapseState.info}
+ onToggle={(expanded) =>
+ updateCollapseState('info', expanded)
+ }
+ collapseKey='info'
+ >
+ }
+ isEditing={isEditing}
+ type='payment'
+ labelWidth='225px'
+ objectData={objectData}
+ />
+
+
+ )}
+
+
+ }
+ active={collapseState.notes}
+ onToggle={(expanded) => updateCollapseState('notes', expanded)}
+ collapseKey='notes'
+ >
+
+
+
+
+ }
+ active={collapseState.auditLogs}
+ onToggle={(expanded) =>
+ updateCollapseState('auditLogs', expanded)
+ }
+ collapseKey='auditLogs'
+ >
+ {objectFormState.loading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {
+ setPostPaymentOpen(false)
+ }}
+ width={500}
+ footer={null}
+ destroyOnHidden={true}
+ centered={true}
+ >
+ {
+ setPostPaymentOpen(false)
+ actions.reload()
+ }}
+ objectData={objectFormState.objectData}
+ />
+
+ >
+ )
+}
+
+export default PaymentInfo
+
diff --git a/src/components/Dashboard/Finance/Payments/PostPayment.jsx b/src/components/Dashboard/Finance/Payments/PostPayment.jsx
new file mode 100644
index 0000000..4e55694
--- /dev/null
+++ b/src/components/Dashboard/Finance/Payments/PostPayment.jsx
@@ -0,0 +1,43 @@
+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 PostPayment = ({ onOk, objectData }) => {
+ const [postLoading, setPostLoading] = useState(false)
+ const { sendObjectFunction } = useContext(ApiServerContext)
+
+ const handlePost = async () => {
+ setPostLoading(true)
+ try {
+ const result = await sendObjectFunction(objectData._id, 'Payment', 'post')
+ if (result) {
+ message.success('Payment posted successfully')
+ onOk(result)
+ }
+ } catch (error) {
+ console.error('Error posting payment:', error)
+ } finally {
+ setPostLoading(false)
+ }
+ }
+
+ return (
+
+ )
+}
+
+PostPayment.propTypes = {
+ onOk: PropTypes.func.isRequired,
+ objectData: PropTypes.object
+}
+
+export default PostPayment
+
diff --git a/src/components/Icons/PaymentIcon.jsx b/src/components/Icons/PaymentIcon.jsx
new file mode 100644
index 0000000..48d9e0b
--- /dev/null
+++ b/src/components/Icons/PaymentIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/paymenticon.svg?react'
+
+const PaymentIcon = (props) =>
+
+export default PaymentIcon
diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js
index 62f56cd..83196f9 100644
--- a/src/database/ObjectModels.js
+++ b/src/database/ObjectModels.js
@@ -31,6 +31,7 @@ import { DocumentJob } from './models/DocumentJob.js'
import { TaxRate } from './models/TaxRate.js'
import { TaxRecord } from './models/TaxRecord.js'
import { Invoice } from './models/Invoice.js'
+import { Payment } from './models/Payment.js'
import { Client } from './models/Client.js'
import { SalesOrder } from './models/SalesOrder.js'
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
@@ -69,6 +70,7 @@ export const objectModels = [
TaxRate,
TaxRecord,
Invoice,
+ Payment,
Client,
SalesOrder
]
@@ -108,6 +110,7 @@ export {
TaxRate,
TaxRecord,
Invoice,
+ Payment,
Client,
SalesOrder
}
diff --git a/src/database/models/Payment.js b/src/database/models/Payment.js
new file mode 100644
index 0000000..84039ea
--- /dev/null
+++ b/src/database/models/Payment.js
@@ -0,0 +1,252 @@
+import PaymentIcon from '../../components/Icons/PaymentIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
+import CheckIcon from '../../components/Icons/CheckIcon'
+import EditIcon from '../../components/Icons/EditIcon'
+import XMarkIcon from '../../components/Icons/XMarkIcon'
+import BinIcon from '../../components/Icons/BinIcon'
+
+export const Payment = {
+ name: 'payment',
+ label: 'Payment',
+ prefix: 'PAY',
+ icon: PaymentIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/finance/payments/info?paymentId=${_id}`
+ },
+ {
+ name: 'edit',
+ label: 'Edit',
+ type: 'button',
+ icon: EditIcon,
+ url: (_id) =>
+ `/dashboard/finance/payments/info?paymentId=${_id}&action=edit`,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ },
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'draft'
+ }
+ },
+ {
+ name: 'cancelEdit',
+ label: 'Cancel Edit',
+ type: 'button',
+ icon: XMarkIcon,
+ url: (_id) =>
+ `/dashboard/finance/payments/info?paymentId=${_id}&action=cancelEdit`,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'finishEdit',
+ label: 'Finish Edit',
+ type: 'button',
+ icon: CheckIcon,
+ url: (_id) =>
+ `/dashboard/finance/payments/info?paymentId=${_id}&action=finishEdit`,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'delete',
+ label: 'Delete',
+ type: 'button',
+ icon: BinIcon,
+ danger: true,
+ url: (_id) =>
+ `/dashboard/finance/payments/info?paymentId=${_id}&action=delete`,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ },
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'draft'
+ }
+ },
+ { type: 'divider' },
+ {
+ name: 'post',
+ label: 'Post',
+ type: 'button',
+ icon: CheckIcon,
+ url: (_id) =>
+ `/dashboard/finance/payments/info?paymentId=${_id}&action=post`,
+ visible: (objectData) => {
+ return objectData?.state?.type == 'draft'
+ }
+ },
+ {
+ name: 'cancel',
+ label: 'Cancel',
+ type: 'button',
+ icon: XMarkIcon,
+ danger: true,
+ url: (_id) =>
+ `/dashboard/finance/payments/info?paymentId=${_id}&action=cancel`,
+ disabled: (objectData) => {
+ return objectData?.state?.type == 'cancelled'
+ },
+ visible: (objectData) => {
+ return (
+ objectData?.state?.type == 'draft' ||
+ objectData?.state?.type == 'posted'
+ )
+ }
+ }
+ ],
+ group: ['vendor', 'client', 'invoice'],
+ filters: ['vendor', 'client', 'invoice'],
+ sorters: ['createdAt', 'state', 'updatedAt', 'paymentDate'],
+ columns: [
+ '_reference',
+ 'state',
+ 'invoice',
+ 'vendor',
+ 'client',
+ 'paymentDate',
+ 'amount',
+ 'paymentMethod',
+ 'createdAt',
+ 'updatedAt'
+ ],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ type: 'id',
+ columnFixed: 'left',
+ objectType: 'payment',
+ columnWidth: 140,
+ showCopy: true
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: '_reference',
+ label: 'Reference',
+ type: 'reference',
+ columnFixed: 'left',
+ required: true,
+ objectType: 'payment',
+ showCopy: true,
+ readOnly: true
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ { name: 'state', label: 'State', type: 'state', readOnly: true },
+ {
+ name: 'invoice',
+ label: 'Invoice',
+ type: 'object',
+ objectType: 'invoice',
+ required: true,
+ showHyperlink: true
+ },
+ {
+ name: 'useRemainingAmount',
+ label: 'Use Remaining Amount',
+ type: 'boolean',
+ required: true
+ },
+ {
+ name: 'vendor',
+ label: 'Vendor',
+ type: 'object',
+ objectType: 'vendor',
+ showHyperlink: true,
+ readOnly: true
+ },
+ {
+ name: 'client',
+ label: 'Client',
+ type: 'object',
+ objectType: 'client',
+ showHyperlink: true,
+ readOnly: true
+ },
+ {
+ name: 'paymentDate',
+ label: 'Payment Date',
+ type: 'dateTime',
+ required: true
+ },
+ {
+ name: 'postedAt',
+ label: 'Posted At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'cancelledAt',
+ label: 'Cancelled At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'amount',
+ label: 'Amount',
+ type: 'number',
+ prefix: '£',
+ roundNumber: 2,
+ required: true,
+ columnWidth: 150
+ },
+ {
+ name: 'paymentMethod',
+ label: 'Payment Method',
+ type: 'string',
+ required: false
+ },
+ {
+ name: 'notes',
+ label: 'Notes',
+ type: 'text',
+ required: false
+ }
+ ],
+ stats: [
+ {
+ name: 'draft.draftCount.count',
+ label: 'Draft',
+ type: 'number',
+ color: 'default'
+ },
+ {
+ name: 'draft.draftAmount.sum',
+ label: 'Draft Amount',
+ type: 'number',
+ prefix: '£',
+ roundNumber: 2,
+ color: 'default'
+ },
+ {
+ name: 'posted.postedCount.count',
+ label: 'Posted',
+ type: 'number',
+ color: 'cyan'
+ },
+ {
+ name: 'posted.postedAmount.sum',
+ label: 'Posted Amount',
+ type: 'number',
+ prefix: '£',
+ roundNumber: 2,
+ color: 'cyan'
+ }
+ ]
+}