import { createElement, lazy } from 'react' const ProductStockInfo = lazy( () => import('../../components/Dashboard/Inventory/ProductStocks/ProductStockInfo') ) const NewProductStock = lazy( () => import('../../components/Dashboard/Inventory/ProductStocks/NewProductStock') ) const PostProductStock = lazy( () => import('../../components/Dashboard/Inventory/ProductStocks/PostProductStock') ) const DeleteObject = lazy( () => import('../../components/Dashboard/common/DeleteObject') ) import ProductStockIcon from '../../components/Icons/ProductStockIcon' 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 ProductStock = { name: 'productStock', label: 'Product Stock', labelPlural: 'Product Stocks', url: '/dashboard/inventory/productstocks', prefix: 'PDS', icon: ProductStockIcon, actions: [ { name: 'new', type: 'modal', pageName: 'list', modalWidth: 800, label: 'New Product Stock', icon: PlusIcon, content: (objectData, { onOk } = {}) => { return createElement(NewProductStock, { 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) }, 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) => { if (objectData?._isEditing == true) return true return objectData?.partStockList?.some( (row) => row?.remainingQuantity > 0 ) }, visible: (objectData) => { return objectData?.state?.type == 'draft' }, content: (objectData, { onOk } = {}) => { return createElement(PostProductStock, { objectData, onOk }) } } ], pages: [ { name: 'info', content: () => createElement(ProductStockInfo) } ], filters: [ 'product', 'productSku', 'state', 'currentQuantity', 'stockLocation', 'createdAt', 'updatedAt', '_reference' ], sorters: [ 'product', 'productSku', 'currentQuantity', 'state', 'createdAt', 'updatedAt', 'stockLocation' ], columns: [ '_reference', 'state', 'currentQuantity', 'product', 'productSku', 'stockLocation', 'createdAt', 'updatedAt' ], properties: [ { name: '_id', label: 'ID', type: 'id', objectType: 'productStock', showCopy: true, readOnly: true, columnWidth: 140 }, { name: 'createdAt', label: 'Created At', type: 'dateTime', readOnly: true, columnWidth: 175 }, { name: '_reference', label: 'Reference', type: 'reference', columnFixed: 'left', objectType: 'productStock', showCopy: true, readOnly: true, columnWidth: 180 }, { name: 'updatedAt', label: 'Updated At', type: 'dateTime', readOnly: true, columnWidth: 175 }, { name: 'state', label: 'State', type: 'state', readOnly: true, columnWidth: 260 }, { name: 'postedAt', label: 'Posted At', type: 'dateTime', readOnly: true, columnWidth: 175 }, { name: 'product', label: 'Product', type: 'object', objectType: 'product', required: true, showHyperlink: true, columnWidth: 200 }, { name: 'productSku', label: 'Product SKU', type: 'object', objectType: 'productSku', required: true, showHyperlink: true, columnWidth: 200, masterFilter: (objectData) => { return { product: objectData?.product?._id } } }, { name: 'stockLocation', label: 'Stock location', type: 'object', objectType: 'stockLocation', required: true, showHyperlink: true, columnWidth: 200, readOnly: (objectData) => { return objectData?.state?.type != 'draft' } }, { name: 'currentQuantity', label: 'Current Quantity', type: 'number', columnWidth: 200, required: true }, { name: 'partStockList', label: 'Part Stock List', type: 'objectChildren', canAddRemove: false, size: 'medium', value: (objectData) => { if ( objectData?._isEditing == false || objectData?.state?.type != 'draft' ) { return objectData?.partStockList || [] } const getId = (val) => { if (val == null) return null if (typeof val === 'object') { return val._id != null ? String(val._id) : null } return String(val) } const toPartStocks = (row) => { if (Array.isArray(row?.partStocks)) return row.partStocks if (row?.partStock) return [row.partStock] return [] } const skuParts = objectData?.productSku?.parts const existing = objectData?.partStockList const currentQuantity = Number(objectData?.currentQuantity) || 0 const requiredFromPart = (part) => (Number(part?.quantity) || 0) * currentQuantity const buildRow = (part, existingRow) => ({ ...existingRow, part: part?.part?._id ? part.part : { _id: part.part }, partSku: part.partSku?._id ? part.partSku : { _id: part.partSku }, requiredQuantity: requiredFromPart(part), partStocks: toPartStocks(existingRow) }) const rowsMatchSkuParts = (rows, parts) => { if (!Array.isArray(rows) || rows.length !== parts.length) return false return parts.every((part, index) => { const row = rows[index] return ( getId(row?.partSku) === getId(part.partSku) && getId(row?.part) === getId(part.part) && Number(row?.requiredQuantity) === requiredFromPart(part) && Array.isArray(row?.partStocks) ) }) } if (Array.isArray(skuParts)) { if (skuParts.length === 0) return [] if (rowsMatchSkuParts(existing, skuParts)) return undefined return skuParts.map((part) => { const existingRow = Array.isArray(existing) ? existing.find( (row) => getId(row.partSku) === getId(part.partSku) ) : undefined return buildRow(part, existingRow) }) } if (!Array.isArray(existing)) return [] const needsNormalize = existing.some( (row) => (row.requiredQuantity == null && row.quantity != null) || (row.part == null && row.partSku?.part != null) || !Array.isArray(row.partStocks) ) if (!needsNormalize) return [] return existing.map((row) => ({ ...row, part: row.part ?? row.partSku?.part, requiredQuantity: row.requiredQuantity ?? row.quantity, partStocks: toPartStocks(row) })) }, properties: [ { name: 'part', label: 'Part', type: 'object', objectType: 'part', readOnly: true, required: true, showHyperlink: true }, { name: 'partSku', label: 'Part SKU', type: 'object', objectType: 'partSku', readOnly: true, required: true, showHyperlink: true }, { name: 'partStocks', label: 'Part Stocks', type: 'objectList', objectType: 'partStock', required: false, showHyperlink: true, columnWidth: 260, masterFilter: (objectData, parentData) => { return { part: objectData?.part?._id, partSku: objectData?.partSku?._id, stockLocation: parentData?.stockLocation?._id, $or: [{ 'state.type': 'new' }, { 'state.type': 'used' }] } } }, { name: 'requiredQuantity', label: 'Required Quantity', type: 'number', required: true, columnWidth: 190, readOnly: true, value: (objectData) => objectData?.requiredQuantity ?? objectData?.quantity }, { name: 'remainingQuantity', label: 'Remaining Quantity', type: 'number', columnWidth: 190, readOnly: true, value: (objectData) => { const required = objectData?.requiredQuantity ?? objectData?.quantity ?? 0 const stocks = Array.isArray(objectData?.partStocks) ? objectData.partStocks : [] const available = Math.max( 0, stocks.reduce( (sum, stock) => sum + (Number(stock?.currentQuantity) || 0), 0 ) ) return Math.max(0, required - available) } } ] } ], stats: [ { name: 'draft.count', label: 'Draft', type: 'number', color: 'default' }, { name: 'new.count', label: 'New', type: 'number', color: 'success' }, { name: 'used.count', label: 'Used', type: 'number', color: 'warning' }, { name: 'consumed.count', label: 'Consumed', type: 'number', color: 'default' }, { name: 'totalCurrentQuantity.sum', label: 'Total Current Quantity', type: 'number', roundNumber: 2, cardWidth: 200, historyGroup: 'value' } ] }