Add Payment Management Features
- Introduced Payment model and associated components for managing payments within the finance dashboard. - Added PaymentIcon for visual representation in the sidebar and other components. - Implemented Payments overview, including statistics and a new modal for creating payments. - Enhanced InvoiceInfo component to include payment details and actions for acknowledging and posting payments. - Updated database models to integrate payment functionalities, ensuring comprehensive financial tracking.
This commit is contained in:
parent
455c223ec0
commit
6fc952be4d
7
assets/icons/paymenticon.svg
Normal file
7
assets/icons/paymenticon.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(0.81149,0,0,0.81149,2,10.044134)">
|
||||||
|
<path d="M14.864,42.322L22.52,42.322C24.364,42.322 25.582,41.078 25.582,39.328L25.582,33.547C25.582,31.766 24.364,30.547 22.52,30.547L14.864,30.547C13.02,30.547 11.801,31.766 11.801,33.547L11.801,39.328C11.801,41.078 13.02,42.322 14.864,42.322ZM3.51,20.305L70.459,20.305L70.459,12.998L3.51,12.998L3.51,20.305ZM10.251,54.093L63.687,54.093C70.463,54.093 73.938,50.638 73.938,43.969L73.938,10.169C73.938,3.501 70.463,0.02 63.687,0.02L10.251,0.02C3.506,0.02 0,3.501 0,10.169L0,43.969C0,50.638 3.506,54.093 10.251,54.093ZM10.606,47.97C7.701,47.97 6.122,46.471 6.122,43.429L6.122,10.709C6.122,7.667 7.701,6.142 10.606,6.142L63.332,6.142C66.186,6.142 67.816,7.667 67.816,10.709L67.816,43.429C67.816,46.471 66.186,47.97 63.332,47.97L10.606,47.97Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -13,7 +13,8 @@ const FinanceOverview = () => {
|
|||||||
const [collapseState, updateCollapseState] = useCollapseState(
|
const [collapseState, updateCollapseState] = useCollapseState(
|
||||||
'FinanceOverview',
|
'FinanceOverview',
|
||||||
{
|
{
|
||||||
invoiceStats: true
|
invoiceStats: true,
|
||||||
|
paymentStats: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,6 +42,7 @@ const FinanceOverview = () => {
|
|||||||
}
|
}
|
||||||
className='no-t-padding-collapse'
|
className='no-t-padding-collapse'
|
||||||
collapseKey='invoiceStats'
|
collapseKey='invoiceStats'
|
||||||
|
canCollapse={false}
|
||||||
>
|
>
|
||||||
<Flex
|
<Flex
|
||||||
justify='flex-start'
|
justify='flex-start'
|
||||||
@ -51,6 +53,26 @@ const FinanceOverview = () => {
|
|||||||
<StatsDisplay objectType='invoice' />
|
<StatsDisplay objectType='invoice' />
|
||||||
</Flex>
|
</Flex>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Payment Statistics'
|
||||||
|
icon={null}
|
||||||
|
active={collapseState.paymentStats}
|
||||||
|
onToggle={(isActive) =>
|
||||||
|
updateCollapseState('paymentStats', isActive)
|
||||||
|
}
|
||||||
|
className='no-t-padding-collapse'
|
||||||
|
collapseKey='paymentStats'
|
||||||
|
canCollapse={false}
|
||||||
|
>
|
||||||
|
<Flex
|
||||||
|
justify='flex-start'
|
||||||
|
gap='middle'
|
||||||
|
wrap='wrap'
|
||||||
|
align='flex-start'
|
||||||
|
>
|
||||||
|
<StatsDisplay objectType='payment' />
|
||||||
|
</Flex>
|
||||||
|
</InfoCollapse>
|
||||||
</Flex>
|
</Flex>
|
||||||
</ScrollBox>
|
</ScrollBox>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -58,4 +80,3 @@ const FinanceOverview = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default FinanceOverview
|
export default FinanceOverview
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import DashboardSidebar from '../common/DashboardSidebar'
|
import DashboardSidebar from '../common/DashboardSidebar'
|
||||||
import InvoiceIcon from '../../Icons/InvoiceIcon'
|
import InvoiceIcon from '../../Icons/InvoiceIcon'
|
||||||
|
import PaymentIcon from '../../Icons/PaymentIcon'
|
||||||
import FinanceIcon from '../../Icons/FinanceIcon'
|
import FinanceIcon from '../../Icons/FinanceIcon'
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
@ -16,12 +17,19 @@ const items = [
|
|||||||
label: 'Invoices',
|
label: 'Invoices',
|
||||||
icon: <InvoiceIcon />,
|
icon: <InvoiceIcon />,
|
||||||
path: '/dashboard/finance/invoices'
|
path: '/dashboard/finance/invoices'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'payments',
|
||||||
|
label: 'Payments',
|
||||||
|
icon: <PaymentIcon />,
|
||||||
|
path: '/dashboard/finance/payments'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
const routeKeyMap = {
|
const routeKeyMap = {
|
||||||
'/dashboard/finance/overview': 'overview',
|
'/dashboard/finance/overview': 'overview',
|
||||||
'/dashboard/finance/invoices': 'invoices'
|
'/dashboard/finance/invoices': 'invoices',
|
||||||
|
'/dashboard/finance/payments': 'payments'
|
||||||
}
|
}
|
||||||
|
|
||||||
const FinanceSidebar = (props) => {
|
const FinanceSidebar = (props) => {
|
||||||
@ -43,4 +51,3 @@ const FinanceSidebar = (props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default FinanceSidebar
|
export default FinanceSidebar
|
||||||
|
|
||||||
|
|||||||
@ -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 (
|
||||||
|
<MessageDialogView
|
||||||
|
title={'Are you sure you want to acknowledge this invoice?'}
|
||||||
|
description={`Acknowledging invoice ${objectData?.name || objectData?._reference || objectData?._id} will update its status to acknowledged.`}
|
||||||
|
onOk={handleAcknowledge}
|
||||||
|
okText='Acknowledge'
|
||||||
|
okLoading={acknowledgeLoading}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AcknowledgeInvoice.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
objectData: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AcknowledgeInvoice
|
||||||
|
|
||||||
@ -29,6 +29,8 @@ import {
|
|||||||
import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
|
import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
|
||||||
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
|
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
|
||||||
import PostInvoice from './PostInvoice.jsx'
|
import PostInvoice from './PostInvoice.jsx'
|
||||||
|
import AcknowledgeInvoice from './AcknowledgeInvoice.jsx'
|
||||||
|
import NewPayment from '../Payments/NewPayment.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('InvoiceInfo')
|
const log = loglevel.getLogger('InvoiceInfo')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
@ -42,6 +44,7 @@ const InvoiceInfo = () => {
|
|||||||
info: true,
|
info: true,
|
||||||
invoiceOrderItems: true,
|
invoiceOrderItems: true,
|
||||||
invoiceShipments: true,
|
invoiceShipments: true,
|
||||||
|
payments: true,
|
||||||
notes: true,
|
notes: true,
|
||||||
auditLogs: true
|
auditLogs: true
|
||||||
})
|
})
|
||||||
@ -55,6 +58,8 @@ const InvoiceInfo = () => {
|
|||||||
objectData: {}
|
objectData: {}
|
||||||
})
|
})
|
||||||
const [postInvoiceOpen, setPostInvoiceOpen] = useState(false)
|
const [postInvoiceOpen, setPostInvoiceOpen] = useState(false)
|
||||||
|
const [acknowledgeInvoiceOpen, setAcknowledgeInvoiceOpen] = useState(false)
|
||||||
|
const [newPaymentOpen, setNewPaymentOpen] = useState(false)
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
reload: () => {
|
reload: () => {
|
||||||
@ -80,6 +85,14 @@ const InvoiceInfo = () => {
|
|||||||
post: () => {
|
post: () => {
|
||||||
setPostInvoiceOpen(true)
|
setPostInvoiceOpen(true)
|
||||||
return 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: 'info', label: 'Invoice Information' },
|
||||||
{ key: 'invoiceOrderItems', label: 'Invoice Order Items' },
|
{ key: 'invoiceOrderItems', label: 'Invoice Order Items' },
|
||||||
{ key: 'invoiceShipments', label: 'Invoice Shipments' },
|
{ key: 'invoiceShipments', label: 'Invoice Shipments' },
|
||||||
|
{ key: 'payments', label: 'Payments' },
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
]}
|
]}
|
||||||
@ -205,6 +219,7 @@ const InvoiceInfo = () => {
|
|||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
size='medium'
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
@ -221,12 +236,30 @@ const InvoiceInfo = () => {
|
|||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
size='medium'
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
</Flex>
|
</Flex>
|
||||||
)}
|
)}
|
||||||
</ObjectForm>
|
</ObjectForm>
|
||||||
</ActionHandler>
|
</ActionHandler>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Payments'
|
||||||
|
icon={<PaymentIcon />}
|
||||||
|
active={collapseState.payments}
|
||||||
|
onToggle={(expanded) => updateCollapseState('payments', expanded)}
|
||||||
|
collapseKey='payments'
|
||||||
|
>
|
||||||
|
{objectFormState.loading ? (
|
||||||
|
<InfoCollapsePlaceholder />
|
||||||
|
) : (
|
||||||
|
<ObjectTable
|
||||||
|
type='payment'
|
||||||
|
masterFilter={{ 'invoice._id': invoiceId }}
|
||||||
|
visibleColumns={{ invoice: false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
title='Notes'
|
title='Notes'
|
||||||
icon={<NoteIcon />}
|
icon={<NoteIcon />}
|
||||||
@ -278,6 +311,46 @@ const InvoiceInfo = () => {
|
|||||||
objectData={objectFormState.objectData}
|
objectData={objectFormState.objectData}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
open={acknowledgeInvoiceOpen}
|
||||||
|
onCancel={() => {
|
||||||
|
setAcknowledgeInvoiceOpen(false)
|
||||||
|
}}
|
||||||
|
width={515}
|
||||||
|
footer={null}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
centered={true}
|
||||||
|
>
|
||||||
|
<AcknowledgeInvoice
|
||||||
|
onOk={() => {
|
||||||
|
setAcknowledgeInvoiceOpen(false)
|
||||||
|
actions.reload()
|
||||||
|
}}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
open={newPaymentOpen}
|
||||||
|
styles={{ content: { paddingBottom: '24px' } }}
|
||||||
|
footer={null}
|
||||||
|
width={800}
|
||||||
|
onCancel={() => {
|
||||||
|
setNewPaymentOpen(false)
|
||||||
|
}}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
>
|
||||||
|
<NewPayment
|
||||||
|
onOk={() => {
|
||||||
|
setNewPaymentOpen(false)
|
||||||
|
actions.reload()
|
||||||
|
}}
|
||||||
|
reset={newPaymentOpen}
|
||||||
|
defaultValues={{
|
||||||
|
invoice: { ...objectFormState.objectData },
|
||||||
|
amount: objectFormState.objectData?.grandTotalAmount
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
99
src/components/Dashboard/Finance/Payments.jsx
Normal file
99
src/components/Dashboard/Finance/Payments.jsx
Normal file
@ -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: <PlusIcon />
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
label: 'Reload List',
|
||||||
|
key: 'reloadList',
|
||||||
|
icon: <ReloadIcon />
|
||||||
|
}
|
||||||
|
],
|
||||||
|
onClick: ({ key }) => {
|
||||||
|
if (key === 'reloadList') {
|
||||||
|
tableRef.current?.reload()
|
||||||
|
} else if (key === 'newPayment') {
|
||||||
|
setNewPaymentOpen(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Flex vertical={'true'} gap='large'>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='small'>
|
||||||
|
<Dropdown menu={actionItems}>
|
||||||
|
<Button>Actions</Button>
|
||||||
|
</Dropdown>
|
||||||
|
<ColumnViewButton
|
||||||
|
type='payment'
|
||||||
|
loading={false}
|
||||||
|
visibleState={columnVisibility}
|
||||||
|
updateVisibleState={setColumnVisibility}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
icon={viewMode === 'cards' ? <ListIcon /> : <GridIcon />}
|
||||||
|
onClick={() =>
|
||||||
|
setViewMode(viewMode === 'cards' ? 'list' : 'cards')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
<ObjectTable
|
||||||
|
ref={tableRef}
|
||||||
|
visibleColumns={columnVisibility}
|
||||||
|
type='payment'
|
||||||
|
cards={viewMode === 'cards'}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={newPaymentOpen}
|
||||||
|
styles={{ content: { paddingBottom: '24px' } }}
|
||||||
|
footer={null}
|
||||||
|
width={800}
|
||||||
|
onCancel={() => {
|
||||||
|
setNewPaymentOpen(false)
|
||||||
|
}}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
>
|
||||||
|
<NewPayment
|
||||||
|
onOk={() => {
|
||||||
|
setNewPaymentOpen(false)
|
||||||
|
tableRef.current?.reload()
|
||||||
|
}}
|
||||||
|
reset={newPaymentOpen}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Payments
|
||||||
|
|
||||||
89
src/components/Dashboard/Finance/Payments/NewPayment.jsx
Normal file
89
src/components/Dashboard/Finance/Payments/NewPayment.jsx
Normal file
@ -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 (
|
||||||
|
<NewObjectForm
|
||||||
|
type={'payment'}
|
||||||
|
reset={reset}
|
||||||
|
defaultValues={{
|
||||||
|
state: { type: 'draft' },
|
||||||
|
paymentDate: new Date(),
|
||||||
|
amount: 0,
|
||||||
|
...defaultValues
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Required',
|
||||||
|
key: 'required',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='payment'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
isEditing={true}
|
||||||
|
required={true}
|
||||||
|
objectData={objectData}
|
||||||
|
visibleProperties={{
|
||||||
|
invoice: true,
|
||||||
|
vendor: true,
|
||||||
|
client: true,
|
||||||
|
paymentDate: true,
|
||||||
|
amount: true,
|
||||||
|
paymentMethod: true
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Summary',
|
||||||
|
key: 'summary',
|
||||||
|
content: (
|
||||||
|
<ObjectInfo
|
||||||
|
type='payment'
|
||||||
|
column={1}
|
||||||
|
bordered={false}
|
||||||
|
visibleProperties={{
|
||||||
|
_id: false,
|
||||||
|
createdAt: false,
|
||||||
|
updatedAt: false,
|
||||||
|
_reference: false,
|
||||||
|
postedAt: false,
|
||||||
|
cancelledAt: false
|
||||||
|
}}
|
||||||
|
isEditing={false}
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<WizardView
|
||||||
|
steps={steps}
|
||||||
|
loading={submitLoading}
|
||||||
|
formValid={formValid}
|
||||||
|
title='New Payment'
|
||||||
|
onSubmit={async () => {
|
||||||
|
const result = await handleSubmit()
|
||||||
|
if (result) {
|
||||||
|
onOk()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</NewObjectForm>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewPayment.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
reset: PropTypes.bool,
|
||||||
|
defaultValues: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NewPayment
|
||||||
242
src/components/Dashboard/Finance/Payments/PaymentInfo.jsx
Normal file
242
src/components/Dashboard/Finance/Payments/PaymentInfo.jsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<Flex
|
||||||
|
gap='large'
|
||||||
|
vertical='true'
|
||||||
|
style={{
|
||||||
|
maxHeight: '100%',
|
||||||
|
minHeight: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Flex justify={'space-between'}>
|
||||||
|
<Space size='middle'>
|
||||||
|
<Space size='small'>
|
||||||
|
<ObjectActions
|
||||||
|
type='payment'
|
||||||
|
id={paymentId}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
<ViewButton
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
items={[
|
||||||
|
{ key: 'info', label: 'Payment Information' },
|
||||||
|
{ key: 'notes', label: 'Notes' },
|
||||||
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
|
]}
|
||||||
|
visibleState={collapseState}
|
||||||
|
updateVisibleState={updateCollapseState}
|
||||||
|
/>
|
||||||
|
<DocumentPrintButton
|
||||||
|
type='payment'
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
disabled={objectFormState.loading}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
<LockIndicator lock={objectFormState.lock} />
|
||||||
|
</Space>
|
||||||
|
<Space>
|
||||||
|
<EditButtons
|
||||||
|
isEditing={objectFormState.isEditing}
|
||||||
|
handleUpdate={() => {
|
||||||
|
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}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<ScrollBox>
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<ActionHandler
|
||||||
|
actions={actions}
|
||||||
|
loading={objectFormState.loading}
|
||||||
|
ref={actionHandlerRef}
|
||||||
|
>
|
||||||
|
<ObjectForm
|
||||||
|
id={paymentId}
|
||||||
|
type='payment'
|
||||||
|
style={{ height: '100%' }}
|
||||||
|
ref={objectFormRef}
|
||||||
|
onStateChange={(state) => {
|
||||||
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ loading, isEditing, objectData }) => (
|
||||||
|
<Flex vertical gap={'large'}>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Payment Information'
|
||||||
|
icon={<InfoCircleIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('info', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='info'
|
||||||
|
>
|
||||||
|
<ObjectInfo
|
||||||
|
loading={loading}
|
||||||
|
indicator={<LoadingOutlined />}
|
||||||
|
isEditing={isEditing}
|
||||||
|
type='payment'
|
||||||
|
labelWidth='225px'
|
||||||
|
objectData={objectData}
|
||||||
|
/>
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
</ObjectForm>
|
||||||
|
</ActionHandler>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Notes'
|
||||||
|
icon={<NoteIcon />}
|
||||||
|
active={collapseState.notes}
|
||||||
|
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
||||||
|
collapseKey='notes'
|
||||||
|
>
|
||||||
|
<Card>
|
||||||
|
<NotesPanel _id={paymentId} type='payment' />
|
||||||
|
</Card>
|
||||||
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Audit Logs'
|
||||||
|
icon={<AuditLogIcon />}
|
||||||
|
active={collapseState.auditLogs}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('auditLogs', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='auditLogs'
|
||||||
|
>
|
||||||
|
{objectFormState.loading ? (
|
||||||
|
<InfoCollapsePlaceholder />
|
||||||
|
) : (
|
||||||
|
<ObjectTable
|
||||||
|
type='auditLog'
|
||||||
|
masterFilter={{ 'parent._id': paymentId }}
|
||||||
|
visibleColumns={{ _id: false, 'parent._id': false }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InfoCollapse>
|
||||||
|
</Flex>
|
||||||
|
</ScrollBox>
|
||||||
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={postPaymentOpen}
|
||||||
|
onCancel={() => {
|
||||||
|
setPostPaymentOpen(false)
|
||||||
|
}}
|
||||||
|
width={500}
|
||||||
|
footer={null}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
centered={true}
|
||||||
|
>
|
||||||
|
<PostPayment
|
||||||
|
onOk={() => {
|
||||||
|
setPostPaymentOpen(false)
|
||||||
|
actions.reload()
|
||||||
|
}}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaymentInfo
|
||||||
|
|
||||||
43
src/components/Dashboard/Finance/Payments/PostPayment.jsx
Normal file
43
src/components/Dashboard/Finance/Payments/PostPayment.jsx
Normal file
@ -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 (
|
||||||
|
<MessageDialogView
|
||||||
|
title={'Are you sure you want to post this payment?'}
|
||||||
|
description={`Posting payment ${objectData?.name || objectData?._reference || objectData?._id} will set it to posted status.`}
|
||||||
|
onOk={handlePost}
|
||||||
|
okText='Post'
|
||||||
|
okLoading={postLoading}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PostPayment.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
objectData: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PostPayment
|
||||||
|
|
||||||
6
src/components/Icons/PaymentIcon.jsx
Normal file
6
src/components/Icons/PaymentIcon.jsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import CustomIconSvg from '../../../assets/icons/paymenticon.svg?react'
|
||||||
|
|
||||||
|
const PaymentIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||||
|
|
||||||
|
export default PaymentIcon
|
||||||
@ -31,6 +31,7 @@ import { DocumentJob } from './models/DocumentJob.js'
|
|||||||
import { TaxRate } from './models/TaxRate.js'
|
import { TaxRate } from './models/TaxRate.js'
|
||||||
import { TaxRecord } from './models/TaxRecord.js'
|
import { TaxRecord } from './models/TaxRecord.js'
|
||||||
import { Invoice } from './models/Invoice.js'
|
import { Invoice } from './models/Invoice.js'
|
||||||
|
import { Payment } from './models/Payment.js'
|
||||||
import { Client } from './models/Client.js'
|
import { Client } from './models/Client.js'
|
||||||
import { SalesOrder } from './models/SalesOrder.js'
|
import { SalesOrder } from './models/SalesOrder.js'
|
||||||
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
|
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
|
||||||
@ -69,6 +70,7 @@ export const objectModels = [
|
|||||||
TaxRate,
|
TaxRate,
|
||||||
TaxRecord,
|
TaxRecord,
|
||||||
Invoice,
|
Invoice,
|
||||||
|
Payment,
|
||||||
Client,
|
Client,
|
||||||
SalesOrder
|
SalesOrder
|
||||||
]
|
]
|
||||||
@ -108,6 +110,7 @@ export {
|
|||||||
TaxRate,
|
TaxRate,
|
||||||
TaxRecord,
|
TaxRecord,
|
||||||
Invoice,
|
Invoice,
|
||||||
|
Payment,
|
||||||
Client,
|
Client,
|
||||||
SalesOrder
|
SalesOrder
|
||||||
}
|
}
|
||||||
|
|||||||
252
src/database/models/Payment.js
Normal file
252
src/database/models/Payment.js
Normal file
@ -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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user