From 972fdb62ad0ac6cfc00f11b277a7cb066aaa1e74 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Tue, 1 Sep 2026 15:41:35 +0100 Subject: [PATCH] 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. --- assets/icons/stockauditlevelicon.svg | 13 +- .../Inventory/StockAudits/NewStockAudit.jsx | 8 +- .../Inventory/StockAudits/PostStockAudit.jsx | 46 +++ .../Inventory/StockAudits/StockAuditInfo.jsx | 324 +++++++++--------- .../Dashboard/Management/StockAuditLevels.jsx | 83 +++++ .../StockAuditLevels/NewStockAuditLevel.jsx | 75 ++++ .../StockAuditLevels/StockAuditLevelInfo.jsx | 231 +++++++++++++ src/components/Icons/sidebarIconMap.jsx | 2 + src/database/ObjectModels.js | 3 + src/database/models/StockAudit.js | 205 ++++++++++- src/database/models/StockAuditLevel.js | 239 +++++++++++++ src/database/models/StockTransfer.js | 47 +-- src/database/sidebars/management.js | 6 + src/routes/ManagementRoutes.jsx | 10 +- 14 files changed, 1095 insertions(+), 197 deletions(-) create mode 100644 src/components/Dashboard/Inventory/StockAudits/PostStockAudit.jsx create mode 100644 src/components/Dashboard/Management/StockAuditLevels.jsx create mode 100644 src/components/Dashboard/Management/StockAuditLevels/NewStockAuditLevel.jsx create mode 100644 src/components/Dashboard/Management/StockAuditLevels/StockAuditLevelInfo.jsx create mode 100644 src/database/models/StockAuditLevel.js diff --git a/assets/icons/stockauditlevelicon.svg b/assets/icons/stockauditlevelicon.svg index 9aa57148..dca83d34 100644 --- a/assets/icons/stockauditlevelicon.svg +++ b/assets/icons/stockauditlevelicon.svg @@ -1,17 +1,16 @@ - - - - - + + + + - + - + diff --git a/src/components/Dashboard/Inventory/StockAudits/NewStockAudit.jsx b/src/components/Dashboard/Inventory/StockAudits/NewStockAudit.jsx index 18f511f2..104537f3 100644 --- a/src/components/Dashboard/Inventory/StockAudits/NewStockAudit.jsx +++ b/src/components/Dashboard/Inventory/StockAudits/NewStockAudit.jsx @@ -6,9 +6,9 @@ import WizardView from '../../common/WizardView' const NewStockAudit = ({ onOk, reset, defaultValues }) => { return ( {({ handleSubmit, submitLoading, objectData, formValid }) => { const steps = [ @@ -23,6 +23,7 @@ const NewStockAudit = ({ onOk, reset, defaultValues }) => { isEditing={true} required={true} objectData={objectData} + visibleProperties={{ auditLines: false }} /> ) }, @@ -38,7 +39,8 @@ const NewStockAudit = ({ onOk, reset, defaultValues }) => { _id: false, _reference: false, createdAt: false, - updatedAt: false + updatedAt: false, + auditLines: false }} isEditing={false} objectData={objectData} diff --git a/src/components/Dashboard/Inventory/StockAudits/PostStockAudit.jsx b/src/components/Dashboard/Inventory/StockAudits/PostStockAudit.jsx new file mode 100644 index 00000000..f4574c4f --- /dev/null +++ b/src/components/Dashboard/Inventory/StockAudits/PostStockAudit.jsx @@ -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 ( + + ) +} + +PostStockAudit.propTypes = { + onOk: PropTypes.func.isRequired, + objectData: PropTypes.object +} + +export default PostStockAudit diff --git a/src/components/Dashboard/Inventory/StockAudits/StockAuditInfo.jsx b/src/components/Dashboard/Inventory/StockAudits/StockAuditInfo.jsx index 9e22b45f..d59abca4 100644 --- a/src/components/Dashboard/Inventory/StockAudits/StockAuditInfo.jsx +++ b/src/components/Dashboard/Inventory/StockAudits/StockAuditInfo.jsx @@ -8,10 +8,12 @@ import useCollapseState from '../../hooks/useCollapseState.jsx' import NotesPanel from '../../common/NotesPanel.jsx' import InfoCollapse from '../../common/InfoCollapse.jsx' import ObjectInfo from '../../common/ObjectInfo.jsx' +import ObjectProperty from '../../common/ObjectProperty.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 StockAuditIcon from '../../../Icons/StockAuditIcon.jsx' import ObjectForm from '../../common/ObjectForm.jsx' import EditButtons from '../../common/EditButtons.jsx' import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx' @@ -23,7 +25,7 @@ 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 { getModelByName } from '../../../../database/ObjectModels.js' +import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' import ScrollBox from '../../common/ScrollBox.jsx' const log = loglevel.getLogger('StockAuditInfo') @@ -38,7 +40,7 @@ const StockAuditInfo = () => { 'StockAuditInfo', { info: true, - stocks: true, + auditLines: true, notes: true, auditLogs: false } @@ -83,163 +85,177 @@ const StockAuditInfo = () => { } return ( - <> - - + + + - - - - - - - + + + - - { - 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} - /> - - - - - - - } - active={collapseState.info} - onToggle={(expanded) => updateCollapseState('info', expanded)} - collapseKey='info' - > - { - setEditFormState((prev) => ({ ...prev, ...state })) - }} - > - {({ loading, isEditing, objectData }) => { - return ( - } - isEditing={isEditing} - type='stockAudit' - objectData={objectData} - visibleProperties={{ - content: false, - testObject: false - }} - /> - ) - }} - - - - - } - active={collapseState.notes} - onToggle={(expanded) => updateCollapseState('notes', expanded)} - collapseKey='notes' - > - } - > - - - - - - - } - active={collapseState.auditLogs} - onToggle={(expanded) => - updateCollapseState('auditLogs', expanded) - } - collapseKey='auditLogs' - > - {objectFormState.loading ? ( - - ) : ( - - )} - - - + + + + { + 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} + /> + - + + + + + { + setEditFormState((prev) => ({ ...prev, ...state })) + }} + > + {({ loading, isEditing, objectData }) => ( + + } + active={collapseState.info} + onToggle={(expanded) => updateCollapseState('info', expanded)} + collapseKey='info' + > + } + isEditing={isEditing} + type='stockAudit' + objectData={objectData} + visibleProperties={{ + auditLines: false + }} + /> + + } + active={collapseState.auditLines} + onToggle={(expanded) => + updateCollapseState('auditLines', expanded) + } + collapseKey='auditLines' + > + + + + )} + + + + } + active={collapseState.notes} + onToggle={(expanded) => updateCollapseState('notes', expanded)} + collapseKey='notes' + > + } + > + + + + + + + } + active={collapseState.auditLogs} + onToggle={(expanded) => + updateCollapseState('auditLogs', expanded) + } + collapseKey='auditLogs' + > + {objectFormState.loading ? ( + + ) : ( + + )} + + + + ) } diff --git a/src/components/Dashboard/Management/StockAuditLevels.jsx b/src/components/Dashboard/Management/StockAuditLevels.jsx new file mode 100644 index 00000000..b01b9b7d --- /dev/null +++ b/src/components/Dashboard/Management/StockAuditLevels.jsx @@ -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 ( + + + + tableRef.current?.reload()} + /> + + + + + setShowSortSidebar(!showSortSidebar)} + /> + setShowFilterSidebar(!showFilterSidebar)} + /> + + + + + + ) +} + +export default StockAuditLevels diff --git a/src/components/Dashboard/Management/StockAuditLevels/NewStockAuditLevel.jsx b/src/components/Dashboard/Management/StockAuditLevels/NewStockAuditLevel.jsx new file mode 100644 index 00000000..42e00f44 --- /dev/null +++ b/src/components/Dashboard/Management/StockAuditLevels/NewStockAuditLevel.jsx @@ -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 ( + + {({ handleSubmit, submitLoading, objectData, formValid }) => { + const steps = [ + { + title: 'Required', + key: 'required', + content: ( + + ) + }, + { + title: 'Summary', + key: 'summary', + content: ( + + ) + } + ] + return ( + { + const result = await handleSubmit() + if (result) { + onOk() + } + }} + /> + ) + }} + + ) +} + +NewStockAuditLevel.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool, + defaultValues: PropTypes.object +} + +export default NewStockAuditLevel diff --git a/src/components/Dashboard/Management/StockAuditLevels/StockAuditLevelInfo.jsx b/src/components/Dashboard/Management/StockAuditLevels/StockAuditLevelInfo.jsx new file mode 100644 index 00000000..4761e38f --- /dev/null +++ b/src/components/Dashboard/Management/StockAuditLevels/StockAuditLevelInfo.jsx @@ -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( + () => ( + + ), + [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 ( + + + + + + + + + + + + + { + 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} + /> + + + + + + { + setEditFormState((prev) => ({ ...prev, ...state })) + }} + > + {({ loading, isEditing, objectData }) => ( + + } + active={collapseState.info} + onToggle={(expanded) => + updateCollapseState('info', expanded) + } + collapseKey='info' + > + } + isEditing={isEditing} + type='stockAuditLevel' + objectData={objectData} + visibleProperties={{ auditLines: false }} + /> + + } + active={collapseState.auditLines} + onToggle={(expanded) => + updateCollapseState('auditLines', expanded) + } + collapseKey='auditLines' + > + + + + )} + + + } + active={collapseState.auditLogs} + onToggle={(expanded) => + updateCollapseState('auditLogs', expanded) + } + collapseKey='auditLogs' + > + {objectFormState.loading ? ( + + ) : ( + + )} + + + + + ) +} + +export default StockAuditLevelInfo diff --git a/src/components/Icons/sidebarIconMap.jsx b/src/components/Icons/sidebarIconMap.jsx index d5300e0f..3d5d4b1f 100644 --- a/src/components/Icons/sidebarIconMap.jsx +++ b/src/components/Icons/sidebarIconMap.jsx @@ -11,6 +11,7 @@ import PartStockIcon from './PartStockIcon' import ProductStockIcon from './ProductStockIcon' import StockEventIcon from './StockEventIcon' import StockAuditIcon from './StockAuditIcon' +import StockAuditLevelIcon from './StockAuditLevelIcon' import PurchaseOrderIcon from './PurchaseOrderIcon' import ShipmentIcon from './ShipmentIcon' import OrderItemIcon from './OrderItemIcon' @@ -74,6 +75,7 @@ const sidebarIconMap = { productStock: , stockEvent: , stockAudit: , + stockAuditLevel: , purchaseOrder: , orderItem: , shipment: , diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js index dab7aba6..267ff7ef 100644 --- a/src/database/ObjectModels.js +++ b/src/database/ObjectModels.js @@ -22,6 +22,7 @@ import { Initial } from './models/Initial' import { FilamentStock } from './models/FilamentStock' import { StockEvent } from './models/StockEvent' import { StockAudit } from './models/StockAudit' +import { StockAuditLevel } from './models/StockAuditLevel' import { PartStock } from './models/PartStock' import { ProductStock } from './models/ProductStock' import { StockLocation } from './models/StockLocation' @@ -79,6 +80,7 @@ export const objectModels = [ FilamentStock, StockEvent, StockAudit, + StockAuditLevel, PartStock, ProductStock, StockLocation, @@ -137,6 +139,7 @@ export { FilamentStock, StockEvent, StockAudit, + StockAuditLevel, PartStock, ProductStock, StockLocation, diff --git a/src/database/models/StockAudit.js b/src/database/models/StockAudit.js index fbabcfdd..0f1a758d 100644 --- a/src/database/models/StockAudit.js +++ b/src/database/models/StockAudit.js @@ -1,17 +1,26 @@ import { createElement, lazy } from 'react' const StockAuditInfo = lazy( - () => import('../../components/Dashboard/Inventory/StockAudits/StockAuditInfo') + () => + import('../../components/Dashboard/Inventory/StockAudits/StockAuditInfo') ) const NewStockAudit = lazy( () => 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 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 StockAudit = { @@ -30,7 +39,11 @@ export const StockAudit = { label: 'New Stock Audit', icon: PlusIcon, 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, visible: (objectData) => { 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 } }, + { 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: [ { @@ -87,17 +137,26 @@ export const StockAudit = { content: () => createElement(StockAuditInfo) } ], - columns: ['_reference', 'state', 'createdAt', 'updatedAt'], - filters: [ - 'status', - 'type', - 'createdBy', + columns: [ + '_reference', 'state', + 'auditLevel', + 'stockLocation', + 'postedAt', + 'createdAt', + 'updatedAt' + ], + filters: [ + 'state', + 'state.type', + 'auditLevel', + 'stockLocation', + 'postedAt', 'createdAt', 'updatedAt', '_reference' ], - sorters: ['createdAt','updatedAt','state'], + sorters: ['createdAt', 'updatedAt', 'postedAt', 'state'], group: ['state'], properties: [ { @@ -126,6 +185,13 @@ export const StockAudit = { readOnly: true, columnWidth: 180 }, + { + name: 'updatedAt', + label: 'Updated At', + type: 'dateTime', + readOnly: true, + columnWidth: 175 + }, { name: 'state', label: 'State', @@ -134,11 +200,130 @@ export const StockAudit = { columnWidth: 260 }, { - name: 'updatedAt', - label: 'Updated At', + name: 'postedAt', + label: 'Posted At', type: 'dateTime', readOnly: true, 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' } ] } diff --git a/src/database/models/StockAuditLevel.js b/src/database/models/StockAuditLevel.js new file mode 100644 index 00000000..11d64ef3 --- /dev/null +++ b/src/database/models/StockAuditLevel.js @@ -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 + } + } + ] + } + ] +} diff --git a/src/database/models/StockTransfer.js b/src/database/models/StockTransfer.js index 053ad4f4..bca878e6 100644 --- a/src/database/models/StockTransfer.js +++ b/src/database/models/StockTransfer.js @@ -1,13 +1,16 @@ import { createElement, lazy } from 'react' const StockTransferInfo = lazy( - () => import('../../components/Dashboard/Inventory/StockTransfers/StockTransferInfo') + () => + import('../../components/Dashboard/Inventory/StockTransfers/StockTransferInfo') ) const NewStockTransfer = lazy( - () => import('../../components/Dashboard/Inventory/StockTransfers/NewStockTransfer') + () => + import('../../components/Dashboard/Inventory/StockTransfers/NewStockTransfer') ) const PostStockTransfer = lazy( - () => import('../../components/Dashboard/Inventory/StockTransfers/PostStockTransfer') + () => + import('../../components/Dashboard/Inventory/StockTransfers/PostStockTransfer') ) const DeleteObject = lazy( () => import('../../components/Dashboard/common/DeleteObject') @@ -37,7 +40,11 @@ export const StockTransfer = { label: 'New Stock Transfer', icon: PlusIcon, 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', type: 'modal', modalWidth: 520, + modalCentered: true, label: 'Post', icon: CheckIcon, visible: (objectData) => { @@ -138,13 +146,7 @@ export const StockTransfer = { 'updatedAt', '_reference' ], - sorters: [ - 'name', - 'createdAt', - 'postedAt', - 'state', - 'updatedAt' - ], + sorters: ['name', 'createdAt', 'postedAt', 'state', 'updatedAt'], columns: [ '_reference', 'name', @@ -180,6 +182,13 @@ export const StockTransfer = { readOnly: true, columnWidth: 180 }, + { + name: 'updatedAt', + label: 'Updated At', + type: 'dateTime', + readOnly: true, + columnWidth: 175 + }, { name: 'name', label: 'Name', @@ -188,13 +197,6 @@ export const StockTransfer = { columnWidth: 220, columnFixed: 'left' }, - { - name: 'state', - label: 'State', - type: 'state', - readOnly: true, - columnWidth: 260 - }, { name: 'postedAt', label: 'Posted At', @@ -203,12 +205,13 @@ export const StockTransfer = { columnWidth: 175 }, { - name: 'updatedAt', - label: 'Updated At', - type: 'dateTime', + name: 'state', + label: 'State', + type: 'state', readOnly: true, - columnWidth: 175 + columnWidth: 260 }, + { name: 'lines', label: 'Lines', diff --git a/src/database/sidebars/management.js b/src/database/sidebars/management.js index d81bf317..a1aaf26c 100644 --- a/src/database/sidebars/management.js +++ b/src/database/sidebars/management.js @@ -53,6 +53,12 @@ const managementSidebarItems = [ label: 'Materials', path: '/dashboard/management/materials' }, + { + key: 'stockAuditLevels', + iconKey: 'stockAuditLevel', + label: 'Stock Audit Levels', + path: '/dashboard/management/stockauditlevels' + }, { type: 'divider' }, { key: 'couriers', diff --git a/src/routes/ManagementRoutes.jsx b/src/routes/ManagementRoutes.jsx index b7c2a5a2..349b3447 100644 --- a/src/routes/ManagementRoutes.jsx +++ b/src/routes/ManagementRoutes.jsx @@ -73,6 +73,9 @@ const Files = lazy(() => import('../components/Dashboard/Management/Files.jsx')) const TaxRates = lazy( () => 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 ManagementRoutes = [ @@ -151,7 +154,12 @@ const ManagementRoutes = [ } />, } />, } />, - } /> + } />, + } + /> ] export default ManagementRoutes