Add Stock Audit Level Management Features and Enhance Stock Audit Components

- Introduced Stock Audit Level management components, including StockAuditLevels, NewStockAuditLevel, and StockAuditLevelInfo, to facilitate the creation and management of stock audit levels.
- Enhanced existing StockAudit components by adding a PostStockAudit feature for posting stock audits and updating the NewStockAudit form to handle draft states.
- Updated StockAuditInfo to include new properties and improved visibility settings for audit lines, enhancing user experience and data representation.
- Refactored ObjectModels and sidebar configurations to integrate Stock Audit Levels into the management dashboard, improving navigation and accessibility.
- Added new utility functions and improved data handling across components to ensure seamless integration and performance.
This commit is contained in:
Tom Butcher 2026-09-01 15:41:35 +01:00
parent a5b888333d
commit 972fdb62ad
14 changed files with 1095 additions and 197 deletions

View File

@ -1,17 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?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"> <!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;"> <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(1.0066,0,0,1.0066,5.43531,-1.54828)"> <path d="M32.468,59.712C32.725,59.914 32.837,60.249 32.754,60.565C32.671,60.881 32.408,61.117 32.085,61.166C31.147,61.264 30.216,61.073 29.3,60.555L8.262,48.803C5.729,47.378 4.441,45.879 4.441,42.127L4.441,20.65C4.441,17.814 5.489,16.02 7.877,14.692L26.541,4.215C29.828,2.367 33.176,2.367 36.463,4.215L55.149,14.692C57.515,16.02 58.563,17.814 58.563,20.65C58.563,21.207 58.294,21.73 57.84,22.053C57.386,22.376 56.804,22.46 56.278,22.277C53.589,21.35 50.911,20.452 49.823,20.095C49.813,20.092 49.803,20.089 49.793,20.086C49.564,20.017 49.398,19.817 49.372,19.579C49.346,19.341 49.464,19.11 49.673,18.993C49.673,18.992 49.674,18.992 49.674,18.992C49.978,18.821 50.166,18.499 50.166,18.15C50.167,17.801 49.979,17.479 49.674,17.307C45.559,14.986 34.365,8.675 34.365,8.675C32.407,7.556 30.587,7.587 28.656,8.675L24.257,11.165C24.257,11.165 34.347,16.813 39.002,19.419C39.405,19.645 39.637,20.087 39.595,20.547C39.553,21.007 39.244,21.399 38.807,21.548C37.999,21.826 37.152,22.12 36.313,22.416C35.556,22.682 34.72,22.61 34.021,22.217C30.425,20.197 19.274,13.934 19.274,13.934L11.856,18.15C11.856,18.15 22.923,24.355 26.284,26.24C26.797,26.528 27.006,27.16 26.765,27.696C26.444,28.447 26.307,29.369 26.307,30.532C26.307,30.829 26.149,31.104 25.892,31.254C25.635,31.404 25.318,31.406 25.06,31.26C21.115,29.032 9.169,22.284 9.169,22.284L9.169,41.664C9.169,43.059 9.681,43.919 11.137,44.722C11.137,44.722 22.059,50.89 26.083,53.162C27.034,53.7 27.832,54.471 28.402,55.402C29.329,56.93 30.681,58.316 32.468,59.712Z" style="fill-rule:nonzero;"/>
<path d="M26.391,65.119C26.941,65.119 27.788,64.903 28.58,64.471C46.553,54.66 52.781,50.171 52.781,38.771L52.781,15.146C52.781,11.443 51.347,10.136 48.246,8.814C44.773,7.367 33.063,3.249 29.657,2.086C28.622,1.754 27.44,1.538 26.391,1.538C25.341,1.538 24.159,1.811 23.15,2.086C19.744,3.058 8.009,7.393 4.535,8.814C1.46,10.11 0,11.443 0,15.146L0,38.771C0,50.171 6.254,54.634 24.201,64.471C25.019,64.903 25.84,65.119 26.391,65.119ZM26.391,58.557C25.941,58.557 25.498,58.392 24.588,57.841C10.303,49.13 5.869,46.787 5.869,37.603L5.869,16.142C5.869,15.041 6.079,14.601 6.965,14.251C11.591,12.416 20.574,9.417 24.758,7.773C25.461,7.509 25.946,7.419 26.391,7.419C26.836,7.419 27.315,7.535 28.023,7.773C32.208,9.417 41.139,12.595 45.842,14.251C46.697,14.575 46.913,15.041 46.913,16.142L46.913,37.603C46.913,46.838 42.321,49.318 28.193,57.841C27.309,58.386 26.84,58.557 26.391,58.557Z" style="fill-rule:nonzero;"/> <g transform="matrix(0.652268,0,0,0.652268,28.5941,21.1337)">
</g> <path d="M27.141,65.719C27.781,65.719 28.688,65.453 29.5,65.031C47.813,55.5 54.281,50.531 54.281,39.141L54.281,16.016C54.281,11.563 52.797,9.766 48.906,8.094C45.813,6.797 34.453,2.969 31.547,2.016C30.172,1.594 28.5,1.328 27.141,1.328C25.781,1.328 24.109,1.641 22.75,2.016C19.844,2.828 8.469,6.813 5.375,8.094C1.5,9.75 0,11.563 0,16.016L0,39.141C0,50.531 6.484,55.484 24.781,65.031C25.609,65.453 26.5,65.719 27.141,65.719ZM27.141,57.547C26.781,57.547 26.438,57.422 25.578,56.891C11.563,48.25 7.469,46.297 7.469,37.953L7.469,17.063C7.469,15.891 7.719,15.391 8.625,15.031C13.031,13.266 21.594,10.438 24.828,9.203C25.891,8.859 26.516,8.719 27.141,8.719C27.766,8.719 28.375,8.875 29.453,9.203C32.688,10.438 41.219,13.375 45.672,15.031C46.547,15.375 46.813,15.891 46.813,17.063L46.813,37.953C46.813,46.438 42.281,48.828 28.703,56.891C27.859,57.406 27.5,57.547 27.141,57.547Z" style="fill-rule:nonzero;"/>
<g transform="matrix(1,0,0,1,0,3.06619)"> <g transform="matrix(0,-1.228947,0.741699,0,5.037355,87.672304)">
<g transform="matrix(6.59906e-17,-1.07771,0.650424,3.9827e-17,12.7469,98.4759)">
<path d="M58.139,21.607C58.139,19.1 56.911,17.066 55.398,17.066C53.885,17.066 52.657,19.1 52.657,21.607L52.657,37.595C52.657,40.101 53.885,42.136 55.398,42.136C56.911,42.136 58.139,40.101 58.139,37.595L58.139,21.607Z"/> <path d="M58.139,21.607C58.139,19.1 56.911,17.066 55.398,17.066C53.885,17.066 52.657,19.1 52.657,21.607L52.657,37.595C52.657,40.101 53.885,42.136 55.398,42.136C56.911,42.136 58.139,40.101 58.139,37.595L58.139,21.607Z"/>
</g> </g>
<g transform="matrix(6.59906e-17,-1.07771,0.650424,3.9827e-17,12.7469,76.7291)"> <g transform="matrix(0,-1.228947,0.741699,0,5.037355,112.47087)">
<path d="M58.139,21.607C58.139,19.1 56.911,17.066 55.398,17.066C53.885,17.066 52.657,19.1 52.657,21.607L52.657,37.595C52.657,40.101 53.885,42.136 55.398,42.136C56.911,42.136 58.139,40.101 58.139,37.595L58.139,21.607Z"/> <path d="M58.139,21.607C58.139,19.1 56.911,17.066 55.398,17.066C53.885,17.066 52.657,19.1 52.657,21.607L52.657,37.595C52.657,40.101 53.885,42.136 55.398,42.136C56.911,42.136 58.139,40.101 58.139,37.595L58.139,21.607Z"/>
</g> </g>
<g transform="matrix(6.59906e-17,-1.07771,1.05902,6.48461e-17,0.652146,87.6054)"> <g transform="matrix(0,-1.228947,1.207634,0,-8.754676,100.074894)">
<path d="M58.139,19.855C58.139,18.315 56.911,17.066 55.398,17.066C53.885,17.066 52.657,18.315 52.657,19.855L52.657,39.347C52.657,40.886 53.885,42.136 55.398,42.136C56.911,42.136 58.139,40.886 58.139,39.347L58.139,19.855Z"/> <path d="M58.139,19.855C58.139,18.315 56.911,17.066 55.398,17.066C53.885,17.066 52.657,18.315 52.657,19.855L52.657,39.347C52.657,40.886 53.885,42.136 55.398,42.136C56.911,42.136 58.139,40.886 58.139,39.347L58.139,19.855Z"/>
</g> </g>
</g> </g>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -6,9 +6,9 @@ import WizardView from '../../common/WizardView'
const NewStockAudit = ({ onOk, reset, defaultValues }) => { const NewStockAudit = ({ onOk, reset, defaultValues }) => {
return ( return (
<NewObjectForm <NewObjectForm
type={'stockAudit'} type='stockAudit'
reset={reset} reset={reset}
defaultValues={{ state: { type: 'new' }, ...defaultValues }} defaultValues={{ state: { type: 'draft' }, auditLines: [], ...defaultValues }}
> >
{({ handleSubmit, submitLoading, objectData, formValid }) => { {({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [ const steps = [
@ -23,6 +23,7 @@ const NewStockAudit = ({ onOk, reset, defaultValues }) => {
isEditing={true} isEditing={true}
required={true} required={true}
objectData={objectData} objectData={objectData}
visibleProperties={{ auditLines: false }}
/> />
) )
}, },
@ -38,7 +39,8 @@ const NewStockAudit = ({ onOk, reset, defaultValues }) => {
_id: false, _id: false,
_reference: false, _reference: false,
createdAt: false, createdAt: false,
updatedAt: false updatedAt: false,
auditLines: false
}} }}
isEditing={false} isEditing={false}
objectData={objectData} objectData={objectData}

View File

@ -0,0 +1,46 @@
import { useState, useContext } from 'react'
import PropTypes from 'prop-types'
import { ApiServerContext } from '../../context/ApiServerContext'
import { message } from 'antd'
import MessageDialogView from '../../common/MessageDialogView.jsx'
const PostStockAudit = ({ onOk, objectData }) => {
const [postLoading, setPostLoading] = useState(false)
const { sendObjectFunction } = useContext(ApiServerContext)
const handlePost = async () => {
setPostLoading(true)
try {
const result = await sendObjectFunction(
objectData._id,
'StockAudit',
'post'
)
if (result) {
message.success('Stock audit posted')
onOk(result)
}
} catch (error) {
console.error('Error posting stock audit:', error)
} finally {
setPostLoading(false)
}
}
return (
<MessageDialogView
title={'Post this stock audit?'}
description={`Posting will create stock events for audit lines with variance, adjust stock for each SKU at the selected location, and mark this audit as complete.`}
onOk={handlePost}
okText='Post'
okLoading={postLoading}
/>
)
}
PostStockAudit.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default PostStockAudit

View File

@ -8,10 +8,12 @@ import useCollapseState from '../../hooks/useCollapseState.jsx'
import NotesPanel from '../../common/NotesPanel.jsx' import NotesPanel from '../../common/NotesPanel.jsx'
import InfoCollapse from '../../common/InfoCollapse.jsx' import InfoCollapse from '../../common/InfoCollapse.jsx'
import ObjectInfo from '../../common/ObjectInfo.jsx' import ObjectInfo from '../../common/ObjectInfo.jsx'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import ViewButton from '../../common/ViewButton.jsx' import ViewButton from '../../common/ViewButton.jsx'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx' import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import NoteIcon from '../../../Icons/NoteIcon.jsx' import NoteIcon from '../../../Icons/NoteIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx' import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import StockAuditIcon from '../../../Icons/StockAuditIcon.jsx'
import ObjectForm from '../../common/ObjectForm.jsx' import ObjectForm from '../../common/ObjectForm.jsx'
import EditButtons from '../../common/EditButtons.jsx' import EditButtons from '../../common/EditButtons.jsx'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx' import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
@ -23,7 +25,7 @@ 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 UserNotifierToggle from '../../common/UserNotifierToggle.jsx' import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelByName } from '../../../../database/ObjectModels.js' import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx' import ScrollBox from '../../common/ScrollBox.jsx'
const log = loglevel.getLogger('StockAuditInfo') const log = loglevel.getLogger('StockAuditInfo')
@ -38,7 +40,7 @@ const StockAuditInfo = () => {
'StockAuditInfo', 'StockAuditInfo',
{ {
info: true, info: true,
stocks: true, auditLines: true,
notes: true, notes: true,
auditLogs: false auditLogs: false
} }
@ -83,163 +85,177 @@ const StockAuditInfo = () => {
} }
return ( return (
<> <Flex
<Flex gap='large'
gap='large' vertical='true'
vertical='true' style={{
style={{ maxHeight: '100%',
maxHeight: '100%', minHeight: 0
minHeight: 0 }}
}} >
> <Flex justify={'space-between'}>
<Flex justify={'space-between'}> <Space size='small'>
<Space size='small'> <Space size='small'>
<Space size='small'> <ObjectActions
<ObjectActions type='stockAudit'
type='stockAudit'
pageName='info' pageName='info'
id={stockAuditId} id={stockAuditId}
disabled={objectFormState.loading} disabled={objectFormState.loading}
objectData={objectFormState.objectData} objectData={objectFormState.objectData}
/> />
<ViewButton <ViewButton
disabled={objectFormState.loading} disabled={objectFormState.loading}
items={[ items={[
{ key: 'info', label: 'Stock Audit Information' }, { key: 'info', label: 'Stock Audit Information' },
{ key: 'notes', label: 'Notes' }, { key: 'auditLines', label: 'Audit Lines' },
{ key: 'auditLogs', label: 'Audit Logs' } { key: 'notes', label: 'Notes' },
]} { key: 'auditLogs', label: 'Audit Logs' }
visibleState={collapseState} ]}
updateVisibleState={updateCollapseState} visibleState={collapseState}
/> updateVisibleState={updateCollapseState}
<UserNotifierToggle />
type='stockAudit' <UserNotifierToggle
objectData={objectFormState.objectData} type='stockAudit'
disabled={objectFormState.loading} objectData={objectFormState.objectData}
/> disabled={objectFormState.loading}
<DocumentPrintButton />
type='stockAudit' <DocumentPrintButton
objectData={objectFormState.objectData} type='stockAudit'
disabled={objectFormState.loading} objectData={objectFormState.objectData}
/> disabled={objectFormState.loading}
</Space>
<ActivityIndicator
activities={objectFormState.activities}
showStartingDivider={true}
/> />
</Space> </Space>
<Space> <ActivityIndicator
<EditButtons activities={objectFormState.activities}
isEditing={objectFormState.isEditing} showStartingDivider={true}
handleUpdate={() => { />
actionHandlerRef.current.callAction('finishEdit') </Space>
}} <Space>
cancelEditing={() => { <EditButtons
actionHandlerRef.current.callAction('cancelEdit') isEditing={objectFormState.isEditing}
}} handleUpdate={() => {
startEditing={() => { actionHandlerRef.current.callAction('finishEdit')
actionHandlerRef.current.callAction('edit') }}
}} cancelEditing={() => {
editLoading={objectFormState.editLoading} actionHandlerRef.current.callAction('cancelEdit')
formValid={objectFormState.formValid} }}
disabled={ startEditing={() => {
objectFormState.beingEditedByOther || actionHandlerRef.current.callAction('edit')
objectFormState.loading || }}
objectFormState.editDisabled editLoading={objectFormState.editLoading}
} formValid={objectFormState.formValid}
loading={objectFormState.editLoading} disabled={
/> objectFormState.beingEditedByOther ||
</Space> objectFormState.loading ||
</Flex> objectFormState.editDisabled
}
<ScrollBox> loading={objectFormState.editLoading}
<Flex vertical gap={'large'}> />
<ActionHandler </Space>
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<InfoCollapse
title='Stock Audit Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
>
<ObjectForm
id={stockAuditId}
type='stockAudit'
style={{ height: '100%' }}
ref={objectFormRef}
setCurrentObject={true}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => {
return (
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='stockAudit'
objectData={objectData}
visibleProperties={{
content: false,
testObject: false
}}
/>
)
}}
</ObjectForm>
</InfoCollapse>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Spin
spinning={objectFormState.loading}
indicator={<LoadingOutlined />}
>
<Card>
<NotesPanel _id={stockAuditId} type='stockAudit' />
</Card>
</Spin>
</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:
getModelByName('stockAudit').prefix +
':' +
stockAuditId
}}
visibleColumns={{ _id: false, parent: false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex> </Flex>
</>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={stockAuditId}
type='stockAudit'
style={{ height: '100%' }}
ref={objectFormRef}
setCurrentObject={true}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap={'large'}>
<InfoCollapse
title='Stock Audit Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) => updateCollapseState('info', expanded)}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='stockAudit'
objectData={objectData}
visibleProperties={{
auditLines: false
}}
/>
</InfoCollapse>
<InfoCollapse
title='Audit Lines'
icon={<StockAuditIcon />}
active={collapseState.auditLines}
onToggle={(expanded) =>
updateCollapseState('auditLines', expanded)
}
collapseKey='auditLines'
>
<ObjectProperty
{...getModelProperty('stockAudit', 'auditLines')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Notes'
icon={<NoteIcon />}
active={collapseState.notes}
onToggle={(expanded) => updateCollapseState('notes', expanded)}
collapseKey='notes'
>
<Spin
spinning={objectFormState.loading}
indicator={<LoadingOutlined />}
>
<Card>
<NotesPanel _id={stockAuditId} type='stockAudit' />
</Card>
</Spin>
</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:
getModelByName('stockAudit').prefix +
':' +
stockAuditId
}}
visibleColumns={{ _id: false, parent: false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
) )
} }

View File

@ -0,0 +1,83 @@
import { useRef } from 'react'
import { Flex, Space } from 'antd'
import ObjectTable from '../common/ObjectTable'
import ObjectActions from '../common/ObjectActions'
import useColumnVisibility from '../hooks/useColumnVisibility'
import ObjectTableViewButton from '../common/ObjectTableViewButton'
import FilterSidebarButton from '../common/FilterSidebarButton'
import SortSidebarButton from '../common/SortSidebarButton'
import useViewMode from '../hooks/useViewMode'
import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility'
import useSortSidebarVisibility from '../hooks/useSortSidebarVisibility'
import ColumnViewButton from '../common/ColumnViewButton'
import ExportListButton from '../common/ExportListButton'
const StockAuditLevels = () => {
const tableRef = useRef()
const [viewMode, setViewMode] = useViewMode('stockAuditLevel')
const [columnVisibility, setColumnVisibility] =
useColumnVisibility('stockAuditLevel')
const [showFilterSidebar, setShowFilterSidebar] =
useFilterSidebarVisibility('StockAuditLevels')
const [showSortSidebar, setShowSortSidebar] =
useSortSidebarVisibility('StockAuditLevels')
return (
<Flex vertical={'true'} gap='large' className='h-100'>
<Flex justify={'space-between'} style={{ minHeight: 0 }}>
<Space size='small'>
<ObjectActions
type='stockAuditLevel'
pageName='list'
onReload={() => tableRef.current?.reload()}
/>
<ColumnViewButton
type='stockAuditLevel'
loading={false}
visibleState={columnVisibility}
updateVisibleState={setColumnVisibility}
/>
<ExportListButton objectType='stockAuditLevel' />
</Space>
<Space>
<SortSidebarButton
active={showSortSidebar}
onClick={() => setShowSortSidebar(!showSortSidebar)}
/>
<FilterSidebarButton
active={showFilterSidebar}
onClick={() => setShowFilterSidebar(!showFilterSidebar)}
/>
<ObjectTableViewButton
viewMode={viewMode}
setViewMode={setViewMode}
showEndingDivider={true}
/>
</Space>
</Flex>
<ObjectTable
ref={tableRef}
visibleColumns={columnVisibility}
type='stockAuditLevel'
cards={viewMode === 'cards'}
showFilterSidebar={showFilterSidebar}
showSortSidebar={showSortSidebar}
expandHeight={true}
saveFilterInSession={true}
saveFilterInUrl={true}
useFilterInSession={true}
useFilterInUrl={true}
saveSortInSession={true}
saveSortInUrl={true}
useSortInSession={true}
useSortInUrl={true}
/>
</Flex>
)
}
export default StockAuditLevels

View File

@ -0,0 +1,75 @@
import PropTypes from 'prop-types'
import ObjectInfo from '../../common/ObjectInfo'
import NewObjectForm from '../../common/NewObjectForm'
import WizardView from '../../common/WizardView'
const NewStockAuditLevel = ({ onOk, reset, defaultValues }) => {
return (
<NewObjectForm
type='stockAuditLevel'
reset={reset}
defaultValues={{ auditLines: [], ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [
{
title: 'Required',
key: 'required',
content: (
<ObjectInfo
type='stockAuditLevel'
column={1}
bordered={false}
isEditing={true}
required={true}
objectData={objectData}
visibleProperties={{ auditLines: false }}
/>
)
},
{
title: 'Summary',
key: 'summary',
content: (
<ObjectInfo
type='stockAuditLevel'
column={1}
bordered={false}
visibleProperties={{
_id: false,
_reference: false,
createdAt: false,
updatedAt: false
}}
isEditing={false}
objectData={objectData}
/>
)
}
]
return (
<WizardView
steps={steps}
loading={submitLoading}
formValid={formValid}
title='New Stock Audit Level'
onSubmit={async () => {
const result = await handleSubmit()
if (result) {
onOk()
}
}}
/>
)
}}
</NewObjectForm>
)
}
NewStockAuditLevel.propTypes = {
onOk: PropTypes.func.isRequired,
reset: PropTypes.bool,
defaultValues: PropTypes.object
}
export default NewStockAuditLevel

View File

@ -0,0 +1,231 @@
import { useRef, useState, useMemo } from 'react'
import { useLocation } from 'react-router-dom'
import { Flex, Space } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import useCollapseState from '../../hooks/useCollapseState'
import InfoCollapse from '../../common/InfoCollapse'
import ObjectInfo from '../../common/ObjectInfo'
import ObjectProperty from '../../common/ObjectProperty.jsx'
import ViewButton from '../../common/ViewButton'
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
import StockAuditLevelIcon from '../../../Icons/StockAuditLevelIcon.jsx'
import ObjectForm from '../../common/ObjectForm'
import EditButtons from '../../common/EditButtons'
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
import ActivityIndicator from '../../common/ActivityIndicator.jsx'
import ActionHandler from '../../common/ActionHandler.jsx'
import ObjectActions from '../../common/ObjectActions.jsx'
import { useDashboardObjectTools } from '../../context/DashboardObjectToolsContext'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
import ScrollBox from '../../common/ScrollBox.jsx'
const StockAuditLevelInfo = () => {
const location = useLocation()
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const stockAuditLevelId = new URLSearchParams(location.search).get(
'stockAuditLevelId'
)
const [collapseState, updateCollapseState] = useCollapseState(
'StockAuditLevelInfo',
{
info: true,
auditLines: true,
auditLogs: false
}
)
const [objectFormState, setEditFormState] = useState({
isEditing: false,
editLoading: false,
formValid: false,
loading: false,
editDisabled: false,
objectData: {}
})
const currentObjectTools = useMemo(
() => (
<ObjectTableNavigationButtons
disabled={objectFormState.loading || objectFormState.isEditing}
_id={stockAuditLevelId}
objectType='stockAuditLevel'
showEndingDivider={false}
/>
),
[objectFormState.loading, objectFormState.isEditing, stockAuditLevelId]
)
useDashboardObjectTools(currentObjectTools)
const actions = {
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
},
cancelEdit: () => {
objectFormRef?.current?.cancelEditing?.()
return true
},
finishEdit: () => {
objectFormRef?.current?.handleUpdate?.()
return true
}
}
return (
<Flex
gap='large'
vertical='true'
style={{ maxHeight: '100%', minHeight: 0 }}
>
<Flex justify={'space-between'}>
<Space size='small'>
<Space size='small'>
<ObjectActions
type='stockAuditLevel'
pageName='info'
id={stockAuditLevelId}
disabled={objectFormState.loading}
objectData={objectFormState.objectData}
/>
<ViewButton
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Stock Audit Level Information' },
{ key: 'auditLines', label: 'Audit Lines' },
{ key: 'auditLogs', label: 'Audit Logs' }
]}
visibleState={collapseState}
updateVisibleState={updateCollapseState}
/>
<UserNotifierToggle
type='stockAuditLevel'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
<DocumentPrintButton
type='stockAuditLevel'
objectData={objectFormState.objectData}
disabled={objectFormState.loading}
/>
</Space>
<ActivityIndicator
activities={objectFormState.activities}
showStartingDivider={true}
/>
</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.beingEditedByOther ||
objectFormState.loading ||
objectFormState.editDisabled
}
loading={objectFormState.editLoading}
/>
</Space>
</Flex>
<ScrollBox>
<Flex vertical gap={'large'}>
<ActionHandler
actions={actions}
loading={objectFormState.loading}
ref={actionHandlerRef}
>
<ObjectForm
id={stockAuditLevelId}
type='stockAuditLevel'
style={{ height: '100%' }}
ref={objectFormRef}
setCurrentObject={true}
onStateChange={(state) => {
setEditFormState((prev) => ({ ...prev, ...state }))
}}
>
{({ loading, isEditing, objectData }) => (
<Flex vertical gap={'large'}>
<InfoCollapse
title='Stock Audit Level Information'
icon={<InfoCircleIcon />}
active={collapseState.info}
onToggle={(expanded) =>
updateCollapseState('info', expanded)
}
collapseKey='info'
>
<ObjectInfo
loading={loading}
indicator={<LoadingOutlined />}
isEditing={isEditing}
type='stockAuditLevel'
objectData={objectData}
visibleProperties={{ auditLines: false }}
/>
</InfoCollapse>
<InfoCollapse
title='Audit Lines'
icon={<StockAuditLevelIcon />}
active={collapseState.auditLines}
onToggle={(expanded) =>
updateCollapseState('auditLines', expanded)
}
collapseKey='auditLines'
>
<ObjectProperty
{...getModelProperty('stockAuditLevel', 'auditLines')}
isEditing={isEditing}
objectData={objectData}
loading={loading}
/>
</InfoCollapse>
</Flex>
)}
</ObjectForm>
</ActionHandler>
<InfoCollapse
title='Audit Logs'
icon={<AuditLogIcon />}
active={collapseState.auditLogs}
onToggle={(expanded) =>
updateCollapseState('auditLogs', expanded)
}
collapseKey='auditLogs'
>
{objectFormState.loading ? (
<InfoCollapsePlaceholder />
) : (
<ObjectTable
type='auditLog'
masterFilter={{
parent:
getModelByName('stockAuditLevel').prefix +
':' +
stockAuditLevelId
}}
visibleColumns={{ _id: false, parent: false }}
/>
)}
</InfoCollapse>
</Flex>
</ScrollBox>
</Flex>
)
}
export default StockAuditLevelInfo

