From 9fb65bdacd0964955bb9a02f09161d429c8f214b Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 27 Feb 2026 09:29:33 +0700 Subject: [PATCH] feat(FE): Validate quantity against available stock --- .../form/InventoryAdjustmentForm.tsx | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx b/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx index faa73a3a..a2530277 100644 --- a/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx +++ b/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx @@ -46,6 +46,7 @@ import { TRANSACTION_SUBTYPE_OPTIONS, } from '@/config/constant'; import NumberInput from '@/components/input/NumberInput'; +import { formatNumber } from '@/lib/helper'; interface InventoryAdjustmentFormProps { type?: 'add' | 'detail'; @@ -62,6 +63,7 @@ const InventoryAdjustmentForm = ({ setInventoryAdjustmentFormErrorMessage, ] = useState(''); const [quantityLabel, setQuantityLabel] = useState('Kuantitas'); + const [quantityError, setQuantityError] = useState(''); const [selectedLocation, setSelectedLocation] = useState( null @@ -211,6 +213,25 @@ const InventoryAdjustmentForm = ({ : []; }, [productWarehouses]); + // Get available quantity from selected product + const selectedProductQuantity = useMemo(() => { + if (!selectedProduct) return 0; + const product = productOptions.find( + (opt) => opt.value === selectedProduct.value + ); + return product?.quantity ?? 0; + }, [selectedProduct, productOptions]); + + // Check if current transaction subtype reduces stock + const isStockOutSubtype = useMemo(() => { + const subtype = selectedTransactionSubtype?.value; + return ( + subtype === 'RECORDING_STOCK_OUT' || + subtype === 'RECORDING_DEPLETION_OUT' || + subtype === 'MARKETING_OUT' + ); + }, [selectedTransactionSubtype]); + const selectedProductFlags = useMemo(() => { if (!selectedProduct) return []; const product = productOptions.find( @@ -372,6 +393,28 @@ const InventoryAdjustmentForm = ({ } }, [selectedTransactionSubtype]); + // Validate quantity against available stock + useEffect(() => { + const qty = Number(formik.values.qty); + if ( + isStockOutSubtype && + selectedProduct && + qty > 0 && + qty > selectedProductQuantity + ) { + setQuantityError( + `Kuantitas ${formatNumber(qty)} melebihi stok tersedia (${formatNumber(selectedProductQuantity)})` + ); + } else { + setQuantityError(''); + } + }, [ + formik.values.qty, + isStockOutSubtype, + selectedProduct, + selectedProductQuantity, + ]); + useEffect(() => { if (selectedTransactionType?.value === 'RECORDING' && selectedProduct) { setSelectedTransactionSubtype(null); @@ -804,11 +847,18 @@ const InventoryAdjustmentForm = ({ value={formik.values.qty} onChange={formik.handleChange} onBlur={formik.handleBlur} - isError={formik.touched.qty && Boolean(formik.errors.qty)} - errorMessage={formik.errors.qty as string} + isError={ + (formik.touched.qty && Boolean(formik.errors.qty)) || + Boolean(quantityError) + } + errorMessage={(formik.errors.qty as string) || quantityError} readOnly={type === 'detail'} + bottomLabel={ + selectedProduct + ? `Kuantitas: ${formatNumber(selectedProductQuantity)}` + : undefined + } /> - {/* Number Input Price */} Submit