diff --git a/assets/icons/filamentskuicon.svg b/assets/icons/filamentskuicon.svg
new file mode 100644
index 0000000..59c490e
--- /dev/null
+++ b/assets/icons/filamentskuicon.svg
@@ -0,0 +1,13 @@
+
+
+
diff --git a/src/components/Dashboard/Inventory/OrderItems/NewOrderItem.jsx b/src/components/Dashboard/Inventory/OrderItems/NewOrderItem.jsx
index 4cc3a6b..1da0f5f 100644
--- a/src/components/Dashboard/Inventory/OrderItems/NewOrderItem.jsx
+++ b/src/components/Dashboard/Inventory/OrderItems/NewOrderItem.jsx
@@ -46,6 +46,34 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
/>
)
},
+
+ {
+ title: 'Optional',
+ key: 'optional',
+ content: (
+
+ )
+ },
{
title: 'Pricing',
key: 'pricing',
@@ -67,32 +95,6 @@ const NewOrderItem = ({ onOk, reset, defaultValues }) => {
/>
)
},
- {
- title: 'Optional',
- key: 'optional',
- content: (
-
- )
- },
{
title: 'Summary',
key: 'summary',
diff --git a/src/components/Dashboard/Management/FilamentSkus.jsx b/src/components/Dashboard/Management/FilamentSkus.jsx
new file mode 100644
index 0000000..acd9d20
--- /dev/null
+++ b/src/components/Dashboard/Management/FilamentSkus.jsx
@@ -0,0 +1,104 @@
+import { useState, useRef } from 'react'
+
+import { Button, Flex, Space, Modal, Dropdown } from 'antd'
+
+import NewFilamentSku from './FilamentSkus/NewFilamentSku'
+import PlusIcon from '../../Icons/PlusIcon'
+import ReloadIcon from '../../Icons/ReloadIcon'
+import useColumnVisibility from '../hooks/useColumnVisibility'
+import ObjectTable from '../common/ObjectTable'
+import ListIcon from '../../Icons/ListIcon'
+import GridIcon from '../../Icons/GridIcon'
+import useViewMode from '../hooks/useViewMode'
+import ColumnViewButton from '../common/ColumnViewButton'
+import ExportListButton from '../common/ExportListButton'
+
+const FilamentSkus = () => {
+ const tableRef = useRef()
+
+ const [newFilamentSkuOpen, setNewFilamentSkuOpen] = useState(false)
+
+ const [viewMode, setViewMode] = useViewMode('filamentSkus')
+
+ const [columnVisibility, setColumnVisibility] =
+ useColumnVisibility('filamentSku')
+
+ const actionItems = {
+ items: [
+ {
+ label: 'New Filament SKU',
+ key: 'newFilamentSku',
+ icon:
+ },
+ { type: 'divider' },
+ {
+ label: 'Reload List',
+ key: 'reloadList',
+ icon:
+ }
+ ],
+ onClick: ({ key }) => {
+ if (key === 'reloadList') {
+ tableRef.current?.reload()
+ } else if (key === 'newFilamentSku') {
+ setNewFilamentSkuOpen(true)
+ }
+ }
+ }
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ : }
+ onClick={() =>
+ setViewMode(viewMode === 'cards' ? 'list' : 'cards')
+ }
+ />
+
+
+
+
+
+ {
+ setNewFilamentSkuOpen(false)
+ }}
+ destroyOnHidden={true}
+ >
+ {
+ setNewFilamentSkuOpen(false)
+ tableRef.current?.reload()
+ }}
+ reset={newFilamentSkuOpen}
+ />
+
+ >
+ )
+}
+
+export default FilamentSkus
diff --git a/src/components/Dashboard/Management/FilamentSkus/FilamentSkuInfo.jsx b/src/components/Dashboard/Management/FilamentSkus/FilamentSkuInfo.jsx
new file mode 100644
index 0000000..b8315e7
--- /dev/null
+++ b/src/components/Dashboard/Management/FilamentSkus/FilamentSkuInfo.jsx
@@ -0,0 +1,197 @@
+import { useRef, useState } from 'react'
+import { useLocation } from 'react-router-dom'
+import { Space, Flex, Card } from 'antd'
+import useCollapseState from '../../hooks/useCollapseState'
+import NotesPanel from '../../common/NotesPanel'
+import InfoCollapse from '../../common/InfoCollapse'
+import ObjectInfo from '../../common/ObjectInfo'
+import ViewButton from '../../common/ViewButton'
+import ObjectForm from '../../common/ObjectForm'
+import EditButtons from '../../common/EditButtons'
+import LockIndicator from '../../common/LockIndicator.jsx'
+import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
+import NoteIcon from '../../../Icons/NoteIcon.jsx'
+import AuditLogIcon from '../../../Icons/AuditLogIcon.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 FilamentSkuInfo = () => {
+ const location = useLocation()
+ const objectFormRef = useRef(null)
+ const actionHandlerRef = useRef(null)
+ const filamentSkuId = new URLSearchParams(location.search).get('filamentSkuId')
+ const [collapseState, updateCollapseState] = useCollapseState(
+ 'FilamentSkuInfo',
+ {
+ info: true,
+ notes: true,
+ auditLogs: true
+ }
+ )
+ const [objectFormState, setEditFormState] = useState({
+ isEditing: false,
+ editLoading: false,
+ formValid: false,
+ lock: null,
+ loading: false,
+ objectData: {}
+ })
+
+ const actions = {
+ reload: () => {
+ objectFormRef?.current?.fetchObject?.()
+ 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
+ }
+ }
+
+ 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 }) => (
+
+ )}
+
+
+
+ }
+ active={collapseState.notes}
+ onToggle={(expanded) => updateCollapseState('notes', expanded)}
+ collapseKey='notes'
+ >
+
+
+
+
+ }
+ active={collapseState.auditLogs}
+ onToggle={(expanded) =>
+ updateCollapseState('auditLogs', expanded)
+ }
+ collapseKey='auditLogs'
+ >
+ {objectFormState.loading ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ >
+ )
+}
+
+export default FilamentSkuInfo
diff --git a/src/components/Dashboard/Management/FilamentSkus/NewFilamentSku.jsx b/src/components/Dashboard/Management/FilamentSkus/NewFilamentSku.jsx
new file mode 100644
index 0000000..32c80e9
--- /dev/null
+++ b/src/components/Dashboard/Management/FilamentSkus/NewFilamentSku.jsx
@@ -0,0 +1,123 @@
+import PropTypes from 'prop-types'
+import ObjectInfo from '../../common/ObjectInfo'
+import NewObjectForm from '../../common/NewObjectForm'
+import WizardView from '../../common/WizardView'
+
+const NewFilamentSku = ({ onOk, reset, defaultValues }) => {
+ return (
+
+ {({ handleSubmit, submitLoading, objectData, formValid }) => {
+ const steps = [
+ {
+ title: 'Required',
+ key: 'required',
+ content: (
+
+ )
+ },
+ {
+ title: 'Color & Cost',
+ key: 'colorCost',
+ content: (
+
+ )
+ },
+ {
+ title: 'Optional',
+ key: 'optional',
+ content: (
+
+ )
+ },
+ {
+ title: 'Summary',
+ key: 'summary',
+ content: (
+
+ )
+ }
+ ]
+ return (
+ {
+ const result = await handleSubmit()
+ if (result) {
+ onOk()
+ }
+ }}
+ />
+ )
+ }}
+
+ )
+}
+
+NewFilamentSku.propTypes = {
+ onOk: PropTypes.func.isRequired,
+ reset: PropTypes.bool,
+ defaultValues: PropTypes.object
+}
+
+export default NewFilamentSku
diff --git a/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx b/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx
index a39e41d..9901245 100644
--- a/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx
+++ b/src/components/Dashboard/Management/Filaments/FilamentInfo.jsx
@@ -1,6 +1,6 @@
import { useRef, useState } from 'react'
import { useLocation } from 'react-router-dom'
-import { Space, Flex, Card } from 'antd'
+import { Space, Flex, Card, Modal } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import loglevel from 'loglevel'
import config from '../../../../config'
@@ -20,9 +20,11 @@ import ObjectActions from '../../common/ObjectActions.jsx'
import ObjectTable from '../../common/ObjectTable.jsx'
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
import FilamentIcon from '../../../Icons/FilamentIcon.jsx'
+import FilamentSkuIcon from '../../../Icons/FilamentSkuIcon.jsx'
import DocumentPrintButton from '../../common/DocumentPrintButton.jsx'
import ScrollBox from '../../common/ScrollBox.jsx'
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
+import NewFilamentSku from '../FilamentSkus/NewFilamentSku'
const log = loglevel.getLogger('FilamentInfo')
log.setLevel(config.logLevel)
@@ -32,10 +34,13 @@ const FilamentInfo = () => {
const objectFormRef = useRef(null)
const actionHandlerRef = useRef(null)
const filamentId = new URLSearchParams(location.search).get('filamentId')
+ const [newFilamentSkuOpen, setNewFilamentSkuOpen] = useState(false)
+ const filamentSkusTableRef = useRef()
const [collapseState, updateCollapseState] = useCollapseState(
'FilamentInfo',
{
info: true,
+ filamentSkus: true,
stocks: true,
notes: true,
auditLogs: true
@@ -56,6 +61,10 @@ const FilamentInfo = () => {
objectFormRef?.current?.fetchObject?.()
return true
},
+ newFilamentSku: () => {
+ setNewFilamentSkuOpen(true)
+ return false
+ },
edit: () => {
objectFormRef?.current?.startEditing?.()
return false
@@ -93,6 +102,7 @@ const FilamentInfo = () => {
disabled={objectFormState.loading}
items={[
{ key: 'info', label: 'Filament Information' },
+ { key: 'filamentSkus', label: 'Filament SKUs' },
{ key: 'stocks', label: 'Filament Stocks' },
{ key: 'notes', label: 'Notes' },
{ key: 'auditLogs', label: 'Audit Logs' }
@@ -171,6 +181,27 @@ const FilamentInfo = () => {
+ }
+ active={collapseState.filamentSkus}
+ onToggle={(expanded) =>
+ updateCollapseState('filamentSkus', expanded)
+ }
+ collapseKey='filamentSkus'
+ >
+ {objectFormState.loading ? (
+
+ ) : (
+
+ )}
+
+
}
@@ -183,16 +214,36 @@ const FilamentInfo = () => {
) : (
)}
+ setNewFilamentSkuOpen(false)}
+ destroyOnClose
+ >
+ {
+ setNewFilamentSkuOpen(false)
+ filamentSkusTableRef.current?.reload?.()
+ }}
+ reset={newFilamentSkuOpen}
+ defaultValues={{
+ filament: filamentId ? { _id: filamentId } : undefined
+ }}
+ />
+
+
}
diff --git a/src/components/Dashboard/Management/ManagementSidebar.jsx b/src/components/Dashboard/Management/ManagementSidebar.jsx
index e0e0b1e..96f8609 100644
--- a/src/components/Dashboard/Management/ManagementSidebar.jsx
+++ b/src/components/Dashboard/Management/ManagementSidebar.jsx
@@ -1,6 +1,7 @@
import { useLocation } from 'react-router-dom'
import DashboardSidebar from '../common/DashboardSidebar'
import FilamentIcon from '../../Icons/FilamentIcon'
+import FilamentSkuIcon from '../../Icons/FilamentSkuIcon'
import PartIcon from '../../Icons/PartIcon'
import PartSkuIcon from '../../Icons/PartSkuIcon'
import ProductIcon from '../../Icons/ProductIcon'
@@ -32,6 +33,12 @@ const items = [
label: 'Filaments',
path: '/dashboard/management/filaments'
},
+ {
+ key: 'filamentSkus',
+ icon: ,
+ label: 'Filament SKUs',
+ path: '/dashboard/management/filamentskus'
+ },
{
key: 'parts',
icon: ,
@@ -186,6 +193,7 @@ if (import.meta.env.MODE === 'development') {
const routeKeyMap = {
'/dashboard/management/filaments': 'filaments',
+ '/dashboard/management/filamentskus': 'filamentSkus',
'/dashboard/management/parts': 'parts',
'/dashboard/management/partskus': 'partSkus',
'/dashboard/management/users': 'users',
diff --git a/src/components/Dashboard/Management/PartSkus/NewPartSku.jsx b/src/components/Dashboard/Management/PartSkus/NewPartSku.jsx
index c4a08ee..47213db 100644
--- a/src/components/Dashboard/Management/PartSkus/NewPartSku.jsx
+++ b/src/components/Dashboard/Management/PartSkus/NewPartSku.jsx
@@ -52,7 +52,7 @@ const NewPartSku = ({ onOk, reset, defaultValues }) => {
_id: false,
createdAt: false,
updatedAt: false,
- sku: false,
+ barcode: false,
part: false,
name: false,
description: false
@@ -72,6 +72,7 @@ const NewPartSku = ({ onOk, reset, defaultValues }) => {
column={1}
labelWidth={100}
visibleProperties={{
+ barcode: true,
description: true
}}
bordered={false}
diff --git a/src/components/Dashboard/Management/ProductSkus/NewProductSku.jsx b/src/components/Dashboard/Management/ProductSkus/NewProductSku.jsx
index 9064b51..7d32848 100644
--- a/src/components/Dashboard/Management/ProductSkus/NewProductSku.jsx
+++ b/src/components/Dashboard/Management/ProductSkus/NewProductSku.jsx
@@ -53,7 +53,7 @@ const NewProductSku = ({ onOk, reset, defaultValues }) => {
_id: false,
createdAt: false,
updatedAt: false,
- sku: false,
+ barcode: false,
product: false,
name: false,
description: false,
@@ -77,7 +77,7 @@ const NewProductSku = ({ onOk, reset, defaultValues }) => {
_id: false,
createdAt: false,
updatedAt: false,
- sku: false,
+ barcode: false,
product: false,
name: false,
description: false,
@@ -107,6 +107,7 @@ const NewProductSku = ({ onOk, reset, defaultValues }) => {
column={1}
labelWidth={100}
visibleProperties={{
+ barcode: true,
description: true
}}
bordered={false}
diff --git a/src/components/Dashboard/common/ObjectForm.jsx b/src/components/Dashboard/common/ObjectForm.jsx
index ee55da9..6d3a11b 100644
--- a/src/components/Dashboard/common/ObjectForm.jsx
+++ b/src/components/Dashboard/common/ObjectForm.jsx
@@ -521,11 +521,15 @@ const ObjectForm = forwardRef(
onEdit(allFormValues)
}
- // Calculate computed values based on current form data
- const currentFormData = {
- ...(serverObjectData.current || {}),
- ...changedValues
- }
+ // Recompute derived fields from the full current form snapshot so
+ // toggles like overridePrice/overrideCost are preserved while typing.
+ const currentFormData = mergeWith(
+ {},
+ serverObjectData.current || {},
+ objectData || {},
+ allFormValues,
+ arrayReplaceCustomizer
+ )
const computedEntries = calculateComputedValues(
currentFormData,
model
diff --git a/src/components/Icons/FilamentSkuIcon.jsx b/src/components/Icons/FilamentSkuIcon.jsx
new file mode 100644
index 0000000..92a0965
--- /dev/null
+++ b/src/components/Icons/FilamentSkuIcon.jsx
@@ -0,0 +1,8 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/filamentskuicon.svg?react'
+
+const FilamentSkuIcon = (props) => (
+
+)
+
+export default FilamentSkuIcon
diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js
index 6adf26f..ac4976a 100644
--- a/src/database/ObjectModels.js
+++ b/src/database/ObjectModels.js
@@ -1,6 +1,7 @@
import { Printer } from './models/Printer.js'
import { Host } from './models/Host.js'
import { Filament } from './models/Filament.js'
+import { FilamentSku } from './models/FilamentSku.js'
import { Spool } from './models/Spool'
import { GCodeFile } from './models/GCodeFile'
import { Job } from './models/Job'
@@ -43,6 +44,7 @@ export const objectModels = [
Printer,
Host,
Filament,
+ FilamentSku,
Spool,
GCodeFile,
Job,
@@ -86,6 +88,7 @@ export {
Printer,
Host,
Filament,
+ FilamentSku,
Spool,
GCodeFile,
Job,
diff --git a/src/database/models/Filament.js b/src/database/models/Filament.js
index 3c45ac6..d66cab8 100644
--- a/src/database/models/Filament.js
+++ b/src/database/models/Filament.js
@@ -4,6 +4,7 @@ import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
import ReloadIcon from '../../components/Icons/ReloadIcon'
import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
+import PlusIcon from '../../components/Icons/PlusIcon'
export const Filament = {
name: 'filament',
@@ -56,31 +57,33 @@ export const Filament = {
visible: (objectData) => {
return objectData?._isEditing && objectData?._isEditing == true
}
+ },
+ { type: 'divider' },
+ {
+ name: 'newFilamentSku',
+ label: 'New Filament SKU',
+ type: 'button',
+ icon: PlusIcon,
+ url: (_id) =>
+ `/dashboard/management/filaments/info?filamentId=${_id}&action=newFilamentSku`,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ }
}
],
columns: [
'_reference',
'name',
'type',
- 'color',
- 'vendor',
- 'cost',
'density',
'diameter',
+ 'cost',
'createdAt',
'updatedAt'
],
- filters: ['_id', 'name', 'type', 'color', 'cost', 'vendor'],
- sorters: [
- 'name',
- 'createdAt',
- 'type',
- 'vendor',
- 'cost',
- 'updatedAt',
- 'createdAt'
- ],
- group: ['diameter', 'type', 'vendor'],
+ filters: ['_id', 'name', 'type'],
+ sorters: ['name', 'createdAt', 'type', 'updatedAt'],
+ group: ['diameter', 'type'],
properties: [
{
name: '_id',
@@ -109,14 +112,6 @@ export const Filament = {
type: 'dateTime',
readOnly: true
},
- {
- name: 'vendor',
- label: 'Vendor',
- required: true,
- type: 'object',
- objectType: 'vendor',
- showHyperlink: true
- },
{
name: 'type',
label: 'Material',
@@ -124,53 +119,6 @@ export const Filament = {
columnWidth: 150,
type: 'material'
},
- {
- name: 'cost',
- label: 'Cost',
- columnWidth: 150,
- required: true,
- type: 'number',
- prefix: '£'
- },
- {
- name: 'costWithTax',
- label: 'Cost w/ Tax',
- columnWidth: 150,
- required: true,
- readOnly: true,
- type: 'number',
- prefix: '£',
- value: (objectData) => {
- if (objectData?.costTaxRate?.rateType == 'percentage') {
- return (
- (
- objectData?.cost *
- (1 + objectData?.costTaxRate?.rate / 100)
- ).toFixed(2) || undefined
- )
- } else if (objectData?.costTaxRate?.rateType == 'amount') {
- return (
- (objectData?.cost + objectData?.costTaxRate?.rate).toFixed(2) ||
- undefined
- )
- }
- }
- },
- {
- name: 'costTaxRate',
- label: 'Cost Tax Rate',
- required: true,
- type: 'object',
- objectType: 'taxRate',
- showHyperlink: true
- },
- {
- name: 'color',
- label: 'Color',
- columnWidth: 150,
- required: true,
- type: 'color'
- },
{
name: 'diameter',
label: 'Diameter',
@@ -195,6 +143,47 @@ export const Filament = {
type: 'number',
suffix: 'g'
},
+ {
+ name: 'cost',
+ label: 'Cost',
+ required: false,
+ columnWidth: 120,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01
+ },
+ {
+ name: 'costWithTax',
+ label: 'Cost w/ Tax',
+ required: false,
+ readOnly: true,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01,
+ value: (objectData) => {
+ const cost = objectData?.cost
+ if (!cost) return undefined
+ if (objectData?.costTaxRate?.rateType == 'percentage') {
+ return (
+ (cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) ||
+ undefined
+ )
+ } else if (objectData?.costTaxRate?.rateType == 'amount') {
+ return (cost + objectData?.costTaxRate?.rate).toFixed(2) || undefined
+ }
+ return cost
+ }
+ },
+ {
+ name: 'costTaxRate',
+ label: 'Cost Tax Rate',
+ required: false,
+ type: 'object',
+ objectType: 'taxRate',
+ showHyperlink: true
+ },
{
name: 'url',
label: 'Link',
diff --git a/src/database/models/FilamentSku.js b/src/database/models/FilamentSku.js
new file mode 100644
index 0000000..7457e28
--- /dev/null
+++ b/src/database/models/FilamentSku.js
@@ -0,0 +1,184 @@
+import FilamentSkuIcon from '../../components/Icons/FilamentSkuIcon'
+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 ReloadIcon from '../../components/Icons/ReloadIcon'
+
+export const FilamentSku = {
+ name: 'filamentSku',
+ label: 'Filament SKU',
+ prefix: 'FSU',
+ icon: FilamentSkuIcon,
+ actions: [
+ {
+ name: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon,
+ url: (_id) => `/dashboard/management/filamentskus/info?filamentSkuId=${_id}`
+ },
+ {
+ name: 'reload',
+ label: 'Reload',
+ icon: ReloadIcon,
+ url: (_id) =>
+ `/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=reload`
+ },
+ {
+ name: 'edit',
+ label: 'Edit',
+ row: true,
+ icon: EditIcon,
+ url: (_id) =>
+ `/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=edit`,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ }
+ },
+ {
+ name: 'finishEdit',
+ label: 'Save Edits',
+ icon: CheckIcon,
+ url: (_id) =>
+ `/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=finishEdit`,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'cancelEdit',
+ label: 'Cancel Edits',
+ icon: XMarkIcon,
+ url: (_id) =>
+ `/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=cancelEdit`,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ { type: 'divider' },
+ {
+ name: 'delete',
+ label: 'Delete',
+ icon: BinIcon,
+ danger: true,
+ url: (_id) =>
+ `/dashboard/management/filamentskus/info?filamentSkuId=${_id}&action=delete`
+ }
+ ],
+ url: (id) => `/dashboard/management/filamentskus/info?filamentSkuId=${id}`,
+ columns: ['_reference', 'barcode', 'filament', 'name', 'color', 'overrideCost', 'cost', 'createdAt', 'updatedAt'],
+ filters: ['_id', 'barcode', 'filament', 'filament._id', 'name', 'color', 'cost'],
+ sorters: ['barcode', 'filament', 'name', 'color', 'cost', 'createdAt', 'updatedAt'],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ type: 'id',
+ objectType: 'filamentSku',
+ showCopy: true,
+ readOnly: true
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ required: true,
+ type: 'text'
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true
+ },
+ {
+ name: 'filament',
+ label: 'Filament',
+ type: 'object',
+ objectType: 'filament',
+ required: true,
+ showHyperlink: true
+ },
+ {
+ name: 'barcode',
+ label: 'Barcode',
+ required: false,
+ type: 'text'
+ },
+ {
+ name: 'description',
+ label: 'Description',
+ required: false,
+ type: 'text'
+ },
+ {
+ name: 'color',
+ label: 'Color',
+ required: true,
+ type: 'color'
+ },
+ {
+ name: 'overrideCost',
+ label: 'Override Cost',
+ required: false,
+ type: 'bool',
+ value: (objectData) => objectData?.overrideCost ?? false
+ },
+ {
+ name: 'cost',
+ label: 'Cost',
+ required: false,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01,
+ disabled: (objectData) => !objectData?.overrideCost,
+ value: (objectData) =>
+ objectData?.overrideCost ? objectData?.cost : undefined
+ },
+ {
+ name: 'costWithTax',
+ label: 'Cost w/ Tax',
+ required: false,
+ readOnly: true,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01,
+ disabled: (objectData) => !objectData?.overrideCost,
+ value: (objectData) => {
+ if (!objectData?.overrideCost) return undefined
+ const cost = objectData?.cost
+ const taxRate = objectData?.costTaxRate
+ if (!cost) return undefined
+ if (taxRate?.rateType == 'percentage') {
+ return (
+ (cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
+ )
+ } else if (taxRate?.rateType == 'amount') {
+ return (cost + taxRate?.rate).toFixed(2) || undefined
+ }
+ return cost
+ }
+ },
+ {
+ name: 'costTaxRate',
+ label: 'Cost Tax Rate',
+ required: false,
+ type: 'object',
+ objectType: 'taxRate',
+ showHyperlink: true,
+ disabled: (objectData) => !objectData?.overrideCost,
+ value: (objectData) =>
+ objectData?.overrideCost ? objectData?.costTaxRate : undefined
+ }
+ ]
+}
diff --git a/src/database/models/FilamentStock.js b/src/database/models/FilamentStock.js
index f193f2e..18d0433 100644
--- a/src/database/models/FilamentStock.js
+++ b/src/database/models/FilamentStock.js
@@ -22,13 +22,13 @@ export const FilamentStock = {
'state',
'currentWeight',
'startingWeight',
- 'filament',
+ 'filamentSku',
'createdAt',
'updatedAt'
],
filters: ['_id'],
sorters: ['createdAt', 'updatedAt'],
- group: ['filament'],
+ group: ['filamentSku'],
properties: [
{
name: '_id',
@@ -58,10 +58,10 @@ export const FilamentStock = {
readOnly: true
},
{
- name: 'filament',
- label: 'Filament',
+ name: 'filamentSku',
+ label: 'Filament SKU',
type: 'object',
- objectType: 'filament',
+ objectType: 'filamentSku',
readOnly: true,
initial: true,
required: true,
@@ -93,7 +93,7 @@ export const FilamentStock = {
required: true,
columnWidth: 300,
difference: (objectData) => {
- return objectData?.filament?.emptySpoolWeight
+ return objectData?.filamentSku?.filament?.emptySpoolWeight
}
}
],
diff --git a/src/database/models/GCodeFile.js b/src/database/models/GCodeFile.js
index 8b74732..2559163 100644
--- a/src/database/models/GCodeFile.js
+++ b/src/database/models/GCodeFile.js
@@ -71,7 +71,7 @@ export const GCodeFile = {
columns: [
'name',
'_reference',
- 'filament',
+ 'filamentSku',
'gcodeFileInfo.estimatedPrintingTimeNormalMode',
'gcodeFileInfo.sparseInfillDensity',
'gcodeFileInfo.sparseInfillPattern',
@@ -81,7 +81,7 @@ export const GCodeFile = {
],
filters: ['_id', 'name', 'updatedAt'],
sorters: ['name', 'createdAt', 'updatedAt'],
- group: ['filament'],
+ group: ['filamentSku'],
properties: [
{
name: '_id',
@@ -125,11 +125,11 @@ export const GCodeFile = {
filter: ['.gcode', '.g']
},
{
- name: 'filament',
- label: 'Filament',
+ name: 'filamentSku',
+ label: 'Filament SKU',
type: 'object',
value: null,
- objectType: 'filament',
+ objectType: 'filamentSku',
required: true,
showHyperlink: true
},
@@ -139,10 +139,11 @@ export const GCodeFile = {
type: 'number',
roundNumber: 2,
value: (objectData) => {
- return (
- objectData?.file?.metaData?.filamentUsedG *
- (objectData?.filament?.cost / 1000)
- )
+ const fs = objectData?.filamentSku
+ const costPerKg =
+ fs?.overrideCost ? fs?.cost : fs?.filament?.cost
+ if (!costPerKg || !objectData?.file?.metaData?.filamentUsedG) return undefined
+ return objectData.file.metaData.filamentUsedG * (costPerKg / 1000)
},
readOnly: true,
prefix: '£'
diff --git a/src/database/models/OrderItem.js b/src/database/models/OrderItem.js
index 625e6e4..d118225 100644
--- a/src/database/models/OrderItem.js
+++ b/src/database/models/OrderItem.js
@@ -78,7 +78,7 @@ export const OrderItem = {
}
],
group: [],
- filters: ['itemType', 'item', 'order'],
+ filters: ['itemType', 'item', 'sku', 'order'],
sorters: ['createdAt', 'updatedAt', 'itemAmount', 'quantity'],
columns: [
'_reference',
@@ -86,6 +86,7 @@ export const OrderItem = {
'state',
'itemType',
'item',
+ 'sku',
'itemAmount',
'quantity',
'totalAmount',
@@ -141,7 +142,7 @@ export const OrderItem = {
type: 'text',
readOnly: true,
value: (objectData) => {
- return objectData?.item?.name
+ return objectData?.sku?.name ?? objectData?.item?.name
}
},
{
@@ -213,6 +214,35 @@ export const OrderItem = {
showHyperlink: true,
columnWidth: 300
},
+ {
+ name: 'sku',
+ label: 'SKU',
+ type: 'object',
+ objectType: (objectData) => {
+ if (objectData?.itemType === 'filament') return 'filamentSku'
+ if (objectData?.itemType === 'part') return 'partSku'
+ if (objectData?.itemType === 'product') return 'productSku'
+ return undefined
+ },
+ required: false,
+ showHyperlink: true,
+ columnWidth: 300,
+ visible: (objectData) =>
+ ['filament', 'part', 'product'].includes(objectData?.itemType),
+ masterFilter: (objectData) => {
+ console.log(objectData)
+ if (objectData?.itemType === 'filament' && objectData?.item?._id) {
+ return { filament: objectData.item._id }
+ }
+ if (objectData?.itemType === 'part' && objectData?.item?._id) {
+ return { part: objectData.item._id }
+ }
+ if (objectData?.itemType === 'product' && objectData?.item?._id) {
+ return { product: objectData.item._id }
+ }
+ return undefined
+ }
+ },
{
name: 'syncAmount',
label: 'Sync Amount',
@@ -239,11 +269,24 @@ export const OrderItem = {
},
columnWidth: 150,
value: (objectData) => {
- if (objectData?.item?.cost && objectData?.syncAmount == 'itemCost') {
- return objectData?.item?.cost || undefined
+ const sku = objectData?.sku
+ const item = objectData?.item
+ if (objectData?.syncAmount == 'itemCost') {
+ const cost =
+ sku && sku.overrideCost ? sku.cost : (item?.cost ?? sku?.cost)
+ return cost ?? objectData?.itemAmount
}
- if (objectData?.item?.price && objectData?.syncAmount == 'itemPrice') {
- return objectData?.item?.price || undefined
+ if (objectData?.syncAmount == 'itemPrice') {
+ if (sku && sku.overridePrice) {
+ return sku.price ?? objectData?.itemAmount
+ }
+ const priceMode = item?.priceMode ?? sku?.priceMode
+ const margin = item?.margin ?? sku?.margin
+ const cost = item?.cost ?? sku?.cost
+ if (priceMode == 'margin' && margin != null && cost != null) {
+ return cost * (1 + margin / 100)
+ }
+ return item?.price ?? sku?.price ?? objectData?.itemAmount
}
return objectData?.itemAmount || undefined
}
@@ -283,19 +326,19 @@ export const OrderItem = {
objectType: 'taxRate',
showHyperlink: true,
value: (objectData) => {
- if (
- objectData?.item?.costTaxRate?._id &&
- objectData?.syncAmount == 'itemCost'
- ) {
- return objectData?.item?.costTaxRate || undefined
- } else if (
- objectData?.item?.priceTaxRate?._id &&
- objectData?.syncAmount == 'itemPrice'
- ) {
- return objectData?.item?.priceTaxRate || undefined
- } else {
- return objectData?.taxRate || undefined
+ const sku = objectData?.sku
+ const item = objectData?.item
+ if (objectData?.syncAmount == 'itemCost') {
+ const source = sku && sku.overrideCost ? sku : item
+ return source?.costTaxRate ?? sku?.costTaxRate ?? objectData?.taxRate
}
+ if (objectData?.syncAmount == 'itemPrice') {
+ const source = sku && sku.overridePrice ? sku : item
+ return (
+ source?.priceTaxRate ?? sku?.priceTaxRate ?? objectData?.taxRate
+ )
+ }
+ return objectData?.taxRate || undefined
},
readOnly: (objectData) => {
return objectData?.syncAmount != null
diff --git a/src/database/models/Part.js b/src/database/models/Part.js
index a04b90f..0a70106 100644
--- a/src/database/models/Part.js
+++ b/src/database/models/Part.js
@@ -71,7 +71,7 @@ export const Part = {
}
}
],
- columns: ['name', '_reference', 'createdAt'],
+ columns: ['name', '_reference', 'cost', 'price', 'createdAt'],
filters: ['name', '_id'],
sorters: ['name', 'createdAt', '_id'],
properties: [
@@ -116,6 +116,127 @@ export const Part = {
value: null,
required: false,
showHyperlink: true
+ },
+ {
+ name: 'cost',
+ label: 'Cost',
+ required: false,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01
+ },
+ {
+ name: 'costWithTax',
+ label: 'Cost w/ Tax',
+ required: false,
+ readOnly: true,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01,
+ value: (objectData) => {
+ const cost = objectData?.cost
+ if (!cost) return undefined
+ if (objectData?.costTaxRate?.rateType == 'percentage') {
+ return (
+ (cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) ||
+ undefined
+ )
+ } else if (objectData?.costTaxRate?.rateType == 'amount') {
+ return (cost + objectData?.costTaxRate?.rate).toFixed(2) || undefined
+ }
+ return cost
+ }
+ },
+ {
+ name: 'costTaxRate',
+ label: 'Cost Tax Rate',
+ required: false,
+ type: 'object',
+ objectType: 'taxRate',
+ showHyperlink: true
+ },
+ {
+ name: 'priceMode',
+ label: 'Price Mode',
+ required: false,
+ type: 'priceMode'
+ },
+ {
+ name: 'price',
+ label: 'Price',
+ required: false,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.1,
+ readOnly: (objectData) => objectData?.priceMode == 'margin',
+ value: (objectData) => {
+ if (
+ objectData?.priceMode == 'margin' &&
+ objectData?.margin !== undefined &&
+ objectData?.margin !== null &&
+ objectData?.cost != null
+ ) {
+ return (
+ (objectData.cost * (1 + objectData.margin / 100)).toFixed(2) ||
+ undefined
+ )
+ }
+ return objectData?.price
+ }
+ },
+ {
+ name: 'margin',
+ label: 'Margin',
+ required: false,
+ type: 'number',
+ disabled: (objectData) => objectData?.priceMode == 'amount',
+ suffix: '%',
+ min: 0,
+ max: 100,
+ step: 0.01
+ },
+ {
+ name: 'priceWithTax',
+ label: 'Price w/ Tax',
+ required: false,
+ readOnly: true,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01,
+ value: (objectData) => {
+ let price
+ if (
+ objectData?.priceMode == 'margin' &&
+ objectData?.margin != null &&
+ objectData?.cost != null
+ ) {
+ price = objectData.cost * (1 + objectData.margin / 100)
+ } else {
+ price = objectData?.price
+ }
+ if (!price) return undefined
+ if (objectData?.priceTaxRate?.rateType == 'percentage') {
+ return (
+ (price * (1 + objectData?.priceTaxRate?.rate / 100)).toFixed(2) ||
+ undefined
+ )
+ } else if (objectData?.priceTaxRate?.rateType == 'amount') {
+ return (price + objectData?.priceTaxRate?.rate).toFixed(2) || undefined
+ }
+ return price
+ }
+ },
+ {
+ name: 'priceTaxRate',
+ label: 'Price Tax Rate',
+ required: false,
+ type: 'object',
+ objectType: 'taxRate',
+ showHyperlink: true
}
]
}
diff --git a/src/database/models/PartSku.js b/src/database/models/PartSku.js
index b6d7104..0c7721c 100644
--- a/src/database/models/PartSku.js
+++ b/src/database/models/PartSku.js
@@ -69,9 +69,20 @@ export const PartSku = {
}
],
url: (id) => `/dashboard/management/partskus/info?partSkuId=${id}`,
- columns: ['_reference', 'sku', 'part', 'name', 'cost', 'price', 'createdAt', 'updatedAt'],
- filters: ['_id', 'sku', 'part', 'part._id', 'name', 'cost', 'price'],
- sorters: ['sku', 'part', 'name', 'cost', 'price', 'createdAt', 'updatedAt'],
+ columns: [
+ '_reference',
+ 'barcode',
+ 'part',
+ 'name',
+ 'overrideCost',
+ 'cost',
+ 'overridePrice',
+ 'price',
+ 'createdAt',
+ 'updatedAt'
+ ],
+ filters: ['_id', 'barcode', 'part', 'part._id', 'name', 'cost', 'price'],
+ sorters: ['barcode', 'part', 'name', 'cost', 'price', 'createdAt', 'updatedAt'],
properties: [
{
name: '_id',
@@ -108,9 +119,9 @@ export const PartSku = {
showHyperlink: true
},
{
- name: 'sku',
- label: 'SKU',
- required: true,
+ name: 'barcode',
+ label: 'Barcode',
+ required: false,
type: 'text'
},
{
@@ -125,6 +136,20 @@ export const PartSku = {
required: false,
type: 'priceMode'
},
+ {
+ name: 'overrideCost',
+ label: 'Override Cost',
+ required: false,
+ type: 'bool',
+ value: (objectData) => objectData?.overrideCost ?? false
+ },
+ {
+ name: 'overridePrice',
+ label: 'Override Price',
+ required: false,
+ type: 'bool',
+ value: (objectData) => objectData?.overridePrice ?? false
+ },
{
name: 'cost',
label: 'Cost',
@@ -132,7 +157,10 @@ export const PartSku = {
type: 'number',
prefix: '£',
min: 0,
- step: 0.01
+ step: 0.01,
+ disabled: (objectData) => !objectData?.overrideCost,
+ value: (objectData) =>
+ objectData?.overrideCost ? objectData?.cost : undefined
},
{
name: 'costWithTax',
@@ -143,22 +171,18 @@ export const PartSku = {
prefix: '£',
min: 0,
step: 0.01,
+ disabled: (objectData) => !objectData?.overrideCost,
value: (objectData) => {
- if (objectData?.costTaxRate?.rateType == 'percentage') {
- return (
- (
- objectData?.cost *
- (1 + objectData?.costTaxRate?.rate / 100)
- ).toFixed(2) || undefined
- )
- } else if (objectData?.costTaxRate?.rateType == 'amount') {
- return (
- (objectData?.cost + objectData?.costTaxRate?.rate).toFixed(2) ||
- undefined
- )
- } else {
- return objectData?.cost || undefined
+ if (!objectData?.overrideCost) return undefined
+ const cost = objectData?.cost
+ const taxRate = objectData?.costTaxRate
+ if (!cost) return undefined
+ if (taxRate?.rateType == 'percentage') {
+ return (cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
+ } else if (taxRate?.rateType == 'amount') {
+ return (cost + taxRate?.rate).toFixed(2) || undefined
}
+ return cost
}
},
{
@@ -167,7 +191,10 @@ export const PartSku = {
required: false,
type: 'object',
objectType: 'taxRate',
- showHyperlink: true
+ showHyperlink: true,
+ disabled: (objectData) => !objectData?.overrideCost,
+ value: (objectData) =>
+ objectData?.overrideCost ? objectData?.costTaxRate : undefined
},
{
name: 'price',
@@ -177,22 +204,27 @@ export const PartSku = {
prefix: '£',
min: 0,
step: 0.1,
- readOnly: (objectData) => {
- return objectData?.priceMode == 'margin'
- },
+ disabled: (objectData) => !objectData?.overridePrice,
+ readOnly: (objectData) =>
+ objectData?.overridePrice && objectData?.priceMode == 'margin',
value: (objectData) => {
+ if (!objectData?.overridePrice) return undefined
+ const priceMode = objectData?.priceMode ?? objectData?.part?.priceMode
+ const cost = objectData?.overrideCost
+ ? objectData?.cost
+ : objectData?.part?.cost
+ const margin = objectData?.margin ?? objectData?.part?.margin
if (
- objectData?.priceMode == 'margin' &&
- objectData?.margin !== undefined &&
- objectData?.margin !== null
+ priceMode == 'margin' &&
+ margin !== undefined &&
+ margin !== null &&
+ cost != null
) {
return (
- (objectData?.cost * (1 + objectData?.margin / 100)).toFixed(2) ||
- undefined
+ (cost * (1 + margin / 100)).toFixed(2) || undefined
)
- } else {
- return objectData?.price || undefined
}
+ return objectData?.price
}
},
{
@@ -204,22 +236,32 @@ export const PartSku = {
prefix: '£',
min: 0,
step: 0.01,
+ disabled: (objectData) => !objectData?.overridePrice,
value: (objectData) => {
- if (objectData?.priceTaxRate?.rateType == 'percentage') {
- return (
- (
- objectData?.price *
- (1 + objectData?.priceTaxRate?.rate / 100)
- ).toFixed(2) || undefined
- )
- } else if (objectData?.priceTaxRate?.rateType == 'amount') {
- return (
- (objectData?.price + objectData?.priceTaxRate?.rate).toFixed(2) ||
- undefined
- )
+ if (!objectData?.overridePrice) return undefined
+ let price
+ const priceMode = objectData?.priceMode ?? objectData?.part?.priceMode
+ const cost = objectData?.overrideCost
+ ? objectData?.cost
+ : objectData?.part?.cost
+ const margin = objectData?.margin ?? objectData?.part?.margin
+ if (
+ priceMode == 'margin' &&
+ margin != null &&
+ cost != null
+ ) {
+ price = cost * (1 + margin / 100)
} else {
- return objectData?.price
+ price = objectData?.price
}
+ if (price == null) return undefined
+ const taxRate = objectData?.priceTaxRate ?? objectData?.part?.priceTaxRate
+ if (taxRate?.rateType == 'percentage') {
+ return (price * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
+ } else if (taxRate?.rateType == 'amount') {
+ return (price + taxRate?.rate).toFixed(2) || undefined
+ }
+ return price
}
},
{
@@ -227,25 +269,14 @@ export const PartSku = {
label: 'Margin',
required: false,
type: 'number',
- disabled: (objectData) => {
- return objectData?.priceMode == 'amount'
- },
+ disabled: (objectData) =>
+ !objectData?.overridePrice || objectData?.priceMode == 'amount',
suffix: '%',
min: 0,
max: 100,
- step: 0.01
- },
- {
- name: 'amount',
- label: 'Amount',
- disabled: (objectData) => {
- return objectData?.priceMode == 'margin'
- },
- type: 'number',
- required: false,
- prefix: '£',
- min: 0,
- step: 0.1
+ step: 0.01,
+ value: (objectData) =>
+ objectData?.overridePrice ? objectData?.margin : undefined
},
{
name: 'priceTaxRate',
@@ -253,15 +284,10 @@ export const PartSku = {
required: false,
type: 'object',
objectType: 'taxRate',
- showHyperlink: true
- },
- {
- name: 'vendor',
- label: 'Vendor',
- required: false,
- type: 'object',
- objectType: 'vendor',
- showHyperlink: true
+ showHyperlink: true,
+ disabled: (objectData) => !objectData?.overridePrice,
+ value: (objectData) =>
+ objectData?.overridePrice ? objectData?.priceTaxRate : undefined
}
]
}
diff --git a/src/database/models/Product.js b/src/database/models/Product.js
index c3fca79..5540d0c 100644
--- a/src/database/models/Product.js
+++ b/src/database/models/Product.js
@@ -78,6 +78,8 @@ export const Product = {
'name',
'tags',
'vendor',
+ 'cost',
+ 'price',
'createdAt',
'updatedAt'
],
@@ -129,6 +131,127 @@ export const Product = {
label: 'Tags',
required: false,
type: 'tags'
+ },
+ {
+ name: 'cost',
+ label: 'Cost',
+ required: false,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01
+ },
+ {
+ name: 'costWithTax',
+ label: 'Cost w/ Tax',
+ required: false,
+ readOnly: true,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01,
+ value: (objectData) => {
+ const cost = objectData?.cost
+ if (!cost) return undefined
+ if (objectData?.costTaxRate?.rateType == 'percentage') {
+ return (
+ (cost * (1 + objectData?.costTaxRate?.rate / 100)).toFixed(2) ||
+ undefined
+ )
+ } else if (objectData?.costTaxRate?.rateType == 'amount') {
+ return (cost + objectData?.costTaxRate?.rate).toFixed(2) || undefined
+ }
+ return cost
+ }
+ },
+ {
+ name: 'costTaxRate',
+ label: 'Cost Tax Rate',
+ required: false,
+ type: 'object',
+ objectType: 'taxRate',
+ showHyperlink: true
+ },
+ {
+ name: 'priceMode',
+ label: 'Price Mode',
+ required: false,
+ type: 'priceMode'
+ },
+ {
+ name: 'price',
+ label: 'Price',
+ required: false,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.1,
+ readOnly: (objectData) => objectData?.priceMode == 'margin',
+ value: (objectData) => {
+ if (
+ objectData?.priceMode == 'margin' &&
+ objectData?.margin !== undefined &&
+ objectData?.margin !== null &&
+ objectData?.cost != null
+ ) {
+ return (
+ (objectData.cost * (1 + objectData.margin / 100)).toFixed(2) ||
+ undefined
+ )
+ }
+ return objectData?.price
+ }
+ },
+ {
+ name: 'margin',
+ label: 'Margin',
+ required: false,
+ type: 'number',
+ disabled: (objectData) => objectData?.priceMode == 'amount',
+ suffix: '%',
+ min: 0,
+ max: 100,
+ step: 0.01
+ },
+ {
+ name: 'priceWithTax',
+ label: 'Price w/ Tax',
+ required: false,
+ readOnly: true,
+ type: 'number',
+ prefix: '£',
+ min: 0,
+ step: 0.01,
+ value: (objectData) => {
+ let price
+ if (
+ objectData?.priceMode == 'margin' &&
+ objectData?.margin != null &&
+ objectData?.cost != null
+ ) {
+ price = objectData.cost * (1 + objectData.margin / 100)
+ } else {
+ price = objectData?.price
+ }
+ if (!price) return undefined
+ if (objectData?.priceTaxRate?.rateType == 'percentage') {
+ return (
+ (price * (1 + objectData?.priceTaxRate?.rate / 100)).toFixed(2) ||
+ undefined
+ )
+ } else if (objectData?.priceTaxRate?.rateType == 'amount') {
+ return (price + objectData?.priceTaxRate?.rate).toFixed(2) || undefined
+ }
+ return price
+ }
+ },
+ {
+ name: 'priceTaxRate',
+ label: 'Price Tax Rate',
+ required: false,
+ type: 'object',
+ objectType: 'taxRate',
+ showHyperlink: true
}
]
}
diff --git a/src/database/models/ProductSku.js b/src/database/models/ProductSku.js
index 717084c..be0f97a 100644
--- a/src/database/models/ProductSku.js
+++ b/src/database/models/ProductSku.js
@@ -69,9 +69,9 @@ export const ProductSku = {
}
],
url: (id) => `/dashboard/management/productskus/info?productSkuId=${id}`,
- columns: ['_reference', 'sku', 'product', 'name', 'cost', 'price', 'createdAt', 'updatedAt'],
- filters: ['_id', 'sku', 'product', 'product._id', 'name', 'cost', 'price'],
- sorters: ['sku', 'product', 'name', 'cost', 'price', 'createdAt', 'updatedAt'],
+ columns: ['_reference', 'barcode', 'product', 'name', 'overrideCost', 'cost', 'overridePrice', 'price', 'createdAt', 'updatedAt'],
+ filters: ['_id', 'barcode', 'product', 'product._id', 'name', 'cost', 'price'],
+ sorters: ['barcode', 'product', 'name', 'cost', 'price', 'createdAt', 'updatedAt'],
properties: [
{
name: '_id',
@@ -108,9 +108,9 @@ export const ProductSku = {
showHyperlink: true
},
{
- name: 'sku',
- label: 'SKU',
- required: true,
+ name: 'barcode',
+ label: 'Barcode',
+ required: false,
type: 'text'
},
{
@@ -125,6 +125,20 @@ export const ProductSku = {
required: false,
type: 'priceMode'
},
+ {
+ name: 'overrideCost',
+ label: 'Override Cost',
+ required: false,
+ type: 'bool',
+ value: (objectData) => objectData?.overrideCost ?? false
+ },
+ {
+ name: 'overridePrice',
+ label: 'Override Price',
+ required: false,
+ type: 'bool',
+ value: (objectData) => objectData?.overridePrice ?? false
+ },
{
name: 'cost',
label: 'Cost',
@@ -132,7 +146,10 @@ export const ProductSku = {
type: 'number',
prefix: '£',
min: 0,
- step: 0.01
+ step: 0.01,
+ disabled: (objectData) => !objectData?.overrideCost,
+ value: (objectData) =>
+ objectData?.overrideCost ? objectData?.cost : undefined
},
{
name: 'costWithTax',
@@ -143,22 +160,20 @@ export const ProductSku = {
prefix: '£',
min: 0,
step: 0.01,
+ disabled: (objectData) => !objectData?.overrideCost,
value: (objectData) => {
- if (objectData?.costTaxRate?.rateType == 'percentage') {
+ if (!objectData?.overrideCost) return undefined
+ const cost = objectData?.cost
+ const taxRate = objectData?.costTaxRate
+ if (!cost) return undefined
+ if (taxRate?.rateType == 'percentage') {
return (
- (
- objectData?.cost *
- (1 + objectData?.costTaxRate?.rate / 100)
- ).toFixed(2) || undefined
+ (cost * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
)
- } else if (objectData?.costTaxRate?.rateType == 'amount') {
- return (
- (objectData?.cost + objectData?.costTaxRate?.rate).toFixed(2) ||
- undefined
- )
- } else {
- return objectData?.cost || undefined
+ } else if (taxRate?.rateType == 'amount') {
+ return (cost + taxRate?.rate).toFixed(2) || undefined
}
+ return cost
}
},
{
@@ -167,7 +182,10 @@ export const ProductSku = {
required: false,
type: 'object',
objectType: 'taxRate',
- showHyperlink: true
+ showHyperlink: true,
+ disabled: (objectData) => !objectData?.overrideCost,
+ value: (objectData) =>
+ objectData?.overrideCost ? objectData?.costTaxRate : undefined
},
{
name: 'price',
@@ -177,22 +195,27 @@ export const ProductSku = {
prefix: '£',
min: 0,
step: 0.1,
- readOnly: (objectData) => {
- return objectData?.priceMode == 'margin'
- },
+ disabled: (objectData) => !objectData?.overridePrice,
+ readOnly: (objectData) =>
+ objectData?.overridePrice && objectData?.priceMode == 'margin',
value: (objectData) => {
+ if (!objectData?.overridePrice) return undefined
+ const priceMode = objectData?.priceMode ?? objectData?.product?.priceMode
+ const cost = objectData?.overrideCost
+ ? objectData?.cost
+ : objectData?.product?.cost
+ const margin = objectData?.margin ?? objectData?.product?.margin
if (
- objectData?.priceMode == 'margin' &&
- objectData?.margin !== undefined &&
- objectData?.margin !== null
+ priceMode == 'margin' &&
+ margin !== undefined &&
+ margin !== null &&
+ cost != null
) {
return (
- (objectData?.cost * (1 + objectData?.margin / 100)).toFixed(2) ||
- undefined
+ (cost * (1 + margin / 100)).toFixed(2) || undefined
)
- } else {
- return objectData?.price || undefined
}
+ return objectData?.price
}
},
{
@@ -204,22 +227,34 @@ export const ProductSku = {
prefix: '£',
min: 0,
step: 0.01,
+ disabled: (objectData) => !objectData?.overridePrice,
value: (objectData) => {
- if (objectData?.priceTaxRate?.rateType == 'percentage') {
- return (
- (
- objectData?.price *
- (1 + objectData?.priceTaxRate?.rate / 100)
- ).toFixed(2) || undefined
- )
- } else if (objectData?.priceTaxRate?.rateType == 'amount') {
- return (
- (objectData?.price + objectData?.priceTaxRate?.rate).toFixed(2) ||
- undefined
- )
+ if (!objectData?.overridePrice) return undefined
+ let price
+ const priceMode = objectData?.priceMode ?? objectData?.product?.priceMode
+ const cost = objectData?.overrideCost
+ ? objectData?.cost
+ : objectData?.product?.cost
+ const margin = objectData?.margin ?? objectData?.product?.margin
+ if (
+ priceMode == 'margin' &&
+ margin != null &&
+ cost != null
+ ) {
+ price = cost * (1 + margin / 100)
} else {
- return objectData?.price
+ price = objectData?.price
}
+ if (price == null) return undefined
+ const taxRate = objectData?.priceTaxRate ?? objectData?.product?.priceTaxRate
+ if (taxRate?.rateType == 'percentage') {
+ return (
+ (price * (1 + taxRate?.rate / 100)).toFixed(2) || undefined
+ )
+ } else if (taxRate?.rateType == 'amount') {
+ return (price + taxRate?.rate).toFixed(2) || undefined
+ }
+ return price
}
},
{
@@ -227,25 +262,14 @@ export const ProductSku = {
label: 'Margin',
required: false,
type: 'number',
- disabled: (objectData) => {
- return objectData?.priceMode == 'amount'
- },
+ disabled: (objectData) =>
+ !objectData?.overridePrice || objectData?.priceMode == 'amount',
suffix: '%',
min: 0,
max: 100,
- step: 0.01
- },
- {
- name: 'amount',
- label: 'Amount',
- disabled: (objectData) => {
- return objectData?.priceMode == 'margin'
- },
- type: 'number',
- required: false,
- prefix: '£',
- min: 0,
- step: 0.1
+ step: 0.01,
+ value: (objectData) =>
+ objectData?.overridePrice ? objectData?.margin : undefined
},
{
name: 'priceTaxRate',
@@ -253,15 +277,10 @@ export const ProductSku = {
required: false,
type: 'object',
objectType: 'taxRate',
- showHyperlink: true
- },
- {
- name: 'vendor',
- label: 'Vendor',
- required: false,
- type: 'object',
- objectType: 'vendor',
- showHyperlink: true
+ showHyperlink: true,
+ disabled: (objectData) => !objectData?.overridePrice,
+ value: (objectData) =>
+ objectData?.overridePrice ? objectData?.priceTaxRate : undefined
},
{
name: 'parts',
diff --git a/src/routes/ManagementRoutes.jsx b/src/routes/ManagementRoutes.jsx
index 6e110a3..8c4aab4 100644
--- a/src/routes/ManagementRoutes.jsx
+++ b/src/routes/ManagementRoutes.jsx
@@ -3,6 +3,8 @@ import { Route } from 'react-router-dom'
const Filaments = lazy(() => import('../components/Dashboard/Management/Filaments'))
const FilamentInfo = lazy(() => import('../components/Dashboard/Management/Filaments/FilamentInfo.jsx'))
+const FilamentSkus = lazy(() => import('../components/Dashboard/Management/FilamentSkus.jsx'))
+const FilamentSkuInfo = lazy(() => import('../components/Dashboard/Management/FilamentSkus/FilamentSkuInfo.jsx'))
const Parts = lazy(() => import('../components/Dashboard/Management/Parts.jsx'))
const PartInfo = lazy(() => import('../components/Dashboard/Management/Parts/PartInfo.jsx'))
const PartSkus = lazy(() => import('../components/Dashboard/Management/PartSkus.jsx'))
@@ -52,6 +54,12 @@ const ManagementRoutes = [
path='management/filaments/info'
element={}
/>,
+ } />,
+ }
+ />,
} />,