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.
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit

This commit is contained in:
Tom Butcher 2026-03-07 20:37:52 +00:00
parent 04d26600fd
commit 5ec394cbc0
4 changed files with 172 additions and 4 deletions

View File

@ -8,7 +8,7 @@ const NewProductStock = ({ onOk, reset, defaultValues }) => {
<NewObjectForm
type={'productStock'}
reset={reset}
defaultValues={{ state: { type: 'new' }, ...defaultValues }}
defaultValues={{ state: { type: 'draft' }, ...defaultValues }}
>
{({ handleSubmit, submitLoading, objectData, formValid }) => {
const steps = [

View File

@ -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 (
<MessageDialogView
title={'Are you sure you want to post this product stock?'}
description={`Posting product stock ${objectData?.name || objectData?._reference || objectData?._id} will finalize it and make it read-only.`}
onOk={handlePost}
okText='Post'
okLoading={postLoading}
/>
)
}
PostProductStock.propTypes = {
onOk: PropTypes.func.isRequired,
objectData: PropTypes.object
}
export default PostProductStock

View File

@ -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 (
<>
<Flex
@ -131,7 +146,11 @@ const ProductStockInfo = () => {
}}
editLoading={objectFormState.editLoading}
formValid={objectFormState.formValid}
disabled={objectFormState.lock?.locked || objectFormState.loading}
disabled={
objectFormState.lock?.locked ||
objectFormState.loading ||
editDisabled
}
loading={objectFormState.editLoading}
/>
</Space>
@ -230,6 +249,24 @@ const ProductStockInfo = () => {
</Flex>
</ScrollBox>
</Flex>
<Modal
open={postProductStockOpen}
onCancel={() => {
setPostProductStockOpen(false)
}}
width={500}
footer={null}
destroyOnHidden={true}
centered={true}
>
<PostProductStock
onOk={() => {
setPostProductStockOpen(false)
actions.reload()
}}
objectData={objectFormState.objectData}
/>
</Modal>
</>
)
}

View File

@ -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',