farmcontrol-ui/src/database/models/ProductStock.js
Tom Butcher e0a5ff3cf8
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
Refactor Product Stock Components and Update Property Names
- Renamed 'partStocks' to 'partStockList' in NewProductStock and ProductStockInfo components for consistency and clarity.
- Updated visible properties and object data handling to reflect the new naming convention.
- Refactored utility functions in NewObjectForm and ObjectForm to utilize a new mergeFormData function for improved data management.
- Enhanced ObjectInfo and ObjectProperty components by removing unnecessary state management and ensuring proper data flow.
- Adjusted layout properties in ObjectList and ObjectSelect components for better responsiveness and usability.
2026-08-24 22:40:11 +01:00

439 lines
11 KiB
JavaScript

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) => {
return {
part: objectData?.part?._id,
partSku: objectData?.partSku?._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: 'totalCurrentQuantity.sum',
label: 'Total Current Quantity',
type: 'number',
roundNumber: 2,
cardWidth: 200
}
]
}