View File

@ -11,6 +11,7 @@ import PartStockIcon from './PartStockIcon'
import ProductStockIcon from './ProductStockIcon' import ProductStockIcon from './ProductStockIcon'
import StockEventIcon from './StockEventIcon' import StockEventIcon from './StockEventIcon'
import StockAuditIcon from './StockAuditIcon' import StockAuditIcon from './StockAuditIcon'
import StockAuditLevelIcon from './StockAuditLevelIcon'
import PurchaseOrderIcon from './PurchaseOrderIcon' import PurchaseOrderIcon from './PurchaseOrderIcon'
import ShipmentIcon from './ShipmentIcon' import ShipmentIcon from './ShipmentIcon'
import OrderItemIcon from './OrderItemIcon' import OrderItemIcon from './OrderItemIcon'
@ -74,6 +75,7 @@ const sidebarIconMap = {
productStock: <ProductStockIcon />, productStock: <ProductStockIcon />,
stockEvent: <StockEventIcon />, stockEvent: <StockEventIcon />,
stockAudit: <StockAuditIcon />, stockAudit: <StockAuditIcon />,
stockAuditLevel: <StockAuditLevelIcon />,
purchaseOrder: <PurchaseOrderIcon />, purchaseOrder: <PurchaseOrderIcon />,
orderItem: <OrderItemIcon />, orderItem: <OrderItemIcon />,
shipment: <ShipmentIcon />, shipment: <ShipmentIcon />,

View File

@ -22,6 +22,7 @@ import { Initial } from './models/Initial'
import { FilamentStock } from './models/FilamentStock' import { FilamentStock } from './models/FilamentStock'
import { StockEvent } from './models/StockEvent' import { StockEvent } from './models/StockEvent'
import { StockAudit } from './models/StockAudit' import { StockAudit } from './models/StockAudit'
import { StockAuditLevel } from './models/StockAuditLevel'
import { PartStock } from './models/PartStock' import { PartStock } from './models/PartStock'
import { ProductStock } from './models/ProductStock' import { ProductStock } from './models/ProductStock'
import { StockLocation } from './models/StockLocation' import { StockLocation } from './models/StockLocation'
@ -79,6 +80,7 @@ export const objectModels = [
FilamentStock, FilamentStock,
StockEvent, StockEvent,
StockAudit, StockAudit,
StockAuditLevel,
PartStock, PartStock,
ProductStock, ProductStock,
StockLocation, StockLocation,
@ -137,6 +139,7 @@ export {
FilamentStock, FilamentStock,
StockEvent, StockEvent,
StockAudit, StockAudit,
StockAuditLevel,
PartStock, PartStock,
ProductStock, ProductStock,
StockLocation, StockLocation,

View File

@ -1,17 +1,26 @@
import { createElement, lazy } from 'react' import { createElement, lazy } from 'react'
const StockAuditInfo = lazy( const StockAuditInfo = lazy(
() => import('../../components/Dashboard/Inventory/StockAudits/StockAuditInfo') () =>
import('../../components/Dashboard/Inventory/StockAudits/StockAuditInfo')
) )
const NewStockAudit = lazy( const NewStockAudit = lazy(
() => import('../../components/Dashboard/Inventory/StockAudits/NewStockAudit') () => import('../../components/Dashboard/Inventory/StockAudits/NewStockAudit')
) )
const DeleteObject = lazy(
() => import('../../components/Dashboard/common/DeleteObject')
)
const PostStockAudit = lazy(
() =>
import('../../components/Dashboard/Inventory/StockAudits/PostStockAudit')
)
import StockAuditIcon from '../../components/Icons/StockAuditIcon' import StockAuditIcon from '../../components/Icons/StockAuditIcon'
import PlusIcon from '../../components/Icons/PlusIcon' import PlusIcon from '../../components/Icons/PlusIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon' import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon' import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon' import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon' import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
import ListIcon from '../../components/Icons/ListIcon' import ListIcon from '../../components/Icons/ListIcon'
export const StockAudit = { export const StockAudit = {
@ -30,7 +39,11 @@ export const StockAudit = {
label: 'New Stock Audit', label: 'New Stock Audit',
icon: PlusIcon, icon: PlusIcon,
content: (objectData, { onOk } = {}) => { content: (objectData, { onOk } = {}) => {
return createElement(NewStockAudit, { defaultValues: objectData, onOk, reset: true }) return createElement(NewStockAudit, {
defaultValues: objectData,
onOk,
reset: true
})
} }
}, },
{ {
@ -58,6 +71,9 @@ export const StockAudit = {
icon: EditIcon, icon: EditIcon,
visible: (objectData) => { visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true) return !(objectData?._isEditing && objectData?._isEditing == true)
},
disabled: (objectData) => {
return objectData?.state?.type != 'draft'
} }
}, },
{ {
@ -80,6 +96,40 @@ export const StockAudit = {
return objectData?._isEditing && objectData?._isEditing == true return objectData?._isEditing && objectData?._isEditing == true
} }
}, },
{ type: 'divider' },
{
name: 'delete',
type: 'modal',
modalWidth: 520,
modalCentered: true,
label: 'Delete',
icon: BinIcon,
danger: true,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
},
disabled: (objectData) => {
return objectData?.state?.type != 'draft'
},
content: (objectData, { onOk } = {}) => {
return createElement(DeleteObject, { objectData, onOk })
}
},
{ type: 'divider' },
{
name: 'post',
type: 'modal',
modalWidth: 520,
modalCentered: true,
label: 'Post',
icon: CheckIcon,
visible: (objectData) => {
return objectData?.state?.type == 'draft'
},
content: (objectData, { onOk } = {}) => {
return createElement(PostStockAudit, { objectData, onOk })
}
}
], ],
pages: [ pages: [
{ {
@ -87,17 +137,26 @@ export const StockAudit = {
content: () => createElement(StockAuditInfo) content: () => createElement(StockAuditInfo)
} }
], ],
columns: ['_reference', 'state', 'createdAt', 'updatedAt'], columns: [
filters: [ '_reference',
'status',
'type',
'createdBy',
'state', 'state',
'auditLevel',
'stockLocation',
'postedAt',
'createdAt',
'updatedAt'
],
filters: [
'state',
'state.type',
'auditLevel',
'stockLocation',
'postedAt',
'createdAt', 'createdAt',
'updatedAt', 'updatedAt',
'_reference' '_reference'
], ],
sorters: ['createdAt','updatedAt','state'], sorters: ['createdAt', 'updatedAt', 'postedAt', 'state'],
group: ['state'], group: ['state'],
properties: [ properties: [
{ {
@ -126,6 +185,13 @@ export const StockAudit = {
readOnly: true, readOnly: true,
columnWidth: 180 columnWidth: 180
}, },
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{ {
name: 'state', name: 'state',
label: 'State', label: 'State',
@ -134,11 +200,130 @@ export const StockAudit = {
columnWidth: 260 columnWidth: 260
}, },
{ {
name: 'updatedAt', name: 'postedAt',
label: 'Updated At', label: 'Posted At',
type: 'dateTime', type: 'dateTime',
readOnly: true, readOnly: true,
columnWidth: 175 columnWidth: 175
},
{
name: 'auditLevel',
label: 'Audit Level',
type: 'object',
objectType: 'stockAuditLevel',
showHyperlink: true,
required: true,
columnWidth: 220
},
{
name: 'stockLocation',
label: 'Stock Location',
type: 'object',
objectType: 'stockLocation',
showHyperlink: true,
required: true,
columnWidth: 220
},
{
name: 'auditLines',
label: 'Audit Lines',
type: 'objectChildren',
required: false,
size: 'medium',
canAddRemove: false,
columns: [
'itemType',
'item',
'itemSku',
'current',
'actual',
'new',
'variance'
],
properties: [
{
name: 'itemType',
label: 'Item Type',
type: 'objectType',
readOnly: true,
columnWidth: 150
},
{
name: 'item',
label: 'Item',
type: 'object',
objectType: (row) => row?.itemType,
readOnly: true,
showHyperlink: true,
columnWidth: 220
},
{
name: 'itemSku',
label: 'Item SKU',
type: 'object',
objectType: (row) => {
if (row?.itemType === 'filament') return 'filamentSku'
if (row?.itemType === 'part') return 'partSku'
if (row?.itemType === 'product') return 'productSku'
return undefined
},
readOnly: true,
showHyperlink: true,
columnWidth: 220
},
{
name: 'current',
label: 'Current',
type: 'number',
readOnly: true,
columnWidth: 150,
suffix: (row) => (row?.itemType === 'filament' ? 'g net' : null)
},
{
name: 'actual',
label: 'Actual',
type: 'number',
columnWidth: 150,
suffix: (row) => (row?.itemType === 'filament' ? 'g net' : null)
},
{
name: 'new',
label: 'New',
type: 'number',
readOnly: true,
columnWidth: 150,
suffix: (row) => (row?.itemType === 'filament' ? 'g net' : null),
value: (row) => Number(row?.actual) || 0
},
{
name: 'variance',
label: 'Variance',
type: 'variance',
readOnly: true,
columnWidth: 150,
suffix: (row) => (row?.itemType === 'filament' ? 'g net' : null),
value: (row) => {
const current = Number(row?.current) || 0
const actual = Number(row?.actual) || 0
return actual - current
}
}
]
}
],
stats: [
{
name: 'draft.count',
label: 'Draft',
type: 'number',
color: 'default'
},
{
name: 'complete.count',
label: 'Complete',
type: 'number',
color: 'success'
} }
] ]
} }

View File

@ -0,0 +1,239 @@
import { createElement, lazy } from 'react'
const StockAuditLevelInfo = lazy(
() =>
import('../../components/Dashboard/Management/StockAuditLevels/StockAuditLevelInfo')
)
const NewStockAuditLevel = lazy(
() =>
import('../../components/Dashboard/Management/StockAuditLevels/NewStockAuditLevel')
)
const DeleteObject = lazy(
() => import('../../components/Dashboard/common/DeleteObject')
)
import StockAuditLevelIcon from '../../components/Icons/StockAuditLevelIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import EditIcon from '../../components/Icons/EditIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
import ListIcon from '../../components/Icons/ListIcon'
export const StockAuditLevel = {
name: 'stockAuditLevel',
label: 'Stock Audit Level',
labelPlural: 'Stock Audit Levels',
url: '/dashboard/management/stockauditlevels',
prefix: 'SAL',
icon: StockAuditLevelIcon,
actions: [
{
name: 'new',
type: 'modal',
pageName: 'list',
modalWidth: 900,
label: 'New Stock Audit Level',
icon: PlusIcon,
content: (objectData, { onOk } = {}) => {
return createElement(NewStockAuditLevel, {
defaultValues: objectData,
onOk,
reset: true
})
}
},
{
name: 'list',
type: 'page',
pageName: 'list',
label: 'List',
icon: ListIcon
},
{
name: 'info',
type: 'page',
pageName: 'info',
label: 'Info',
default: true,
row: true,
icon: InfoCircleIcon
},
{
name: 'edit',
type: 'page',
pageName: 'info',
label: 'Edit',
row: true,
icon: EditIcon,
visible: (objectData) => {
return !(objectData?._isEditing && objectData?._isEditing == true)
}
},
{
name: 'cancelEdit',
label: 'Cancel Edits',
type: 'page',
pageName: 'info',
icon: XMarkIcon,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{
name: 'finishEdit',
label: 'Save Edits',
type: 'page',
pageName: 'info',
icon: CheckIcon,
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
},
{ type: 'divider' },
{
name: 'delete',
type: 'modal',
modalWidth: 520,
modalCentered: true,
label: 'Delete',
icon: BinIcon,
danger: true,
content: (objectData, { onOk } = {}) => {
return createElement(DeleteObject, { objectData, onOk })
}
}
],
pages: [
{
name: 'info',
content: () => createElement(StockAuditLevelInfo)
}
],
columns: ['_reference', 'name', 'tags', 'createdAt', 'updatedAt'],
filters: ['name', 'tags', 'createdAt', 'updatedAt', '_reference'],
sorters: ['name', 'createdAt', 'updatedAt'],
group: ['tags'],
properties: [
{
name: '_id',
label: 'ID',
type: 'id',
objectType: 'stockAuditLevel',
showCopy: true,
readOnly: true,
columnWidth: 140
},
{
name: 'createdAt',
label: 'Created At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{
name: '_reference',
label: 'Reference',
type: 'reference',
columnFixed: 'left',
objectType: 'stockAuditLevel',
showCopy: true,
readOnly: true,
columnWidth: 180
},
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{
name: 'name',
label: 'Name',
type: 'text',
required: true,
columnWidth: 220,
columnFixed: 'left'
},
{
name: 'tags',
label: 'Tags',
type: 'tags',
required: false,
columnWidth: 200
},
{
name: 'auditLines',
label: 'Audit Lines',
type: 'objectChildren',
required: false,
size: 'medium',
canAddRemove: true,
columns: ['itemType', 'allItems', 'item', 'allSkus', 'itemSku'],
properties: [
{
name: 'itemType',
label: 'Item Type',
type: 'objectType',
required: true,
columnWidth: 150,
masterFilter: ['filament', 'part', 'product']
},
{
name: 'allItems',
label: 'All Items',
type: 'bool',
columnWidth: 110
},
{
name: 'item',
label: 'Item',
type: 'object',
objectType: (row) => row?.itemType,
showHyperlink: true,
columnWidth: 220,
disabled: (row) => row?.allItems === true,
value: (row) => (row?.allItems ? null : row?.item)
},
{
name: 'allSkus',
label: 'All SKUs',
type: 'bool',
columnWidth: 110,
disabled: (row) => row?.allItems === true,
value: (row) => (row?.allItems ? true : row?.allSkus)
},
{
name: 'itemSku',
label: 'Item SKU',
type: 'object',
objectType: (row) => {
if (row?.itemType === 'filament') return 'filamentSku'
if (row?.itemType === 'part') return 'partSku'
if (row?.itemType === 'product') return 'productSku'
return undefined
},
showHyperlink: true,
columnWidth: 220,
disabled: (row) => row?.allItems === true || row?.allSkus === true,
value: (row) =>
row?.allItems || row?.allSkus ? null : row?.itemSku,
masterFilter: (row) => {
const itemId = row?.item?._id ?? row?.item
if (row?.itemType === 'filament' && itemId) {
return { filament: itemId }
}
if (row?.itemType === 'part' && itemId) {
return { part: itemId }
}
if (row?.itemType === 'product' && itemId) {
return { product: itemId }
}
return undefined
}
}
]
}
]
}

View File

@ -1,13 +1,16 @@
import { createElement, lazy } from 'react' import { createElement, lazy } from 'react'
const StockTransferInfo = lazy( const StockTransferInfo = lazy(
() => import('../../components/Dashboard/Inventory/StockTransfers/StockTransferInfo') () =>
import('../../components/Dashboard/Inventory/StockTransfers/StockTransferInfo')
) )
const NewStockTransfer = lazy( const NewStockTransfer = lazy(
() => import('../../components/Dashboard/Inventory/StockTransfers/NewStockTransfer') () =>
import('../../components/Dashboard/Inventory/StockTransfers/NewStockTransfer')
) )
const PostStockTransfer = lazy( const PostStockTransfer = lazy(
() => import('../../components/Dashboard/Inventory/StockTransfers/PostStockTransfer') () =>
import('../../components/Dashboard/Inventory/StockTransfers/PostStockTransfer')
) )
const DeleteObject = lazy( const DeleteObject = lazy(
() => import('../../components/Dashboard/common/DeleteObject') () => import('../../components/Dashboard/common/DeleteObject')
@ -37,7 +40,11 @@ export const StockTransfer = {
label: 'New Stock Transfer', label: 'New Stock Transfer',
icon: PlusIcon, icon: PlusIcon,
content: (objectData, { onOk } = {}) => { content: (objectData, { onOk } = {}) => {
return createElement(NewStockTransfer, { defaultValues: objectData, onOk, reset: true }) return createElement(NewStockTransfer, {
defaultValues: objectData,
onOk,
reset: true
})
} }
}, },
{ {
@ -113,6 +120,7 @@ export const StockTransfer = {
name: 'post', name: 'post',
type: 'modal', type: 'modal',
modalWidth: 520, modalWidth: 520,
modalCentered: true,
label: 'Post', label: 'Post',
icon: CheckIcon, icon: CheckIcon,
visible: (objectData) => { visible: (objectData) => {
@ -138,13 +146,7 @@ export const StockTransfer = {
'updatedAt', 'updatedAt',
'_reference' '_reference'
], ],
sorters: [ sorters: ['name', 'createdAt', 'postedAt', 'state', 'updatedAt'],
'name',
'createdAt',
'postedAt',
'state',
'updatedAt'
],
columns: [ columns: [
'_reference', '_reference',
'name', 'name',
@ -180,6 +182,13 @@ export const StockTransfer = {
readOnly: true, readOnly: true,
columnWidth: 180 columnWidth: 180
}, },
{
name: 'updatedAt',
label: 'Updated At',
type: 'dateTime',
readOnly: true,
columnWidth: 175
},
{ {
name: 'name', name: 'name',
label: 'Name', label: 'Name',
@ -188,13 +197,6 @@ export const StockTransfer = {
columnWidth: 220, columnWidth: 220,
columnFixed: 'left' columnFixed: 'left'
}, },
{
name: 'state',
label: 'State',
type: 'state',
readOnly: true,
columnWidth: 260
},
{ {
name: 'postedAt', name: 'postedAt',
label: 'Posted At', label: 'Posted At',
@ -203,12 +205,13 @@ export const StockTransfer = {
columnWidth: 175 columnWidth: 175
}, },
{ {
name: 'updatedAt', name: 'state',
label: 'Updated At', label: 'State',
type: 'dateTime', type: 'state',
readOnly: true, readOnly: true,
columnWidth: 175 columnWidth: 260
}, },
{ {
name: 'lines', name: 'lines',
label: 'Lines', label: 'Lines',

View File

@ -53,6 +53,12 @@ const managementSidebarItems = [
label: 'Materials', label: 'Materials',
path: '/dashboard/management/materials' path: '/dashboard/management/materials'
}, },
{
key: 'stockAuditLevels',
iconKey: 'stockAuditLevel',
label: 'Stock Audit Levels',
path: '/dashboard/management/stockauditlevels'
},
{ type: 'divider' }, { type: 'divider' },
{ {
key: 'couriers', key: 'couriers',

View File

@ -73,6 +73,9 @@ const Files = lazy(() => import('../components/Dashboard/Management/Files.jsx'))
const TaxRates = lazy( const TaxRates = lazy(
() => import('../components/Dashboard/Management/TaxRates.jsx') () => import('../components/Dashboard/Management/TaxRates.jsx')
) )
const StockAuditLevels = lazy(
() => import('../components/Dashboard/Management/StockAuditLevels.jsx')
)
const About = lazy(() => import('../components/Dashboard/Management/About.jsx')) const About = lazy(() => import('../components/Dashboard/Management/About.jsx'))
const ManagementRoutes = [ const ManagementRoutes = [
@ -151,7 +154,12 @@ const ManagementRoutes = [
<Route key='settings' path='management/settings' element={<Settings />} />, <Route key='settings' path='management/settings' element={<Settings />} />,
<Route key='about' path='management/about' element={<About />} />, <Route key='about' path='management/about' element={<About />} />,
<Route key='auditlogs' path='management/auditlogs' element={<AuditLogs />} />, <Route key='auditlogs' path='management/auditlogs' element={<AuditLogs />} />,
<Route key='taxrates' path='management/taxrates' element={<TaxRates />} /> <Route key='taxrates' path='management/taxrates' element={<TaxRates />} />,
<Route
key='stockauditlevels'
path='management/stockauditlevels'
element={<StockAuditLevels />}
/>
] ]
export default ManagementRoutes export default ManagementRoutes