Compare commits
No commits in common. "94406a1bfcf99470e457bcccbaec7120244cc2ae" and "d7827ecb6d013dbe73a654b85b1e46960c68d210" have entirely different histories.
94406a1bfc
...
d7827ecb6d
@ -1 +0,0 @@
|
|||||||
<?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><path d="M29.953,40.788l10.342,0c1.761,0 3.18,1.258 3.18,3.055c0,1.788 -1.349,3.03 -3.18,3.03l-18.004,0c-1.785,0 -3.086,-1.234 -3.086,-3.075c0,-1.581 0.953,-2.598 2.336,-3.193l0.024,-0.01c2.054,-0.833 3.153,-2.551 3.153,-4.757c0,-0.425 -0.052,-0.856 -0.133,-1.305l-2.8,0c-1.588,0 -2.701,-1.131 -2.701,-2.591c0,-1.408 1.105,-2.56 2.701,-2.56l1.544,-0c-0.168,-0.87 -0.242,-1.672 -0.242,-2.485c0,-6.288 4.756,-9.887 11.404,-9.887c2.234,0 3.578,0.148 5.274,0.766c1.53,0.469 2.646,1.469 2.646,3.106c0,0.878 -0.321,1.553 -0.828,2.023c-0.48,0.446 -1.159,0.725 -2.011,0.725c-0.479,0 -1.089,-0.115 -1.715,-0.248l-0.045,-0.01c-0.697,-0.176 -1.631,-0.301 -2.775,-0.301c-2.932,0 -5.078,1.33 -5.078,3.98c0,0.734 0.074,1.394 0.272,2.332l7.292,0c1.585,0 2.701,1.163 2.701,2.56c0,1.449 -1.124,2.591 -2.701,2.591l-6.16,0c0.024,0.361 0.034,0.743 0.034,1.147c0,1.832 -0.479,3.671 -1.444,5.108Z"/><path d="M12.892,61l38.215,0c6.616,0 9.892,-3.245 9.892,-9.735l0,-38.499c0,-6.49 -3.276,-9.766 -9.892,-9.766l-38.215,-0c-6.584,0 -9.892,3.276 -9.892,9.766l0,38.499c0,6.49 3.308,9.735 9.892,9.735Zm0.063,-5.072c-3.15,0 -4.883,-1.67 -4.883,-4.946l0,-37.932c0,-3.276 1.733,-4.978 4.883,-4.978l38.089,0c3.119,0 4.883,1.701 4.883,4.978l0,37.932c0,3.276 -1.764,4.946 -4.883,4.946l-38.089,0Z" style="fill-rule:nonzero;"/></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,14 +0,0 @@
|
|||||||
<?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.58577,0,0,0.58577,29,29)">
|
|
||||||
<path d="M11.031,59.75L48.719,59.75C55.859,59.75 59.75,55.891 59.75,48.797L59.75,10.969C59.75,3.891 55.859,-0 48.719,-0L11.031,-0C3.906,-0 -0,3.891 -0,10.969L-0,48.797C-0,55.891 3.906,59.75 11.031,59.75ZM11.906,51.688C9.391,51.688 8.063,50.469 8.063,47.813L8.063,11.969C8.063,9.313 9.391,8.078 11.906,8.078L47.844,8.078C50.344,8.078 51.688,9.313 51.688,11.969L51.688,47.813C51.688,50.469 50.344,51.688 47.844,51.688L11.906,51.688Z" style="fill-rule:nonzero;"/>
|
|
||||||
<g transform="matrix(0.664312,0,0,0.664312,5.121464,12.135807)">
|
|
||||||
<path d="M37.594,52.969C39.062,52.969 40.625,52.281 41.875,51L61.844,31.094C62.938,30 63.656,28.188 63.656,26.5C63.656,24.812 62.938,23 61.844,21.906L41.875,1.969C40.625,0.688 39.062,0 37.594,0C33.812,0 31.406,2.562 31.406,5.906C31.406,7.875 32.312,9.25 33.5,10.406L40.5,17.344L50.219,26.5L40.5,35.656L33.5,42.562C32.312,43.688 31.406,45.094 31.406,47.062C31.406,50.406 33.812,52.969 37.594,52.969ZM1.485,32.781L37.75,32.781L51.969,32.094C55.531,31.938 57.906,29.844 57.906,26.5C57.906,23.156 55.531,21.062 51.969,20.906L37.75,20.219L1.485,20.219C-2.515,20.219 -5.14,22.719 -5.14,26.5C-5.14,30.281 -2.515,32.781 1.485,32.781Z" style="fill-rule:nonzero;"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.739137,0,0,0.739137,-0,2.611674)">
|
|
||||||
<path d="M35.853,52.047L25.734,52.047C20.578,52.047 17.797,48.969 17.047,43.844L12.484,12.719L3.906,12.719C1.812,12.719 0,10.906 0,8.766C0,6.625 1.812,4.828 3.906,4.828L14.172,4.828C18.125,4.828 19.609,6.437 20.047,9.625L20.402,12.109L69.062,12.109C71.922,12.109 73.438,13.75 73.438,16.031C73.438,16.5 73.344,17.062 73.281,17.516L71.172,31.781C71.146,31.963 71.118,32.143 71.087,32.319L63.084,32.319C63.272,32.028 63.394,31.657 63.453,31.219L65.078,19.094L21.4,19.094L23.397,33.062L43.157,33.063C41.542,33.617 40.191,34.464 39.103,35.547C37.914,36.73 37.007,38.224 36.457,40.031L24.393,40.031L24.844,43.188C25,44.312 25.656,45.094 26.719,45.094L35.853,45.094L35.853,52.047ZM28.422,68.437C25.078,68.437 22.359,65.766 22.359,62.391C22.359,59.063 25.078,56.359 28.422,56.359C31.781,56.359 34.453,59.063 34.453,62.391C34.453,65.766 31.781,68.437 28.422,68.437Z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB |
@ -31,7 +31,6 @@ import AuthCallback from './components/App/AuthCallback.jsx'
|
|||||||
import {
|
import {
|
||||||
ProductionRoutes,
|
ProductionRoutes,
|
||||||
InventoryRoutes,
|
InventoryRoutes,
|
||||||
FinanceRoutes,
|
|
||||||
ManagementRoutes,
|
ManagementRoutes,
|
||||||
DeveloperRoutes
|
DeveloperRoutes
|
||||||
} from './routes'
|
} from './routes'
|
||||||
@ -97,7 +96,6 @@ const AppContent = () => {
|
|||||||
>
|
>
|
||||||
{ProductionRoutes}
|
{ProductionRoutes}
|
||||||
{InventoryRoutes}
|
{InventoryRoutes}
|
||||||
{FinanceRoutes}
|
|
||||||
{ManagementRoutes}
|
{ManagementRoutes}
|
||||||
{DeveloperRoutes}
|
{DeveloperRoutes}
|
||||||
</Route>
|
</Route>
|
||||||
|
|||||||
@ -1,61 +0,0 @@
|
|||||||
import { useContext } from 'react'
|
|
||||||
import { Flex } from 'antd'
|
|
||||||
import useCollapseState from '../hooks/useCollapseState'
|
|
||||||
import StatsDisplay from '../common/StatsDisplay'
|
|
||||||
import InfoCollapse from '../common/InfoCollapse'
|
|
||||||
import ScrollBox from '../common/ScrollBox'
|
|
||||||
|
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
|
||||||
|
|
||||||
const FinanceOverview = () => {
|
|
||||||
const { connected } = useContext(ApiServerContext)
|
|
||||||
|
|
||||||
const [collapseState, updateCollapseState] = useCollapseState(
|
|
||||||
'FinanceOverview',
|
|
||||||
{
|
|
||||||
invoiceStats: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!connected) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
gap='large'
|
|
||||||
vertical='true'
|
|
||||||
style={{
|
|
||||||
maxHeight: '100%',
|
|
||||||
minHeight: 0
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ScrollBox>
|
|
||||||
<Flex vertical gap={'large'}>
|
|
||||||
<InfoCollapse
|
|
||||||
title='Invoice Statistics'
|
|
||||||
icon={null}
|
|
||||||
active={collapseState.invoiceStats}
|
|
||||||
onToggle={(isActive) =>
|
|
||||||
updateCollapseState('invoiceStats', isActive)
|
|
||||||
}
|
|
||||||
className='no-t-padding-collapse'
|
|
||||||
collapseKey='invoiceStats'
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
justify='flex-start'
|
|
||||||
gap='middle'
|
|
||||||
wrap='wrap'
|
|
||||||
align='flex-start'
|
|
||||||
>
|
|
||||||
<StatsDisplay objectType='invoice' />
|
|
||||||
</Flex>
|
|
||||||
</InfoCollapse>
|
|
||||||
</Flex>
|
|
||||||
</ScrollBox>
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FinanceOverview
|
|
||||||
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
import { useLocation } from 'react-router-dom'
|
|
||||||
import DashboardSidebar from '../common/DashboardSidebar'
|
|
||||||
import InvoiceIcon from '../../Icons/InvoiceIcon'
|
|
||||||
import FinanceIcon from '../../Icons/FinanceIcon'
|
|
||||||
|
|
||||||
const items = [
|
|
||||||
{
|
|
||||||
key: 'overview',
|
|
||||||
label: 'Overview',
|
|
||||||
icon: <FinanceIcon />,
|
|
||||||
path: '/dashboard/finance/overview'
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
key: 'invoices',
|
|
||||||
label: 'Invoices',
|
|
||||||
icon: <InvoiceIcon />,
|
|
||||||
path: '/dashboard/finance/invoices'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const routeKeyMap = {
|
|
||||||
'/dashboard/finance/overview': 'overview',
|
|
||||||
'/dashboard/finance/invoices': 'invoices'
|
|
||||||
}
|
|
||||||
|
|
||||||
const FinanceSidebar = (props) => {
|
|
||||||
const location = useLocation()
|
|
||||||
const selectedKey = (() => {
|
|
||||||
const match = Object.keys(routeKeyMap).find((path) => {
|
|
||||||
const pathSplit = path.split('/')
|
|
||||||
const locationPathSplit = location.pathname.split('/')
|
|
||||||
if (pathSplit.length > locationPathSplit.length) return false
|
|
||||||
for (let i = 0; i < pathSplit.length; i++) {
|
|
||||||
if (pathSplit[i] !== locationPathSplit[i]) return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return match ? routeKeyMap[match] : 'overview'
|
|
||||||
})()
|
|
||||||
|
|
||||||
return <DashboardSidebar items={items} selectedKey={selectedKey} {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FinanceSidebar
|
|
||||||
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
import { useState, useRef } from 'react'
|
|
||||||
import { Button, Flex, Space, Dropdown, Modal } from 'antd'
|
|
||||||
import NewInvoice from './Invoices/NewInvoice'
|
|
||||||
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 Invoices = () => {
|
|
||||||
const [newInvoiceOpen, setNewInvoiceOpen] = useState(false)
|
|
||||||
const tableRef = useRef()
|
|
||||||
|
|
||||||
const [viewMode, setViewMode] = useViewMode('invoices')
|
|
||||||
|
|
||||||
const [columnVisibility, setColumnVisibility] =
|
|
||||||
useColumnVisibility('invoices')
|
|
||||||
|
|
||||||
const actionItems = {
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
label: 'New Invoice',
|
|
||||||
key: 'newInvoice',
|
|
||||||
icon: <PlusIcon />
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
label: 'Reload List',
|
|
||||||
key: 'reloadList',
|
|
||||||
icon: <ReloadIcon />
|
|
||||||
}
|
|
||||||
],
|
|
||||||
onClick: ({ key }) => {
|
|
||||||
if (key === 'reloadList') {
|
|
||||||
tableRef.current?.reload()
|
|
||||||
} else if (key === 'newInvoice') {
|
|
||||||
setNewInvoiceOpen(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Flex vertical={'true'} gap='large'>
|
|
||||||
<Flex justify={'space-between'}>
|
|
||||||
<Space size='small'>
|
|
||||||
<Dropdown menu={actionItems}>
|
|
||||||
<Button>Actions</Button>
|
|
||||||
</Dropdown>
|
|
||||||
<ColumnViewButton
|
|
||||||
type='invoice'
|
|
||||||
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='invoice'
|
|
||||||
cards={viewMode === 'cards'}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
<Modal
|
|
||||||
open={newInvoiceOpen}
|
|
||||||
styles={{ content: { paddingBottom: '24px' } }}
|
|
||||||
footer={null}
|
|
||||||
width={800}
|
|
||||||
onCancel={() => {
|
|
||||||
setNewInvoiceOpen(false)
|
|
||||||
}}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
>
|
|
||||||
<NewInvoice
|
|
||||||
onOk={() => {
|
|
||||||
setNewInvoiceOpen(false)
|
|
||||||
tableRef.current?.reload()
|
|
||||||
}}
|
|
||||||
reset={newInvoiceOpen}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Invoices
|
|
||||||
@ -1,218 +0,0 @@
|
|||||||
import { useRef, useState } from 'react'
|
|
||||||
import { useLocation } from 'react-router-dom'
|
|
||||||
import { Space, Flex, Card } 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'
|
|
||||||
|
|
||||||
const log = loglevel.getLogger('InvoiceInfo')
|
|
||||||
log.setLevel(config.logLevel)
|
|
||||||
|
|
||||||
const InvoiceInfo = () => {
|
|
||||||
const location = useLocation()
|
|
||||||
const objectFormRef = useRef(null)
|
|
||||||
const actionHandlerRef = useRef(null)
|
|
||||||
const invoiceId = new URLSearchParams(location.search).get('invoiceId')
|
|
||||||
const [collapseState, updateCollapseState] = useCollapseState(
|
|
||||||
'InvoiceInfo',
|
|
||||||
{
|
|
||||||
info: true,
|
|
||||||
notes: true,
|
|
||||||
auditLogs: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const [objectFormState, setEditFormState] = useState({
|
|
||||||
isEditing: false,
|
|
||||||
editLoading: false,
|
|
||||||
formValid: false,
|
|
||||||
lock: null,
|
|
||||||
loading: false,
|
|
||||||
objectData: {}
|
|
||||||
})
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const editDisabled = getModelByName('invoice')
|
|
||||||
?.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='invoice'
|
|
||||||
id={invoiceId}
|
|
||||||
disabled={objectFormState.loading}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
<ViewButton
|
|
||||||
disabled={objectFormState.loading}
|
|
||||||
items={[
|
|
||||||
{ key: 'info', label: 'Invoice Information' },
|
|
||||||
{ key: 'notes', label: 'Notes' },
|
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
|
||||||
]}
|
|
||||||
visibleState={collapseState}
|
|
||||||
updateVisibleState={updateCollapseState}
|
|
||||||
/>
|
|
||||||
<DocumentPrintButton
|
|
||||||
type='invoice'
|
|
||||||
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={invoiceId}
|
|
||||||
type='invoice'
|
|
||||||
style={{ height: '100%' }}
|
|
||||||
ref={objectFormRef}
|
|
||||||
onStateChange={(state) => {
|
|
||||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ loading, isEditing, objectData }) => (
|
|
||||||
<Flex vertical gap={'large'}>
|
|
||||||
<InfoCollapse
|
|
||||||
title='Invoice Information'
|
|
||||||
icon={<InfoCircleIcon />}
|
|
||||||
active={collapseState.info}
|
|
||||||
onToggle={(expanded) =>
|
|
||||||
updateCollapseState('info', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='info'
|
|
||||||
>
|
|
||||||
<ObjectInfo
|
|
||||||
loading={loading}
|
|
||||||
indicator={<LoadingOutlined />}
|
|
||||||
isEditing={isEditing}
|
|
||||||
type='invoice'
|
|
||||||
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={invoiceId} type='invoice' />
|
|
||||||
</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': invoiceId }}
|
|
||||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</InfoCollapse>
|
|
||||||
</Flex>
|
|
||||||
</ScrollBox>
|
|
||||||
</Flex>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default InvoiceInfo
|
|
||||||
|
|
||||||
@ -1,113 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import ObjectInfo from '../../common/ObjectInfo'
|
|
||||||
import NewObjectForm from '../../common/NewObjectForm'
|
|
||||||
import WizardView from '../../common/WizardView'
|
|
||||||
|
|
||||||
const NewInvoice = ({ onOk, reset, defaultValues }) => {
|
|
||||||
return (
|
|
||||||
<NewObjectForm
|
|
||||||
type={'invoice'}
|
|
||||||
reset={reset}
|
|
||||||
defaultValues={{
|
|
||||||
state: { type: 'draft' },
|
|
||||||
invoiceType: 'sales',
|
|
||||||
...defaultValues
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
|
||||||
const steps = [
|
|
||||||
{
|
|
||||||
title: 'Required',
|
|
||||||
key: 'required',
|
|
||||||
content: (
|
|
||||||
<ObjectInfo
|
|
||||||
type='invoice'
|
|
||||||
column={1}
|
|
||||||
bordered={false}
|
|
||||||
isEditing={true}
|
|
||||||
required={true}
|
|
||||||
objectData={objectData}
|
|
||||||
visibleProperties={{
|
|
||||||
orderType: true,
|
|
||||||
order: true,
|
|
||||||
vendor: true,
|
|
||||||
invoiceDate: true,
|
|
||||||
dueDate: true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Optional',
|
|
||||||
key: 'optional',
|
|
||||||
content: (
|
|
||||||
<ObjectInfo
|
|
||||||
type='invoice'
|
|
||||||
column={1}
|
|
||||||
bordered={false}
|
|
||||||
isEditing={true}
|
|
||||||
required={false}
|
|
||||||
objectData={objectData}
|
|
||||||
visibleProperties={{
|
|
||||||
relatedOrderType: true,
|
|
||||||
relatedOrder: true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Summary',
|
|
||||||
key: 'summary',
|
|
||||||
content: (
|
|
||||||
<ObjectInfo
|
|
||||||
type='invoice'
|
|
||||||
column={1}
|
|
||||||
bordered={false}
|
|
||||||
visibleProperties={{
|
|
||||||
_id: false,
|
|
||||||
createdAt: false,
|
|
||||||
updatedAt: false,
|
|
||||||
_reference: false,
|
|
||||||
totalAmount: false,
|
|
||||||
totalAmountWithTax: false,
|
|
||||||
totalTaxAmount: false,
|
|
||||||
shippingAmount: false,
|
|
||||||
shippingAmountWithTax: false,
|
|
||||||
grandTotalAmount: false,
|
|
||||||
sentAt: false,
|
|
||||||
paidAt: false,
|
|
||||||
cancelledAt: false,
|
|
||||||
overdueAt: false
|
|
||||||
}}
|
|
||||||
isEditing={false}
|
|
||||||
objectData={objectData}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
]
|
|
||||||
return (
|
|
||||||
<WizardView
|
|
||||||
steps={steps}
|
|
||||||
loading={submitLoading}
|
|
||||||
formValid={formValid}
|
|
||||||
title='New Invoice'
|
|
||||||
onSubmit={async () => {
|
|
||||||
const result = await handleSubmit()
|
|
||||||
if (result) {
|
|
||||||
onOk()
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
</NewObjectForm>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
NewInvoice.propTypes = {
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
reset: PropTypes.bool,
|
|
||||||
defaultValues: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewInvoice
|
|
||||||
@ -13,8 +13,7 @@ const InventoryOverview = () => {
|
|||||||
const [collapseState, updateCollapseState] = useCollapseState(
|
const [collapseState, updateCollapseState] = useCollapseState(
|
||||||
'InventoryOverview',
|
'InventoryOverview',
|
||||||
{
|
{
|
||||||
inventoryStats: true,
|
inventoryStats: true
|
||||||
purchaseOrderStats: true
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,27 +55,6 @@ const InventoryOverview = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|
||||||
<InfoCollapse
|
|
||||||
title='Purchase Order Statistics'
|
|
||||||
icon={null}
|
|
||||||
canCollapse={false}
|
|
||||||
active={collapseState.purchaseOrderStats}
|
|
||||||
onToggle={(isActive) =>
|
|
||||||
updateCollapseState('purchaseOrderStats', isActive)
|
|
||||||
}
|
|
||||||
className='no-t-padding-collapse'
|
|
||||||
collapseKey='purchaseOrderStats'
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
justify='flex-start'
|
|
||||||
gap='middle'
|
|
||||||
wrap='wrap'
|
|
||||||
align='flex-start'
|
|
||||||
>
|
|
||||||
<StatsDisplay objectType='purchaseOrder' />
|
|
||||||
</Flex>
|
|
||||||
</InfoCollapse>
|
|
||||||
|
|
||||||
<Flex gap='large' wrap='wrap'>
|
<Flex gap='large' wrap='wrap'>
|
||||||
<Flex flex={1} vertical style={{ minWidth: '300px' }}>
|
<Flex flex={1} vertical style={{ minWidth: '300px' }}>
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
|
|||||||
@ -8,12 +8,7 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
|
|||||||
<NewObjectForm
|
<NewObjectForm
|
||||||
type={'orderItem'}
|
type={'orderItem'}
|
||||||
reset={reset}
|
reset={reset}
|
||||||
defaultValues={{
|
defaultValues={{ syncAmount: null, quantity: 1, ...defaultValues }}
|
||||||
state: { type: 'draft' },
|
|
||||||
syncAmount: null,
|
|
||||||
quantity: 1,
|
|
||||||
...defaultValues
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
const steps = [
|
const steps = [
|
||||||
@ -59,23 +54,6 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Optional',
|
|
||||||
key: 'optional',
|
|
||||||
content: (
|
|
||||||
<ObjectInfo
|
|
||||||
type='orderItem'
|
|
||||||
column={1}
|
|
||||||
bordered={false}
|
|
||||||
isEditing={true}
|
|
||||||
required={false}
|
|
||||||
objectData={objectData}
|
|
||||||
visibleProperties={{
|
|
||||||
shipment: true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Summary',
|
title: 'Summary',
|
||||||
key: 'summary',
|
key: 'summary',
|
||||||
@ -87,8 +65,7 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
|
|||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
_id: false,
|
_id: false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
updatedAt: false,
|
updatedAt: false
|
||||||
_reference: false
|
|
||||||
}}
|
}}
|
||||||
isEditing={false}
|
isEditing={false}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
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 AcknowledgePurchaseOrder = ({ onOk, objectData }) => {
|
|
||||||
const [acknowledgeLoading, setAcknowledgeLoading] = useState(false)
|
|
||||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
|
||||||
|
|
||||||
const handleAcknowledge = async () => {
|
|
||||||
setAcknowledgeLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await sendObjectFunction(
|
|
||||||
objectData._id,
|
|
||||||
'PurchaseOrder',
|
|
||||||
'acknowledge'
|
|
||||||
)
|
|
||||||
if (result) {
|
|
||||||
message.success('Purchase order acknowledged successfully')
|
|
||||||
onOk(result)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error acknowledging purchase order:', error)
|
|
||||||
} finally {
|
|
||||||
setAcknowledgeLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MessageDialogView
|
|
||||||
title={'Are you sure you want to acknowledge this purchase order?'}
|
|
||||||
description={`Acknowledging purchase order ${objectData?.name || objectData?._reference || objectData?._id} will update its status to acknowledged.`}
|
|
||||||
onOk={handleAcknowledge}
|
|
||||||
okText='Acknowledge'
|
|
||||||
okLoading={acknowledgeLoading}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
AcknowledgePurchaseOrder.propTypes = {
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
objectData: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AcknowledgePurchaseOrder
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
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 CancelPurchaseOrder = ({ onOk, objectData }) => {
|
|
||||||
const [cancelLoading, setCancelLoading] = useState(false)
|
|
||||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
|
||||||
|
|
||||||
const handleCancel = async () => {
|
|
||||||
setCancelLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await sendObjectFunction(
|
|
||||||
objectData._id,
|
|
||||||
'PurchaseOrder',
|
|
||||||
'cancel'
|
|
||||||
)
|
|
||||||
if (result) {
|
|
||||||
message.success('Purchase order cancelled successfully')
|
|
||||||
onOk(result)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error cancelling purchase order:', error)
|
|
||||||
} finally {
|
|
||||||
setCancelLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MessageDialogView
|
|
||||||
title={'Are you sure you want to cancel this purchase order?'}
|
|
||||||
description={`Cancelling purchase order ${objectData?.name || objectData?._reference || objectData?._id} will update its status to cancelled.`}
|
|
||||||
onOk={handleCancel}
|
|
||||||
okText='Cancel'
|
|
||||||
okLoading={cancelLoading}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
CancelPurchaseOrder.propTypes = {
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
objectData: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CancelPurchaseOrder
|
|
||||||
@ -2,15 +2,15 @@ import PropTypes from 'prop-types'
|
|||||||
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'
|
||||||
|
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||||
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
|
|
||||||
const NewPurchaseOrder = ({ onOk, reset, defaultValues }) => {
|
const NewPurchaseOrder = ({ onOk, reset, defaultValues }) => {
|
||||||
return (
|
return (
|
||||||
<NewObjectForm
|
<NewObjectForm
|
||||||
type={'purchaseOrder'}
|
type={'purchaseOrder'}
|
||||||
reset={reset}
|
reset={reset}
|
||||||
defaultValues={{
|
defaultValues={{ state: { type: 'new' }, ...defaultValues }}
|
||||||
state: { type: 'draft' },
|
|
||||||
...defaultValues
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
const steps = [
|
const steps = [
|
||||||
@ -26,13 +26,24 @@ const NewPurchaseOrder = ({ onOk, reset, defaultValues }) => {
|
|||||||
required={true}
|
required={true}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
_reference: false,
|
|
||||||
items: false,
|
items: false,
|
||||||
cost: false
|
cost: false
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Items',
|
||||||
|
key: 'items',
|
||||||
|
content: (
|
||||||
|
<ObjectProperty
|
||||||
|
{...getModelProperty('purchaseOrder', 'items')}
|
||||||
|
isEditing={true}
|
||||||
|
objectData={objectData}
|
||||||
|
loading={submitLoading}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Summary',
|
title: 'Summary',
|
||||||
key: 'summary',
|
key: 'summary',
|
||||||
@ -45,15 +56,7 @@ const NewPurchaseOrder = ({ onOk, reset, defaultValues }) => {
|
|||||||
_id: false,
|
_id: false,
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
_reference: false,
|
items: false
|
||||||
totalAmount: false,
|
|
||||||
totalAmountWithTax: false,
|
|
||||||
totalTaxAmount: false,
|
|
||||||
postedAt: false,
|
|
||||||
acknowledgedAt: false,
|
|
||||||
shippingAmount: false,
|
|
||||||
shippingAmountWithTax: false,
|
|
||||||
grandTotalAmount: false
|
|
||||||
}}
|
}}
|
||||||
isEditing={false}
|
isEditing={false}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
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 PostPurchaseOrder = ({ onOk, objectData }) => {
|
|
||||||
const [postLoading, setPostLoading] = useState(false)
|
|
||||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
|
||||||
|
|
||||||
const handlePost = async () => {
|
|
||||||
setPostLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await sendObjectFunction(
|
|
||||||
objectData._id,
|
|
||||||
'PurchaseOrder',
|
|
||||||
'post'
|
|
||||||
)
|
|
||||||
if (result) {
|
|
||||||
message.success('Purchase order posted successfully')
|
|
||||||
onOk(result)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error posting purchase order:', error)
|
|
||||||
} finally {
|
|
||||||
setPostLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MessageDialogView
|
|
||||||
title={'Are you sure you want to post this purchase order?'}
|
|
||||||
description={`Posting purchase order ${objectData?.name || objectData?._reference || objectData?._id} will finalize it and update inventory levels where applicable.`}
|
|
||||||
onOk={handlePost}
|
|
||||||
okText='Post'
|
|
||||||
okLoading={postLoading}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
PostPurchaseOrder.propTypes = {
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
objectData: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PostPurchaseOrder
|
|
||||||
@ -23,12 +23,6 @@ import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
|
|||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
|
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
|
||||||
import NewOrderItem from '../OrderItems/NewOrderItem.jsx'
|
import NewOrderItem from '../OrderItems/NewOrderItem.jsx'
|
||||||
import NewShipment from '../Shipments/NewShipment.jsx'
|
|
||||||
import PostPurchaseOrder from './PostPurchaseOrder.jsx'
|
|
||||||
import AcknowledgePurchaseOrder from './AcknowledgePurchaseOrder.jsx'
|
|
||||||
import CancelPurchaseOrder from './CancelPurchaseOrder.jsx'
|
|
||||||
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
|
|
||||||
import { getModelByName } from '../../../../database/ObjectModels.js'
|
|
||||||
|
|
||||||
const log = loglevel.getLogger('PurchaseOrderInfo')
|
const log = loglevel.getLogger('PurchaseOrderInfo')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
@ -36,15 +30,8 @@ log.setLevel(config.logLevel)
|
|||||||
const PurchaseOrderInfo = () => {
|
const PurchaseOrderInfo = () => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const objectFormRef = useRef(null)
|
const objectFormRef = useRef(null)
|
||||||
const orderItemsTableRef = useRef(null)
|
|
||||||
const shipmentsTableRef = useRef(null)
|
|
||||||
const actionHandlerRef = useRef(null)
|
const actionHandlerRef = useRef(null)
|
||||||
const [newOrderItemOpen, setNewOrderItemOpen] = useState(false)
|
const [newOrderItemOpen, setNewOrderItemOpen] = useState(false)
|
||||||
const [newShipmentOpen, setNewShipmentOpen] = useState(false)
|
|
||||||
const [postPurchaseOrderOpen, setPostPurchaseOrderOpen] = useState(false)
|
|
||||||
const [acknowledgePurchaseOrderOpen, setAcknowledgePurchaseOrderOpen] =
|
|
||||||
useState(false)
|
|
||||||
const [cancelPurchaseOrderOpen, setCancelPurchaseOrderOpen] = useState(false)
|
|
||||||
const purchaseOrderId = new URLSearchParams(location.search).get(
|
const purchaseOrderId = new URLSearchParams(location.search).get(
|
||||||
'purchaseOrderId'
|
'purchaseOrderId'
|
||||||
)
|
)
|
||||||
@ -72,17 +59,14 @@ const PurchaseOrderInfo = () => {
|
|||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
edit: () => {
|
edit: () => {
|
||||||
orderItemsTableRef?.current?.startEditing?.()
|
|
||||||
objectFormRef?.current?.startEditing?.()
|
objectFormRef?.current?.startEditing?.()
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
cancelEdit: () => {
|
cancelEdit: () => {
|
||||||
orderItemsTableRef?.current?.cancelEditing?.()
|
|
||||||
objectFormRef?.current?.cancelEditing?.()
|
objectFormRef?.current?.cancelEditing?.()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
finishEdit: () => {
|
finishEdit: () => {
|
||||||
orderItemsTableRef?.current?.handleUpdate?.()
|
|
||||||
objectFormRef?.current?.handleUpdate?.()
|
objectFormRef?.current?.handleUpdate?.()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
@ -93,29 +77,9 @@ const PurchaseOrderInfo = () => {
|
|||||||
newOrderItem: () => {
|
newOrderItem: () => {
|
||||||
setNewOrderItemOpen(true)
|
setNewOrderItemOpen(true)
|
||||||
return true
|
return true
|
||||||
},
|
|
||||||
newShipment: () => {
|
|
||||||
setNewShipmentOpen(true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
post: () => {
|
|
||||||
setPostPurchaseOrderOpen(true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
acknowledge: () => {
|
|
||||||
setAcknowledgePurchaseOrderOpen(true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
cancel: () => {
|
|
||||||
setCancelPurchaseOrderOpen(true)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const editDisabled = getModelByName('purchaseOrder')
|
|
||||||
.actions.find((action) => action.name === 'edit')
|
|
||||||
.disabled(objectFormState.objectData)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex
|
<Flex
|
||||||
@ -139,8 +103,6 @@ const PurchaseOrderInfo = () => {
|
|||||||
disabled={objectFormState.loading}
|
disabled={objectFormState.loading}
|
||||||
items={[
|
items={[
|
||||||
{ key: 'info', label: 'Purchase Order Information' },
|
{ key: 'info', label: 'Purchase Order Information' },
|
||||||
{ key: 'orderItems', label: 'Order Items' },
|
|
||||||
{ key: 'shipments', label: 'Shipments' },
|
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
]}
|
]}
|
||||||
@ -169,11 +131,7 @@ const PurchaseOrderInfo = () => {
|
|||||||
}}
|
}}
|
||||||
editLoading={objectFormState.editLoading}
|
editLoading={objectFormState.editLoading}
|
||||||
formValid={objectFormState.formValid}
|
formValid={objectFormState.formValid}
|
||||||
disabled={
|
disabled={objectFormState.lock?.locked || objectFormState.loading}
|
||||||
objectFormState.lock?.locked ||
|
|
||||||
objectFormState.loading ||
|
|
||||||
editDisabled
|
|
||||||
}
|
|
||||||
loading={objectFormState.editLoading}
|
loading={objectFormState.editLoading}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
@ -211,7 +169,6 @@ const PurchaseOrderInfo = () => {
|
|||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
type='purchaseOrder'
|
type='purchaseOrder'
|
||||||
labelWidth='225px'
|
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
items: false
|
items: false
|
||||||
@ -221,39 +178,16 @@ const PurchaseOrderInfo = () => {
|
|||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
title='Purchase Order Items'
|
title='Purchase Order Items'
|
||||||
icon={<OrderItemsIcon />}
|
icon={<OrderItemsIcon />}
|
||||||
active={collapseState.orderItems}
|
active={collapseState.info}
|
||||||
onToggle={(expanded) =>
|
onToggle={(expanded) =>
|
||||||
updateCollapseState('orderItems', expanded)
|
updateCollapseState('info', expanded)
|
||||||
}
|
}
|
||||||
collapseKey='orderItems'
|
collapseKey='info'
|
||||||
>
|
>
|
||||||
<ObjectTable
|
<ObjectTable
|
||||||
type='orderItem'
|
type='orderItem'
|
||||||
masterFilter={{
|
masterFilter={{ 'order._id': purchaseOrderId }}
|
||||||
'order._id': purchaseOrderId,
|
|
||||||
orderType: 'purchaseOrder'
|
|
||||||
}}
|
|
||||||
visibleColumns={{ order: false }}
|
visibleColumns={{ order: false }}
|
||||||
ref={orderItemsTableRef}
|
|
||||||
/>
|
|
||||||
</InfoCollapse>
|
|
||||||
<InfoCollapse
|
|
||||||
title='Shipments'
|
|
||||||
icon={<ShipmentIcon />}
|
|
||||||
active={collapseState.shipments}
|
|
||||||
onToggle={(expanded) =>
|
|
||||||
updateCollapseState('shipments', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='shipments'
|
|
||||||
>
|
|
||||||
<ObjectTable
|
|
||||||
type='shipment'
|
|
||||||
masterFilter={{
|
|
||||||
'order._id': purchaseOrderId,
|
|
||||||
orderType: 'purchaseOrder'
|
|
||||||
}}
|
|
||||||
visibleColumns={{ order: false }}
|
|
||||||
ref={shipmentsTableRef}
|
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -314,80 +248,6 @@ const PurchaseOrderInfo = () => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
<Modal
|
|
||||||
open={newShipmentOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setNewShipmentOpen(false)
|
|
||||||
}}
|
|
||||||
width={800}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
>
|
|
||||||
<NewShipment
|
|
||||||
onOk={() => {
|
|
||||||
setNewShipmentOpen(false)
|
|
||||||
}}
|
|
||||||
reset={newShipmentOpen}
|
|
||||||
defaultValues={{
|
|
||||||
orderType: 'purchaseOrder',
|
|
||||||
order: { _id: purchaseOrderId }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={postPurchaseOrderOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setPostPurchaseOrderOpen(false)
|
|
||||||
}}
|
|
||||||
width={500}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
centered={true}
|
|
||||||
>
|
|
||||||
<PostPurchaseOrder
|
|
||||||
onOk={() => {
|
|
||||||
setPostPurchaseOrderOpen(false)
|
|
||||||
actions.reload()
|
|
||||||
}}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={acknowledgePurchaseOrderOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setAcknowledgePurchaseOrderOpen(false)
|
|
||||||
}}
|
|
||||||
width={515}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
centered={true}
|
|
||||||
>
|
|
||||||
<AcknowledgePurchaseOrder
|
|
||||||
onOk={() => {
|
|
||||||
setAcknowledgePurchaseOrderOpen(false)
|
|
||||||
actions.reload()
|
|
||||||
}}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={cancelPurchaseOrderOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setCancelPurchaseOrderOpen(false)
|
|
||||||
}}
|
|
||||||
width={515}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
centered={true}
|
|
||||||
>
|
|
||||||
<CancelPurchaseOrder
|
|
||||||
onOk={() => {
|
|
||||||
setCancelPurchaseOrderOpen(false)
|
|
||||||
actions.reload()
|
|
||||||
}}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,46 +0,0 @@
|
|||||||
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 CancelShipment = ({ onOk, objectData }) => {
|
|
||||||
const [cancelLoading, setCancelLoading] = useState(false)
|
|
||||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
|
||||||
|
|
||||||
const handleCancel = async () => {
|
|
||||||
setCancelLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await sendObjectFunction(
|
|
||||||
objectData._id,
|
|
||||||
'Shipment',
|
|
||||||
'cancel'
|
|
||||||
)
|
|
||||||
if (result) {
|
|
||||||
message.success('Shipment cancelled successfully')
|
|
||||||
onOk(result)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error cancelling shipment:', error)
|
|
||||||
} finally {
|
|
||||||
setCancelLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MessageDialogView
|
|
||||||
title={'Are you sure you want to cancel this shipment?'}
|
|
||||||
description={`Cancelling shipment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to cancelled.`}
|
|
||||||
onOk={handleCancel}
|
|
||||||
okText='Cancel'
|
|
||||||
okLoading={cancelLoading}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
CancelShipment.propTypes = {
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
objectData: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default CancelShipment
|
|
||||||
@ -2,13 +2,15 @@ import PropTypes from 'prop-types'
|
|||||||
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'
|
||||||
|
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||||
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
|
|
||||||
const NewShipment = ({ onOk, reset, defaultValues }) => {
|
const NewShipment = ({ onOk, reset, defaultValues }) => {
|
||||||
return (
|
return (
|
||||||
<NewObjectForm
|
<NewObjectForm
|
||||||
type={'shipment'}
|
type={'shipment'}
|
||||||
reset={reset}
|
reset={reset}
|
||||||
defaultValues={{ state: { type: 'draft' }, ...defaultValues }}
|
defaultValues={{ state: { type: 'pending' }, ...defaultValues }}
|
||||||
>
|
>
|
||||||
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
{({ handleSubmit, submitLoading, objectData, formValid }) => {
|
||||||
const steps = [
|
const steps = [
|
||||||
@ -24,48 +26,25 @@ const NewShipment = ({ onOk, reset, defaultValues }) => {
|
|||||||
required={true}
|
required={true}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
amount: false,
|
items: false,
|
||||||
taxRate: false,
|
cost: false,
|
||||||
taxAmount: false,
|
shippedDate: false,
|
||||||
amountWithTax: false,
|
expectedDeliveryDate: false,
|
||||||
syncAmount: false
|
actualDeliveryDate: false,
|
||||||
|
notes: false
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Pricing',
|
title: 'Items',
|
||||||
key: 'pricing',
|
key: 'items',
|
||||||
content: (
|
content: (
|
||||||
<ObjectInfo
|
<ObjectProperty
|
||||||
type='shipment'
|
{...getModelProperty('shipment', 'items')}
|
||||||
column={1}
|
|
||||||
bordered={false}
|
|
||||||
isEditing={true}
|
isEditing={true}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
visibleProperties={{
|
loading={submitLoading}
|
||||||
amount: true,
|
|
||||||
taxRate: true,
|
|
||||||
taxAmount: true,
|
|
||||||
amountWithTax: true,
|
|
||||||
syncAmount: true
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Optional',
|
|
||||||
key: 'optional',
|
|
||||||
content: (
|
|
||||||
<ObjectInfo
|
|
||||||
type='shipment'
|
|
||||||
column={1}
|
|
||||||
bordered={false}
|
|
||||||
isEditing={true}
|
|
||||||
objectData={objectData}
|
|
||||||
visibleProperties={{
|
|
||||||
trackingNumber: true
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -79,13 +58,9 @@ const NewShipment = ({ onOk, reset, defaultValues }) => {
|
|||||||
bordered={false}
|
bordered={false}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
_id: false,
|
_id: false,
|
||||||
_reference: false,
|
|
||||||
createdAt: false,
|
createdAt: false,
|
||||||
updatedAt: false,
|
updatedAt: false,
|
||||||
shippedAt: false,
|
items: false
|
||||||
expectedAt: false,
|
|
||||||
deliveredAt: false,
|
|
||||||
taxRecord: false
|
|
||||||
}}
|
}}
|
||||||
isEditing={false}
|
isEditing={false}
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
|
|||||||
@ -1,362 +0,0 @@
|
|||||||
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 OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
|
|
||||||
import NewOrderItem from '../OrderItems/NewOrderItem.jsx'
|
|
||||||
import NewShipment from './NewShipment.jsx'
|
|
||||||
import PostPurchaseOrder from '../PurchaseOrders/PostPurchaseOrder.jsx'
|
|
||||||
import AcknowledgePurchaseOrder from '../PurchaseOrders/AcknowledgePurchaseOrder.jsx'
|
|
||||||
import ShipmentIcon from '../../../Icons/ShipmentIcon.jsx'
|
|
||||||
|
|
||||||
const log = loglevel.getLogger('PurchaseOrderInfo')
|
|
||||||
log.setLevel(config.logLevel)
|
|
||||||
|
|
||||||
const PurchaseOrderInfo = () => {
|
|
||||||
const location = useLocation()
|
|
||||||
const objectFormRef = useRef(null)
|
|
||||||
const orderItemsTableRef = useRef(null)
|
|
||||||
const shipmentsTableRef = useRef(null)
|
|
||||||
const actionHandlerRef = useRef(null)
|
|
||||||
const [newOrderItemOpen, setNewOrderItemOpen] = useState(false)
|
|
||||||
const [newShipmentOpen, setNewShipmentOpen] = useState(false)
|
|
||||||
const [postPurchaseOrderOpen, setPostPurchaseOrderOpen] = useState(false)
|
|
||||||
const [acknowledgePurchaseOrderOpen, setAcknowledgePurchaseOrderOpen] =
|
|
||||||
useState(false)
|
|
||||||
const purchaseOrderId = new URLSearchParams(location.search).get(
|
|
||||||
'purchaseOrderId'
|
|
||||||
)
|
|
||||||
const [collapseState, updateCollapseState] = useCollapseState(
|
|
||||||
'PurchaseOrderInfo',
|
|
||||||
{
|
|
||||||
info: true,
|
|
||||||
notes: true,
|
|
||||||
auditLogs: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const [objectFormState, setEditFormState] = useState({
|
|
||||||
isEditing: false,
|
|
||||||
editLoading: false,
|
|
||||||
formValid: false,
|
|
||||||
lock: null,
|
|
||||||
loading: false,
|
|
||||||
objectData: {}
|
|
||||||
})
|
|
||||||
|
|
||||||
const actions = {
|
|
||||||
reload: () => {
|
|
||||||
objectFormRef?.current?.handleFetchObject?.()
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
edit: () => {
|
|
||||||
orderItemsTableRef?.current?.startEditing?.()
|
|
||||||
objectFormRef?.current?.startEditing?.()
|
|
||||||
return false
|
|
||||||
},
|
|
||||||
cancelEdit: () => {
|
|
||||||
orderItemsTableRef?.current?.cancelEditing?.()
|
|
||||||
objectFormRef?.current?.cancelEditing?.()
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
finishEdit: () => {
|
|
||||||
orderItemsTableRef?.current?.handleUpdate?.()
|
|
||||||
objectFormRef?.current?.handleUpdate?.()
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
delete: () => {
|
|
||||||
objectFormRef?.current?.handleDelete?.()
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
newOrderItem: () => {
|
|
||||||
setNewOrderItemOpen(true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
newShipment: () => {
|
|
||||||
setNewShipmentOpen(true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
post: () => {
|
|
||||||
setPostPurchaseOrderOpen(true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
acknowledge: () => {
|
|
||||||
setAcknowledgePurchaseOrderOpen(true)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Flex
|
|
||||||
gap='large'
|
|
||||||
vertical='true'
|
|
||||||
style={{
|
|
||||||
maxHeight: '100%',
|
|
||||||
minHeight: 0
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex justify={'space-between'}>
|
|
||||||
<Space size='middle'>
|
|
||||||
<Space size='small'>
|
|
||||||
<ObjectActions
|
|
||||||
type='purchaseOrder'
|
|
||||||
id={purchaseOrderId}
|
|
||||||
disabled={objectFormState.loading}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
<ViewButton
|
|
||||||
disabled={objectFormState.loading}
|
|
||||||
items={[
|
|
||||||
{ key: 'info', label: 'Purchase Order Information' },
|
|
||||||
{ key: 'orderItems', label: 'Order Items' },
|
|
||||||
{ key: 'shipments', label: 'Shipments' },
|
|
||||||
{ key: 'notes', label: 'Notes' },
|
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
|
||||||
]}
|
|
||||||
visibleState={collapseState}
|
|
||||||
updateVisibleState={updateCollapseState}
|
|
||||||
/>
|
|
||||||
<DocumentPrintButton
|
|
||||||
type='purchaseOrder'
|
|
||||||
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}
|
|
||||||
loading={objectFormState.editLoading}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
<ScrollBox>
|
|
||||||
<Flex vertical gap={'large'}>
|
|
||||||
<ActionHandler
|
|
||||||
actions={actions}
|
|
||||||
loading={objectFormState.loading}
|
|
||||||
ref={actionHandlerRef}
|
|
||||||
>
|
|
||||||
<ObjectForm
|
|
||||||
id={purchaseOrderId}
|
|
||||||
type='purchaseOrder'
|
|
||||||
style={{ height: '100%' }}
|
|
||||||
ref={objectFormRef}
|
|
||||||
onStateChange={(state) => {
|
|
||||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{({ loading, isEditing, objectData }) => (
|
|
||||||
<Flex vertical gap={'large'}>
|
|
||||||
<InfoCollapse
|
|
||||||
title='Purchase Order Information'
|
|
||||||
icon={<InfoCircleIcon />}
|
|
||||||
active={collapseState.info}
|
|
||||||
onToggle={(expanded) =>
|
|
||||||
updateCollapseState('info', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='info'
|
|
||||||
>
|
|
||||||
<ObjectInfo
|
|
||||||
loading={loading}
|
|
||||||
indicator={<LoadingOutlined />}
|
|
||||||
isEditing={isEditing}
|
|
||||||
type='purchaseOrder'
|
|
||||||
labelWidth='225px'
|
|
||||||
objectData={objectData}
|
|
||||||
visibleProperties={{
|
|
||||||
items: false
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</InfoCollapse>
|
|
||||||
<InfoCollapse
|
|
||||||
title='Purchase Order Items'
|
|
||||||
icon={<OrderItemsIcon />}
|
|
||||||
active={collapseState.orderItems}
|
|
||||||
onToggle={(expanded) =>
|
|
||||||
updateCollapseState('orderItems', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='orderItems'
|
|
||||||
>
|
|
||||||
<ObjectTable
|
|
||||||
type='orderItem'
|
|
||||||
masterFilter={{
|
|
||||||
'order._id': purchaseOrderId,
|
|
||||||
orderType: 'purchaseOrder'
|
|
||||||
}}
|
|
||||||
visibleColumns={{ order: false }}
|
|
||||||
ref={orderItemsTableRef}
|
|
||||||
/>
|
|
||||||
</InfoCollapse>
|
|
||||||
<InfoCollapse
|
|
||||||
title='Shipments'
|
|
||||||
icon={<ShipmentIcon />}
|
|
||||||
active={collapseState.shipments}
|
|
||||||
onToggle={(expanded) =>
|
|
||||||
updateCollapseState('shipments', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='shipments'
|
|
||||||
>
|
|
||||||
<ObjectTable
|
|
||||||
type='shipment'
|
|
||||||
masterFilter={{
|
|
||||||
'order._id': purchaseOrderId,
|
|
||||||
orderType: 'purchaseOrder'
|
|
||||||
}}
|
|
||||||
visibleColumns={{ order: false }}
|
|
||||||
ref={shipmentsTableRef}
|
|
||||||
/>
|
|
||||||
</InfoCollapse>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</ObjectForm>
|
|
||||||
</ActionHandler>
|
|
||||||
<InfoCollapse
|
|
||||||
title='Notes'
|
|
||||||
icon={<NoteIcon />}
|
|
||||||
active={collapseState.notes}
|
|
||||||
onToggle={(expanded) => updateCollapseState('notes', expanded)}
|
|
||||||
collapseKey='notes'
|
|
||||||
>
|
|
||||||
<Card>
|
|
||||||
<NotesPanel _id={purchaseOrderId} type='purchaseOrder' />
|
|
||||||
</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': purchaseOrderId }}
|
|
||||||
visibleColumns={{ _id: false, 'parent._id': false }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</InfoCollapse>
|
|
||||||
</Flex>
|
|
||||||
</ScrollBox>
|
|
||||||
</Flex>
|
|
||||||
<Modal
|
|
||||||
open={newOrderItemOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setNewOrderItemOpen(false)
|
|
||||||
}}
|
|
||||||
width={800}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
>
|
|
||||||
<NewOrderItem
|
|
||||||
onOk={() => {
|
|
||||||
setNewOrderItemOpen(false)
|
|
||||||
}}
|
|
||||||
reset={newOrderItemOpen}
|
|
||||||
defaultValues={{
|
|
||||||
order: { _id: purchaseOrderId },
|
|
||||||
orderType: 'purchaseOrder',
|
|
||||||
syncAmount: 'itemCost'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={newShipmentOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setNewShipmentOpen(false)
|
|
||||||
}}
|
|
||||||
width={800}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
>
|
|
||||||
<NewShipment
|
|
||||||
onOk={() => {
|
|
||||||
setNewShipmentOpen(false)
|
|
||||||
}}
|
|
||||||
reset={newShipmentOpen}
|
|
||||||
defaultValues={{
|
|
||||||
orderType: 'purchaseOrder',
|
|
||||||
order: { _id: purchaseOrderId }
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={postPurchaseOrderOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setPostPurchaseOrderOpen(false)
|
|
||||||
}}
|
|
||||||
width={500}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
centered={true}
|
|
||||||
>
|
|
||||||
<PostPurchaseOrder
|
|
||||||
onOk={() => {
|
|
||||||
setPostPurchaseOrderOpen(false)
|
|
||||||
actions.reload()
|
|
||||||
}}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={acknowledgePurchaseOrderOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setAcknowledgePurchaseOrderOpen(false)
|
|
||||||
}}
|
|
||||||
width={515}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
centered={true}
|
|
||||||
>
|
|
||||||
<AcknowledgePurchaseOrder
|
|
||||||
onOk={() => {
|
|
||||||
setAcknowledgePurchaseOrderOpen(false)
|
|
||||||
actions.reload()
|
|
||||||
}}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PurchaseOrderInfo
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
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 ReceiveShipment = ({ onOk, objectData }) => {
|
|
||||||
const [receiveLoading, setReceiveLoading] = useState(false)
|
|
||||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
|
||||||
|
|
||||||
const handleReceive = async () => {
|
|
||||||
setReceiveLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await sendObjectFunction(
|
|
||||||
objectData._id,
|
|
||||||
'Shipment',
|
|
||||||
'receive'
|
|
||||||
)
|
|
||||||
if (result) {
|
|
||||||
message.success('Shipment received successfully')
|
|
||||||
onOk(result)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error receiving shipment:', error)
|
|
||||||
} finally {
|
|
||||||
setReceiveLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MessageDialogView
|
|
||||||
title={'Are you sure you want to receive this shipment?'}
|
|
||||||
description={`Receiving shipment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to delivered.`}
|
|
||||||
onOk={handleReceive}
|
|
||||||
okText='Receive'
|
|
||||||
okLoading={receiveLoading}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ReceiveShipment.propTypes = {
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
objectData: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ReceiveShipment
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
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 ShipShipment = ({ onOk, objectData }) => {
|
|
||||||
const [shipLoading, setShipLoading] = useState(false)
|
|
||||||
const { sendObjectFunction } = useContext(ApiServerContext)
|
|
||||||
|
|
||||||
const handleShip = async () => {
|
|
||||||
setShipLoading(true)
|
|
||||||
try {
|
|
||||||
const result = await sendObjectFunction(
|
|
||||||
objectData._id,
|
|
||||||
'Shipment',
|
|
||||||
'ship'
|
|
||||||
)
|
|
||||||
if (result) {
|
|
||||||
message.success('Shipment shipped successfully')
|
|
||||||
onOk(result)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error shipping shipment:', error)
|
|
||||||
} finally {
|
|
||||||
setShipLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MessageDialogView
|
|
||||||
title={'Are you sure you want to ship this shipment?'}
|
|
||||||
description={`Shipping shipment ${objectData?.name || objectData?._reference || objectData?._id} will update its status to shipped.`}
|
|
||||||
onOk={handleShip}
|
|
||||||
okText='Ship'
|
|
||||||
okLoading={shipLoading}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
ShipShipment.propTypes = {
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
objectData: PropTypes.object
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ShipShipment
|
|
||||||
@ -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, Modal } from 'antd'
|
import { Space, Flex, Card } 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'
|
||||||
@ -21,10 +21,9 @@ 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 OrderItemIcon from '../../../Icons/OrderItemIcon.jsx'
|
import { getModelProperty } from '../../../../database/ObjectModels.js'
|
||||||
import ShipShipment from './ShipShipment.jsx'
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
import ReceiveShipment from './ReceiveShipment.jsx'
|
import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx'
|
||||||
import CancelShipment from './CancelShipment.jsx'
|
|
||||||
|
|
||||||
const log = loglevel.getLogger('ShipmentInfo')
|
const log = loglevel.getLogger('ShipmentInfo')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
@ -52,9 +51,6 @@ const ShipmentInfo = () => {
|
|||||||
objectData: {}
|
objectData: {}
|
||||||
})
|
})
|
||||||
|
|
||||||
const [shipShipmentOpen, setShipShipmentOpen] = useState(false)
|
|
||||||
const [receiveShipmentOpen, setReceiveShipmentOpen] = useState(false)
|
|
||||||
const [cancelShipmentOpen, setCancelShipmentOpen] = useState(false)
|
|
||||||
const actions = {
|
const actions = {
|
||||||
reload: () => {
|
reload: () => {
|
||||||
objectFormRef?.current?.handleFetchObject?.()
|
objectFormRef?.current?.handleFetchObject?.()
|
||||||
@ -75,18 +71,6 @@ const ShipmentInfo = () => {
|
|||||||
delete: () => {
|
delete: () => {
|
||||||
objectFormRef?.current?.handleDelete?.()
|
objectFormRef?.current?.handleDelete?.()
|
||||||
return true
|
return true
|
||||||
},
|
|
||||||
ship: () => {
|
|
||||||
setShipShipmentOpen(true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
receive: () => {
|
|
||||||
setReceiveShipmentOpen(true)
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
cancel: () => {
|
|
||||||
setCancelShipmentOpen(true)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,26 +167,22 @@ const ShipmentInfo = () => {
|
|||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
items: false
|
items: false
|
||||||
}}
|
}}
|
||||||
labelWidth='175px'
|
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
title='Shipment Order Items'
|
title='Shipment Items'
|
||||||
icon={<OrderItemIcon />}
|
icon={<OrderItemsIcon />}
|
||||||
active={collapseState.orderItems}
|
active={collapseState.info}
|
||||||
onToggle={(expanded) =>
|
onToggle={(expanded) =>
|
||||||
updateCollapseState('orderItems', expanded)
|
updateCollapseState('info', expanded)
|
||||||
}
|
}
|
||||||
collapseKey='orderItems'
|
collapseKey='info'
|
||||||
>
|
>
|
||||||
<ObjectTable
|
<ObjectProperty
|
||||||
type='orderItem'
|
{...getModelProperty('shipment', 'items')}
|
||||||
masterFilter={{ 'shipment._id': shipmentId }}
|
isEditing={isEditing}
|
||||||
visibleColumns={{
|
objectData={objectData}
|
||||||
shipment: false,
|
loading={loading}
|
||||||
order: false,
|
|
||||||
orderType: false
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -242,60 +222,6 @@ const ShipmentInfo = () => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
</ScrollBox>
|
</ScrollBox>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Modal
|
|
||||||
open={shipShipmentOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setShipShipmentOpen(false)
|
|
||||||
}}
|
|
||||||
width={515}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
centered={true}
|
|
||||||
>
|
|
||||||
<ShipShipment
|
|
||||||
onOk={() => {
|
|
||||||
setShipShipmentOpen(false)
|
|
||||||
actions.reload()
|
|
||||||
}}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={receiveShipmentOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setReceiveShipmentOpen(false)
|
|
||||||
}}
|
|
||||||
width={515}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
centered={true}
|
|
||||||
>
|
|
||||||
<ReceiveShipment
|
|
||||||
onOk={() => {
|
|
||||||
setReceiveShipmentOpen(false)
|
|
||||||
actions.reload()
|
|
||||||
}}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
<Modal
|
|
||||||
open={cancelShipmentOpen}
|
|
||||||
onCancel={() => {
|
|
||||||
setCancelShipmentOpen(false)
|
|
||||||
}}
|
|
||||||
width={515}
|
|
||||||
footer={null}
|
|
||||||
destroyOnHidden={true}
|
|
||||||
centered={true}
|
|
||||||
>
|
|
||||||
<CancelShipment
|
|
||||||
onOk={() => {
|
|
||||||
setCancelShipmentOpen(false)
|
|
||||||
actions.reload()
|
|
||||||
}}
|
|
||||||
objectData={objectFormState.objectData}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { Layout, Flex } from 'antd'
|
|||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
import ProductionSidebar from './Production/ProductionSidebar'
|
import ProductionSidebar from './Production/ProductionSidebar'
|
||||||
import InventorySidebar from './Inventory/InventorySidebar'
|
import InventorySidebar from './Inventory/InventorySidebar'
|
||||||
import FinanceSidebar from './Finance/FinanceSidebar'
|
|
||||||
import ManagementSidebar from './Management/ManagementSidebar'
|
import ManagementSidebar from './Management/ManagementSidebar'
|
||||||
import DashboardNavigation from './common/DashboardNavigation'
|
import DashboardNavigation from './common/DashboardNavigation'
|
||||||
import DashboardBreadcrumb from './common/DashboardBreadcrumb'
|
import DashboardBreadcrumb from './common/DashboardBreadcrumb'
|
||||||
@ -18,7 +17,6 @@ const DashboardLayout = ({ children }) => {
|
|||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const isProduction = location.pathname.startsWith('/dashboard/production')
|
const isProduction = location.pathname.startsWith('/dashboard/production')
|
||||||
const isInventory = location.pathname.startsWith('/dashboard/inventory')
|
const isInventory = location.pathname.startsWith('/dashboard/inventory')
|
||||||
const isFinance = location.pathname.startsWith('/dashboard/finance')
|
|
||||||
const isManagement = location.pathname.startsWith('/dashboard/management')
|
const isManagement = location.pathname.startsWith('/dashboard/management')
|
||||||
const isDeveloper = location.pathname.startsWith('/dashboard/developer')
|
const isDeveloper = location.pathname.startsWith('/dashboard/developer')
|
||||||
|
|
||||||
@ -36,8 +34,6 @@ const DashboardLayout = ({ children }) => {
|
|||||||
<ProductionSidebar />
|
<ProductionSidebar />
|
||||||
) : isInventory ? (
|
) : isInventory ? (
|
||||||
<InventorySidebar />
|
<InventorySidebar />
|
||||||
) : isFinance ? (
|
|
||||||
<FinanceSidebar />
|
|
||||||
) : isManagement ? (
|
) : isManagement ? (
|
||||||
<ManagementSidebar />
|
<ManagementSidebar />
|
||||||
) : isDeveloper ? (
|
) : isDeveloper ? (
|
||||||
|
|||||||
@ -10,7 +10,6 @@ const breadcrumbNameMap = {
|
|||||||
inventory: 'Inventory',
|
inventory: 'Inventory',
|
||||||
management: 'Management',
|
management: 'Management',
|
||||||
developer: 'Developer',
|
developer: 'Developer',
|
||||||
finance: 'Finance',
|
|
||||||
overview: 'Overview',
|
overview: 'Overview',
|
||||||
info: 'Info',
|
info: 'Info',
|
||||||
design: 'Design',
|
design: 'Design',
|
||||||
|
|||||||
@ -30,7 +30,6 @@ import FarmControlLogoSmall from '../../Logos/FarmControlLogoSmall'
|
|||||||
import MenuIcon from '../../Icons/MenuIcon'
|
import MenuIcon from '../../Icons/MenuIcon'
|
||||||
import ProductionIcon from '../../Icons/ProductionIcon'
|
import ProductionIcon from '../../Icons/ProductionIcon'
|
||||||
import InventoryIcon from '../../Icons/InventoryIcon'
|
import InventoryIcon from '../../Icons/InventoryIcon'
|
||||||
import FinanceIcon from '../../Icons/FinanceIcon'
|
|
||||||
import PersonIcon from '../../Icons/PersonIcon'
|
import PersonIcon from '../../Icons/PersonIcon'
|
||||||
import CloudIcon from '../../Icons/CloudIcon'
|
import CloudIcon from '../../Icons/CloudIcon'
|
||||||
import BellIcon from '../../Icons/BellIcon'
|
import BellIcon from '../../Icons/BellIcon'
|
||||||
@ -70,11 +69,6 @@ const DashboardNavigation = () => {
|
|||||||
label: 'Inventory',
|
label: 'Inventory',
|
||||||
icon: <InventoryIcon />
|
icon: <InventoryIcon />
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'finance',
|
|
||||||
label: 'Finance',
|
|
||||||
icon: <FinanceIcon />
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'management',
|
key: 'management',
|
||||||
label: 'Management',
|
label: 'Management',
|
||||||
@ -136,8 +130,6 @@ const DashboardNavigation = () => {
|
|||||||
navigate('/dashboard/production/overview')
|
navigate('/dashboard/production/overview')
|
||||||
} else if (key === 'inventory') {
|
} else if (key === 'inventory') {
|
||||||
navigate('/dashboard/inventory/overview')
|
navigate('/dashboard/inventory/overview')
|
||||||
} else if (key === 'finance') {
|
|
||||||
navigate('/dashboard/finance/overview')
|
|
||||||
} else if (key === 'management') {
|
} else if (key === 'management') {
|
||||||
navigate('/dashboard/management/filaments')
|
navigate('/dashboard/management/filaments')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -253,15 +253,10 @@ const HistoryDisplay = ({
|
|||||||
// Create color mapping from model stats
|
// Create color mapping from model stats
|
||||||
const colors = {
|
const colors = {
|
||||||
success: themeColors.colorSuccess,
|
success: themeColors.colorSuccess,
|
||||||
processing: themeColors.colorInfo,
|
info: themeColors.colorInfo,
|
||||||
error: themeColors.colorError,
|
error: themeColors.colorError,
|
||||||
warning: themeColors.colorWarning,
|
warning: themeColors.colorWarning,
|
||||||
default: '#8c8c8c',
|
default: '#8c8c8c'
|
||||||
cyan: themeColors.colorCyan,
|
|
||||||
pink: themeColors.colorPink,
|
|
||||||
purple: themeColors.colorPurple,
|
|
||||||
magenta: themeColors.colorMagenta,
|
|
||||||
volcano: themeColors.colorVolcano
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build color range array based on model stats order
|
// Build color range array based on model stats order
|
||||||
|
|||||||
@ -1,40 +0,0 @@
|
|||||||
import PropTypes from 'prop-types'
|
|
||||||
import { Flex, Typography, Button } from 'antd'
|
|
||||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx'
|
|
||||||
|
|
||||||
const { Text } = Typography
|
|
||||||
|
|
||||||
const MessageDialogView = ({
|
|
||||||
icon,
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
onOk,
|
|
||||||
okText,
|
|
||||||
okLoading
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<Flex vertical gap={'middle'}>
|
|
||||||
<Flex gap={'middle'}>
|
|
||||||
{icon || <InfoCircleIcon />}
|
|
||||||
<Text strong>{title}</Text>
|
|
||||||
</Flex>
|
|
||||||
{description && <Text>{description}</Text>}
|
|
||||||
<Flex justify={'end'}>
|
|
||||||
<Button type='primary' onClick={onOk} loading={okLoading}>
|
|
||||||
{okText || 'OK'}
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDialogView.propTypes = {
|
|
||||||
icon: PropTypes.node,
|
|
||||||
title: PropTypes.node.isRequired,
|
|
||||||
description: PropTypes.node,
|
|
||||||
onOk: PropTypes.func.isRequired,
|
|
||||||
okText: PropTypes.string,
|
|
||||||
okLoading: PropTypes.bool
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MessageDialogView
|
|
||||||
@ -31,10 +31,7 @@ const buildObjectFromEntries = (entries = []) => {
|
|||||||
* }) => ReactNode
|
* }) => ReactNode
|
||||||
*/
|
*/
|
||||||
const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
const NewObjectForm = ({ type, style, defaultValues = {}, children }) => {
|
||||||
const [objectData, setObjectData] = useState({
|
const [objectData, setObjectData] = useState(defaultValues)
|
||||||
...defaultValues,
|
|
||||||
_isEditing: true
|
|
||||||
})
|
|
||||||
const [submitLoading, setSubmitLoading] = useState(false)
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
const [formValid, setFormValid] = useState(false)
|
const [formValid, setFormValid] = useState(false)
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import DeleteObjectModal from './DeleteObjectModal'
|
|||||||
import merge from 'lodash/merge'
|
import merge from 'lodash/merge'
|
||||||
import set from 'lodash/set'
|
import set from 'lodash/set'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import { useNavigate } from 'react-router-dom'
|
|
||||||
|
|
||||||
const buildObjectFromEntries = (entries = []) => {
|
const buildObjectFromEntries = (entries = []) => {
|
||||||
return entries.reduce((acc, entry) => {
|
return entries.reduce((acc, entry) => {
|
||||||
@ -73,7 +72,7 @@ const ObjectForm = forwardRef(
|
|||||||
flushFile
|
flushFile
|
||||||
} = useContext(ApiServerContext)
|
} = useContext(ApiServerContext)
|
||||||
const { token } = useContext(AuthContext)
|
const { token } = useContext(AuthContext)
|
||||||
const navigate = useNavigate()
|
|
||||||
// Get the model definition for this object type
|
// Get the model definition for this object type
|
||||||
const model = getModelByName(type)
|
const model = getModelByName(type)
|
||||||
|
|
||||||
@ -463,7 +462,6 @@ const ObjectForm = forwardRef(
|
|||||||
await deleteObject(id, type)
|
await deleteObject(id, type)
|
||||||
setDeleteModalOpen(false)
|
setDeleteModalOpen(false)
|
||||||
showSuccess('Deleted successfully')
|
showSuccess('Deleted successfully')
|
||||||
navigate(-2)
|
|
||||||
// Optionally: trigger a callback to parent to remove this object from view
|
// Optionally: trigger a callback to parent to remove this object from view
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
|||||||
@ -87,7 +87,6 @@ const ObjectProperty = ({
|
|||||||
showPreview = true,
|
showPreview = true,
|
||||||
options = [],
|
options = [],
|
||||||
roundNumber = false,
|
roundNumber = false,
|
||||||
fixedNumber = false,
|
|
||||||
showHyperlink,
|
showHyperlink,
|
||||||
showSince,
|
showSince,
|
||||||
properties = [],
|
properties = [],
|
||||||
@ -273,9 +272,6 @@ const ObjectProperty = ({
|
|||||||
if (roundNumber != false && typeof value === 'number') {
|
if (roundNumber != false && typeof value === 'number') {
|
||||||
roundedValue = round(value, roundNumber)
|
roundedValue = round(value, roundNumber)
|
||||||
}
|
}
|
||||||
if (fixedNumber != false && typeof value === 'number') {
|
|
||||||
roundedValue = value.toFixed(fixedNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Text {...textParams}>
|
<Text {...textParams}>
|
||||||
|
|||||||
@ -47,8 +47,6 @@ const ObjectSelect = ({
|
|||||||
const [objectList, setObjectList] = useState([])
|
const [objectList, setObjectList] = useState([])
|
||||||
const [treeSelectValue, setTreeSelectValue] = useState(null)
|
const [treeSelectValue, setTreeSelectValue] = useState(null)
|
||||||
const [initialLoading, setInitialLoading] = useState(true)
|
const [initialLoading, setInitialLoading] = useState(true)
|
||||||
const [expandedKeys, setExpandedKeys] = useState([])
|
|
||||||
const [treeVersion, setTreeVersion] = useState(0)
|
|
||||||
const valueRef = useRef(null)
|
const valueRef = useRef(null)
|
||||||
|
|
||||||
// Refs to track value changes
|
// Refs to track value changes
|
||||||
@ -213,7 +211,6 @@ const ObjectSelect = ({
|
|||||||
objectType={type}
|
objectType={type}
|
||||||
objectData={object}
|
objectData={object}
|
||||||
isEditing={false}
|
isEditing={false}
|
||||||
showHyperlink={false}
|
|
||||||
style={{ top: '-0.5px' }}
|
style={{ top: '-0.5px' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -249,15 +246,6 @@ const ObjectSelect = ({
|
|||||||
value: valueString
|
value: valueString
|
||||||
})
|
})
|
||||||
|
|
||||||
var nodeChildren = buildTreeData(
|
|
||||||
children,
|
|
||||||
pIdx + 1,
|
|
||||||
parentKeys.concat(valueString),
|
|
||||||
newFilterPath
|
|
||||||
)
|
|
||||||
if (nodeChildren.length == 0) {
|
|
||||||
nodeChildren = undefined
|
|
||||||
}
|
|
||||||
const modelProperty = getModelProperty(type, property)
|
const modelProperty = getModelProperty(type, property)
|
||||||
return {
|
return {
|
||||||
title: <ObjectProperty {...modelProperty} value={value} />,
|
title: <ObjectProperty {...modelProperty} value={value} />,
|
||||||
@ -269,7 +257,12 @@ const ObjectSelect = ({
|
|||||||
filterPath: newFilterPath,
|
filterPath: newFilterPath,
|
||||||
selectable: false,
|
selectable: false,
|
||||||
isLeaf: false,
|
isLeaf: false,
|
||||||
children: nodeChildren
|
children: buildTreeData(
|
||||||
|
children,
|
||||||
|
pIdx + 1,
|
||||||
|
parentKeys.concat(valueString),
|
||||||
|
newFilterPath
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
@ -279,7 +272,6 @@ const ObjectSelect = ({
|
|||||||
|
|
||||||
// --- loadData for async loading on expand ---
|
// --- loadData for async loading on expand ---
|
||||||
const loadData = async (node) => {
|
const loadData = async (node) => {
|
||||||
console.log('loading data for node', node)
|
|
||||||
// node.property is the property name, node.value is the value key
|
// node.property is the property name, node.value is the value key
|
||||||
if (!node.property) return
|
if (!node.property) return
|
||||||
if (type == 'unknown') return
|
if (type == 'unknown') return
|
||||||
@ -392,39 +384,24 @@ const ObjectSelect = ({
|
|||||||
// console.log('fullValue', fullValue)
|
// console.log('fullValue', fullValue)
|
||||||
// Build a new filter from value's properties that are in the properties list
|
// Build a new filter from value's properties that are in the properties list
|
||||||
const valueFilter = { ...filter }
|
const valueFilter = { ...filter }
|
||||||
const pathKeys = []
|
|
||||||
const parentKeys = []
|
|
||||||
properties.forEach((prop) => {
|
properties.forEach((prop) => {
|
||||||
if (Object.prototype.hasOwnProperty.call(fullValue, prop)) {
|
if (Object.prototype.hasOwnProperty.call(fullValue, prop)) {
|
||||||
const filterValue = fullValue[prop]
|
const filterValue = fullValue[prop]
|
||||||
let valueString = filterValue
|
|
||||||
if (
|
if (
|
||||||
filterValue &&
|
filterValue &&
|
||||||
typeof filterValue === 'object' &&
|
typeof filterValue === 'object' &&
|
||||||
filterValue._id
|
filterValue._id
|
||||||
) {
|
) {
|
||||||
valueFilter[prop] = filterValue._id
|
valueFilter[prop] = filterValue._id
|
||||||
valueString = filterValue._id
|
|
||||||
} else if (filterValue?.name) {
|
} else if (filterValue?.name) {
|
||||||
valueFilter[prop] = filterValue.name
|
valueFilter[prop] = filterValue.name
|
||||||
valueString = filterValue.name
|
|
||||||
} else if (Array.isArray(filterValue)) {
|
} else if (Array.isArray(filterValue)) {
|
||||||
valueFilter[prop] = filterValue.join(',')
|
valueFilter[prop] = filterValue.join(',')
|
||||||
valueString = filterValue.join(',')
|
|
||||||
} else {
|
} else {
|
||||||
valueFilter[prop] = filterValue
|
valueFilter[prop] = filterValue
|
||||||
valueString = filterValue
|
|
||||||
}
|
}
|
||||||
// Build the path key for this property level
|
|
||||||
const nodeKey = parentKeys
|
|
||||||
.concat(prop + ':' + valueString)
|
|
||||||
.join('-')
|
|
||||||
pathKeys.push(nodeKey)
|
|
||||||
parentKeys.push(valueString)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Expand the path to the object
|
|
||||||
setExpandedKeys(pathKeys)
|
|
||||||
// Fetch with the new filter
|
// Fetch with the new filter
|
||||||
handleFetchObjectsProperties(valueFilter)
|
handleFetchObjectsProperties(valueFilter)
|
||||||
// console.log('setting treeSelectValue', valueRef.current._id)
|
// console.log('setting treeSelectValue', valueRef.current._id)
|
||||||
@ -485,8 +462,6 @@ const ObjectSelect = ({
|
|||||||
setObjectPropertiesTree({})
|
setObjectPropertiesTree({})
|
||||||
setObjectList([])
|
setObjectList([])
|
||||||
setTreeData([])
|
setTreeData([])
|
||||||
setTreeVersion((v) => v + 1)
|
|
||||||
setExpandedKeys([])
|
|
||||||
setInitialized(false)
|
setInitialized(false)
|
||||||
onTreeSelectChange(null)
|
onTreeSelectChange(null)
|
||||||
setTreeSelectValue(null)
|
setTreeSelectValue(null)
|
||||||
@ -554,11 +529,8 @@ const ObjectSelect = ({
|
|||||||
// --- Main TreeSelect UI ---
|
// --- Main TreeSelect UI ---
|
||||||
return (
|
return (
|
||||||
<TreeSelect
|
<TreeSelect
|
||||||
key={treeVersion}
|
|
||||||
treeDataSimpleMode={false}
|
treeDataSimpleMode={false}
|
||||||
treeDefaultExpandAll={true}
|
treeDefaultExpandAll={true}
|
||||||
treeExpandedKeys={expandedKeys}
|
|
||||||
onTreeExpand={setExpandedKeys}
|
|
||||||
treeData={treeData}
|
treeData={treeData}
|
||||||
showSearch={showSearch}
|
showSearch={showSearch}
|
||||||
multiple={multiple}
|
multiple={multiple}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import {
|
|||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
useCallback,
|
useCallback,
|
||||||
useMemo,
|
|
||||||
createElement
|
createElement
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import {
|
import {
|
||||||
@ -20,8 +19,7 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Input,
|
Input,
|
||||||
Space,
|
Space,
|
||||||
Tooltip,
|
Tooltip
|
||||||
Form
|
|
||||||
} from 'antd'
|
} from 'antd'
|
||||||
import { LoadingOutlined } from '@ant-design/icons'
|
import { LoadingOutlined } from '@ant-design/icons'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
@ -45,46 +43,6 @@ import { ElectronContext } from '../context/ElectronContext'
|
|||||||
const logger = loglevel.getLogger('DasboardTable')
|
const logger = loglevel.getLogger('DasboardTable')
|
||||||
logger.setLevel(config.logLevel)
|
logger.setLevel(config.logLevel)
|
||||||
|
|
||||||
const RowForm = ({ record, isEditing, onRegister, children }) => {
|
|
||||||
const [form] = Form.useForm()
|
|
||||||
useEffect(() => {
|
|
||||||
if (isEditing && record && !record.isSkeleton) {
|
|
||||||
form.setFieldsValue(record)
|
|
||||||
onRegister(record._id, form)
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
if (record?._id) onRegister(record._id, null)
|
|
||||||
}
|
|
||||||
}, [isEditing, record, form, onRegister])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form form={form} component={false}>
|
|
||||||
{children}
|
|
||||||
</Form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
RowForm.propTypes = {
|
|
||||||
record: PropTypes.object,
|
|
||||||
isEditing: PropTypes.bool,
|
|
||||||
onRegister: PropTypes.func,
|
|
||||||
children: PropTypes.node
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditableRow = ({ record, isEditing, onRegister, ...props }) => {
|
|
||||||
return (
|
|
||||||
<RowForm record={record} isEditing={isEditing} onRegister={onRegister}>
|
|
||||||
<tr {...props} />
|
|
||||||
</RowForm>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
EditableRow.propTypes = {
|
|
||||||
record: PropTypes.object,
|
|
||||||
isEditing: PropTypes.bool,
|
|
||||||
onRegister: PropTypes.func
|
|
||||||
}
|
|
||||||
|
|
||||||
const ObjectTable = forwardRef(
|
const ObjectTable = forwardRef(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@ -96,26 +54,17 @@ const ObjectTable = forwardRef(
|
|||||||
cards = false,
|
cards = false,
|
||||||
visibleColumns = {},
|
visibleColumns = {},
|
||||||
masterFilter = {},
|
masterFilter = {},
|
||||||
size = 'middle',
|
size = 'middle'
|
||||||
onStateChange
|
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const { token } = useContext(AuthContext)
|
const { token } = useContext(AuthContext)
|
||||||
const { isElectron } = useContext(ElectronContext)
|
const { isElectron } = useContext(ElectronContext)
|
||||||
const onStateChangeRef = useRef(onStateChange)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onStateChangeRef.current = onStateChange
|
|
||||||
}, [onStateChange])
|
|
||||||
const {
|
const {
|
||||||
fetchObjects,
|
fetchObjects,
|
||||||
connected,
|
connected,
|
||||||
subscribeToObjectUpdates,
|
subscribeToObjectUpdates,
|
||||||
subscribeToObjectTypeUpdates,
|
subscribeToObjectTypeUpdates
|
||||||
updateMultipleObjects,
|
|
||||||
lockObject,
|
|
||||||
unlockObject
|
|
||||||
} = useContext(ApiServerContext)
|
} = useContext(ApiServerContext)
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -149,21 +98,6 @@ const ObjectTable = forwardRef(
|
|||||||
const [lazyLoading, setLazyLoading] = useState(false)
|
const [lazyLoading, setLazyLoading] = useState(false)
|
||||||
const [tableData, setTableData] = useState([])
|
const [tableData, setTableData] = useState([])
|
||||||
|
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
|
||||||
const [editLoading, setEditLoading] = useState(false)
|
|
||||||
const rowFormsRef = useRef({})
|
|
||||||
const registerForm = useCallback((id, form) => {
|
|
||||||
if (form) {
|
|
||||||
rowFormsRef.current[id] = form
|
|
||||||
} else {
|
|
||||||
delete rowFormsRef.current[id]
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
onStateChangeRef.current?.({ isEditing, editLoading })
|
|
||||||
}, [isEditing, editLoading])
|
|
||||||
|
|
||||||
const subscribedIdsRef = useRef([])
|
const subscribedIdsRef = useRef([])
|
||||||
// const [typeSubscribed, setTypeSubscribed] = useState(false)
|
// const [typeSubscribed, setTypeSubscribed] = useState(false)
|
||||||
const unsubscribesRef = useRef([])
|
const unsubscribesRef = useRef([])
|
||||||
@ -366,49 +300,6 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
}, [fetchData])
|
}, [fetchData])
|
||||||
|
|
||||||
const startEditing = useCallback(() => {
|
|
||||||
setIsEditing(true)
|
|
||||||
tableData.forEach((item) => {
|
|
||||||
if (!item.isSkeleton) {
|
|
||||||
console.log('Locking object:', item)
|
|
||||||
lockObject(item._id, type)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [tableData, lockObject, type])
|
|
||||||
|
|
||||||
const cancelEditing = useCallback(() => {
|
|
||||||
setIsEditing(false)
|
|
||||||
tableData.forEach((item) => {
|
|
||||||
if (!item.isSkeleton) {
|
|
||||||
unlockObject(item._id, type)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, [tableData, unlockObject, type])
|
|
||||||
|
|
||||||
const handleUpdate = useCallback(async () => {
|
|
||||||
setEditLoading(true)
|
|
||||||
try {
|
|
||||||
const updates = await Promise.all(
|
|
||||||
Object.entries(rowFormsRef.current).map(async ([id, form]) => {
|
|
||||||
const values = await form.validateFields()
|
|
||||||
return { _id: id, ...values }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
await updateMultipleObjects(type, updates)
|
|
||||||
setIsEditing(false)
|
|
||||||
reload()
|
|
||||||
tableData.forEach((item) => {
|
|
||||||
if (!item.isSkeleton) {
|
|
||||||
unlockObject(item._id, type)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
} finally {
|
|
||||||
setEditLoading(false)
|
|
||||||
}
|
|
||||||
}, [type, updateMultipleObjects, reload, tableData, unlockObject])
|
|
||||||
|
|
||||||
// Update event handler for real-time updates
|
// Update event handler for real-time updates
|
||||||
const updateEventHandler = useCallback((id, updatedData) => {
|
const updateEventHandler = useCallback((id, updatedData) => {
|
||||||
setPages((prevPages) =>
|
setPages((prevPages) =>
|
||||||
@ -425,12 +316,6 @@ const ObjectTable = forwardRef(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('updatedData', updatedData)
|
|
||||||
|
|
||||||
if (rowFormsRef.current[id]) {
|
|
||||||
rowFormsRef.current[id].setFieldsValue(updatedData)
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Store the latest updateEventHandler in a ref
|
// Store the latest updateEventHandler in a ref
|
||||||
@ -464,9 +349,6 @@ const ObjectTable = forwardRef(
|
|||||||
updateEventHandlerRef.current(itemId, updateData)
|
updateEventHandlerRef.current(itemId, updateData)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
console.log('unsubscribe', unsubscribe)
|
|
||||||
console.log('subscribedIdsRef', subscribedIdsRef.current)
|
|
||||||
console.log('unsubscribesRef', unsubscribesRef.current)
|
|
||||||
subscribedIdsRef.current.push(itemId)
|
subscribedIdsRef.current.push(itemId)
|
||||||
if (unsubscribe) {
|
if (unsubscribe) {
|
||||||
unsubscribesRef.current.push(unsubscribe)
|
unsubscribesRef.current.push(unsubscribe)
|
||||||
@ -526,8 +408,8 @@ const ObjectTable = forwardRef(
|
|||||||
}, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])
|
}, [type, subscribeToObjectTypeUpdates, connected, newEventHandler])
|
||||||
|
|
||||||
const updateData = useCallback(
|
const updateData = useCallback(
|
||||||
(id, updatedData) => {
|
(_id, updatedData) => {
|
||||||
updateEventHandler(id, updatedData)
|
updateEventHandler({ _id, ...updatedData })
|
||||||
},
|
},
|
||||||
[updateEventHandler]
|
[updateEventHandler]
|
||||||
)
|
)
|
||||||
@ -563,12 +445,7 @@ const ObjectTable = forwardRef(
|
|||||||
setData: (newData) => {
|
setData: (newData) => {
|
||||||
setPages([{ pageNum: 1, items: newData }])
|
setPages([{ pageNum: 1, items: newData }])
|
||||||
},
|
},
|
||||||
updateData,
|
updateData
|
||||||
startEditing,
|
|
||||||
cancelEditing,
|
|
||||||
handleUpdate,
|
|
||||||
isEditing,
|
|
||||||
editLoading
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -737,7 +614,7 @@ const ObjectTable = forwardRef(
|
|||||||
{...prop}
|
{...prop}
|
||||||
longId={false}
|
longId={false}
|
||||||
objectData={record}
|
objectData={record}
|
||||||
isEditing={isEditing}
|
isEditing={false}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -837,11 +714,6 @@ const ObjectTable = forwardRef(
|
|||||||
styles={{ body: { padding: 0 } }}
|
styles={{ body: { padding: 0 } }}
|
||||||
loading={record.isSkeleton}
|
loading={record.isSkeleton}
|
||||||
variant={'borderless'}
|
variant={'borderless'}
|
||||||
>
|
|
||||||
<RowForm
|
|
||||||
record={record}
|
|
||||||
isEditing={isEditing}
|
|
||||||
onRegister={registerForm}
|
|
||||||
>
|
>
|
||||||
<Flex align={'center'} vertical gap={'middle'}>
|
<Flex align={'center'} vertical gap={'middle'}>
|
||||||
<Descriptions
|
<Descriptions
|
||||||
@ -878,8 +750,7 @@ const ObjectTable = forwardRef(
|
|||||||
{...prop}
|
{...prop}
|
||||||
longId={false}
|
longId={false}
|
||||||
objectData={record}
|
objectData={record}
|
||||||
isEditing={isEditing}
|
isEditing={false}
|
||||||
name={prop.name}
|
|
||||||
/>
|
/>
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
)
|
)
|
||||||
@ -889,10 +760,7 @@ const ObjectTable = forwardRef(
|
|||||||
// Add actions if they exist (same as table)
|
// Add actions if they exist (same as table)
|
||||||
if (rowActions.length > 0) {
|
if (rowActions.length > 0) {
|
||||||
descriptionItems.push(
|
descriptionItems.push(
|
||||||
<Descriptions.Item
|
<Descriptions.Item label={'Actions'} key={'actions'}>
|
||||||
label={'Actions'}
|
|
||||||
key={'actions'}
|
|
||||||
>
|
|
||||||
{renderActions(record)}
|
{renderActions(record)}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
)
|
)
|
||||||
@ -902,7 +770,6 @@ const ObjectTable = forwardRef(
|
|||||||
})()}
|
})()}
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
</Flex>
|
</Flex>
|
||||||
</RowForm>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
@ -910,25 +777,8 @@ const ObjectTable = forwardRef(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const components = useMemo(
|
return (
|
||||||
() => ({
|
<>
|
||||||
body: {
|
|
||||||
row: EditableRow
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
[]
|
|
||||||
)
|
|
||||||
|
|
||||||
const onRow = useCallback(
|
|
||||||
(record) => ({
|
|
||||||
record,
|
|
||||||
isEditing,
|
|
||||||
onRegister: registerForm
|
|
||||||
}),
|
|
||||||
[isEditing, registerForm]
|
|
||||||
)
|
|
||||||
|
|
||||||
const tableContent = (
|
|
||||||
<Flex gap={'middle'} vertical>
|
<Flex gap={'middle'} vertical>
|
||||||
<Table
|
<Table
|
||||||
ref={tableRef}
|
ref={tableRef}
|
||||||
@ -944,8 +794,6 @@ const ObjectTable = forwardRef(
|
|||||||
showSorterTooltip={false}
|
showSorterTooltip={false}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
size={size}
|
size={size}
|
||||||
components={components}
|
|
||||||
onRow={onRow}
|
|
||||||
/>
|
/>
|
||||||
{cards ? (
|
{cards ? (
|
||||||
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
<Spin indicator={<LoadingOutlined />} spinning={loading}>
|
||||||
@ -953,9 +801,8 @@ const ObjectTable = forwardRef(
|
|||||||
</Spin>
|
</Spin>
|
||||||
) : null}
|
) : null}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
return tableContent
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -971,8 +818,7 @@ ObjectTable.propTypes = {
|
|||||||
cardRenderer: PropTypes.func,
|
cardRenderer: PropTypes.func,
|
||||||
visibleColumns: PropTypes.object,
|
visibleColumns: PropTypes.object,
|
||||||
masterFilter: PropTypes.object,
|
masterFilter: PropTypes.object,
|
||||||
size: PropTypes.string,
|
size: PropTypes.string
|
||||||
onStateChange: PropTypes.func
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObjectTable
|
export default ObjectTable
|
||||||
|
|||||||
@ -96,46 +96,6 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
|
|||||||
status = 'success'
|
status = 'success'
|
||||||
text = 'Unconsumed'
|
text = 'Unconsumed'
|
||||||
break
|
break
|
||||||
case 'sent':
|
|
||||||
status = 'cyan'
|
|
||||||
text = 'Sent'
|
|
||||||
break
|
|
||||||
case 'acknowledged':
|
|
||||||
status = 'processing'
|
|
||||||
text = 'Acknowledged'
|
|
||||||
break
|
|
||||||
case 'ordered':
|
|
||||||
status = 'cyan'
|
|
||||||
text = 'Ordered'
|
|
||||||
break
|
|
||||||
case 'received':
|
|
||||||
status = 'success'
|
|
||||||
text = 'Received'
|
|
||||||
break
|
|
||||||
case 'invoiced':
|
|
||||||
status = 'warning'
|
|
||||||
text = 'Invoiced'
|
|
||||||
break
|
|
||||||
case 'planned':
|
|
||||||
status = 'warning'
|
|
||||||
text = 'Planned'
|
|
||||||
break
|
|
||||||
case 'partiallyShipped':
|
|
||||||
status = 'processing'
|
|
||||||
text = 'Partially Shipped'
|
|
||||||
break
|
|
||||||
case 'shipped':
|
|
||||||
status = 'processing'
|
|
||||||
text = 'Shipped'
|
|
||||||
break
|
|
||||||
case 'delivered':
|
|
||||||
status = 'success'
|
|
||||||
text = 'Delivered'
|
|
||||||
break
|
|
||||||
case 'paid':
|
|
||||||
status = 'success'
|
|
||||||
text = 'Paid'
|
|
||||||
break
|
|
||||||
default:
|
default:
|
||||||
status = 'default'
|
status = 'default'
|
||||||
text = state || 'Unknown'
|
text = state || 'Unknown'
|
||||||
@ -144,22 +104,10 @@ const StateTag = ({ state, showBadge = true, style = {} }) => {
|
|||||||
return { badgeStatus: status, badgeText: text }
|
return { badgeStatus: status, badgeText: text }
|
||||||
}, [state])
|
}, [state])
|
||||||
|
|
||||||
var badgeProps = {
|
|
||||||
status: badgeStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
!['success', 'warning', 'error', 'processing', 'default'].includes(
|
|
||||||
badgeStatus
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
badgeProps = { color: badgeStatus }
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tag color={badgeStatus} style={{ marginRight: 0, ...style }}>
|
<Tag color={badgeStatus} style={{ marginRight: 0, ...style }}>
|
||||||
<Flex gap={6}>
|
<Flex gap={6}>
|
||||||
{showBadge && <Badge {...badgeProps} />}
|
{showBadge && <Badge status={badgeStatus} />}
|
||||||
{badgeText}
|
{badgeText}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Tag>
|
</Tag>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState, useContext, useRef } from 'react'
|
import { useEffect, useState, useContext, useRef } from 'react'
|
||||||
import { Flex, Tag, Card, Typography, Skeleton, Badge } from 'antd'
|
import { Flex, Alert, Card, Typography, Skeleton } from 'antd'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { getModelByName } from '../../../database/ObjectModels'
|
import { getModelByName } from '../../../database/ObjectModels'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
@ -9,9 +9,9 @@ import { round } from '../utils/Utils'
|
|||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps stat names to Tag colors for visual indication
|
* Maps stat names to Alert types for visual indication
|
||||||
*/
|
*/
|
||||||
const getTagColor = (statName) => {
|
const getAlertType = (statName) => {
|
||||||
const name = statName.toLowerCase()
|
const name = statName.toLowerCase()
|
||||||
|
|
||||||
// Success states
|
// Success states
|
||||||
@ -43,14 +43,14 @@ const getTagColor = (statName) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (name.includes('printing')) {
|
if (name.includes('printing')) {
|
||||||
return 'processing'
|
return 'info'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default states
|
// Default states
|
||||||
return 'default'
|
return 'default'
|
||||||
}
|
}
|
||||||
|
|
||||||
/*i*
|
/**
|
||||||
* Gets a nested value from an object using dot notation
|
* Gets a nested value from an object using dot notation
|
||||||
* e.g., getNestedValue(obj, 'states.ready') -> obj.states.ready
|
* e.g., getNestedValue(obj, 'states.ready') -> obj.states.ready
|
||||||
*/
|
*/
|
||||||
@ -255,41 +255,18 @@ const StatsDisplay = ({ objectType, stats: statsProp }) => {
|
|||||||
{modelStats.map((statDef) => {
|
{modelStats.map((statDef) => {
|
||||||
const baseStatName = extractBaseStatName(statDef.name)
|
const baseStatName = extractBaseStatName(statDef.name)
|
||||||
var statValue = getStatValue(stats, baseStatName, statDef)
|
var statValue = getStatValue(stats, baseStatName, statDef)
|
||||||
const tagColor = statDef.color || getTagColor(statDef.name)
|
const alertType = getAlertType(statDef.name)
|
||||||
const label = statDef.label || statDef.name
|
const label = statDef.label || statDef.name
|
||||||
|
|
||||||
if (statDef?.roundNumber) {
|
if (statDef?.roundNumber) {
|
||||||
statValue = round(statValue, statDef?.roundNumber)
|
statValue = round(statValue, statDef?.roundNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusColors = [
|
|
||||||
'success',
|
|
||||||
'warning',
|
|
||||||
'error',
|
|
||||||
'processing',
|
|
||||||
'default'
|
|
||||||
]
|
|
||||||
|
|
||||||
var badgeProps = {
|
|
||||||
status: tagColor
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!statusColors.includes(tagColor)) {
|
|
||||||
badgeProps = { color: tagColor }
|
|
||||||
}
|
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<Flex vertical gap='3px'>
|
<Flex vertical>
|
||||||
<Flex gap={'12px'} align='center'>
|
<Text type='secondary'>{label}</Text>
|
||||||
<Badge {...badgeProps} />
|
<Flex gap={'small'}>
|
||||||
<Text>{label}</Text>
|
{Icon && <Icon style={{ fontSize: 26 }} />}
|
||||||
</Flex>
|
|
||||||
<Flex gap={'12px'} align='center'>
|
|
||||||
{Icon && (
|
|
||||||
<Text>
|
|
||||||
<Icon style={{ fontSize: 26 }} />
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Flex justify='center' align='center' style={{ height: 44 }}>
|
<Flex justify='center' align='center' style={{ height: 44 }}>
|
||||||
<Skeleton.Button
|
<Skeleton.Button
|
||||||
@ -309,12 +286,12 @@ const StatsDisplay = ({ objectType, stats: statsProp }) => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (tagColor === 'default') {
|
if (alertType === 'default') {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
key={statDef.name}
|
key={statDef.name}
|
||||||
style={{ minWidth: statDef?.cardWidth || 175 }}
|
style={{ minWidth: statDef?.cardWidth || 175 }}
|
||||||
styles={{ body: { padding: '16px 24px' } }}
|
styles={{ body: { padding: '20px 24px' } }}
|
||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</Card>
|
</Card>
|
||||||
@ -322,17 +299,12 @@ const StatsDisplay = ({ objectType, stats: statsProp }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tag
|
<Alert
|
||||||
key={statDef.name}
|
key={statDef.name}
|
||||||
color={tagColor}
|
type={alertType}
|
||||||
style={{
|
style={{ minWidth: statDef?.cardWidth || 175 }}
|
||||||
minWidth: statDef?.cardWidth || 175,
|
description={content}
|
||||||
padding: '16px 24px',
|
/>
|
||||||
margin: 0
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</Tag>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@ -797,28 +797,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update multiple objects
|
|
||||||
const updateMultipleObjects = async (type, objects) => {
|
|
||||||
const updateUrl = `${config.backendUrl}/${type.toLowerCase()}s`
|
|
||||||
logger.debug('Updating multiple objects for ' + type)
|
|
||||||
try {
|
|
||||||
const response = await axios.put(updateUrl, objects, {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
logger.debug('Objects updated successfully')
|
|
||||||
return response.data
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
showError(err, () => {
|
|
||||||
updateMultipleObjects(type, objects)
|
|
||||||
})
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update filament information
|
// Update filament information
|
||||||
const deleteObject = async (id, type) => {
|
const deleteObject = async (id, type) => {
|
||||||
const deleteUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
|
const deleteUrl = `${config.backendUrl}/${type.toLowerCase()}s/${id}`
|
||||||
@ -862,27 +840,6 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call a function on an object
|
|
||||||
const sendObjectFunction = async (id, type, functionName, value = {}) => {
|
|
||||||
const url = `${config.backendUrl}/${type.toLowerCase()}s/${id}/${functionName}`
|
|
||||||
logger.debug(`Calling object function ${functionName} for ${id} at ${url}`)
|
|
||||||
try {
|
|
||||||
const response = await axios.post(url, value, {
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${token}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return response.data
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err)
|
|
||||||
showError(err, () => {
|
|
||||||
sendObjectFunction(id, type, functionName, value)
|
|
||||||
})
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download GCode file content
|
// Download GCode file content
|
||||||
const fetchFileContent = async (file, download = false) => {
|
const fetchFileContent = async (file, download = false) => {
|
||||||
try {
|
try {
|
||||||
@ -1264,9 +1221,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
unlockObject,
|
unlockObject,
|
||||||
fetchObjectLock,
|
fetchObjectLock,
|
||||||
updateObject,
|
updateObject,
|
||||||
updateMultipleObjects,
|
|
||||||
createObject,
|
createObject,
|
||||||
sendObjectFunction,
|
|
||||||
deleteObject,
|
deleteObject,
|
||||||
subscribeToObjectUpdates,
|
subscribeToObjectUpdates,
|
||||||
subscribeToObjectEvent,
|
subscribeToObjectEvent,
|
||||||
|
|||||||
@ -88,12 +88,7 @@ export const ThemeProvider = ({ children }) => {
|
|||||||
colorWarning: '#FF9230',
|
colorWarning: '#FF9230',
|
||||||
colorError: '#FF4245',
|
colorError: '#FF4245',
|
||||||
colorInfo: '#0A84FF',
|
colorInfo: '#0A84FF',
|
||||||
colorLink: '#5AC8F5',
|
colorLink: '#5AC8F5'
|
||||||
colorCyan: '#5AC8F5',
|
|
||||||
colorPink: '#FF69B4',
|
|
||||||
colorPurple: '#800080',
|
|
||||||
colorMagenta: '#FF00FF',
|
|
||||||
colorVolcano: '#FF4500'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getColors = () => {
|
const getColors = () => {
|
||||||
|
|||||||
@ -1,6 +0,0 @@
|
|||||||
import Icon from '@ant-design/icons'
|
|
||||||
import CustomIconSvg from '../../../assets/icons/financeicon.svg?react'
|
|
||||||
|
|
||||||
const FinanceIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
|
||||||
|
|
||||||
export default FinanceIcon
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import Icon from '@ant-design/icons'
|
|
||||||
import CustomIconSvg from '../../../assets/icons/invoiceicon.svg?react'
|
|
||||||
|
|
||||||
const InvoiceIcon = (props) => (
|
|
||||||
<Icon component={CustomIconSvg} {...props} />
|
|
||||||
)
|
|
||||||
|
|
||||||
export default InvoiceIcon
|
|
||||||
|
|
||||||
@ -30,7 +30,6 @@ import { DocumentPrinter } from './models/DocumentPrinter.js'
|
|||||||
import { DocumentJob } from './models/DocumentJob.js'
|
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 QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
|
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
|
||||||
|
|
||||||
export const objectModels = [
|
export const objectModels = [
|
||||||
@ -65,8 +64,7 @@ export const objectModels = [
|
|||||||
DocumentPrinter,
|
DocumentPrinter,
|
||||||
DocumentJob,
|
DocumentJob,
|
||||||
TaxRate,
|
TaxRate,
|
||||||
TaxRecord,
|
TaxRecord
|
||||||
Invoice
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// Re-export individual models for direct access
|
// Re-export individual models for direct access
|
||||||
@ -102,8 +100,7 @@ export {
|
|||||||
DocumentPrinter,
|
DocumentPrinter,
|
||||||
DocumentJob,
|
DocumentJob,
|
||||||
TaxRate,
|
TaxRate,
|
||||||
TaxRecord,
|
TaxRecord
|
||||||
Invoice
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getModelByName(name, ignoreCase = false) {
|
export function getModelByName(name, ignoreCase = false) {
|
||||||
|
|||||||
@ -1,336 +0,0 @@
|
|||||||
import InvoiceIcon from '../../components/Icons/InvoiceIcon'
|
|
||||||
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 Invoice = {
|
|
||||||
name: 'invoice',
|
|
||||||
label: 'Invoice',
|
|
||||||
prefix: 'INV',
|
|
||||||
icon: InvoiceIcon,
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
name: 'info',
|
|
||||||
label: 'Info',
|
|
||||||
default: true,
|
|
||||||
row: true,
|
|
||||||
icon: InfoCircleIcon,
|
|
||||||
url: (_id) => `/dashboard/finance/invoices/info?invoiceId=${_id}`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'edit',
|
|
||||||
label: 'Edit',
|
|
||||||
type: 'button',
|
|
||||||
icon: EditIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/finance/invoices/info?invoiceId=${_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/invoices/info?invoiceId=${_id}&action=cancelEdit`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?._isEditing && objectData?._isEditing == true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'finishEdit',
|
|
||||||
label: 'Finish Edit',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/finance/invoices/info?invoiceId=${_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/invoices/info?invoiceId=${_id}&action=delete`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
|
||||||
},
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
name: 'send',
|
|
||||||
label: 'Send',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=send`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
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',
|
|
||||||
label: 'Cancel',
|
|
||||||
type: 'button',
|
|
||||||
icon: XMarkIcon,
|
|
||||||
danger: true,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/finance/invoices/info?invoiceId=${_id}&action=cancel`,
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return (
|
|
||||||
objectData?.state?.type == 'cancelled' ||
|
|
||||||
objectData?.state?.type == 'paid'
|
|
||||||
)
|
|
||||||
},
|
|
||||||
visible: (objectData) => {
|
|
||||||
return (
|
|
||||||
objectData?.state?.type == 'draft' ||
|
|
||||||
objectData?.state?.type == 'sent'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
group: ['vendor', 'customer', 'invoiceType'],
|
|
||||||
filters: ['vendor', 'customer', 'invoiceType'],
|
|
||||||
sorters: ['createdAt', 'state', 'updatedAt', 'invoiceDate', 'dueDate'],
|
|
||||||
columns: [
|
|
||||||
'_id',
|
|
||||||
'_reference',
|
|
||||||
'state',
|
|
||||||
'invoiceType',
|
|
||||||
'vendor',
|
|
||||||
'customer',
|
|
||||||
'invoiceDate',
|
|
||||||
'dueDate',
|
|
||||||
'totalAmount',
|
|
||||||
'totalAmountWithTax',
|
|
||||||
'totalTaxAmount',
|
|
||||||
'shippingAmount',
|
|
||||||
'shippingAmountWithTax',
|
|
||||||
'grandTotalAmount',
|
|
||||||
'createdAt',
|
|
||||||
'updatedAt'
|
|
||||||
],
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
name: '_id',
|
|
||||||
label: 'ID',
|
|
||||||
type: 'id',
|
|
||||||
columnFixed: 'left',
|
|
||||||
objectType: 'invoice',
|
|
||||||
columnWidth: 140,
|
|
||||||
showCopy: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'createdAt',
|
|
||||||
label: 'Created At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '_reference',
|
|
||||||
label: 'Reference',
|
|
||||||
type: 'reference',
|
|
||||||
required: true,
|
|
||||||
objectType: 'invoice',
|
|
||||||
showCopy: true,
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'updatedAt',
|
|
||||||
label: 'Updated At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
|
||||||
{
|
|
||||||
name: 'invoiceDate',
|
|
||||||
label: 'Invoice Date',
|
|
||||||
type: 'date',
|
|
||||||
readOnly: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dueDate',
|
|
||||||
label: 'Due Date',
|
|
||||||
type: 'date',
|
|
||||||
readOnly: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'vendor',
|
|
||||||
label: 'Vendor',
|
|
||||||
required: true,
|
|
||||||
type: 'object',
|
|
||||||
objectType: 'vendor',
|
|
||||||
showHyperlink: true,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?.invoiceType === 'purchase' || objectData?.vendor
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'orderType',
|
|
||||||
label: 'Order Type',
|
|
||||||
type: 'objectType',
|
|
||||||
masterFilter: ['purchaseOrder', 'salesOrder'],
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'order',
|
|
||||||
label: 'Order',
|
|
||||||
type: 'object',
|
|
||||||
objectType: (objectData) => {
|
|
||||||
return objectData?.orderType
|
|
||||||
},
|
|
||||||
masterFilter: (objectData) => {
|
|
||||||
return {
|
|
||||||
vendor: objectData?.vendor?._id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
required: true,
|
|
||||||
showHyperlink: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sentAt',
|
|
||||||
label: 'Sent At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'paidAt',
|
|
||||||
label: 'Paid At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cancelledAt',
|
|
||||||
label: 'Cancelled At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'overdueAt',
|
|
||||||
label: 'Overdue At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'totalTaxAmount',
|
|
||||||
label: 'Total Tax Amount',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
roundNumber: 2,
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 175
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'totalAmountWithTax',
|
|
||||||
label: 'Total Amount w/ Tax',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 175,
|
|
||||||
roundNumber: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'shippingAmount',
|
|
||||||
label: 'Shipping Amount',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
roundNumber: 2,
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'shippingAmountWithTax',
|
|
||||||
label: 'Shipping Amount w/ Tax',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
readOnly: true,
|
|
||||||
roundNumber: 2,
|
|
||||||
columnWidth: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'totalAmount',
|
|
||||||
label: 'Total Amount',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
roundNumber: 2,
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'grandTotalAmount',
|
|
||||||
label: 'Grand Total Amount',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
roundNumber: 2,
|
|
||||||
columnWidth: 175,
|
|
||||||
readOnly: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
stats: [
|
|
||||||
{
|
|
||||||
name: 'draft.count',
|
|
||||||
label: 'Draft',
|
|
||||||
type: 'number',
|
|
||||||
color: 'default'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sent.count',
|
|
||||||
label: 'Sent',
|
|
||||||
type: 'number',
|
|
||||||
color: 'cyan'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'partiallyPaid.count',
|
|
||||||
label: 'Partially Paid',
|
|
||||||
type: 'number',
|
|
||||||
color: 'processing'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'paid.count',
|
|
||||||
label: 'Paid',
|
|
||||||
type: 'number',
|
|
||||||
color: 'success'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'overdue.count',
|
|
||||||
label: 'Overdue',
|
|
||||||
type: 'number',
|
|
||||||
color: 'error'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cancelled.count',
|
|
||||||
label: 'Cancelled',
|
|
||||||
type: 'number',
|
|
||||||
color: 'default'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@ -155,7 +155,7 @@ export const Job = {
|
|||||||
name: 'printing.count',
|
name: 'printing.count',
|
||||||
label: 'Printing',
|
label: 'Printing',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
color: 'processing'
|
color: 'info'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'failed.count',
|
name: 'failed.count',
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
import OrderItemIcon from '../../components/Icons/OrderItemIcon'
|
import OrderItemIcon from '../../components/Icons/OrderItemIcon'
|
||||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||||
import EditIcon from '../../components/Icons/EditIcon'
|
|
||||||
import BinIcon from '../../components/Icons/BinIcon'
|
|
||||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
|
||||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
|
||||||
|
|
||||||
export const OrderItem = {
|
export const OrderItem = {
|
||||||
name: 'orderItem',
|
name: 'orderItem',
|
||||||
@ -18,63 +14,6 @@ export const OrderItem = {
|
|||||||
row: true,
|
row: true,
|
||||||
icon: InfoCircleIcon,
|
icon: InfoCircleIcon,
|
||||||
url: (_id) => `/dashboard/inventory/orderitems/info?orderItemId=${_id}`
|
url: (_id) => `/dashboard/inventory/orderitems/info?orderItemId=${_id}`
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'edit',
|
|
||||||
label: 'Edit',
|
|
||||||
type: 'button',
|
|
||||||
icon: EditIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/orderitems/info?orderItemId=${_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/inventory/orderitems/info?orderItemId=${_id}&action=cancelEdit`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?._isEditing && objectData?._isEditing == true
|
|
||||||
},
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'finishEdit',
|
|
||||||
label: 'Finish Edit',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/orderitems/info?orderItemId=${_id}&action=finishEdit`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?._isEditing && objectData?._isEditing == true
|
|
||||||
},
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'delete',
|
|
||||||
label: 'Delete',
|
|
||||||
type: 'button',
|
|
||||||
icon: BinIcon,
|
|
||||||
danger: true,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/orderitems/info?orderItemId=${_id}&action=delete`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
|
||||||
},
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
group: [],
|
group: [],
|
||||||
@ -82,8 +21,7 @@ export const OrderItem = {
|
|||||||
sorters: ['createdAt', 'updatedAt', 'itemAmount', 'quantity'],
|
sorters: ['createdAt', 'updatedAt', 'itemAmount', 'quantity'],
|
||||||
columns: [
|
columns: [
|
||||||
'_id',
|
'_id',
|
||||||
'_reference',
|
|
||||||
'state',
|
|
||||||
'itemType',
|
'itemType',
|
||||||
'item',
|
'item',
|
||||||
'itemAmount',
|
'itemAmount',
|
||||||
@ -92,7 +30,6 @@ export const OrderItem = {
|
|||||||
'taxRate',
|
'taxRate',
|
||||||
'totalAmountWithTax',
|
'totalAmountWithTax',
|
||||||
'order',
|
'order',
|
||||||
'shipment',
|
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt'
|
'updatedAt'
|
||||||
],
|
],
|
||||||
@ -151,16 +88,7 @@ export const OrderItem = {
|
|||||||
type: 'object',
|
type: 'object',
|
||||||
objectType: 'shipment',
|
objectType: 'shipment',
|
||||||
showHyperlink: true,
|
showHyperlink: true,
|
||||||
required: false,
|
required: true
|
||||||
columnWidth: 250,
|
|
||||||
readOnly: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
},
|
|
||||||
masterFilter: (objectData) => {
|
|
||||||
return {
|
|
||||||
order: objectData?.order?._id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'itemType',
|
name: 'itemType',
|
||||||
@ -168,7 +96,7 @@ export const OrderItem = {
|
|||||||
type: 'objectType',
|
type: 'objectType',
|
||||||
masterFilter: ['part', 'packaging', 'filament'],
|
masterFilter: ['part', 'packaging', 'filament'],
|
||||||
required: true,
|
required: true,
|
||||||
columnWidth: 175
|
columnWidth: 125
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'item',
|
name: 'item',
|
||||||
|
|||||||
@ -378,7 +378,7 @@ export const Printer = {
|
|||||||
name: 'printing.count',
|
name: 'printing.count',
|
||||||
label: 'Printing',
|
label: 'Printing',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
color: 'processing'
|
color: 'info'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'error.count',
|
name: 'error.count',
|
||||||
|
|||||||
@ -1,10 +1,6 @@
|
|||||||
import PurchaseOrderIcon from '../../components/Icons/PurchaseOrderIcon'
|
import PurchaseOrderIcon from '../../components/Icons/PurchaseOrderIcon'
|
||||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||||
import PlusIcon from '../../components/Icons/PlusIcon'
|
import PlusIcon from '../../components/Icons/PlusIcon'
|
||||||
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 PurchaseOrder = {
|
export const PurchaseOrder = {
|
||||||
name: 'purchaseOrder',
|
name: 'purchaseOrder',
|
||||||
@ -21,168 +17,19 @@ export const PurchaseOrder = {
|
|||||||
url: (_id) =>
|
url: (_id) =>
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}`
|
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}`
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'edit',
|
|
||||||
label: 'Edit',
|
|
||||||
type: 'button',
|
|
||||||
icon: EditIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_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/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=cancelEdit`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?._isEditing && objectData?._isEditing == true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'finishEdit',
|
|
||||||
label: 'Finish Edit',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=finishEdit`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?._isEditing && objectData?._isEditing == true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'delete',
|
|
||||||
label: 'Delete',
|
|
||||||
type: 'button',
|
|
||||||
icon: BinIcon,
|
|
||||||
danger: true,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=delete`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
|
||||||
},
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
{
|
||||||
name: 'New Order Item',
|
name: 'New Order Item',
|
||||||
label: 'New Order Item',
|
label: 'New Order Item',
|
||||||
type: 'button',
|
type: 'button',
|
||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
url: (_id) =>
|
url: (_id) =>
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=newOrderItem`,
|
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=newOrderItem`
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'New Shipment',
|
|
||||||
label: 'New Shipment',
|
|
||||||
type: 'button',
|
|
||||||
icon: PlusIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=newShipment`,
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'New Invoice',
|
|
||||||
label: 'New Invoice',
|
|
||||||
type: 'button',
|
|
||||||
icon: PlusIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=newInvoice`,
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'received'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'divider'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'post',
|
|
||||||
label: 'Post',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=post`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?.state?.type == 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'acknowledge',
|
|
||||||
label: 'Acknowledge',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=acknowledge`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?.state?.type == 'sent'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'complete',
|
|
||||||
label: 'Complete',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/shipments/info?shipmentId=${_id}&action=complete`,
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'received'
|
|
||||||
},
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?.state?.type == 'received'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'cancel',
|
|
||||||
label: 'Cancel',
|
|
||||||
type: 'button',
|
|
||||||
icon: XMarkIcon,
|
|
||||||
danger: true,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/purchaseorders/info?purchaseOrderId=${_id}&action=cancel`,
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type == 'cancelled'
|
|
||||||
},
|
|
||||||
visible: (objectData) => {
|
|
||||||
return (
|
|
||||||
objectData?.state?.type != 'draft' &&
|
|
||||||
objectData?.state?.type != 'completed' &&
|
|
||||||
objectData?.state?.type != 'received'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
group: ['vendor'],
|
group: ['vendor'],
|
||||||
filters: ['vendor'],
|
filters: ['vendor'],
|
||||||
sorters: ['createdAt', 'state', 'updatedAt'],
|
sorters: ['createdAt', 'state', 'updatedAt'],
|
||||||
columns: [
|
columns: ['_id', 'createdAt', 'state', 'updatedAt', 'vendor'],
|
||||||
'_id',
|
|
||||||
'_reference',
|
|
||||||
'state',
|
|
||||||
'vendor',
|
|
||||||
'totalAmount',
|
|
||||||
'totalAmountWithTax',
|
|
||||||
'totalTaxAmount',
|
|
||||||
'shippingAmount',
|
|
||||||
'shippingAmountWithTax',
|
|
||||||
'grandTotalAmount',
|
|
||||||
'createdAt',
|
|
||||||
'updatedAt',
|
|
||||||
'vendor'
|
|
||||||
],
|
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
name: '_id',
|
name: '_id',
|
||||||
@ -215,7 +62,6 @@ export const PurchaseOrder = {
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
||||||
{ name: 'postedAt', label: 'Posted At', type: 'dateTime', readOnly: true },
|
|
||||||
{
|
{
|
||||||
name: 'vendor',
|
name: 'vendor',
|
||||||
label: 'Vendor',
|
label: 'Vendor',
|
||||||
@ -225,24 +71,10 @@ export const PurchaseOrder = {
|
|||||||
showHyperlink: true
|
showHyperlink: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'acknowledgedAt',
|
name: 'totalAmount',
|
||||||
label: 'Acknowledged At',
|
label: 'Total Amount',
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'totalTaxAmount',
|
|
||||||
label: 'Total Tax Amount',
|
|
||||||
type: 'number',
|
type: 'number',
|
||||||
prefix: '£',
|
prefix: '£',
|
||||||
roundNumber: 2,
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 175
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'CompletedAt',
|
|
||||||
label: 'Completed At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -250,89 +82,14 @@ export const PurchaseOrder = {
|
|||||||
label: 'Total Amount w/ Tax',
|
label: 'Total Amount w/ Tax',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
prefix: '£',
|
prefix: '£',
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 175,
|
|
||||||
roundNumber: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'shippingAmount',
|
|
||||||
label: 'Shipping Amount',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
roundNumber: 2,
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'shippingAmountWithTax',
|
|
||||||
label: 'Shipping Amount w/ Tax',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
readOnly: true,
|
|
||||||
roundNumber: 2,
|
|
||||||
columnWidth: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'totalAmount',
|
|
||||||
label: 'Total Amount',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
roundNumber: 2,
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'grandTotalAmount',
|
|
||||||
label: 'Grand Total Amount',
|
|
||||||
type: 'number',
|
|
||||||
prefix: '£',
|
|
||||||
roundNumber: 2,
|
|
||||||
columnWidth: 175,
|
|
||||||
readOnly: true
|
readOnly: true
|
||||||
}
|
|
||||||
],
|
|
||||||
stats: [
|
|
||||||
{
|
|
||||||
name: 'draft.count',
|
|
||||||
label: 'Draft',
|
|
||||||
type: 'number',
|
|
||||||
color: 'default'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'sent.count',
|
name: 'totalTaxAmount',
|
||||||
label: 'Sent',
|
label: 'Total Tax Amount',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
color: 'cyan'
|
prefix: '£',
|
||||||
},
|
readOnly: true
|
||||||
{
|
|
||||||
name: 'acknowledged.count',
|
|
||||||
label: 'Acknowledged',
|
|
||||||
type: 'number',
|
|
||||||
color: 'processing'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'partiallyShipped.count',
|
|
||||||
label: 'Partially Shipped',
|
|
||||||
type: 'number',
|
|
||||||
color: 'processing'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'shipped.count',
|
|
||||||
label: 'Shipped',
|
|
||||||
type: 'number',
|
|
||||||
color: 'processing'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'partiallyReceived.count',
|
|
||||||
label: 'Partially Received',
|
|
||||||
type: 'number',
|
|
||||||
color: 'success'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'received.count',
|
|
||||||
label: 'Received',
|
|
||||||
type: 'number',
|
|
||||||
color: 'success'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,10 @@
|
|||||||
import ShipmentIcon from '../../components/Icons/ShipmentIcon'
|
import ShipmentIcon from '../../components/Icons/ShipmentIcon'
|
||||||
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
|
||||||
import EditIcon from '../../components/Icons/EditIcon'
|
|
||||||
import BinIcon from '../../components/Icons/BinIcon'
|
|
||||||
import CheckIcon from '../../components/Icons/CheckIcon'
|
|
||||||
import XMarkIcon from '../../components/Icons/XMarkIcon'
|
|
||||||
|
|
||||||
export const Shipment = {
|
export const Shipment = {
|
||||||
name: 'shipment',
|
name: 'shipment',
|
||||||
label: 'Shipment',
|
label: 'Shipment',
|
||||||
prefix: 'SHP',
|
prefix: 'SHM',
|
||||||
icon: ShipmentIcon,
|
icon: ShipmentIcon,
|
||||||
actions: [
|
actions: [
|
||||||
{
|
{
|
||||||
@ -18,126 +14,25 @@ export const Shipment = {
|
|||||||
row: true,
|
row: true,
|
||||||
icon: InfoCircleIcon,
|
icon: InfoCircleIcon,
|
||||||
url: (_id) => `/dashboard/inventory/shipments/info?shipmentId=${_id}`
|
url: (_id) => `/dashboard/inventory/shipments/info?shipmentId=${_id}`
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'edit',
|
|
||||||
label: 'Edit',
|
|
||||||
type: 'button',
|
|
||||||
icon: EditIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/shipments/info?shipmentId=${_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/inventory/shipments/info?shipmentId=${_id}&action=cancelEdit`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?._isEditing && objectData?._isEditing == true
|
|
||||||
},
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'finishEdit',
|
|
||||||
label: 'Finish Edit',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/shipments/info?shipmentId=${_id}&action=finishEdit`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return objectData?._isEditing && objectData?._isEditing == true
|
|
||||||
},
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'delete',
|
|
||||||
label: 'Delete',
|
|
||||||
type: 'button',
|
|
||||||
icon: BinIcon,
|
|
||||||
danger: true,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/shipments/info?shipmentId=${_id}&action=delete`,
|
|
||||||
visible: (objectData) => {
|
|
||||||
return !(objectData?._isEditing && objectData?._isEditing == true)
|
|
||||||
},
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'draft'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ type: 'divider' },
|
|
||||||
{
|
|
||||||
name: 'ship',
|
|
||||||
label: 'Ship',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/shipments/info?shipmentId=${_id}&action=ship`,
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'planned'
|
|
||||||
},
|
|
||||||
visible: (objectData) => {
|
|
||||||
return (
|
|
||||||
objectData?.state?.type == 'planned' ||
|
|
||||||
objectData?.state?.type == 'draft'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'receive',
|
|
||||||
label: 'Receive',
|
|
||||||
type: 'button',
|
|
||||||
icon: CheckIcon,
|
|
||||||
url: (_id) =>
|
|
||||||
`/dashboard/inventory/shipments/info?shipmentId=${_id}&action=receive`,
|
|
||||||
disabled: (objectData) => {
|
|
||||||
return objectData?.state?.type != 'shipped'
|
|
||||||
},
|
|
||||||
visible: (objectData) => {
|
|
||||||
return (
|
|
||||||
objectData?.state?.type == 'shipped' ||
|
|
||||||
objectData?.state?.type == 'delivered'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
group: [],
|
group: ['vendor', 'purchaseOrder'],
|
||||||
filters: ['orderType', 'order', 'state', 'courierService'],
|
filters: ['vendor', 'purchaseOrder', 'state', 'courierService'],
|
||||||
sorters: [
|
sorters: [
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'state',
|
'state',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
'shippedAt',
|
'shippedDate',
|
||||||
'expectedAt',
|
'expectedDeliveryDate'
|
||||||
'deliveredAt'
|
|
||||||
],
|
],
|
||||||
columns: [
|
columns: [
|
||||||
'_id',
|
'_id',
|
||||||
'_reference',
|
'createdAt',
|
||||||
'state',
|
'state',
|
||||||
'orderType',
|
|
||||||
'order',
|
|
||||||
'amount',
|
|
||||||
'amountWithTax',
|
|
||||||
'taxRate',
|
|
||||||
'taxAmount',
|
|
||||||
'trackingNumber',
|
|
||||||
'shippedAt',
|
|
||||||
'expectedAt',
|
|
||||||
'deliveredAt',
|
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
'createdAt'
|
'vendor',
|
||||||
|
'purchaseOrder',
|
||||||
|
'trackingNumber'
|
||||||
],
|
],
|
||||||
properties: [
|
properties: [
|
||||||
{
|
{
|
||||||
@ -155,139 +50,231 @@ export const Shipment = {
|
|||||||
type: 'dateTime',
|
type: 'dateTime',
|
||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
{
|
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
||||||
name: '_reference',
|
|
||||||
label: 'Reference',
|
|
||||||
type: 'reference',
|
|
||||||
objectType: 'shipment',
|
|
||||||
showCopy: true,
|
|
||||||
readOnly: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'updatedAt',
|
name: 'updatedAt',
|
||||||
label: 'Updated At',
|
label: 'Updated At',
|
||||||
type: 'dateTime',
|
type: 'dateTime',
|
||||||
readOnly: true
|
readOnly: true
|
||||||
},
|
},
|
||||||
{ name: 'state', label: 'State', type: 'state', readOnly: true },
|
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'shippedAt',
|
name: 'purchaseOrder',
|
||||||
label: 'Shipped At',
|
label: 'Purchase Order',
|
||||||
type: 'dateTime',
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'orderType',
|
|
||||||
label: 'Order Type',
|
|
||||||
required: true,
|
|
||||||
type: 'objectType',
|
|
||||||
masterFilter: ['purchaseOrder', 'salesOrder'],
|
|
||||||
showHyperlink: true
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
name: 'expectedAt',
|
|
||||||
label: 'Expected At',
|
|
||||||
type: 'dateTime',
|
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'order',
|
|
||||||
label: 'Order',
|
|
||||||
required: true,
|
required: true,
|
||||||
type: 'object',
|
type: 'object',
|
||||||
showHyperlink: true,
|
objectType: 'purchaseOrder',
|
||||||
objectType: (objectData) => {
|
showHyperlink: true
|
||||||
return objectData?.orderType
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'deliveredAt',
|
name: 'vendor',
|
||||||
label: 'Delivered At',
|
label: 'Vendor',
|
||||||
type: 'dateTime',
|
required: true,
|
||||||
required: false
|
type: 'object',
|
||||||
|
objectType: 'vendor',
|
||||||
|
showHyperlink: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'courierService',
|
name: 'courierService',
|
||||||
label: 'Courier Service',
|
label: 'Courier Service',
|
||||||
required: true,
|
required: false,
|
||||||
type: 'object',
|
type: 'object',
|
||||||
objectType: 'courierService'
|
objectType: 'courierService'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'trackingNumber',
|
name: 'trackingNumber',
|
||||||
label: 'Tracking Number',
|
label: 'Tracking Number',
|
||||||
type: 'text',
|
type: 'string',
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'items',
|
||||||
|
label: 'Shipment Items',
|
||||||
|
type: 'objectChildren',
|
||||||
|
required: true,
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
name: 'itemType',
|
||||||
|
label: 'Item Type',
|
||||||
|
type: 'objectType',
|
||||||
|
masterFilter: ['part', 'packaging'],
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'item',
|
||||||
|
label: 'Item',
|
||||||
|
type: 'object',
|
||||||
|
objectType: (objectData) => {
|
||||||
|
return objectData?.itemType
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
showHyperlink: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'itemCost',
|
||||||
|
label: 'Item Cost',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
prefix: '£',
|
||||||
|
min: 0,
|
||||||
|
step: 0.01,
|
||||||
|
columnWidth: 150,
|
||||||
|
value: (objectData) => {
|
||||||
|
if (objectData?.item) {
|
||||||
|
return objectData?.item?.cost || undefined
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'quantity',
|
||||||
|
label: 'Quantity',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
columnWidth: 150
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalCost',
|
||||||
|
label: 'Total Cost',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
prefix: '£',
|
||||||
|
min: 0,
|
||||||
|
step: 0.01,
|
||||||
|
columnWidth: 150,
|
||||||
|
value: (objectData) => {
|
||||||
|
return (
|
||||||
|
(objectData?.itemCost || 0) * (objectData?.quantity || 0) ||
|
||||||
|
undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'taxRate',
|
name: 'taxRate',
|
||||||
label: 'Tax Rate',
|
label: 'Tax Rate',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
objectType: 'taxRate',
|
objectType: 'taxRate',
|
||||||
showHyperlink: true
|
showHyperlink: true,
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'taxRecord',
|
|
||||||
label: 'Tax Record',
|
|
||||||
type: 'object',
|
|
||||||
objectType: 'taxRecord',
|
|
||||||
showHyperlink: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'amount',
|
|
||||||
label: 'Amount',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
prefix: '£',
|
|
||||||
min: 0,
|
|
||||||
step: 0.01,
|
|
||||||
columnWidth: 150
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'taxAmount',
|
|
||||||
label: 'Tax Amount',
|
|
||||||
type: 'number',
|
|
||||||
required: true,
|
|
||||||
prefix: '£',
|
|
||||||
fixedNumber: 2,
|
|
||||||
min: 0,
|
|
||||||
step: 0.01,
|
|
||||||
readOnly: true,
|
|
||||||
value: (objectData) => {
|
value: (objectData) => {
|
||||||
return (objectData?.amount * objectData?.taxRate?.rate) / 100 || 0
|
if (objectData?.item) {
|
||||||
|
return objectData?.item?.costTaxRate || undefined
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'amountWithTax',
|
name: 'totalCostWithTax',
|
||||||
label: 'Amount w/ Tax',
|
label: 'Total Cost w/ Tax',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
|
readOnly: true,
|
||||||
prefix: '£',
|
prefix: '£',
|
||||||
min: 0,
|
min: 0,
|
||||||
step: 0.01,
|
step: 0.01,
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 175,
|
columnWidth: 175,
|
||||||
value: (objectData) => {
|
value: (objectData) => {
|
||||||
if (objectData?._isEditing == true) {
|
if (objectData?.taxRate?.rateType == 'percentage') {
|
||||||
return (
|
return (
|
||||||
(objectData?.amount || 0) + (objectData?.taxAmount || 0)
|
(
|
||||||
).toFixed(2)
|
(objectData?.totalCost || 0) *
|
||||||
|
(1 + objectData?.taxRate?.rate / 100)
|
||||||
|
).toFixed(2) || undefined
|
||||||
|
)
|
||||||
|
} else if (objectData?.taxRate?.rateType == 'amount') {
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
(objectData?.totalCost || 0) + objectData?.taxRate?.rate
|
||||||
|
).toFixed(2) || undefined
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return objectData?.totalCost || undefined
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
Number.parseFloat(objectData?.amount) &&
|
|
||||||
(objectData.taxRate == undefined || objectData.taxRate == null)
|
|
||||||
) {
|
|
||||||
return Number.parseFloat(objectData?.amount).toFixed(2)
|
|
||||||
}
|
}
|
||||||
if (Number.parseFloat(objectData?.amountWithTax)) {
|
|
||||||
return Number.parseFloat(objectData?.amountWithTax).toFixed(2)
|
|
||||||
}
|
}
|
||||||
return 0
|
],
|
||||||
|
rollups: [
|
||||||
|
{
|
||||||
|
name: 'totalQuantity',
|
||||||
|
label: 'Total',
|
||||||
|
type: 'number',
|
||||||
|
property: 'quantity',
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.items?.reduce(
|
||||||
|
(acc, item) => acc + item.quantity,
|
||||||
|
0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalCost',
|
||||||
|
label: 'Total',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
property: 'totalCost',
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.items
|
||||||
|
?.reduce((acc, item) => acc + (item.totalCost || 0), 0)
|
||||||
|
.toFixed(2)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'totalCostWithTax',
|
||||||
|
label: 'Total',
|
||||||
|
type: 'number',
|
||||||
|
prefix: '£',
|
||||||
|
property: 'totalCostWithTax',
|
||||||
|
value: (objectData) => {
|
||||||
|
return objectData?.items
|
||||||
|
?.reduce((acc, item) => acc + (item.totalCostWithTax || 0), 0)
|
||||||
|
.toFixed(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'cost',
|
||||||
|
label: 'Cost',
|
||||||
|
type: 'netGross',
|
||||||
|
required: true,
|
||||||
|
prefix: '£',
|
||||||
|
min: 0,
|
||||||
|
step: 0.01,
|
||||||
|
value: (objectData) => {
|
||||||
|
const net = objectData?.items?.reduce(
|
||||||
|
(acc, item) => acc + (item.totalCost || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
const gross = objectData?.items?.reduce(
|
||||||
|
(acc, item) => acc + (item.totalCostWithTax || 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
return { net: net, gross: gross }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'shippedDate',
|
||||||
|
label: 'Shipped Date',
|
||||||
|
type: 'dateTime',
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expectedDeliveryDate',
|
||||||
|
label: 'Expected Delivery Date',
|
||||||
|
type: 'dateTime',
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actualDeliveryDate',
|
||||||
|
label: 'Actual Delivery Date',
|
||||||
|
type: 'dateTime',
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'notes',
|
||||||
|
label: 'Notes',
|
||||||
|
type: 'textarea',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import { lazy } from 'react'
|
|
||||||
import { Route } from 'react-router-dom'
|
|
||||||
|
|
||||||
const Invoices = lazy(
|
|
||||||
() => import('../components/Dashboard/Finance/Invoices.jsx')
|
|
||||||
)
|
|
||||||
const InvoiceInfo = lazy(
|
|
||||||
() => import('../components/Dashboard/Finance/Invoices/InvoiceInfo.jsx')
|
|
||||||
)
|
|
||||||
const FinanceOverview = lazy(
|
|
||||||
() => import('../components/Dashboard/Finance/FinanceOverview.jsx')
|
|
||||||
)
|
|
||||||
|
|
||||||
const FinanceRoutes = [
|
|
||||||
<Route
|
|
||||||
key='overview'
|
|
||||||
path='finance/overview'
|
|
||||||
element={<FinanceOverview />}
|
|
||||||
/>,
|
|
||||||
<Route key='invoices' path='finance/invoices' element={<Invoices />} />,
|
|
||||||
<Route
|
|
||||||
key='invoices-info'
|
|
||||||
path='finance/invoices/info'
|
|
||||||
element={<InvoiceInfo />}
|
|
||||||
/>
|
|
||||||
]
|
|
||||||
|
|
||||||
export default FinanceRoutes
|
|
||||||
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
export { default as ProductionRoutes } from './ProductionRoutes'
|
export { default as ProductionRoutes } from './ProductionRoutes'
|
||||||
export { default as InventoryRoutes } from './InventoryRoutes'
|
export { default as InventoryRoutes } from './InventoryRoutes'
|
||||||
export { default as FinanceRoutes } from './FinanceRoutes'
|
|
||||||
export { default as ManagementRoutes } from './ManagementRoutes'
|
export { default as ManagementRoutes } from './ManagementRoutes'
|
||||||
export { default as DeveloperRoutes } from './DeveloperRoutes'
|
export { default as DeveloperRoutes } from './DeveloperRoutes'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user