From 5ec394cbc044f0a80ac8948d4faa9a54bdaa52ea Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sat, 7 Mar 2026 20:37:52 +0000 Subject: [PATCH] Add PostProductStock component and integrate posting functionality for product stocks. Update NewProductStock to set default state to 'draft'. Enhance ProductStockInfo with modal for posting stocks and update action visibility based on stock state. --- .../ProductStocks/NewProductStock.jsx | 2 +- .../ProductStocks/PostProductStock.jsx | 46 ++++++++++ .../ProductStocks/ProductStockInfo.jsx | 43 +++++++++- src/database/models/ProductStock.js | 85 +++++++++++++++++++ 4 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 src/components/Dashboard/Inventory/ProductStocks/PostProductStock.jsx diff --git a/src/components/Dashboard/Inventory/ProductStocks/NewProductStock.jsx b/src/components/Dashboard/Inventory/ProductStocks/NewProductStock.jsx index 4e40425..3ea1822 100644 --- a/src/components/Dashboard/Inventory/ProductStocks/NewProductStock.jsx +++ b/src/components/Dashboard/Inventory/ProductStocks/NewProductStock.jsx @@ -8,7 +8,7 @@ const NewProductStock = ({ onOk, reset, defaultValues }) => { {({ handleSubmit, submitLoading, objectData, formValid }) => { const steps = [ diff --git a/src/components/Dashboard/Inventory/ProductStocks/PostProductStock.jsx b/src/components/Dashboard/Inventory/ProductStocks/PostProductStock.jsx new file mode 100644 index 0000000..339edb6 --- /dev/null +++ b/src/components/Dashboard/Inventory/ProductStocks/PostProductStock.jsx @@ -0,0 +1,46 @@ +import { useState, useContext } from 'react' +import PropTypes from 'prop-types' +import { ApiServerContext } from '../../context/ApiServerContext' +import { message } from 'antd' +import MessageDialogView from '../../common/MessageDialogView.jsx' + +const PostProductStock = ({ onOk, objectData }) => { + const [postLoading, setPostLoading] = useState(false) + const { sendObjectFunction } = useContext(ApiServerContext) + + const handlePost = async () => { + setPostLoading(true) + try { + const result = await sendObjectFunction( + objectData._id, + 'ProductStock', + 'post' + ) + if (result) { + message.success('Product stock posted successfully') + onOk(result) + } + } catch (error) { + console.error('Error posting product stock:', error) + } finally { + setPostLoading(false) + } + } + + return ( + + ) +} + +PostProductStock.propTypes = { + onOk: PropTypes.func.isRequired, + objectData: PropTypes.object +} + +export default PostProductStock diff --git a/src/components/Dashboard/Inventory/ProductStocks/ProductStockInfo.jsx b/src/components/Dashboard/Inventory/ProductStocks/ProductStockInfo.jsx index 8eb262d..fb55fc4 100644 --- a/src/components/Dashboard/Inventory/ProductStocks/ProductStockInfo.jsx +++ b/src/components/Dashboard/Inventory/ProductStocks/ProductStockInfo.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.js' @@ -10,7 +10,8 @@ import InfoCollapse from '../../common/InfoCollapse.jsx' import ObjectInfo from '../../common/ObjectInfo.jsx' import ObjectProperty from '../../common/ObjectProperty.jsx' import ViewButton from '../../common/ViewButton.jsx' -import { getModelProperty } from '../../../../database/ObjectModels.js' +import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js' +import PostProductStock from './PostProductStock.jsx' import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx' import NoteIcon from '../../../Icons/NoteIcon.jsx' import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx' @@ -36,6 +37,7 @@ const ProductStockInfo = () => { const productStockId = new URLSearchParams(location.search).get( 'productStockId' ) + const [postProductStockOpen, setPostProductStockOpen] = useState(false) const [collapseState, updateCollapseState] = useCollapseState( 'ProductStockInfo', { @@ -71,9 +73,22 @@ const ProductStockInfo = () => { finishEdit: () => { objectFormRef?.current.handleUpdate() return true + }, + delete: () => { + objectFormRef?.current.handleDelete?.() + return true + }, + post: () => { + setPostProductStockOpen(true) + return true } } + const editDisabled = + getModelByName('productStock') + ?.actions?.find((action) => action.name === 'edit') + ?.disabled(objectFormState.objectData) ?? false + return ( <> { }} editLoading={objectFormState.editLoading} formValid={objectFormState.formValid} - disabled={objectFormState.lock?.locked || objectFormState.loading} + disabled={ + objectFormState.lock?.locked || + objectFormState.loading || + editDisabled + } loading={objectFormState.editLoading} /> @@ -230,6 +249,24 @@ const ProductStockInfo = () => { + { + setPostProductStockOpen(false) + }} + width={500} + footer={null} + destroyOnHidden={true} + centered={true} + > + { + setPostProductStockOpen(false) + actions.reload() + }} + objectData={objectFormState.objectData} + /> + ) } diff --git a/src/database/models/ProductStock.js b/src/database/models/ProductStock.js index 540e05e..bce886c 100644 --- a/src/database/models/ProductStock.js +++ b/src/database/models/ProductStock.js @@ -1,5 +1,9 @@ import ProductStockIcon from '../../components/Icons/ProductStockIcon' import InfoCircleIcon from '../../components/Icons/InfoCircleIcon' +import EditIcon from '../../components/Icons/EditIcon' +import CheckIcon from '../../components/Icons/CheckIcon' +import XMarkIcon from '../../components/Icons/XMarkIcon' +import BinIcon from '../../components/Icons/BinIcon' export const ProductStock = { name: 'productStock', @@ -15,6 +19,69 @@ export const ProductStock = { icon: InfoCircleIcon, url: (_id) => `/dashboard/inventory/productstocks/info?productStockId=${_id}` + }, + { + name: 'edit', + label: 'Edit', + type: 'button', + icon: EditIcon, + url: (_id) => + `/dashboard/inventory/productstocks/info?productStockId=${_id}&action=edit`, + visible: (objectData) => { + return !(objectData?._isEditing && objectData?._isEditing == true) + }, + disabled: (objectData) => { + return objectData?.state?.type != 'draft' + } + }, + { + name: 'cancelEdit', + label: 'Cancel Edit', + type: 'button', + icon: XMarkIcon, + url: (_id) => + `/dashboard/inventory/productstocks/info?productStockId=${_id}&action=cancelEdit`, + visible: (objectData) => { + return objectData?._isEditing && objectData?._isEditing == true + } + }, + { + name: 'finishEdit', + label: 'Finish Edit', + type: 'button', + icon: CheckIcon, + url: (_id) => + `/dashboard/inventory/productstocks/info?productStockId=${_id}&action=finishEdit`, + visible: (objectData) => { + return objectData?._isEditing && objectData?._isEditing == true + } + }, + { + name: 'delete', + label: 'Delete', + type: 'button', + icon: BinIcon, + danger: true, + url: (_id) => + `/dashboard/inventory/productstocks/info?productStockId=${_id}&action=delete`, + visible: (objectData) => { + return !(objectData?._isEditing && objectData?._isEditing == true) + }, + disabled: (objectData) => { + return objectData?.state?.type != 'draft' + } + }, + { type: 'divider' }, + { + name: 'post', + label: 'Post', + type: 'button', + icon: CheckIcon, + url: (_id) => + `/dashboard/inventory/productstocks/info?productStockId=${_id}&action=post`, + visible: (objectData) => { + return objectData?.state?.type == 'draft' + } } ], url: (id) => `/dashboard/inventory/productstocks/info?productStockId=${id}`, @@ -50,6 +117,12 @@ export const ProductStock = { readOnly: true, columnWidth: 120 }, + { + name: 'postedAt', + label: 'Posted At', + type: 'dateTime', + readOnly: true + }, { name: 'updatedAt', label: 'Updated At', @@ -109,6 +182,18 @@ export const ProductStock = { } ], stats: [ + { + name: 'draft.count', + label: 'Draft', + type: 'number', + color: 'default' + }, + { + name: 'posted.count', + label: 'Posted', + type: 'number', + color: 'success' + }, { name: 'totalCurrentQuantity.sum', label: 'Total Current Quantity',