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 = [
}
/>,
+ }
+ />,
+ }
+ />,
+ }
+ />,
+ }
+ />,