Enhance Invoice management features with new PostInvoice functionality
- Added PostInvoice component for posting invoices with confirmation dialog. - Updated InvoiceInfo component to include new invoice order items and shipments sections. - Modified NewInvoice component to set default issued and due dates. - Refactored Invoice model to include new fields for issuedAt, dueAt, invoiceOrderItems, and invoiceShipments. - Updated action names from 'send' to 'post' for clarity in the invoice workflow.
This commit is contained in:
parent
0bf16d844e
commit
bace57b436
@ -1,6 +1,6 @@
|
|||||||
import { useRef, useState } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import { Space, Flex, Card } from 'antd'
|
import { Space, Flex, Card, Modal } from 'antd'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import loglevel from 'loglevel'
|
import loglevel from 'loglevel'
|
||||||
import config from '../../../../config.js'
|
import config from '../../../../config.js'
|
||||||
@ -8,6 +8,7 @@ import useCollapseState from '../../hooks/useCollapseState.js'
|
|||||||
import NotesPanel from '../../common/NotesPanel.jsx'
|
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||||
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
||||||
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||||
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
import ViewButton from '../../common/ViewButton.jsx'
|
import ViewButton from '../../common/ViewButton.jsx'
|
||||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
@ -21,7 +22,13 @@ import ObjectTable from '../../common/ObjectTable.jsx'
|
|||||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import { getModelByName } from '../../../../database/ObjectModels.js'
|
import {
|
||||||
|
getModelByName,
|
||||||
|
getModelProperty
|
||||||
|
} from '../../../../database/ObjectModels.js'
|
||||||
|
import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
|
||||||
|
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
|
||||||
|
import PostInvoice from './PostInvoice.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('InvoiceInfo')
|
const log = loglevel.getLogger('InvoiceInfo')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
@ -31,14 +38,13 @@ const InvoiceInfo = () => {
|
|||||||
const objectFormRef = useRef(null)
|
const objectFormRef = useRef(null)
|
||||||
const actionHandlerRef = useRef(null)
|
const actionHandlerRef = useRef(null)
|
||||||
const invoiceId = new URLSearchParams(location.search).get('invoiceId')
|
const invoiceId = new URLSearchParams(location.search).get('invoiceId')
|
||||||
const [collapseState, updateCollapseState] = useCollapseState(
|
const [collapseState, updateCollapseState] = useCollapseState('InvoiceInfo', {
|
||||||
'InvoiceInfo',
|
|
||||||
{
|
|
||||||
info: true,
|
info: true,
|
||||||
|
invoiceOrderItems: true,
|
||||||
|
invoiceShipments: true,
|
||||||
notes: true,
|
notes: true,
|
||||||
auditLogs: true
|
auditLogs: true
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
const [objectFormState, setEditFormState] = useState({
|
const [objectFormState, setEditFormState] = useState({
|
||||||
isEditing: false,
|
isEditing: false,
|
||||||
@ -48,6 +54,7 @@ const InvoiceInfo = () => {
|
|||||||
loading: false,
|
loading: false,
|
||||||
objectData: {}
|
objectData: {}
|
||||||
})
|
})
|
||||||
|
const [postInvoiceOpen, setPostInvoiceOpen] = useState(false)
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
reload: () => {
|
reload: () => {
|
||||||
@ -69,10 +76,15 @@ const InvoiceInfo = () => {
|
|||||||
delete: () => {
|
delete: () => {
|
||||||
objectFormRef?.current?.handleDelete?.()
|
objectFormRef?.current?.handleDelete?.()
|
||||||
return true
|
return true
|
||||||
|
},
|
||||||
|
post: () => {
|
||||||
|
setPostInvoiceOpen(true)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const editDisabled = getModelByName('invoice')
|
const editDisabled =
|
||||||
|
getModelByName('invoice')
|
||||||
?.actions?.find((action) => action.name === 'edit')
|
?.actions?.find((action) => action.name === 'edit')
|
||||||
?.disabled(objectFormState.objectData) ?? false
|
?.disabled(objectFormState.objectData) ?? false
|
||||||
|
|
||||||
@ -99,6 +111,8 @@ const InvoiceInfo = () => {
|
|||||||
disabled={objectFormState.loading}
|
disabled={objectFormState.loading}
|
||||||
items={[
|
items={[
|
||||||
{ key: 'info', label: 'Invoice Information' },
|
{ key: 'info', label: 'Invoice Information' },
|
||||||
|
{ key: 'invoiceOrderItems', label: 'Invoice Order Items' },
|
||||||
|
{ key: 'invoiceShipments', label: 'Invoice Shipments' },
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
]}
|
]}
|
||||||
@ -171,6 +185,42 @@ const InvoiceInfo = () => {
|
|||||||
type='invoice'
|
type='invoice'
|
||||||
labelWidth='225px'
|
labelWidth='225px'
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
|
visibleProperties={{
|
||||||
|
invoiceOrderItems: false,
|
||||||
|
invoiceShipments: false
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Invoice Order Items'
|
||||||
|
icon={<OrderItemIcon />}
|
||||||
|
active={collapseState.invoiceOrderItems}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('invoiceOrderItems', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='invoiceOrderItems'
|
||||||
|
>
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty('invoice', 'invoiceOrderItems')}
|
||||||
|
isEditing={isEditing}
|
||||||
|
objectData={objectData}
|
||||||
|
loading={loading}
|
||||||
|
/>
|
||||||
|
</InfoCollapse>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Invoice Shipments'
|
||||||
|
icon={<ShipmentIcon />}
|
||||||
|
active={collapseState.invoiceShipments}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('invoiceShipments', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='invoiceShipments'
|
||||||
|
>
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty('invoice', 'invoiceShipments')}
|
||||||
|
isEditing={isEditing}
|
||||||
|
objectData={objectData}
|
||||||
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -210,9 +260,26 @@ const InvoiceInfo = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</ScrollBox>
|
</ScrollBox>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Modal
|
||||||
|
open={postInvoiceOpen}
|
||||||
|
onCancel={() => {
|
||||||
|
setPostInvoiceOpen(false)
|
||||||
|
}}
|
||||||
|
width={500}
|
||||||
|
footer={null}
|
||||||
|
destroyOnHidden={true}
|
||||||
|
centered={true}
|
||||||
|
>
|
||||||
|
<PostInvoice
|
||||||
|
onOk={() => {
|
||||||
|
setPostInvoiceOpen(false)
|
||||||
|
actions.reload()
|
||||||
|
}}
|
||||||
|
objectData={objectFormState.objectData}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InvoiceInfo
|
export default InvoiceInfo
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
import ObjectInfo from '../../common/ObjectInfo'
|
||||||
import NewObjectForm from '../../common/NewObjectForm'
|
import NewObjectForm from '../../common/NewObjectForm'
|
||||||
import WizardView from '../../common/WizardView'
|
import WizardView from '../../common/WizardView'
|
||||||
@ -10,7 +11,8 @@ const NewInvoice = ({ onOk, reset, defaultValues }) => {
|
|||||||
reset={reset}
|
reset={reset}
|
||||||
defaultValues={{
|
defaultValues={{
|
||||||
state: { type: 'draft' },
|
state: { type: 'draft' },
|
||||||
invoiceType: 'sales',
|
issuedAt: new Date(),
|
||||||
|
dueAt: dayjs().add(3, 'day').toDate(),
|
||||||
...defaultValues
|
...defaultValues
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -30,27 +32,10 @@ const NewInvoice = ({ onOk, reset, defaultValues }) => {
|
|||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
orderType: true,
|
orderType: true,
|
||||||
order: true,
|
order: true,
|
||||||
vendor: true,
|
to: true,
|
||||||
invoiceDate: true,
|
from: true,
|
||||||
dueDate: true
|
issuedAt: true,
|
||||||
}}
|
dueAt: true
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Optional',
|
|
||||||
key: 'optional',
|
|
||||||
content: (
|
|
||||||
<ObjectInfo
|
|
||||||
type='invoice'
|
|
||||||
column={1}
|
|
||||||
bordered={false}
|
|
||||||
isEditing={true}
|
|
||||||
required={false}
|
|
||||||
objectData={objectData}
|
|
||||||
visibleProperties={{
|
|
||||||
relatedOrderType: true,
|
|
||||||
relatedOrder: true
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -66,6 +51,8 @@ const NewInvoice = ({ onOk, reset, defaultValues }) => {
|
|||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
_id: false,
|
_id: false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
|
invoiceOrderItems: false,
|
||||||
|
invoiceShipments: false,
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
_reference: false,
|
_reference: false,
|
||||||
totalAmount: false,
|
totalAmount: false,
|
||||||
|
|||||||
42
src/components/Dashboard/Finance/Invoices/PostInvoice.jsx
Normal file
42
src/components/Dashboard/Finance/Invoices/PostInvoice.jsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
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 PostInvoice = ({ onOk, objectData }) => {
|
||||||
|
const [postLoading, setPostLoading] = useState(false)
|
||||||
|
const { sendObjectFunction } = useContext(ApiServerContext)
|
||||||
|
|
||||||
|
const handlePost = async () => {
|
||||||
|
setPostLoading(true)
|
||||||
|
try {
|
||||||
|
const result = await sendObjectFunction(objectData._id, 'Invoice', 'post')
|
||||||
|
if (result) {
|
||||||
|
message.success('Invoice posted successfully')
|
||||||
|
onOk(result)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting invoice:', error)
|
||||||
|
} finally {
|
||||||
|
setPostLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MessageDialogView
|
||||||
|
title={'Are you sure you want to post this invoice?'}
|
||||||
|
description={`Posting invoice ${objectData?.name || objectData?._reference || objectData?._id} will set it to sent status.`}
|
||||||
|
onOk={handlePost}
|
||||||
|
okText='Post'
|
||||||
|
okLoading={postLoading}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PostInvoice.propTypes = {
|
||||||
|
onOk: PropTypes.func.isRequired,
|
||||||
|
objectData: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PostInvoice
|
||||||
@ -72,30 +72,16 @@ export const Invoice = {
|
|||||||
},
|
},
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
{
|
{
|
||||||
name: 'send',
|
name: 'post',
|
||||||
label: 'Send',
|
label: 'Post',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
icon: CheckIcon,
|
icon: CheckIcon,
|
||||||
url: (_id) =>
|
url: (_id) =>
|
||||||
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=send`,
|
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=post`,
|
||||||
visible: (objectData) => {
|
visible: (objectData) => {
|
||||||
return objectData?.state?.type == 'draft'
|
return objectData?.state?.type == 'draft'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'markPaid',
|
|
||||||
label: 'Mark Paid',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=markPaid`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return (
|
|
||||||
objectData?.state?.type == 'sent' ||
|
|
||||||
objectData?.state?.type == 'partiallyPaid'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'cancel',
|
name: 'cancel',
|
||||||
label: 'Cancel',
|
label: 'Cancel',
|
||||||
@ -111,10 +97,7 @@ export const Invoice = {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
visible: (objectData) => {
|
visible: (objectData) => {
|
||||||
return (
|
return objectData?.state?.type == 'sent'
|
||||||
objectData?.state?.type == 'draft' ||
|
|
||||||
objectData?.state?.type == 'sent'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -172,16 +155,11 @@ export const Invoice = {
|
|||||||
},
|
},
|
||||||
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
||||||
{
|
{
|
||||||
name: 'invoiceDate',
|
name: 'issuedAt',
|
||||||
label: 'Invoice Date',
|
label: 'Issued At',
|
||||||
type: 'date',
|
type: 'dateTime',
|
||||||
readOnly: false
|
readOnly: false,
|
||||||
},
|
required: true
|
||||||
{
|
|
||||||
name: 'dueDate',
|
|
||||||
label: 'Due Date',
|
|
||||||
type: 'date',
|
|
||||||
readOnly: false
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'orderType',
|
name: 'orderType',
|
||||||
@ -190,6 +168,12 @@ export const Invoice = {
|
|||||||
masterFilter: ['purchaseOrder', 'salesOrder'],
|
masterFilter: ['purchaseOrder', 'salesOrder'],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'dueAt',
|
||||||
|
label: 'Due At',
|
||||||
|
type: 'dateTime',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'order',
|
name: 'order',
|
||||||
label: 'Order',
|
label: 'Order',
|
||||||
@ -201,8 +185,14 @@ export const Invoice = {
|
|||||||
showHyperlink: true
|
showHyperlink: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'vendor',
|
name: 'postedAt',
|
||||||
label: 'Vendor',
|
label: 'Posted At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'from',
|
||||||
|
label: 'From',
|
||||||
required: true,
|
required: true,
|
||||||
type: 'object',
|
type: 'object',
|
||||||
objectType: 'vendor',
|
objectType: 'vendor',
|
||||||
@ -212,34 +202,39 @@ export const Invoice = {
|
|||||||
if (objectData?.orderType == 'purchaseOrder') {
|
if (objectData?.orderType == 'purchaseOrder') {
|
||||||
return objectData?.order?.vendor
|
return objectData?.order?.vendor
|
||||||
} else {
|
} else {
|
||||||
return objectData?.vendor
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'sentAt',
|
|
||||||
label: 'Sent At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'paidAt',
|
name: 'paidAt',
|
||||||
label: 'Paid At',
|
label: 'Paid At',
|
||||||
type: 'dateTime',
|
type: 'dateTime',
|
||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'to',
|
||||||
|
label: 'To',
|
||||||
|
required: true,
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'client',
|
||||||
|
showHyperlink: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: (objectData) => {
|
||||||
|
if (objectData?.orderType == 'salesOrder') {
|
||||||
|
return objectData?.order?.client
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'cancelledAt',
|
name: 'cancelledAt',
|
||||||
label: 'Cancelled At',
|
label: 'Cancelled At',
|
||||||
type: 'dateTime',
|
type: 'dateTime',
|
||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'overdueAt',
|
|
||||||
label: 'Overdue At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'totalTaxAmount',
|
name: 'totalTaxAmount',
|
||||||
label: 'Total Tax Amount',
|
label: 'Total Tax Amount',
|
||||||
@ -293,44 +288,277 @@ export const Invoice = {
|
|||||||
roundNumber: 2,
|
roundNumber: 2,
|
||||||
columnWidth: 175,
|
columnWidth: 175,
|
||||||
readOnly: true
|
readOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invoiceOrderItems',
|
||||||
|
label: 'Invoice Order Items',
|
||||||
|
type: 'objectChildren',
|
||||||
|
objectType: 'orderItem',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'orderItem',
|
||||||
|
label: 'Order Item',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'orderItem',
|
||||||
|
required: true,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invoiceQuantity',
|
||||||
|
label: 'Quantity',
|
||||||
|
type: 'number',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invoiceAmount',
|
||||||
|
label: 'Invoice Amount',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'taxRate',
|
||||||
|
label: 'Tax Rate',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'taxRate',
|
||||||
|
required: false,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invoiceAmountWithTax',
|
||||||
|
label: 'Invoice Amount w/ Tax',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: (objectData) => {
|
||||||
|
const invoiceAmount = objectData?.invoiceAmount || 0
|
||||||
|
if (objectData?.taxRate?.rateType == 'percentage') {
|
||||||
|
return (
|
||||||
|
(invoiceAmount * (1 + objectData?.taxRate?.rate / 100)).toFixed(
|
||||||
|
2
|
||||||
|
) || undefined
|
||||||
|
)
|
||||||
|
} else if (objectData?.taxRate?.rateType == 'amount') {
|
||||||
|
return (
|
||||||
|
(invoiceAmount + objectData?.taxRate?.rate).toFixed(2) ||
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return invoiceAmount || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
rollups: [
|
||||||
|
{
|
||||||
|
name: 'totalQuantity',
|
||||||
|
label: 'Total Quantity',
|
||||||
|
type: 'number',
|
||||||
|
property: 'invoiceQuantity',
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.invoiceOrderItems?.reduce(
|
||||||
|
(acc, item) => acc + (item.invoiceQuantity || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalAmount',
|
||||||
|
label: 'Total Amount',
|
||||||
|
type: 'number',
|
||||||
|
property: 'invoiceAmount',
|
||||||
|
prefix: '£',
|
||||||
|
fixedNumber: 2,
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.invoiceOrderItems
|
||||||
|
?.reduce(
|
||||||
|
(acc, item) =>
|
||||||
|
acc + (Number.parseFloat(item.invoiceAmount) || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.toFixed(2)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalAmountWithTax',
|
||||||
|
label: 'Total Amount w/ Tax',
|
||||||
|
type: 'number',
|
||||||
|
property: 'invoiceAmountWithTax',
|
||||||
|
prefix: '£',
|
||||||
|
fixedNumber: 2,
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.invoiceOrderItems
|
||||||
|
?.reduce(
|
||||||
|
(acc, item) =>
|
||||||
|
acc + (Number.parseFloat(item.invoiceAmountWithTax) || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invoiceShipments',
|
||||||
|
label: 'Invoice Shipments',
|
||||||
|
type: 'objectChildren',
|
||||||
|
objectType: 'shipment',
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'shipment',
|
||||||
|
label: 'Shipment',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'shipment',
|
||||||
|
required: true,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invoiceAmount',
|
||||||
|
label: 'Invoice Amount',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'taxRate',
|
||||||
|
label: 'Tax Rate',
|
||||||
|
type: 'object',
|
||||||
|
objectType: 'taxRate',
|
||||||
|
required: false,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'invoiceAmountWithTax',
|
||||||
|
label: 'Invoice Amount w/ Tax',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
|
value: (objectData) => {
|
||||||
|
const invoiceAmount = objectData?.invoiceAmount || 0
|
||||||
|
if (objectData?.taxRate?.rateType == 'percentage') {
|
||||||
|
return (
|
||||||
|
(invoiceAmount * (1 + objectData?.taxRate?.rate / 100)).toFixed(
|
||||||
|
2
|
||||||
|
) || undefined
|
||||||
|
)
|
||||||
|
} else if (objectData?.taxRate?.rateType == 'amount') {
|
||||||
|
return (
|
||||||
|
(invoiceAmount + objectData?.taxRate?.rate).toFixed(2) ||
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return invoiceAmount || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
rollups: [
|
||||||
|
{
|
||||||
|
name: 'totalAmount',
|
||||||
|
label: 'Total Amount',
|
||||||
|
type: 'number',
|
||||||
|
property: 'invoiceAmount',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.invoiceShipments
|
||||||
|
?.reduce(
|
||||||
|
(acc, shipment) => acc + (shipment.invoiceAmount || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.toFixed(2)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalAmountWithTax',
|
||||||
|
label: 'Total Amount w/ Tax',
|
||||||
|
type: 'number',
|
||||||
|
property: 'invoiceAmountWithTax',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.invoiceShipments
|
||||||
|
?.reduce(
|
||||||
|
(acc, shipment) =>
|
||||||
|
acc + (Number.parseFloat(shipment.invoiceAmountWithTax) || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
.toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: [
|
stats: [
|
||||||
{
|
{
|
||||||
name: 'draft.count',
|
name: 'draft.draftCount.count',
|
||||||
label: 'Draft',
|
label: 'Draft',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
color: 'default'
|
color: 'default'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sent.count',
|
name: 'draft.draftGrandTotalAmount.sum',
|
||||||
|
label: 'Draft Grand Total Amount',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
color: 'default'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sent.sentCount.count',
|
||||||
label: 'Sent',
|
label: 'Sent',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
color: 'cyan'
|
color: 'cyan'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'partiallyPaid.count',
|
name: 'due.dueCount.count',
|
||||||
|
label: 'Due',
|
||||||
|
type: 'number',
|
||||||
|
color: 'warning',
|
||||||
|
sum: [
|
||||||
|
'sent.sentCount.count',
|
||||||
|
'partiallyPaid.partiallyPaidCount.count',
|
||||||
|
'overdue.overdueCount.count'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'due.dueGrandTotalAmount.sum',
|
||||||
|
label: 'Due Grand Total Amount',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
color: 'warning',
|
||||||
|
sum: [
|
||||||
|
'sent.sentGrandTotalAmount.sum',
|
||||||
|
'partiallyPaid.partiallyPaidGrandTotalAmount.sum',
|
||||||
|
'overdue.overdueGrandTotalAmount.sum'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'partiallyPaid.partiallyPaidCount.count',
|
||||||
label: 'Partially Paid',
|
label: 'Partially Paid',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
color: 'processing'
|
color: 'processing'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'paid.count',
|
name: 'overdue.overdueCount.count',
|
||||||
label: 'Paid',
|
|
||||||
type: 'number',
|
|
||||||
color: 'success'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'overdue.count',
|
|
||||||
label: 'Overdue',
|
label: 'Overdue',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
color: 'error'
|
color: 'error'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cancelled.count',
|
name: 'overdue.overdueGrandTotalAmount.sum',
|
||||||
label: 'Cancelled',
|
label: 'Overdue Grand Total Amount',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
color: 'default'
|
prefix: '£',
|
||||||
|
roundNumber: 2,
|
||||||
|
color: 'error'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user