diff --git a/assets/icons/stocklocationicon.svg b/assets/icons/stocklocationicon.svg new file mode 100644 index 0000000..43e29e5 --- /dev/null +++ b/assets/icons/stocklocationicon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/stocktransfericon.svg b/assets/icons/stocktransfericon.svg new file mode 100644 index 0000000..692eaa2 --- /dev/null +++ b/assets/icons/stocktransfericon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/components/Dashboard/Inventory/InventorySidebar.jsx b/src/components/Dashboard/Inventory/InventorySidebar.jsx index 67911ae..06370c4 100644 --- a/src/components/Dashboard/Inventory/InventorySidebar.jsx +++ b/src/components/Dashboard/Inventory/InventorySidebar.jsx @@ -9,6 +9,8 @@ import PurchaseOrderIcon from '../../Icons/PurchaseOrderIcon' import ShipmentIcon from '../../Icons/ShipmentIcon' import OrderItemIcon from '../../Icons/OrderItemIcon' import InventoryIcon from '../../Icons/InventoryIcon' +import StockLocationIcon from '../../Icons/StockLocationIcon' +import StockTransferIcon from '../../Icons/StockTransferIcon' const items = [ { @@ -57,6 +59,12 @@ const items = [ path: '/dashboard/inventory/shipments' }, { type: 'divider' }, + { + key: 'stocklocations', + label: 'Stock Locations', + icon: , + path: '/dashboard/inventory/stocklocations' + }, { key: 'stockevents', label: 'Stock Events', @@ -68,6 +76,12 @@ const items = [ label: 'Stock Audits', icon: , path: '/dashboard/inventory/stockaudits' + }, + { + key: 'stocktransfers', + label: 'Stock Transfers', + icon: , + path: '/dashboard/inventory/stocktransfers' } ] @@ -76,6 +90,8 @@ const routeKeyMap = { '/dashboard/inventory/filamentstocks': 'filamentstocks', '/dashboard/inventory/partstocks': 'partstocks', '/dashboard/inventory/productstocks': 'productstocks', + '/dashboard/inventory/stocklocations': 'stocklocations', + '/dashboard/inventory/stocktransfers': 'stocktransfers', '/dashboard/inventory/stockevents': 'stockevents', '/dashboard/inventory/stockaudits': 'stockaudits', '/dashboard/inventory/purchaseorders': 'purchaseorders', diff --git a/src/components/Dashboard/Inventory/StockLocations.jsx b/src/components/Dashboard/Inventory/StockLocations.jsx new file mode 100644 index 0000000..c56081d --- /dev/null +++ b/src/components/Dashboard/Inventory/StockLocations.jsx @@ -0,0 +1,111 @@ +import { useState, useRef } from 'react' + +import { Button, Flex, Space, Modal, Dropdown } from 'antd' + +import NewStockLocation from './StockLocations/NewStockLocation' +import PlusIcon from '../../Icons/PlusIcon' +import ReloadIcon from '../../Icons/ReloadIcon' +import useColumnVisibility from '../hooks/useColumnVisibility' +import ObjectTable from '../common/ObjectTable' +import ObjectTableViewButton from '../common/ObjectTableViewButton' +import FilterSidebarButton from '../common/FilterSidebarButton' +import useViewMode from '../hooks/useViewMode' +import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility' +import ColumnViewButton from '../common/ColumnViewButton' +import ExportListButton from '../common/ExportListButton' + +const StockLocations = () => { + const tableRef = useRef() + + const [newOpen, setNewOpen] = useState(false) + + const [viewMode, setViewMode] = useViewMode('stockLocations') + + const [columnVisibility, setColumnVisibility] = + useColumnVisibility('stockLocation') + + const [showFilterSidebar, setShowFilterSidebar] = + useFilterSidebarVisibility('StockLocations') + + const actionItems = { + items: [ + { + label: 'New Stock Location', + key: 'new', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + tableRef.current?.reload() + } else if (key === 'new') { + setNewOpen(true) + } + } + } + + return ( + <> + + + + + + + + + + + setShowFilterSidebar(!showFilterSidebar)} + /> + + + + + + + { + setNewOpen(false) + }} + destroyOnHidden={true} + > + { + setNewOpen(false) + tableRef.current?.reload() + }} + reset={newOpen} + /> + + + ) +} + +export default StockLocations diff --git a/src/components/Dashboard/Inventory/StockLocations/NewStockLocation.jsx b/src/components/Dashboard/Inventory/StockLocations/NewStockLocation.jsx new file mode 100644 index 0000000..4cbe3a1 --- /dev/null +++ b/src/components/Dashboard/Inventory/StockLocations/NewStockLocation.jsx @@ -0,0 +1,68 @@ +import PropTypes from 'prop-types' +import ObjectInfo from '../../common/ObjectInfo' +import NewObjectForm from '../../common/NewObjectForm' +import WizardView from '../../common/WizardView' + +const NewStockLocation = ({ onOk, reset }) => { + return ( + + {({ handleSubmit, submitLoading, objectData, formValid }) => { + const steps = [ + { + title: 'Details', + key: 'details', + content: ( + + ) + }, + { + title: 'Summary', + key: 'summary', + content: ( + + ) + } + ] + return ( + { + const result = await handleSubmit() + if (result) { + onOk() + } + }} + /> + ) + }} + + ) +} + +NewStockLocation.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool +} + +export default NewStockLocation diff --git a/src/components/Dashboard/Inventory/StockLocations/StockLocationInfo.jsx b/src/components/Dashboard/Inventory/StockLocations/StockLocationInfo.jsx new file mode 100644 index 0000000..924bce0 --- /dev/null +++ b/src/components/Dashboard/Inventory/StockLocations/StockLocationInfo.jsx @@ -0,0 +1,212 @@ +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.jsx' +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 UserNotifierToggle from '../../common/UserNotifierToggle.jsx' +import ScrollBox from '../../common/ScrollBox.jsx' + +const log = loglevel.getLogger('StockLocationInfo') +log.setLevel(config.logLevel) + +const StockLocationInfo = () => { + const location = useLocation() + const objectFormRef = useRef(null) + const actionHandlerRef = useRef(null) + const stockLocationId = new URLSearchParams(location.search).get( + 'stockLocationId' + ) + const [collapseState, updateCollapseState] = useCollapseState( + 'StockLocationInfo', + { + info: true, + notes: true, + auditLogs: true + } + ) + + const [objectFormState, setEditFormState] = useState({ + isEditing: false, + editLoading: false, + formValid: false, + locked: false, + 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 + } + } + + return ( + <> + + + + + + + + + + + + + { + 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} + /> + + + + + + + } + active={collapseState.info} + onToggle={(expanded) => updateCollapseState('info', expanded)} + collapseKey='info' + > + { + setEditFormState((prev) => ({ ...prev, ...state })) + }} + > + {({ loading, isEditing, objectData }) => { + return ( + } + isEditing={isEditing} + type='stockLocation' + objectData={objectData} + labelWidth='175px' + /> + ) + }} + + + + + } + active={collapseState.notes} + onToggle={(expanded) => updateCollapseState('notes', expanded)} + collapseKey='notes' + > + + + + + + } + active={collapseState.auditLogs} + onToggle={(expanded) => + updateCollapseState('auditLogs', expanded) + } + collapseKey='auditLogs' + > + {objectFormState.loading ? ( + + ) : ( + + )} + + + + + + ) +} + +export default StockLocationInfo diff --git a/src/components/Dashboard/Inventory/StockTransfers.jsx b/src/components/Dashboard/Inventory/StockTransfers.jsx new file mode 100644 index 0000000..4d2d9dd --- /dev/null +++ b/src/components/Dashboard/Inventory/StockTransfers.jsx @@ -0,0 +1,111 @@ +import { useState, useRef } from 'react' + +import { Button, Flex, Space, Modal, Dropdown } from 'antd' + +import NewStockTransfer from './StockTransfers/NewStockTransfer' +import PlusIcon from '../../Icons/PlusIcon' +import ReloadIcon from '../../Icons/ReloadIcon' +import useColumnVisibility from '../hooks/useColumnVisibility' +import ObjectTable from '../common/ObjectTable' +import ObjectTableViewButton from '../common/ObjectTableViewButton' +import FilterSidebarButton from '../common/FilterSidebarButton' +import useViewMode from '../hooks/useViewMode' +import useFilterSidebarVisibility from '../hooks/useFilterSidebarVisibility' +import ColumnViewButton from '../common/ColumnViewButton' +import ExportListButton from '../common/ExportListButton' + +const StockTransfers = () => { + const tableRef = useRef() + + const [newOpen, setNewOpen] = useState(false) + + const [viewMode, setViewMode] = useViewMode('stockTransfers') + + const [columnVisibility, setColumnVisibility] = + useColumnVisibility('stockTransfer') + + const [showFilterSidebar, setShowFilterSidebar] = + useFilterSidebarVisibility('StockTransfers') + + const actionItems = { + items: [ + { + label: 'New Stock Transfer', + key: 'new', + icon: + }, + { type: 'divider' }, + { + label: 'Reload List', + key: 'reloadList', + icon: + } + ], + onClick: ({ key }) => { + if (key === 'reloadList') { + tableRef.current?.reload() + } else if (key === 'new') { + setNewOpen(true) + } + } + } + + return ( + <> + + + + + + + + + + + setShowFilterSidebar(!showFilterSidebar)} + /> + + + + + + + { + setNewOpen(false) + }} + destroyOnHidden={true} + > + { + setNewOpen(false) + tableRef.current?.reload() + }} + reset={newOpen} + /> + + + ) +} + +export default StockTransfers diff --git a/src/components/Dashboard/Inventory/StockTransfers/NewStockTransfer.jsx b/src/components/Dashboard/Inventory/StockTransfers/NewStockTransfer.jsx new file mode 100644 index 0000000..cbf8f6b --- /dev/null +++ b/src/components/Dashboard/Inventory/StockTransfers/NewStockTransfer.jsx @@ -0,0 +1,61 @@ +import PropTypes from 'prop-types' +import ObjectInfo from '../../common/ObjectInfo' +import NewObjectForm from '../../common/NewObjectForm' +import WizardView from '../../common/WizardView' + +const NewStockTransfer = ({ onOk, reset }) => { + return ( + + {({ handleSubmit, submitLoading, objectData }) => { + const steps = [ + { + title: 'Summary', + key: 'summary', + content: ( + + ) + } + ] + return ( + { + const result = await handleSubmit() + if (result) { + onOk() + } + }} + /> + ) + }} + + ) +} + +NewStockTransfer.propTypes = { + onOk: PropTypes.func.isRequired, + reset: PropTypes.bool +} + +export default NewStockTransfer diff --git a/src/components/Dashboard/Inventory/StockTransfers/PostStockTransfer.jsx b/src/components/Dashboard/Inventory/StockTransfers/PostStockTransfer.jsx new file mode 100644 index 0000000..d06477f --- /dev/null +++ b/src/components/Dashboard/Inventory/StockTransfers/PostStockTransfer.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 PostStockTransfer = ({ onOk, objectData }) => { + const [postLoading, setPostLoading] = useState(false) + const { sendObjectFunction } = useContext(ApiServerContext) + + const handlePost = async () => { + setPostLoading(true) + try { + const result = await sendObjectFunction( + objectData._id, + 'StockTransfer', + 'post' + ) + if (result) { + message.success('Stock transfer posted') + onOk(result) + } + } catch (error) { + console.error('Error posting stock transfer:', error) + } finally { + setPostLoading(false) + } + } + + return ( + + ) +} + +PostStockTransfer.propTypes = { + onOk: PropTypes.func.isRequired, + objectData: PropTypes.object +} + +export default PostStockTransfer diff --git a/src/components/Dashboard/Inventory/StockTransfers/StockTransferInfo.jsx b/src/components/Dashboard/Inventory/StockTransfers/StockTransferInfo.jsx new file mode 100644 index 0000000..aa54bde --- /dev/null +++ b/src/components/Dashboard/Inventory/StockTransfers/StockTransferInfo.jsx @@ -0,0 +1,269 @@ +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 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 { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' +import PostStockTransfer from './PostStockTransfer.jsx' +import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx' +import NoteIcon from '../../../Icons/NoteIcon.jsx' +import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx' +import StockTransferIcon from '../../../Icons/StockTransferIcon.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 UserNotifierToggle from '../../common/UserNotifierToggle.jsx' +import ScrollBox from '../../common/ScrollBox.jsx' + +const StockTransferInfo = () => { + const location = useLocation() + const objectFormRef = useRef(null) + const actionHandlerRef = useRef(null) + const stockTransferId = new URLSearchParams(location.search).get( + 'stockTransferId' + ) + const [postOpen, setPostOpen] = useState(false) + const [collapseState, updateCollapseState] = useCollapseState( + 'StockTransferInfo', + { + info: true, + lines: true, + notes: true, + auditLogs: true + } + ) + + const [objectFormState, setEditFormState] = useState({ + isEditing: false, + editLoading: false, + formValid: false, + locked: false, + 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 + }, + post: () => { + setPostOpen(true) + return true + } + } + + const editDisabled = + getModelByName('stockTransfer') + ?.actions?.find((action) => action.name === 'edit') + ?.disabled(objectFormState.objectData) ?? false + + return ( + <> + + + + + + + + + + + + + { + 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} + /> + + + + + + + { + setEditFormState((prev) => ({ ...prev, ...state })) + }} + > + {({ loading, isEditing, objectData }) => ( + + } + active={collapseState.info} + onToggle={(expanded) => + updateCollapseState('info', expanded) + } + collapseKey='info' + > + } + isEditing={isEditing} + type='stockTransfer' + objectData={objectData} + labelWidth='175px' + visibleProperties={{ lines: false }} + /> + + } + active={collapseState.lines} + onToggle={(expanded) => + updateCollapseState('lines', expanded) + } + collapseKey='lines' + > + + + + )} + + + + } + active={collapseState.notes} + onToggle={(expanded) => updateCollapseState('notes', expanded)} + collapseKey='notes' + > + + + + + + } + active={collapseState.auditLogs} + onToggle={(expanded) => + updateCollapseState('auditLogs', expanded) + } + collapseKey='auditLogs' + > + {objectFormState.loading ? ( + + ) : ( + + )} + + + + + { + setPostOpen(false) + }} + width={520} + footer={null} + destroyOnHidden={true} + centered={true} + > + { + setPostOpen(false) + actions.reload() + }} + objectData={objectFormState.objectData} + /> + + + ) +} + +export default StockTransferInfo diff --git a/src/components/Icons/StockLocationIcon.jsx b/src/components/Icons/StockLocationIcon.jsx new file mode 100644 index 0000000..401fc34 --- /dev/null +++ b/src/components/Icons/StockLocationIcon.jsx @@ -0,0 +1,8 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/stocklocationicon.svg?react' + +const StockLocationIcon = (props) => ( + +) + +export default StockLocationIcon diff --git a/src/components/Icons/StockTransferIcon.jsx b/src/components/Icons/StockTransferIcon.jsx new file mode 100644 index 0000000..160ac61 --- /dev/null +++ b/src/components/Icons/StockTransferIcon.jsx @@ -0,0 +1,8 @@ +import Icon from '@ant-design/icons' +import CustomIconSvg from '../../../assets/icons/stocktransfericon.svg?react' + +const StockTransferIcon = (props) => ( + +) + +export default StockTransferIcon diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js index d1a936b..a66b856 100644 --- a/src/database/ObjectModels.js +++ b/src/database/ObjectModels.js @@ -21,6 +21,8 @@ import { StockEvent } from './models/StockEvent' import { StockAudit } from './models/StockAudit' import { PartStock } from './models/PartStock' import { ProductStock } from './models/ProductStock' +import { StockLocation } from './models/StockLocation' +import { StockTransfer } from './models/StockTransfer' import { PurchaseOrder } from './models/PurchaseOrder' import { OrderItem } from './models/OrderItem' import { Shipment } from './models/Shipment' @@ -68,6 +70,8 @@ export const objectModels = [ StockAudit, PartStock, ProductStock, + StockLocation, + StockTransfer, PurchaseOrder, OrderItem, Shipment, @@ -116,6 +120,8 @@ export { StockAudit, PartStock, ProductStock, + StockLocation, + StockTransfer, PurchaseOrder, OrderItem, Shipment, diff --git a/src/database/models/FilamentStock.js b/src/database/models/FilamentStock.js index 7c45fb1..0fcfc82 100644 --- a/src/database/models/FilamentStock.js +++ b/src/database/models/FilamentStock.js @@ -23,6 +23,7 @@ export const FilamentStock = { 'currentWeight', 'startingWeight', 'filamentSku', + 'stockLocation', 'createdAt', 'updatedAt' ], @@ -81,6 +82,15 @@ export const FilamentStock = { showHyperlink: true, columnWidth: 200 }, + { + name: 'stockLocation', + label: 'Stock location', + type: 'object', + objectType: 'stockLocation', + required: false, + showHyperlink: true, + columnWidth: 200 + }, { name: 'currentWeight', label: 'Current Weight', diff --git a/src/database/models/PartStock.js b/src/database/models/PartStock.js index e0184ec..0d39441 100644 --- a/src/database/models/PartStock.js +++ b/src/database/models/PartStock.js @@ -25,6 +25,7 @@ export const PartStock = { 'startingQuantity', 'currentQuantity', 'partSku', + 'stockLocation', 'createdAt', 'updatedAt' ], @@ -75,7 +76,7 @@ export const PartStock = { readOnly: false, columnWidth: 200, required: true, - masterFilter: ['subJob'] + masterFilter: ['subJob', 'stockTransfer'] }, { name: 'partSku', @@ -86,6 +87,15 @@ export const PartStock = { showHyperlink: true, columnWidth: 200 }, + { + name: 'stockLocation', + label: 'Stock location', + type: 'object', + objectType: 'stockLocation', + required: false, + showHyperlink: true, + columnWidth: 200 + }, { name: 'source', diff --git a/src/database/models/ProductStock.js b/src/database/models/ProductStock.js index 76606eb..78f1410 100644 --- a/src/database/models/ProductStock.js +++ b/src/database/models/ProductStock.js @@ -92,6 +92,7 @@ export const ProductStock = { 'state', 'currentQuantity', 'productSku', + 'stockLocation', 'createdAt', 'updatedAt' ], @@ -151,6 +152,18 @@ export const ProductStock = { showHyperlink: true, columnWidth: 200 }, + { + name: 'stockLocation', + label: 'Stock location', + type: 'object', + objectType: 'stockLocation', + required: false, + showHyperlink: true, + columnWidth: 200, + readOnly: (objectData) => { + return objectData?.state?.type != 'draft' + } + }, { name: 'currentQuantity', label: 'Current Quantity', diff --git a/src/database/models/StockLocation.js b/src/database/models/StockLocation.js new file mode 100644 index 0000000..54377e7 --- /dev/null +++ b/src/database/models/StockLocation.js @@ -0,0 +1,80 @@ +import StockLocationIcon from '../../components/Icons/StockLocationIcon' +import InfoCircleIcon from '../../components/Icons/InfoCircleIcon' + +export const StockLocation = { + name: 'stockLocation', + label: 'Stock Location', + prefix: 'SLN', + icon: StockLocationIcon, + actions: [ + { + name: 'info', + label: 'Info', + default: true, + row: true, + icon: InfoCircleIcon, + url: (_id) => + `/dashboard/inventory/stocklocations/info?stockLocationId=${_id}` + } + ], + url: (id) => `/dashboard/inventory/stocklocations/info?stockLocationId=${id}`, + filters: ['_id', 'name'], + sorters: ['name', 'createdAt'], + columns: ['_reference', 'name', 'createdAt', 'updatedAt'], + properties: [ + { + name: '_id', + label: 'ID', + type: 'id', + objectType: 'stockLocation', + 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: 'stockLocation', + showCopy: true, + readOnly: true + }, + { + name: 'updatedAt', + label: 'Updated At', + type: 'dateTime', + readOnly: true, + columnWidth: 175 + }, + { + name: 'name', + label: 'Name', + type: 'text', + required: true, + columnWidth: 220 + }, + { + name: 'notes', + label: 'Notes', + type: 'text', + required: false, + columnWidth: 280 + } + ], + stats: [ + { + name: 'total.count', + label: 'Locations', + type: 'number', + color: 'default' + } + ] +} diff --git a/src/database/models/StockTransfer.js b/src/database/models/StockTransfer.js new file mode 100644 index 0000000..b6e08e4 --- /dev/null +++ b/src/database/models/StockTransfer.js @@ -0,0 +1,224 @@ +import StockTransferIcon from '../../components/Icons/StockTransferIcon' +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' + +export const StockTransfer = { + name: 'stockTransfer', + label: 'Stock Transfer', + prefix: 'STT', + icon: StockTransferIcon, + actions: [ + { + name: 'info', + label: 'Info', + default: true, + row: true, + icon: InfoCircleIcon, + url: (_id) => + `/dashboard/inventory/stocktransfers/info?stockTransferId=${_id}` + }, + { + name: 'edit', + label: 'Edit', + type: 'button', + icon: EditIcon, + url: (_id) => + `/dashboard/inventory/stocktransfers/info?stockTransferId=${_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/stocktransfers/info?stockTransferId=${_id}&action=cancelEdit`, + visible: (objectData) => { + return objectData?._isEditing && objectData?._isEditing == true + } + }, + { + name: 'finishEdit', + label: 'Finish Edit', + type: 'button', + icon: CheckIcon, + url: (_id) => + `/dashboard/inventory/stocktransfers/info?stockTransferId=${_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/stocktransfers/info?stockTransferId=${_id}&action=delete`, + visible: (objectData) => { + return !(objectData?._isEditing && objectData?._isEditing == true) + }, + disabled: (objectData) => { + return objectData?.state?.type != 'draft' + } + }, + { type: 'divider' }, + { + name: 'post', + label: 'Post', + type: 'button', + icon: CheckIcon, + url: (_id) => + `/dashboard/inventory/stocktransfers/info?stockTransferId=${_id}&action=post`, + visible: (objectData) => { + return objectData?.state?.type == 'draft' + } + } + ], + url: (id) => `/dashboard/inventory/stocktransfers/info?stockTransferId=${id}`, + filters: ['_id', 'state'], + sorters: ['createdAt', 'postedAt'], + columns: ['_reference', 'state', 'postedAt', 'createdAt', 'updatedAt'], + properties: [ + { + name: '_id', + label: 'ID', + type: 'id', + objectType: 'stockTransfer', + 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: 'stockTransfer', + showCopy: true, + readOnly: true + }, + { + name: 'state', + label: 'State', + type: 'state', + readOnly: true, + columnWidth: 120 + }, + { + name: 'postedAt', + label: 'Posted At', + type: 'dateTime', + readOnly: true, + columnWidth: 175 + }, + { + name: 'updatedAt', + label: 'Updated At', + type: 'dateTime', + readOnly: true, + columnWidth: 175 + }, + { + name: 'lines', + label: 'Lines', + type: 'objectChildren', + required: false, + canAddRemove: true, + columns: [ + 'fromStockType', + 'fromStock', + 'quantity', + 'toStockLocation', + 'toStockType', + 'toStock' + ], + properties: [ + { + name: 'fromStockType', + label: 'From type', + type: 'objectType', + required: true, + columnWidth: 180, + masterFilter: ['filamentStock', 'partStock', 'productStock'] + }, + { + name: 'fromStock', + label: 'From stock', + type: 'object', + objectType: (row) => row?.fromStockType, + required: true, + showHyperlink: true, + columnWidth: 230 + }, + { + name: 'quantity', + label: 'Quantity', + type: 'number', + required: true, + min: 0, + columnWidth: 140, + suffix: (row) => + row?.fromStockType === 'filamentStock' ? 'g net' : null + }, + { + name: 'toStockLocation', + label: 'To location', + type: 'object', + objectType: 'stockLocation', + required: true, + showHyperlink: true, + columnWidth: 230 + }, + { + name: 'toStockType', + label: 'To type', + type: 'objectType', + readOnly: true, + columnWidth: 180, + visible: (row) => Boolean(row?.toStockType) + }, + { + name: 'toStock', + label: 'To stock', + type: 'object', + objectType: (row) => row?.toStockType, + readOnly: true, + showHyperlink: true, + columnWidth: 230, + visible: (row) => Boolean(row?.toStock) + } + ] + } + ], + stats: [ + { + name: 'draft.count', + label: 'Draft', + type: 'number', + color: 'default' + }, + { + name: 'posted.count', + label: 'Posted', + type: 'number', + color: 'success' + } + ] +} diff --git a/src/routes/InventoryRoutes.jsx b/src/routes/InventoryRoutes.jsx index b99b9ea..e53a449 100644 --- a/src/routes/InventoryRoutes.jsx +++ b/src/routes/InventoryRoutes.jsx @@ -55,6 +55,22 @@ const ShipmentInfo = lazy( const InventoryOverview = lazy( () => import('../components/Dashboard/Inventory/InventoryOverview.jsx') ) +const StockLocations = lazy( + () => import('../components/Dashboard/Inventory/StockLocations.jsx') +) +const StockLocationInfo = lazy( + () => + import('../components/Dashboard/Inventory/StockLocations/StockLocationInfo.jsx') +) +const StockTransfers = lazy( + () => import('../components/Dashboard/Inventory/StockTransfers.jsx') +) +const StockTransferInfo = lazy( + () => + import( + '../components/Dashboard/Inventory/StockTransfers/StockTransferInfo.jsx' + ) +) const InventoryRoutes = [ } />, + } + />, + } + />, + } + />, + } + />,