{filesToRender.map((file, index) => {
@@ -205,6 +231,8 @@ FileList.propTypes = {
showDownload: PropTypes.bool,
defaultPreviewOpen: PropTypes.bool,
card: PropTypes.bool,
+ gallery: PropTypes.bool,
+ uploadSlot: PropTypes.node,
minimal: PropTypes.bool,
maxWidth: PropTypes.string
}
diff --git a/src/components/Dashboard/common/FilePreview.jsx b/src/components/Dashboard/common/FilePreview.jsx
index bc33a53f..73f5e3b6 100644
--- a/src/components/Dashboard/common/FilePreview.jsx
+++ b/src/components/Dashboard/common/FilePreview.jsx
@@ -1,12 +1,22 @@
import PropTypes from 'prop-types'
import { ApiServerContext } from '../context/ApiServerContext'
-import { useCallback, useContext, useEffect, useState, memo } from 'react'
+import {
+ useCallback,
+ useContext,
+ useEffect,
+ useState,
+ memo,
+ useRef
+} from 'react'
import LoadingPlaceholder from './LoadingPlaceholder'
import GCodePreview from './GCodePreview'
import ThreeDPreview from './ThreeDPreview'
import { AuthContext } from '../context/AuthContext'
const FilePreview = ({ file, style = {} }) => {
+ useEffect(() => {
+ console.log('FILEPREVIEWFILE', file)
+ }, [file])
const { token } = useContext(AuthContext)
const { fetchFileContent } = useContext(ApiServerContext)
@@ -14,6 +24,8 @@ const FilePreview = ({ file, style = {} }) => {
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
+ const currentId = useRef(null)
+
const fetchPreview = useCallback(async () => {
if (error != null) {
return
@@ -31,7 +43,8 @@ const FilePreview = ({ file, style = {} }) => {
}, [file, fetchFileContent, error])
useEffect(() => {
- if (file?.type && token != null) {
+ if (file?.type && token != null && file._id !== currentId.current) {
+ currentId.current = file._id
fetchPreview()
}
}, [file._id, file?.type, fetchPreview, token])
diff --git a/src/components/Dashboard/common/FileUpload.jsx b/src/components/Dashboard/common/FileUpload.jsx
index 6fb07185..94dc1c4b 100644
--- a/src/components/Dashboard/common/FileUpload.jsx
+++ b/src/components/Dashboard/common/FileUpload.jsx
@@ -28,6 +28,62 @@ const getFileIdentity = (value, multiple) => {
return value?._id || value || ''
}
+const getMasterFilterAccept = (masterFilter) =>
+ Array.isArray(masterFilter) && masterFilter.length > 0
+ ? masterFilter.join(',')
+ : undefined
+
+const FileUploadDropzone = ({ onUploaded, multiple = true, masterFilter = [] }) => {
+ const { uploadFile } = useContext(ApiServerContext)
+ const [uploading, setUploading] = useState(false)
+ const [uploadProgress, setUploadProgress] = useState(0)
+
+ const handleFileUpload = async (file) => {
+ try {
+ setUploading(true)
+ const uploadedFile = await uploadFile(file, {}, (progress) => {
+ setUploadProgress(progress)
+ })
+ if (uploadedFile) {
+ onUploaded?.(uploadedFile)
+ }
+ } catch (error) {
+ console.error('File upload failed:', error)
+ } finally {
+ setUploading(false)
+ setUploadProgress(0)
+ }
+ return false
+ }
+
+ return (
+
+ {uploading ? (
+ uploadProgress > 0 && uploadProgress < 100 ? (
+ {uploadProgress}%
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+ )
+}
+
+FileUploadDropzone.propTypes = {
+ onUploaded: PropTypes.func,
+ multiple: PropTypes.bool,
+ masterFilter: PropTypes.array
+}
+
const FileUpload = ({
value,
onChange,
@@ -36,6 +92,7 @@ const FileUpload = ({
defaultPreviewOpen = false,
showPreview = true,
showInfo,
+ gallery = false,
masterFilter = []
}) => {
const { uploadFile } = useContext(ApiServerContext)
@@ -135,6 +192,37 @@ const FileUpload = ({
)
}
+ if (gallery) {
+ return (
+
{
+ if (multiple) {
+ const existing = Array.isArray(currentFiles) ? currentFiles : []
+ updateCurrentFiles([...existing, uploadedFile])
+ } else {
+ updateCurrentFiles(uploadedFile)
+ }
+ }}
+ />
+ }
+ onChange={(updatedFiles) => {
+ updateCurrentFiles(updatedFiles)
+ }}
+ />
+ )
+ }
+
return (
{hasNoItems && uploading == false ? (
@@ -220,6 +308,7 @@ FileUpload.propTypes = {
showInfo: PropTypes.bool,
defaultPreviewOpen: PropTypes.bool,
minimal: PropTypes.bool,
+ gallery: PropTypes.bool,
masterFilter: PropTypes.array
}
diff --git a/src/components/Dashboard/common/ObjectForm.jsx b/src/components/Dashboard/common/ObjectForm.jsx
index 45f0f715..3e1e67cb 100644
--- a/src/components/Dashboard/common/ObjectForm.jsx
+++ b/src/components/Dashboard/common/ObjectForm.jsx
@@ -783,19 +783,16 @@ const ObjectForm = forwardRef(
isEditingRef.current = false
onStateChangeRef.current({ isEditing: isEditingRef.current })
- serverObjectData.current = updatedObject
- const computedEntries = calculateComputedValues(
- updatedObject,
- model,
- {
- skipObjectChildrenValue: true
- }
- )
+ const mergedObject = mergeFormData(updatedObject, currentFormData)
+ serverObjectData.current = mergedObject
+ const computedEntries = calculateComputedValues(mergedObject, model, {
+ skipObjectChildrenValue: true
+ })
const nextObjectData = applyComputedEntries(
- updatedObject,
+ mergedObject,
computedEntries
)
- copyObjectChildren(nextObjectData, updatedObject, model)
+ copyObjectChildren(nextObjectData, mergedObject, model)
setObjectData({
...nextObjectData,
_isEditing: false
diff --git a/src/components/Dashboard/common/ObjectProperty.jsx b/src/components/Dashboard/common/ObjectProperty.jsx
index b7a86675..97fa719f 100644
--- a/src/components/Dashboard/common/ObjectProperty.jsx
+++ b/src/components/Dashboard/common/ObjectProperty.jsx
@@ -6,7 +6,8 @@ import {
InputNumber,
Form,
Select,
- Switch
+ Switch,
+ Tree
} from 'antd'
import IdDisplay from './IdDisplay'
import TimeDisplay from './TimeDisplay'
@@ -41,6 +42,7 @@ import ObjectTypeSelect from './ObjectTypeSelect'
import ObjectTypeDisplay from './ObjectTypeDisplay'
import CodeBlockEditor from './CodeBlockEditor'
import CustomSelect from './CustomSelect'
+import CustomTreeSelect, { findTreeLabel } from './CustomTreeSelect'
import StateDisplay from './StateDisplay'
import AlertsDisplay from './AlertsDisplay'
import FileUpload from './FileUpload'
@@ -136,6 +138,7 @@ const ObjectProperty = ({
hiddenPropertyWidth = '100px',
inherit = true,
scrollHeight,
+ gallery = false,
...rest
}) => {
if (typeof value === 'function') {
@@ -270,6 +273,28 @@ const ObjectProperty = ({
)
}
}
+ case 'treeSelect': {
+ if (Array.isArray(value) && value.length) {
+ return (
+
+ )
+ }
+ const treeLabel = findTreeLabel(options, value)
+ if (treeLabel || (value != null && value !== '')) {
+ return {treeLabel || String(value)}
+ }
+ return (
+
+ n/a
+
+ )
+ }
case 'priceMode':
switch (value) {
case 'margin':
@@ -655,6 +680,8 @@ const ObjectProperty = ({
showPreview={showPreview}
showInfo={showHyperlink}
showDownload={showDownload}
+ gallery={gallery}
+ editing={false}
masterFilter={masterFilter}
/>
)
@@ -670,6 +697,8 @@ const ObjectProperty = ({
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
+ gallery={gallery}
+ editing={false}
masterFilter={masterFilter}
/>
)
@@ -838,6 +867,14 @@ const ObjectProperty = ({
{...inputProps}
/>
)
+ case 'treeSelect':
+ return (
+
+ )
case 'state':
if (options?.length && !readOnly) {
return (
@@ -1002,6 +1039,7 @@ const ObjectProperty = ({
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
+ gallery={gallery}
masterFilter={masterFilter}
{...inputProps}
/>
@@ -1014,6 +1052,7 @@ const ObjectProperty = ({
defaultPreviewOpen={previewOpen}
showPreview={showPreview}
showInfo={showHyperlink}
+ gallery={gallery}
masterFilter={masterFilter}
{...inputProps}
/>
@@ -1073,7 +1112,7 @@ const ObjectProperty = ({
}
ObjectProperty.propTypes = {
- type: PropTypes.string.isRequired,
+ type: PropTypes.oneOfType([PropTypes.string, PropTypes.func]).isRequired,
modelType: PropTypes.string,
value: PropTypes.oneOfType([PropTypes.any, PropTypes.func]),
isEditing: PropTypes.bool,
@@ -1101,7 +1140,7 @@ ObjectProperty.propTypes = {
showPreview: PropTypes.bool,
useFormItem: PropTypes.bool,
showHyperlink: PropTypes.bool,
- options: PropTypes.array,
+ options: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
showSince: PropTypes.bool,
inTable: PropTypes.bool,
loading: PropTypes.bool,
@@ -1112,7 +1151,8 @@ ObjectProperty.propTypes = {
style: PropTypes.object,
hiddenPropertyWidth: PropTypes.string,
inherit: PropTypes.bool,
- scrollHeight: PropTypes.string
+ scrollHeight: PropTypes.string,
+ gallery: PropTypes.bool
}
export default ObjectProperty
diff --git a/src/components/Dashboard/common/StateDisplay.jsx b/src/components/Dashboard/common/StateDisplay.jsx
index 881e25a9..5b400b99 100644
--- a/src/components/Dashboard/common/StateDisplay.jsx
+++ b/src/components/Dashboard/common/StateDisplay.jsx
@@ -20,10 +20,19 @@ const StateDisplay = ({
'queued',
'printing',
'used',
- 'deploying'
+ 'deploying',
+ 'syncing',
+ 'publishing',
+ 'unpublishing'
]
const orangeProgressTypes = ['used', 'deploying', 'queued']
- const activeProgressTypes = ['printing', 'deploying']
+ const activeProgressTypes = [
+ 'printing',
+ 'deploying',
+ 'publishing',
+ 'unpublishing',
+ 'syncing'
+ ]
const currentState = state || {
type: 'unknown',
diff --git a/src/components/Dashboard/common/StateTag.jsx b/src/components/Dashboard/common/StateTag.jsx
index 156b3b94..b7635737 100644
--- a/src/components/Dashboard/common/StateTag.jsx
+++ b/src/components/Dashboard/common/StateTag.jsx
@@ -100,6 +100,14 @@ const StateTag = ({ state, showBadge = true, showTag = true, style = {} }) => {
status = 'processing'
text = 'Syncing'
break
+ case 'publishing':
+ status = 'processing'
+ text = 'Publishing'
+ break
+ case 'unpublishing':
+ status = 'processing'
+ text = 'Unpublishing'
+ break
case 'disconnected':
status = 'default'
text = 'Disconnected'
@@ -112,6 +120,10 @@ const StateTag = ({ state, showBadge = true, showTag = true, style = {} }) => {
status = 'warning'
text = 'Queued'
break
+ case 'pending':
+ status = 'default'
+ text = 'Pending'
+ break
case 'used':
status = 'warning'
text = 'Used'
diff --git a/src/components/Dashboard/utils/Utils.js b/src/components/Dashboard/utils/Utils.js
index 3d1ba514..77525603 100644
--- a/src/components/Dashboard/utils/Utils.js
+++ b/src/components/Dashboard/utils/Utils.js
@@ -1,4 +1,8 @@
+import get from 'lodash/get'
import mergeWith from 'lodash/mergeWith'
+import set from 'lodash/set'
+
+const NESTED_OBJECT_KEYS = ['_id', '_reference', 'name', 'state']
export function capitalizeFirstLetter(string) {
try {
@@ -71,3 +75,72 @@ export function mergeFormData(...sources) {
mergeFormCustomizer
)
}
+
+const pickNestedObjectFields = (value) => {
+ if (value == null || typeof value !== 'object') {
+ return value
+ }
+ if (Array.isArray(value)) {
+ return value.map(pickNestedObjectFields)
+ }
+
+ const picked = {}
+ NESTED_OBJECT_KEYS.forEach((key) => {
+ if (value[key] !== undefined) {
+ picked[key] = value[key]
+ }
+ })
+ return picked
+}
+
+const stripProperties = (data, properties) => {
+ if (data == null || typeof data !== 'object' || !Array.isArray(properties)) {
+ return data
+ }
+
+ const next = Array.isArray(data) ? [...data] : { ...data }
+
+ properties.forEach((property) => {
+ if (!property?.name) return
+
+ const current = get(next, property.name)
+
+ if (property.type === 'object') {
+ if (current === undefined) return
+ set(next, property.name, pickNestedObjectFields(current))
+ return
+ }
+
+ if (property.type === 'objectList') {
+ if (!Array.isArray(current)) return
+ set(next, property.name, current.map(pickNestedObjectFields))
+ return
+ }
+
+ if (!Array.isArray(property.properties) || property.properties.length === 0) {
+ return
+ }
+
+ if (property.type === 'objectChildren' || Array.isArray(current)) {
+ if (!Array.isArray(current)) return
+ set(
+ next,
+ property.name,
+ current.map((child) => stripProperties(child, property.properties))
+ )
+ return
+ }
+
+ if (current != null && typeof current === 'object') {
+ set(next, property.name, stripProperties(current, property.properties))
+ }
+ })
+
+ return next
+}
+
+// Before create/update, keep nested object / objectList refs as stubs so we
+// do not post populated fields (e.g. marketplace category trees).
+export function stripNestedObjectProperties(data, modelDefinition) {
+ return stripProperties(data, modelDefinition?.properties)
+}
diff --git a/src/components/Icons/FulfillmentPolicyIcon.jsx b/src/components/Icons/FulfillmentPolicyIcon.jsx
new file mode 100644
index 00000000..d6445a48
--- /dev/null
+++ b/src/components/Icons/FulfillmentPolicyIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/fulfillmentpolicyicon.svg?react'
+
+const FulfillmentPolicyIcon = (props) =>
+
+export default FulfillmentPolicyIcon
diff --git a/src/components/Icons/PaymentPolicyIcon.jsx b/src/components/Icons/PaymentPolicyIcon.jsx
new file mode 100644
index 00000000..d93c0b91
--- /dev/null
+++ b/src/components/Icons/PaymentPolicyIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/paymentpolicyicon.svg?react'
+
+const PaymentPolicyIcon = (props) =>
+
+export default PaymentPolicyIcon
diff --git a/src/components/Icons/ReturnPolicyIcon.jsx b/src/components/Icons/ReturnPolicyIcon.jsx
new file mode 100644
index 00000000..86d45d84
--- /dev/null
+++ b/src/components/Icons/ReturnPolicyIcon.jsx
@@ -0,0 +1,6 @@
+import Icon from '@ant-design/icons'
+import CustomIconSvg from '../../../assets/icons/returnpolicyicon.svg?react'
+
+const ReturnPolicyIcon = (props) =>
+
+export default ReturnPolicyIcon
diff --git a/src/components/Icons/sidebarIconMap.jsx b/src/components/Icons/sidebarIconMap.jsx
index c31016f0..d5300e0f 100644
--- a/src/components/Icons/sidebarIconMap.jsx
+++ b/src/components/Icons/sidebarIconMap.jsx
@@ -21,6 +21,7 @@ import ClientIcon from './ClientIcon'
import SalesOrderIcon from './SalesOrderIcon'
import MarketplaceIcon from './MarketplaceIcon'
import ListingIcon from './ListingIcon'
+import ListingVarientIcon from './ListingVarientIcon'
import FinanceIcon from './FinanceIcon'
import InvoiceIcon from './InvoiceIcon'
import PaymentIcon from './PaymentIcon'
@@ -51,6 +52,9 @@ import CourierIcon from './CourierIcon'
import CourierServiceIcon from './CourierServiceIcon'
import TaxRateIcon from './TaxRateIcon'
import TaxRecordIcon from './TaxRecordIcon'
+import FulfillmentPolicyIcon from './FulfillmentPolicyIcon'
+import ReturnPolicyIcon from './ReturnPolicyIcon'
+import PaymentPolicyIcon from './PaymentPolicyIcon'
import AppPasswordIcon from './AppPasswordIcon'
import InfoCircleIcon from './InfoCircleIcon'
@@ -80,6 +84,10 @@ const sidebarIconMap = {
salesOrder: ,
marketplace: ,
listing: ,
+ listingVarient: ,
+ fulfillmentPolicy: ,
+ returnPolicy: ,
+ paymentPolicy: ,
finance: ,
invoice: ,
payment: ,
diff --git a/src/database/ObjectModels.js b/src/database/ObjectModels.js
index 0cdf4d50..dab7aba6 100644
--- a/src/database/ObjectModels.js
+++ b/src/database/ObjectModels.js
@@ -49,6 +49,9 @@ import { SalesOrder } from './models/SalesOrder.js'
import { Marketplace } from './models/Marketplace.js'
import { Listing } from './models/Listing.js'
import { ListingVarient } from './models/ListingVarient.js'
+import { FulfillmentPolicy } from './models/FulfillmentPolicy.js'
+import { ReturnPolicy } from './models/ReturnPolicy.js'
+import { PaymentPolicy } from './models/PaymentPolicy.js'
import QuestionCircleIcon from '../components/Icons/QuestionCircleIcon'
export const objectModels = [
@@ -102,7 +105,10 @@ export const objectModels = [
SalesOrder,
Marketplace,
Listing,
- ListingVarient
+ ListingVarient,
+ FulfillmentPolicy,
+ ReturnPolicy,
+ PaymentPolicy
]
// Re-export individual models for direct access
@@ -157,7 +163,10 @@ export {
SalesOrder,
Marketplace,
Listing,
- ListingVarient
+ ListingVarient,
+ FulfillmentPolicy,
+ ReturnPolicy,
+ PaymentPolicy
}
export function getModelByName(name, ignoreCase = false) {
@@ -179,6 +188,27 @@ export function getModelByName(name, ignoreCase = false) {
)
}
+export function getModelByPluralName(pluralName, ignoreCase = false) {
+ function formatName(formattedName) {
+ if (ignoreCase == true) {
+ formattedName = formattedName.toUpperCase().replace(' ', '')
+ }
+ return formattedName
+ }
+ return (
+ objectModels.find(
+ (meta) => formatName(meta.labelPlural) === formatName(pluralName)
+ ) || {
+ name: 'unknown',
+ label: 'Unknown',
+ prefix: 'UNK',
+ icon: QuestionCircleIcon,
+ url: () => '#',
+ properties: {}
+ }
+ )
+}
+
export function getModelProperty(name, property) {
const model = getModelByName(name)
diff --git a/src/database/Sidebars.js b/src/database/Sidebars.js
index b16cc600..b4943c06 100644
--- a/src/database/Sidebars.js
+++ b/src/database/Sidebars.js
@@ -24,10 +24,7 @@ const SIDEBARS = {
key: 'sales',
label: 'Sales',
iconKey: 'sales',
- items: salesSidebarItems,
- routeAliases: {
- '/dashboard/sales/listingvarients': 'listings'
- }
+ items: salesSidebarItems
},
finance: {
key: 'finance',
diff --git a/src/database/models/File.js b/src/database/models/File.js
index cb2a08c8..4eff87c5 100644
--- a/src/database/models/File.js
+++ b/src/database/models/File.js
@@ -30,6 +30,15 @@ export const File = {
label: 'List',
icon: ListIcon
},
+ {
+ name: 'new',
+ type: 'page',
+ pageName: 'info',
+ label: 'New File',
+ visible: () => {
+ return false
+ }
+ },
{
name: 'info',
type: 'page',
@@ -117,14 +126,7 @@ export const File = {
'updatedAt',
'_reference'
],
- sorters: [
- 'name',
- 'type',
- 'size',
- 'createdAt',
- 'temp',
- 'updatedAt'
- ],
+ sorters: ['name', 'type', 'size', 'createdAt', 'temp', 'updatedAt'],
group: ['type'],
properties: [
{
diff --git a/src/database/models/FulfillmentPolicy.js b/src/database/models/FulfillmentPolicy.js
new file mode 100644
index 00000000..2e00ac32
--- /dev/null
+++ b/src/database/models/FulfillmentPolicy.js
@@ -0,0 +1,231 @@
+import { createElement, lazy } from 'react'
+
+const FulfillmentPolicyInfo = lazy(
+ () =>
+ import('../../components/Dashboard/Sales/FulfillmentPolicies/FulfillmentPolicyInfo')
+)
+const NewFulfillmentPolicy = lazy(
+ () =>
+ import('../../components/Dashboard/Sales/FulfillmentPolicies/NewFulfillmentPolicy')
+)
+const DeleteObject = lazy(
+ () => import('../../components/Dashboard/common/DeleteObject')
+)
+import FulfillmentPolicyIcon from '../../components/Icons/FulfillmentPolicyIcon'
+import PlusIcon from '../../components/Icons/PlusIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
+import EditIcon from '../../components/Icons/EditIcon'
+import CheckIcon from '../../components/Icons/CheckIcon'
+import XMarkIcon from '../../components/Icons/XMarkIcon'
+import BinIcon from '../../components/Icons/BinIcon'
+import ListIcon from '../../components/Icons/ListIcon'
+import { marketplaceSyncMappingsProperty } from './marketplaceMappings'
+
+export const FulfillmentPolicy = {
+ name: 'fulfillmentPolicy',
+ label: 'Fulfillment Policy',
+ labelPlural: 'Fulfillment Policies',
+ url: '/dashboard/sales/fulfillmentpolicies',
+ prefix: 'FPL',
+ endpoint: 'fulfillmentpolicies',
+ icon: FulfillmentPolicyIcon,
+ actions: [
+ {
+ name: 'new',
+ type: 'modal',
+ pageName: 'list',
+ modalWidth: 700,
+ label: 'New Fulfillment Policy',
+ icon: PlusIcon,
+ content: (objectData, { onOk } = {}) => {
+ return createElement(NewFulfillmentPolicy, {
+ defaultValues: objectData,
+ onOk,
+ reset: true
+ })
+ }
+ },
+ {
+ name: 'list',
+ type: 'page',
+ pageName: 'list',
+ label: 'List',
+ icon: ListIcon
+ },
+ {
+ name: 'info',
+ type: 'page',
+ pageName: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon
+ },
+ {
+ name: 'edit',
+ type: 'page',
+ pageName: 'info',
+ label: 'Edit',
+ row: true,
+ icon: EditIcon,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ }
+ },
+ {
+ name: 'cancelEdit',
+ label: 'Cancel Edits',
+ type: 'page',
+ pageName: 'info',
+ icon: XMarkIcon,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'finishEdit',
+ label: 'Save Edits',
+ type: 'page',
+ pageName: 'info',
+ icon: CheckIcon,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ { type: 'divider' },
+ {
+ name: 'delete',
+ type: 'modal',
+ modalWidth: 520,
+ modalCentered: true,
+ label: 'Delete',
+ icon: BinIcon,
+ danger: true,
+ content: (objectData, { onOk } = {}) => {
+ return createElement(DeleteObject, { objectData, onOk })
+ }
+ }
+ ],
+ pages: [
+ {
+ name: 'info',
+ content: () => createElement(FulfillmentPolicyInfo)
+ }
+ ],
+ columns: [
+ '_reference',
+ 'name',
+ 'handlingTime',
+ 'courierServices',
+ 'createdAt',
+ 'updatedAt'
+ ],
+ filters: [
+ 'name',
+ 'handlingTime',
+ 'courierServices',
+ 'localPickup',
+ 'createdAt',
+ 'updatedAt',
+ '_reference'
+ ],
+ sorters: ['name', 'handlingTime', 'createdAt', '_id', 'updatedAt'],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ columnFixed: 'left',
+ type: 'id',
+ objectType: 'fulfillmentPolicy',
+ showCopy: true,
+ columnWidth: 140
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: '_reference',
+ label: 'Reference',
+ type: 'reference',
+ columnFixed: 'left',
+ objectType: 'fulfillmentPolicy',
+ showCopy: true,
+ readOnly: true,
+ columnWidth: 180
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ columnFixed: 'left',
+ required: true,
+ type: 'text',
+ columnWidth: 220
+ },
+ {
+ name: 'description',
+ label: 'Description',
+ type: 'text',
+ required: false,
+ span: 2,
+ columnWidth: 280
+ },
+ {
+ name: 'handlingTime',
+ label: 'Handling Time',
+ type: 'number',
+ min: 0,
+ required: false,
+ suffix: 'days',
+ columnWidth: 175
+ },
+ {
+ name: 'courierServices',
+ label: 'Courier Services',
+ type: 'objectList',
+ objectType: 'courierService',
+ showHyperlink: true,
+ required: false,
+ columnWidth: 250
+ },
+ {
+ name: 'localPickup',
+ label: 'Local Pickup',
+ type: 'bool',
+ required: false,
+ columnWidth: 140
+ },
+ {
+ name: 'globalShipping',
+ label: 'Global Shipping',
+ type: 'bool',
+ required: false,
+ columnWidth: 150
+ },
+ {
+ name: 'freightShipping',
+ label: 'Freight Shipping',
+ type: 'bool',
+ required: false,
+ columnWidth: 160
+ },
+ {
+ name: 'pickupDropOff',
+ label: 'Pickup Drop Off',
+ type: 'bool',
+ required: false,
+ columnWidth: 160
+ },
+ marketplaceSyncMappingsProperty('fulfillmentPolicy')
+ ]
+}
diff --git a/src/database/models/Listing.js b/src/database/models/Listing.js
index 0d7fc111..7385165e 100644
--- a/src/database/models/Listing.js
+++ b/src/database/models/Listing.js
@@ -112,7 +112,10 @@ export const Listing = {
visible: (objectData) =>
objectData?.state?.type === 'draft' ||
objectData?.state?.type === 'inactive',
- disabled: (objectData) => objectData?.state?.type === 'syncing',
+ disabled: (objectData) =>
+ ['syncing', 'publishing', 'unpublishing'].includes(
+ objectData?.state?.type
+ ),
content: (objectData, { onOk } = {}) => {
return createElement(PublishListing, { objectData, onOk })
}
@@ -125,7 +128,10 @@ export const Listing = {
icon: XMarkIcon,
danger: true,
visible: (objectData) => objectData?.state?.type === 'active',
- disabled: (objectData) => objectData?.state?.type === 'syncing',
+ disabled: (objectData) =>
+ ['syncing', 'publishing', 'unpublishing'].includes(
+ objectData?.state?.type
+ ),
content: (objectData, { onOk } = {}) => {
return createElement(UnpublishListing, { objectData, onOk })
}
@@ -158,6 +164,9 @@ export const Listing = {
'stockLocation',
'stockQuantity',
'marketplace',
+ 'fulfillmentPolicy',
+ 'paymentPolicy',
+ 'returnPolicy',
'courierServices',
'state',
'condition',
@@ -173,6 +182,9 @@ export const Listing = {
'stockLocation',
'stockQuantity',
'marketplace',
+ 'fulfillmentPolicy',
+ 'paymentPolicy',
+ 'returnPolicy',
'courierServices',
'state',
'state.type',
@@ -252,6 +264,14 @@ export const Listing = {
extra:
'Used as the eBay listing description. If empty, the title is sent instead.'
},
+ {
+ name: 'listingImages',
+ label: 'Listing Images',
+ type: 'fileList',
+ gallery: true,
+ required: false,
+ masterFilter: ['.jpg', '.jpeg', '.png', '.gif', '.webp']
+ },
{
name: 'lastSyncedAt',
label: 'Last Synced',
@@ -314,6 +334,58 @@ export const Listing = {
required: true,
columnWidth: 200
},
+ {
+ name: 'fulfillmentPolicy',
+ label: 'Fulfillment Policy',
+ type: 'object',
+ objectType: 'fulfillmentPolicy',
+ showHyperlink: true,
+ readOnly: false,
+ required: false,
+ extra:
+ 'Defaults from the marketplace. Courier services are used as a fallback when this is empty.',
+ value: (objectData) => {
+ if (objectData?.fulfillmentPolicy == undefined) {
+ return objectData?.marketplace?.defaultFulfillmentPolicy
+ }
+ return objectData?.fulfillmentPolicy
+ },
+ columnWidth: 220
+ },
+ {
+ name: 'paymentPolicy',
+ label: 'Payment Policy',
+ type: 'object',
+ objectType: 'paymentPolicy',
+ showHyperlink: true,
+ readOnly: false,
+ required: false,
+ extra: 'Defaults from the marketplace. Required when publishing to eBay.',
+ value: (objectData) => {
+ if (objectData?.paymentPolicy == undefined) {
+ return objectData?.marketplace?.defaultPaymentPolicy
+ }
+ return objectData?.paymentPolicy
+ },
+ columnWidth: 200
+ },
+ {
+ name: 'returnPolicy',
+ label: 'Return Policy',
+ type: 'object',
+ objectType: 'returnPolicy',
+ showHyperlink: true,
+ readOnly: false,
+ required: false,
+ extra: 'Defaults from the marketplace. Required when publishing to eBay.',
+ value: (objectData) => {
+ if (objectData?.returnPolicy == undefined) {
+ return objectData?.marketplace?.defaultReturnPolicy
+ }
+ return objectData?.returnPolicy
+ },
+ columnWidth: 200
+ },
{
name: 'state',
label: 'State',
@@ -378,6 +450,17 @@ export const Listing = {
required: false,
columnWidth: 250
},
+ {
+ name: 'externalReference',
+ label: 'External Reference',
+ type: 'miscId',
+ readOnly: true,
+ required: false,
+ extra:
+ 'eBay item ID for this listing. Publish and unpublish reuse this ID when set.',
+ showCopy: true,
+ columnWidth: 180
+ },
{
name: 'courierServices',
label: 'Courier Services',
@@ -386,6 +469,8 @@ export const Listing = {
objectType: 'courierService',
readOnly: false,
required: true,
+ extra:
+ 'Used for order shipping and as the eBay fulfillment fallback when no fulfillment policy is set.',
columnWidth: 250
}
],
@@ -414,6 +499,18 @@ export const Listing = {
type: 'number',
color: 'processing'
},
+ {
+ name: 'publishing.count',
+ label: 'Publishing',
+ type: 'number',
+ color: 'processing'
+ },
+ {
+ name: 'unpublishing.count',
+ label: 'Unpublishing',
+ type: 'number',
+ color: 'processing'
+ },
{
name: 'suspended.count',
label: 'Suspended',
diff --git a/src/database/models/ListingVarient.js b/src/database/models/ListingVarient.js
index 0666805a..a5f773bc 100644
--- a/src/database/models/ListingVarient.js
+++ b/src/database/models/ListingVarient.js
@@ -108,7 +108,10 @@ export const ListingVarient = {
visible: (objectData) =>
objectData?.state?.type === 'draft' ||
objectData?.state?.type === 'inactive',
- disabled: (objectData) => objectData?.state?.type === 'syncing',
+ disabled: (objectData) =>
+ ['syncing', 'publishing', 'unpublishing'].includes(
+ objectData?.state?.type
+ ),
content: (objectData, { onOk } = {}) => {
return createElement(PublishListingVarient, { objectData, onOk })
}
@@ -121,7 +124,10 @@ export const ListingVarient = {
icon: XMarkIcon,
danger: true,
visible: (objectData) => objectData?.state?.type === 'active',
- disabled: (objectData) => objectData?.state?.type === 'syncing',
+ disabled: (objectData) =>
+ ['syncing', 'publishing', 'unpublishing'].includes(
+ objectData?.state?.type
+ ),
content: (objectData, { onOk } = {}) => {
return createElement(UnpublishListingVarient, { objectData, onOk })
}
@@ -312,6 +318,42 @@ export const ListingVarient = {
type: 'dateTime',
readOnly: true,
columnWidth: 175
+ },
+ {
+ name: 'listingImages',
+ label: 'Listing Images',
+ type: 'fileList',
+ gallery: true,
+ required: false,
+ masterFilter: ['.jpg', '.jpeg', '.png', '.gif', '.webp']
+ },
+ {
+ name: 'aspects',
+ label: 'Aspects',
+ type: 'objectChildren',
+ required: false,
+ canAddRemove: true,
+ size: 'medium',
+ span: 2,
+ extra:
+ 'Required for multi-variation eBay listings. Use the same aspect names on every varient in a listing (e.g. Color, Size).',
+ columns: ['name', 'value'],
+ properties: [
+ {
+ name: 'name',
+ label: 'Name',
+ type: 'text',
+ required: true,
+ columnWidth: 180
+ },
+ {
+ name: 'value',
+ label: 'Value',
+ type: 'text',
+ required: true,
+ columnWidth: 180
+ }
+ ]
}
]
}
diff --git a/src/database/models/Marketplace.js b/src/database/models/Marketplace.js
index f837fa4d..5504bb0e 100644
--- a/src/database/models/Marketplace.js
+++ b/src/database/models/Marketplace.js
@@ -15,6 +15,21 @@ const SyncListings = lazy(
const SyncOrders = lazy(
() => import('../../components/Dashboard/Sales/Marketplaces/SyncOrders')
)
+const SyncFulfillmentPolicies = lazy(
+ () =>
+ import('../../components/Dashboard/Sales/Marketplaces/SyncFulfillmentPolicies')
+)
+const SyncPaymentPolicies = lazy(
+ () =>
+ import('../../components/Dashboard/Sales/Marketplaces/SyncPaymentPolicies')
+)
+const SyncReturnPolicies = lazy(
+ () =>
+ import('../../components/Dashboard/Sales/Marketplaces/SyncReturnPolicies')
+)
+const SyncTaxRates = lazy(
+ () => import('../../components/Dashboard/Sales/Marketplaces/SyncTaxRates')
+)
const DeleteObject = lazy(
() => import('../../components/Dashboard/common/DeleteObject')
)
@@ -134,19 +149,6 @@ export const Marketplace = {
return createElement(SyncListings, { objectData, onOk })
}
},
- {
- name: 'syncMarketplace',
- type: 'modal',
- modalWidth: 520,
- label: 'Sync Marketplace',
- icon: ReloadIcon,
- disabled: (objectData) => {
- return objectData?.state?.type != 'ready'
- },
- content: (objectData, { onOk } = {}) => {
- return createElement(SyncMarketplace, { objectData, onOk })
- }
- },
{
name: 'syncOrders',
type: 'modal',
@@ -159,6 +161,58 @@ export const Marketplace = {
content: (objectData, { onOk } = {}) => {
return createElement(SyncOrders, { objectData, onOk })
}
+ },
+ {
+ name: 'syncFulfillmentPolicies',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Sync Fulfillment Policies',
+ icon: ReloadIcon,
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'ready'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(SyncFulfillmentPolicies, { objectData, onOk })
+ }
+ },
+ {
+ name: 'syncReturnPolicies',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Sync Return Policies',
+ icon: ReloadIcon,
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'ready'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(SyncReturnPolicies, { objectData, onOk })
+ }
+ },
+ {
+ name: 'syncPaymentPolicies',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Sync Payment Policies',
+ icon: ReloadIcon,
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'ready'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(SyncPaymentPolicies, { objectData, onOk })
+ }
+ },
+ {
+ name: 'syncTaxRates',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Sync Tax Rates',
+ icon: ReloadIcon,
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'ready'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(SyncTaxRates, { objectData, onOk })
+ }
}
]
},
@@ -313,17 +367,7 @@ export const Marketplace = {
],
columnWidth: 150
},
- {
- name: 'eBay.availableShippingServices',
- label: 'Available Shipping Services',
- type: 'stringList',
- readOnly: true,
- required: false,
- visible: (objectData) => objectData?.provider === 'ebay',
- extra: 'Loaded from eBay GeteBayDetails when this marketplace is synced.',
- span: 2,
- columnWidth: 280
- },
+
{
name: 'config.accessTokenExpiresAt',
label: 'Access Token Expires At',
@@ -331,7 +375,6 @@ export const Marketplace = {
readOnly: true,
columnWidth: 215
},
-
{
name: 'state',
label: 'State',
@@ -340,21 +383,7 @@ export const Marketplace = {
readOnly: true,
columnWidth: 260
},
- {
- name: 'active',
- label: 'Active',
- type: 'bool',
- readOnly: false,
- required: true,
- columnWidth: 125
- },
- {
- name: 'connected',
- label: 'Connected',
- type: 'bool',
- readOnly: true,
- columnWidth: 125
- },
+
{
name: 'config.refreshTokenExpiresAt',
label: 'Refresh Token Expires At',
@@ -364,15 +393,40 @@ export const Marketplace = {
columnWidth: 200
},
{
- name: 'config.clientId',
- label: 'Client ID',
- type: 'secret',
- readOnly: false,
+ name: 'eBay.availableShippingServices',
+ label: 'Available Shipping Services',
+ type: 'number',
+ readOnly: true,
required: false,
- columnWidth: 200,
- visible: (objectData) => objectData?.provider === 'ebay'
+ visible: (objectData) => objectData?.provider === 'ebay',
+ extra: 'Loaded from eBay GeteBayDetails when this marketplace is synced.',
+ span: 2,
+ columnWidth: 280,
+ value: (objectData) => {
+ return objectData?.eBay?.availableShippingServices?.length
+ }
+ },
+ {
+ name: 'eBay.categoryReferences',
+ label: 'Category References',
+ type: 'treeSelect',
+ readOnly: true,
+ required: false,
+ visible: false,
+ extra:
+ 'Loaded from the eBay taxonomy category tree when this marketplace is synced.',
+ span: 2,
+ columnWidth: 280
},
+ {
+ name: 'active',
+ label: 'Active',
+ type: 'bool',
+ readOnly: false,
+ required: true,
+ columnWidth: 125
+ },
{
name: 'config.lastTokenRefreshAt',
label: 'Last Token Refresh At',
@@ -381,6 +435,23 @@ export const Marketplace = {
showSince: true,
columnWidth: 200
},
+ {
+ name: 'connected',
+ label: 'Connected',
+ type: 'bool',
+ readOnly: true,
+ columnWidth: 125
+ },
+
+ {
+ name: 'config.clientId',
+ label: 'Client ID',
+ type: 'secret',
+ readOnly: false,
+ required: false,
+ columnWidth: 200,
+ visible: (objectData) => objectData?.provider === 'ebay'
+ },
{
name: 'config.clientSecret',
@@ -555,6 +626,37 @@ export const Marketplace = {
}
return `${window.location.origin}/auth/marketplace/callback`
}
+ },
+ {
+ name: 'defaultFulfillmentPolicy',
+ label: 'Default Fulfillment Policy',
+ type: 'object',
+ objectType: 'fulfillmentPolicy',
+ showHyperlink: true,
+ required: false,
+ extra:
+ 'Used on new listings and when a listing has no fulfillment policy.',
+ columnWidth: 240
+ },
+ {
+ name: 'defaultPaymentPolicy',
+ label: 'Default Payment Policy',
+ type: 'object',
+ objectType: 'paymentPolicy',
+ showHyperlink: true,
+ required: false,
+ extra: 'Used on new listings and when a listing has no payment policy.',
+ columnWidth: 220
+ },
+ {
+ name: 'defaultReturnPolicy',
+ label: 'Default Return Policy',
+ type: 'object',
+ objectType: 'returnPolicy',
+ showHyperlink: true,
+ required: false,
+ extra: 'Used on new listings and when a listing has no return policy.',
+ columnWidth: 220
}
],
stats: [
diff --git a/src/database/models/PartStock.js b/src/database/models/PartStock.js
index dd3a22f8..183301b3 100644
--- a/src/database/models/PartStock.js
+++ b/src/database/models/PartStock.js
@@ -6,9 +6,20 @@ const PartStockInfo = lazy(
const NewPartStock = lazy(
() => import('../../components/Dashboard/Inventory/PartStocks/NewPartStock')
)
+const PostPartStock = lazy(
+ () =>
+ import('../../components/Dashboard/Inventory/PartStocks/PostPartStock.jsx')
+)
+const DeleteObject = lazy(
+ () => import('../../components/Dashboard/common/DeleteObject')
+)
import PartStockIcon from '../../components/Icons/PartStockIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
+import EditIcon from '../../components/Icons/EditIcon'
+import CheckIcon from '../../components/Icons/CheckIcon'
+import XMarkIcon from '../../components/Icons/XMarkIcon'
+import BinIcon from '../../components/Icons/BinIcon'
import ListIcon from '../../components/Icons/ListIcon'
export const PartStock = {
@@ -17,7 +28,6 @@ export const PartStock = {
labelPlural: 'Part Stocks',
url: '/dashboard/inventory/partstocks',
prefix: 'PTS',
- readOnly: true,
icon: PartStockIcon,
actions: [
{
@@ -50,6 +60,75 @@ export const PartStock = {
default: true,
row: true,
icon: InfoCircleIcon
+ },
+ {
+ name: 'edit',
+ type: 'page',
+ pageName: 'info',
+ label: 'Edit',
+ row: true,
+ icon: EditIcon,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ },
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'draft'
+ }
+ },
+ {
+ name: 'cancelEdit',
+ label: 'Cancel Edits',
+ type: 'page',
+ pageName: 'info',
+ icon: XMarkIcon,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'finishEdit',
+ label: 'Save Edits',
+ type: 'page',
+ pageName: 'info',
+ icon: CheckIcon,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'delete',
+ type: 'modal',
+ modalWidth: 520,
+ modalCentered: true,
+ label: 'Delete',
+ icon: BinIcon,
+ danger: true,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ },
+ disabled: (objectData) => {
+ return objectData?.state?.type != 'draft'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(DeleteObject, { objectData, onOk })
+ }
+ },
+ { type: 'divider' },
+ {
+ name: 'post',
+ type: 'modal',
+ modalWidth: 520,
+ label: 'Post',
+ icon: CheckIcon,
+ disabled: (objectData) => {
+ return objectData?._isEditing == true
+ },
+ visible: (objectData) => {
+ return objectData?.state?.type == 'draft'
+ },
+ content: (objectData, { onOk } = {}) => {
+ return createElement(PostPartStock, { objectData, onOk })
+ }
}
],
pages: [
@@ -62,7 +141,6 @@ export const PartStock = {
'part',
'partSku',
'state',
- 'startingQuantity',
'currentQuantity',
'stockLocation',
'createdAt',
@@ -72,7 +150,6 @@ export const PartStock = {
sorters: [
'part',
'partSku',
- 'startingQuantity',
'currentQuantity',
'state',
'createdAt',
@@ -81,7 +158,6 @@ export const PartStock = {
columns: [
'_reference',
'state',
- 'startingQuantity',
'currentQuantity',
'part',
'partSku',
@@ -116,13 +192,6 @@ export const PartStock = {
readOnly: true,
columnWidth: 180
},
- {
- name: 'state',
- label: 'State',
- type: 'state',
- readOnly: true,
- columnWidth: 260
- },
{
name: 'updatedAt',
label: 'Updated At',
@@ -131,13 +200,19 @@ export const PartStock = {
columnWidth: 175
},
{
- name: 'sourceType',
- label: 'Source Type',
- type: 'objectType',
- readOnly: false,
- columnWidth: 200,
- required: true,
- masterFilter: ['subJob', 'stockTransfer']
+ name: 'state',
+ label: 'State',
+ type: 'state',
+ readOnly: true,
+ columnWidth: 260
+ },
+
+ {
+ name: 'postedAt',
+ label: 'Posted At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
},
{
name: 'part',
@@ -169,36 +244,22 @@ export const PartStock = {
showHyperlink: true,
columnWidth: 200
},
-
- {
- name: 'source',
- label: 'Source',
- type: 'object',
- readOnly: false,
- required: true,
- columnWidth: 200,
- objectType: (objectData) => {
- return objectData?.sourceType
- },
- showHyperlink: true
- },
{
name: 'currentQuantity',
label: 'Current Quantity',
type: 'number',
- readOnly: true,
columnWidth: 200,
- required: true,
- value: (objectData) => {
- if (objectData?.state?.type === 'new') {
- return objectData?.startingQuantity
- } else {
- return objectData.currentQuantity
- }
- }
+ required: true
}
],
stats: [
+ {
+ name: 'draft.count',
+ label: 'Draft',
+ type: 'number',
+ color: 'default',
+ history: false
+ },
{
name: 'new.count',
label: 'New',
diff --git a/src/database/models/PaymentPolicy.js b/src/database/models/PaymentPolicy.js
new file mode 100644
index 00000000..7c690a1a
--- /dev/null
+++ b/src/database/models/PaymentPolicy.js
@@ -0,0 +1,187 @@
+import { createElement, lazy } from 'react'
+
+const PaymentPolicyInfo = lazy(
+ () =>
+ import('../../components/Dashboard/Finance/PaymentPolicies/PaymentPolicyInfo')
+)
+const NewPaymentPolicy = lazy(
+ () =>
+ import('../../components/Dashboard/Finance/PaymentPolicies/NewPaymentPolicy')
+)
+const DeleteObject = lazy(
+ () => import('../../components/Dashboard/common/DeleteObject')
+)
+import PaymentPolicyIcon from '../../components/Icons/PaymentPolicyIcon'
+import PlusIcon from '../../components/Icons/PlusIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
+import EditIcon from '../../components/Icons/EditIcon'
+import CheckIcon from '../../components/Icons/CheckIcon'
+import XMarkIcon from '../../components/Icons/XMarkIcon'
+import BinIcon from '../../components/Icons/BinIcon'
+import ListIcon from '../../components/Icons/ListIcon'
+import { marketplaceSyncMappingsProperty } from './marketplaceMappings'
+
+export const PaymentPolicy = {
+ name: 'paymentPolicy',
+ label: 'Payment Policy',
+ labelPlural: 'Payment Policies',
+ url: '/dashboard/finance/paymentpolicies',
+ prefix: 'PPL',
+ endpoint: 'paymentpolicies',
+ icon: PaymentPolicyIcon,
+ actions: [
+ {
+ name: 'new',
+ type: 'modal',
+ pageName: 'list',
+ modalWidth: 700,
+ label: 'New Payment Policy',
+ icon: PlusIcon,
+ content: (objectData, { onOk } = {}) => {
+ return createElement(NewPaymentPolicy, {
+ defaultValues: objectData,
+ onOk,
+ reset: true
+ })
+ }
+ },
+ {
+ name: 'list',
+ type: 'page',
+ pageName: 'list',
+ label: 'List',
+ icon: ListIcon
+ },
+ {
+ name: 'info',
+ type: 'page',
+ pageName: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon
+ },
+ {
+ name: 'edit',
+ type: 'page',
+ pageName: 'info',
+ label: 'Edit',
+ row: true,
+ icon: EditIcon,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ }
+ },
+ {
+ name: 'cancelEdit',
+ label: 'Cancel Edits',
+ type: 'page',
+ pageName: 'info',
+ icon: XMarkIcon,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'finishEdit',
+ label: 'Save Edits',
+ type: 'page',
+ pageName: 'info',
+ icon: CheckIcon,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ { type: 'divider' },
+ {
+ name: 'delete',
+ type: 'modal',
+ modalWidth: 520,
+ modalCentered: true,
+ label: 'Delete',
+ icon: BinIcon,
+ danger: true,
+ content: (objectData, { onOk } = {}) => {
+ return createElement(DeleteObject, { objectData, onOk })
+ }
+ }
+ ],
+ pages: [
+ {
+ name: 'info',
+ content: () => createElement(PaymentPolicyInfo)
+ }
+ ],
+ columns: ['_reference', 'name', 'immediatePay', 'createdAt', 'updatedAt'],
+ filters: ['name', 'immediatePay', 'createdAt', 'updatedAt', '_reference'],
+ sorters: ['name', 'immediatePay', 'createdAt', '_id', 'updatedAt'],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ columnFixed: 'left',
+ type: 'id',
+ objectType: 'paymentPolicy',
+ showCopy: true,
+ columnWidth: 140
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: '_reference',
+ label: 'Reference',
+ type: 'reference',
+ columnFixed: 'left',
+ objectType: 'paymentPolicy',
+ showCopy: true,
+ readOnly: true,
+ columnWidth: 180
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ columnFixed: 'left',
+ required: true,
+ type: 'text',
+ columnWidth: 220
+ },
+ {
+ name: 'description',
+ label: 'Description',
+ type: 'text',
+ required: false,
+ span: 2,
+ columnWidth: 280
+ },
+ {
+ name: 'immediatePay',
+ label: 'Immediate Pay',
+ type: 'bool',
+ required: false,
+ default: true,
+ extra: 'Required for eBay managed payments.',
+ columnWidth: 150
+ },
+ {
+ name: 'paymentInstructions',
+ label: 'Payment Instructions',
+ type: 'text',
+ required: false,
+ span: 2,
+ columnWidth: 280
+ },
+ marketplaceSyncMappingsProperty('paymentPolicy')
+ ]
+}
diff --git a/src/database/models/ProductCategory.js b/src/database/models/ProductCategory.js
index 363a492a..0fde6cfa 100644
--- a/src/database/models/ProductCategory.js
+++ b/src/database/models/ProductCategory.js
@@ -1,14 +1,17 @@
import { createElement, lazy } from 'react'
const ProductCategoryInfo = lazy(
- () => import('../../components/Dashboard/Management/ProductCategories/ProductCategoryInfo')
+ () =>
+ import('../../components/Dashboard/Management/ProductCategories/ProductCategoryInfo')
)
const NewProductCategory = lazy(
- () => import('../../components/Dashboard/Management/ProductCategories/NewProductCategory')
+ () =>
+ import('../../components/Dashboard/Management/ProductCategories/NewProductCategory')
)
const DeleteObject = lazy(
() => import('../../components/Dashboard/common/DeleteObject')
)
+import { findTreeNode } from '../../components/Dashboard/common/CustomTreeSelect'
import ProductCategoryIcon from '../../components/Icons/ProductCategoryIcon'
import PlusIcon from '../../components/Icons/PlusIcon'
import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
@@ -35,7 +38,11 @@ export const ProductCategory = {
label: 'New Product Category',
icon: PlusIcon,
content: (objectData, { onOk } = {}) => {
- return createElement(NewProductCategory, { defaultValues: objectData, onOk, reset: true })
+ return createElement(NewProductCategory, {
+ defaultValues: objectData,
+ onOk,
+ reset: true
+ })
}
},
{
@@ -106,7 +113,14 @@ export const ProductCategory = {
}
],
columns: ['_reference', 'name', 'createdAt', 'updatedAt'],
- filters: ['_id','name','createdAt','updatedAt','_reference'],
+ filters: [
+ '_id',
+ 'name',
+ 'createdAt',
+ 'updatedAt',
+ '_reference',
+ 'marketplaces.marketplace'
+ ],
sorters: ['name', 'createdAt', 'updatedAt', '_id'],
properties: [
{
@@ -136,6 +150,14 @@ export const ProductCategory = {
readOnly: true,
columnWidth: 180
},
+
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
{
name: 'name',
label: 'Name',
@@ -145,11 +167,47 @@ export const ProductCategory = {
columnWidth: 200
},
{
- name: 'updatedAt',
- label: 'Updated At',
- type: 'dateTime',
- readOnly: true,
- columnWidth: 175
+ name: 'marketplaces',
+ label: 'Marketplaces',
+ type: 'objectChildren',
+ required: false,
+ canAddRemove: true,
+ size: 'medium',
+ span: 2,
+ columns: ['marketplace', 'externalReference'],
+ properties: [
+ {
+ name: 'marketplace',
+ label: 'Marketplace',
+ type: 'object',
+ objectType: 'marketplace',
+ required: true,
+ showHyperlink: true,
+ columnWidth: 220
+ },
+ {
+ name: 'externalReference',
+ label: 'External Reference',
+ type: (objectData) =>
+ objectData?.marketplace?.provider === 'ebay'
+ ? 'treeSelect'
+ : 'text',
+ options: (objectData) => {
+ const tree = objectData?.marketplace?.eBay?.categoryReferences || []
+ const current = objectData?.externalReference
+ if (!current || findTreeNode(tree, current)) return tree
+ return [
+ { title: current, value: current, selectable: true },
+ ...tree
+ ]
+ },
+ extra:
+ 'For eBay listings, choose a category from the selected marketplace. Sync the marketplace if the tree is empty.',
+ showCopy: true,
+ required: true,
+ columnWidth: 260
+ }
+ ]
}
]
}
diff --git a/src/database/models/ProductStock.js b/src/database/models/ProductStock.js
index 3e2fabf8..3ab91a5f 100644
--- a/src/database/models/ProductStock.js
+++ b/src/database/models/ProductStock.js
@@ -371,10 +371,11 @@ export const ProductStock = {
required: false,
showHyperlink: true,
columnWidth: 260,
- masterFilter: (objectData) => {
+ masterFilter: (objectData, parentData) => {
return {
part: objectData?.part?._id,
partSku: objectData?.partSku?._id,
+ stockLocation: parentData?.stockLocation?._id,
$or: [{ 'state.type': 'new' }, { 'state.type': 'used' }]
}
}
diff --git a/src/database/models/ReturnPolicy.js b/src/database/models/ReturnPolicy.js
new file mode 100644
index 00000000..ac04f437
--- /dev/null
+++ b/src/database/models/ReturnPolicy.js
@@ -0,0 +1,294 @@
+import { createElement, lazy } from 'react'
+
+const ReturnPolicyInfo = lazy(
+ () =>
+ import('../../components/Dashboard/Sales/ReturnPolicies/ReturnPolicyInfo')
+)
+const NewReturnPolicy = lazy(
+ () =>
+ import('../../components/Dashboard/Sales/ReturnPolicies/NewReturnPolicy')
+)
+const DeleteObject = lazy(
+ () => import('../../components/Dashboard/common/DeleteObject')
+)
+import ReturnPolicyIcon from '../../components/Icons/ReturnPolicyIcon'
+import PlusIcon from '../../components/Icons/PlusIcon'
+import InfoCircleIcon from '../../components/Icons/InfoCircleIcon'
+import EditIcon from '../../components/Icons/EditIcon'
+import CheckIcon from '../../components/Icons/CheckIcon'
+import XMarkIcon from '../../components/Icons/XMarkIcon'
+import BinIcon from '../../components/Icons/BinIcon'
+import ListIcon from '../../components/Icons/ListIcon'
+import { marketplaceSyncMappingsProperty } from './marketplaceMappings'
+
+export const ReturnPolicy = {
+ name: 'returnPolicy',
+ label: 'Return Policy',
+ labelPlural: 'Return Policies',
+ url: '/dashboard/sales/returnpolicies',
+ prefix: 'RPL',
+ endpoint: 'returnpolicies',
+ icon: ReturnPolicyIcon,
+ actions: [
+ {
+ name: 'new',
+ type: 'modal',
+ pageName: 'list',
+ modalWidth: 700,
+ label: 'New Return Policy',
+ icon: PlusIcon,
+ content: (objectData, { onOk } = {}) => {
+ return createElement(NewReturnPolicy, {
+ defaultValues: objectData,
+ onOk,
+ reset: true
+ })
+ }
+ },
+ {
+ name: 'list',
+ type: 'page',
+ pageName: 'list',
+ label: 'List',
+ icon: ListIcon
+ },
+ {
+ name: 'info',
+ type: 'page',
+ pageName: 'info',
+ label: 'Info',
+ default: true,
+ row: true,
+ icon: InfoCircleIcon
+ },
+ {
+ name: 'edit',
+ type: 'page',
+ pageName: 'info',
+ label: 'Edit',
+ row: true,
+ icon: EditIcon,
+ visible: (objectData) => {
+ return !(objectData?._isEditing && objectData?._isEditing == true)
+ }
+ },
+ {
+ name: 'cancelEdit',
+ label: 'Cancel Edits',
+ type: 'page',
+ pageName: 'info',
+ icon: XMarkIcon,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ {
+ name: 'finishEdit',
+ label: 'Save Edits',
+ type: 'page',
+ pageName: 'info',
+ icon: CheckIcon,
+ visible: (objectData) => {
+ return objectData?._isEditing && objectData?._isEditing == true
+ }
+ },
+ { type: 'divider' },
+ {
+ name: 'delete',
+ type: 'modal',
+ modalWidth: 520,
+ modalCentered: true,
+ label: 'Delete',
+ icon: BinIcon,
+ danger: true,
+ content: (objectData, { onOk } = {}) => {
+ return createElement(DeleteObject, { objectData, onOk })
+ }
+ }
+ ],
+ pages: [
+ {
+ name: 'info',
+ content: () => createElement(ReturnPolicyInfo)
+ }
+ ],
+ columns: [
+ '_reference',
+ 'name',
+ 'returnsAccepted',
+ 'returnPeriodDays',
+ 'returnShippingCostPayer',
+ 'createdAt',
+ 'updatedAt'
+ ],
+ filters: [
+ 'name',
+ 'returnsAccepted',
+ 'returnPeriodDays',
+ 'returnShippingCostPayer',
+ 'refundMethod',
+ 'createdAt',
+ 'updatedAt',
+ '_reference'
+ ],
+ sorters: [
+ 'name',
+ 'returnsAccepted',
+ 'returnPeriodDays',
+ 'createdAt',
+ '_id',
+ 'updatedAt'
+ ],
+ properties: [
+ {
+ name: '_id',
+ label: 'ID',
+ columnFixed: 'left',
+ type: 'id',
+ objectType: 'returnPolicy',
+ showCopy: true,
+ columnWidth: 140
+ },
+ {
+ name: 'createdAt',
+ label: 'Created At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: '_reference',
+ label: 'Reference',
+ type: 'reference',
+ columnFixed: 'left',
+ objectType: 'returnPolicy',
+ showCopy: true,
+ readOnly: true,
+ columnWidth: 180
+ },
+ {
+ name: 'updatedAt',
+ label: 'Updated At',
+ type: 'dateTime',
+ readOnly: true,
+ columnWidth: 175
+ },
+ {
+ name: 'name',
+ label: 'Name',
+ columnFixed: 'left',
+ required: true,
+ type: 'text',
+ columnWidth: 220
+ },
+ {
+ name: 'description',
+ label: 'Description',
+ type: 'text',
+ required: false,
+ span: 2,
+ columnWidth: 280
+ },
+ {
+ name: 'returnsAccepted',
+ label: 'Returns Accepted',
+ type: 'bool',
+ required: true,
+ default: true,
+ columnWidth: 200
+ },
+ {
+ name: 'returnPeriodDays',
+ label: 'Return Period',
+ type: 'select',
+ options: [
+ { label: '14 days', value: 14 },
+ { label: '30 days', value: 30 },
+ { label: '60 days', value: 60 }
+ ],
+ default: 30,
+ required: false,
+ extra: 'eBay only accepts 14, 30, or 60 days.',
+ visible: (objectData) => objectData?.returnsAccepted !== false,
+ columnWidth: 170
+ },
+ {
+ name: 'returnShippingCostPayer',
+ label: 'Return Shipping Paid By',
+ type: 'select',
+ options: [
+ { label: 'Buyer', value: 'buyer' },
+ { label: 'Seller', value: 'seller' }
+ ],
+ required: false,
+ visible: (objectData) => objectData?.returnsAccepted !== false,
+ columnWidth: 220
+ },
+ {
+ name: 'refundMethod',
+ label: 'Refund Method',
+ type: 'select',
+ options: [
+ { label: 'Money Back', value: 'moneyBack' },
+ { label: 'Merchandise Credit', value: 'merchandiseCredit' }
+ ],
+ required: false,
+ visible: (objectData) => objectData?.returnsAccepted !== false,
+ columnWidth: 180
+ },
+ {
+ name: 'restockingFeePercentage',
+ label: 'Restocking Fee',
+ type: 'number',
+ min: 0,
+ suffix: '%',
+ required: false,
+ extra: 'Stored locally. eBay no longer accepts restocking fees.',
+ columnWidth: 160
+ },
+ {
+ name: 'returnInstructions',
+ label: 'Return Instructions',
+ type: 'text',
+ required: false,
+ span: 2,
+ columnWidth: 280
+ },
+ {
+ name: 'internationalReturnsAccepted',
+ label: 'International Returns',
+ type: 'bool',
+ required: false,
+ columnWidth: 180
+ },
+ {
+ name: 'internationalReturnPeriodDays',
+ label: 'International Return Period',
+ type: 'select',
+ options: [
+ { label: '14 days', value: 14 },
+ { label: '30 days', value: 30 },
+ { label: '60 days', value: 60 }
+ ],
+ required: false,
+ extra: 'eBay only accepts 14, 30, or 60 days.',
+ visible: (objectData) =>
+ objectData?.internationalReturnsAccepted === true,
+ columnWidth: 220
+ },
+ {
+ name: 'internationalReturnShippingCostPayer',
+ label: 'International Return Shipping Paid By',
+ type: 'select',
+ options: [
+ { label: 'Buyer', value: 'buyer' },
+ { label: 'Seller', value: 'seller' }
+ ],
+ required: false,
+ visible: (objectData) =>
+ objectData?.internationalReturnsAccepted === true,
+ columnWidth: 260
+ },
+ marketplaceSyncMappingsProperty('returnPolicy')
+ ]
+}
diff --git a/src/database/models/TaxRate.js b/src/database/models/TaxRate.js
index 814b2095..c0dfc779 100644
--- a/src/database/models/TaxRate.js
+++ b/src/database/models/TaxRate.js
@@ -17,6 +17,7 @@ import CheckIcon from '../../components/Icons/CheckIcon'
import XMarkIcon from '../../components/Icons/XMarkIcon'
import BinIcon from '../../components/Icons/BinIcon'
import ListIcon from '../../components/Icons/ListIcon'
+import { marketplaceSyncMappingsProperty } from './marketplaceMappings'
export const TaxRate = {
name: 'taxRate',
@@ -111,6 +112,7 @@ export const TaxRate = {
'rateType',
'active',
'country',
+ 'jurisdiction',
'createdAt',
'updatedAt'
],
@@ -120,6 +122,7 @@ export const TaxRate = {
'rateType',
'active',
'country',
+ 'jurisdiction',
'createdAt',
'updatedAt',
'_reference'
@@ -130,6 +133,7 @@ export const TaxRate = {
'rateType',
'active',
'country',
+ 'jurisdiction',
'createdAt',
'_id',
'updatedAt'
@@ -237,6 +241,23 @@ export const TaxRate = {
required: false,
columnWidth: 150
},
+ {
+ name: 'jurisdiction',
+ label: 'Jurisdiction',
+ type: 'text',
+ readOnly: false,
+ required: false,
+ extra:
+ 'Marketplace tax table region, for example GU or ON. eBay only uses US territories and Canada.',
+ columnWidth: 140
+ },
+ {
+ name: 'shippingAndHandlingTaxed',
+ label: 'Tax Shipping',
+ type: 'bool',
+ required: false,
+ columnWidth: 140
+ },
{
name: 'description',
label: 'Description',
@@ -244,6 +265,10 @@ export const TaxRate = {
readOnly: false,
required: false,
columnWidth: 200
- }
+ },
+ marketplaceSyncMappingsProperty(
+ 'taxRate',
+ 'eBay tax table key as COUNTRY:JURISDICTION, for example US:GU. Filled when synced.'
+ )
]
}
diff --git a/src/database/models/marketplaceMappings.js b/src/database/models/marketplaceMappings.js
new file mode 100644
index 00000000..32ca9822
--- /dev/null
+++ b/src/database/models/marketplaceMappings.js
@@ -0,0 +1,49 @@
+export function marketplaceSyncMappingsProperty(objectType, extra) {
+ return {
+ name: 'marketplaces',
+ label: 'Marketplaces',
+ type: 'objectChildren',
+ required: false,
+ canAddRemove: true,
+ size: 'medium',
+ span: 2,
+ columns: ['marketplace', 'state', 'externalReference'],
+ properties: [
+ {
+ name: 'marketplace',
+ label: 'Marketplace',
+ type: 'object',
+ objectType: 'marketplace',
+ required: true,
+ showHyperlink: true,
+ columnWidth: 220
+ },
+ {
+ name: 'state',
+ label: 'State',
+ type: 'state',
+ objectType,
+ readOnly: true,
+ columnWidth: 160
+ },
+ {
+ name: 'externalReference',
+ label: 'External Reference',
+ type: (objectData) => {
+ return objectData?.marketplace?.provider === 'ebay'
+ ? 'miscId'
+ : 'text'
+ },
+ readOnly: (objectData) => {
+ return objectData?.marketplace?.provider === 'ebay'
+ },
+ extra:
+ extra ||
+ 'Marketplace policy ID. Filled when the policy is synced; you can paste an existing ID.',
+ showCopy: true,
+ required: false,
+ columnWidth: 240
+ }
+ ]
+ }
+}
diff --git a/src/database/sidebars/finance.js b/src/database/sidebars/finance.js
index 5e321de3..898e7c24 100644
--- a/src/database/sidebars/finance.js
+++ b/src/database/sidebars/finance.js
@@ -18,6 +18,12 @@ const financeSidebarItems = [
iconKey: 'payment',
path: '/dashboard/finance/payments'
},
+ {
+ key: 'paymentpolicies',
+ label: 'Payment Policies',
+ iconKey: 'paymentPolicy',
+ path: '/dashboard/finance/paymentpolicies'
+ },
{
key: 'taxRecords',
iconKey: 'taxRecord',
diff --git a/src/database/sidebars/sales.js b/src/database/sidebars/sales.js
index 9df640a5..ddb14762 100644
--- a/src/database/sidebars/sales.js
+++ b/src/database/sidebars/sales.js
@@ -18,6 +18,7 @@ const salesSidebarItems = [
iconKey: 'salesOrder',
path: '/dashboard/sales/salesorders'
},
+ { type: 'divider' },
{
key: 'marketplaces',
label: 'Marketplaces',
@@ -29,6 +30,24 @@ const salesSidebarItems = [
label: 'Listings',
iconKey: 'listing',
path: '/dashboard/sales/listings'
+ },
+ {
+ key: 'listingvarients',
+ label: 'Listing Varients',
+ iconKey: 'listingVarient',
+ path: '/dashboard/sales/listingvarients'
+ },
+ {
+ key: 'fulfillmentpolicies',
+ label: 'Fulfillment Policies',
+ iconKey: 'fulfillmentPolicy',
+ path: '/dashboard/sales/fulfillmentpolicies'
+ },
+ {
+ key: 'returnpolicies',
+ label: 'Return Policies',
+ iconKey: 'returnPolicy',
+ path: '/dashboard/sales/returnpolicies'
}
]
diff --git a/src/routes/FinanceRoutes.jsx b/src/routes/FinanceRoutes.jsx
index 125176a2..4fb54e25 100644
--- a/src/routes/FinanceRoutes.jsx
+++ b/src/routes/FinanceRoutes.jsx
@@ -23,6 +23,9 @@ const FinanceOverview = lazy(
const TaxRecords = lazy(
() => import('../components/Dashboard/Finance/TaxRecords.jsx')
)
+const PaymentPolicies = lazy(
+ () => import('../components/Dashboard/Finance/PaymentPolicies.jsx')
+)
const FinanceRoutes = [
} />,
} />,
} />,
+ }
+ />,
import('../components/Dashboard/Sales/Listings.jsx')
)
+const ListingVarients = lazy(
+ () => import('../components/Dashboard/Sales/ListingVarients.jsx')
+)
+const FulfillmentPolicies = lazy(
+ () => import('../components/Dashboard/Sales/FulfillmentPolicies.jsx')
+)
+const ReturnPolicies = lazy(
+ () => import('../components/Dashboard/Sales/ReturnPolicies.jsx')
+)
const SalesRoutes = [
} />,
@@ -28,7 +37,22 @@ const SalesRoutes = [
path='sales/marketplaces'
element={}
/>,
- } />
+ } />,
+ }
+ />,
+ }
+ />,
+ }
+ />
]
export default SalesRoutes