refactor(FE-207,212): update PurchaseRequestForm schema and validation, enforce required fields and improve data handling for purchase items

This commit is contained in:
rstubryan
2025-11-03 11:29:43 +07:00
parent b4a9c86c2a
commit ae91e17ac0
3 changed files with 182 additions and 195 deletions
@@ -1,62 +1,106 @@
import * as Yup from 'yup'; import * as Yup from 'yup';
import { Supplier } from '@/types/api/master-data/supplier'; import { Purchase } from '@/types/api/purchase/purchase';
import { Warehouse } from '@/types/api/master-data/warehouse';
import { CreatePurchaseRequestPayload } from '@/types/api/purchase/purchase';
import { Product } from '@/types/api/master-data/product';
import { ProductWarehouse } from '@/types/api/inventory/product-warehouse';
export const PurchaseRequestFormSchema = Yup.object({ type PurchaseRequestFormSchemaType = {
supplier: Yup.object({ supplier?: {
value: Yup.number().min(1).required(), value: number;
label: Yup.string().required(), label: string;
}).nullable(), } | null;
supplier_id: Yup.number() supplier_id: number;
.required('Supplier wajib diisi!') credit_term: number | string;
.min(1, 'Supplier wajib diisi!') notes: string | null;
.typeError('Supplier wajib diisi!'), purchase_items: {
credit_term: Yup.number() warehouse?: {
.required('Termin kredit wajib diisi!') value: number;
.min(1, 'Termin kredit tidak boleh negatif!') label: string;
.typeError('Termin kredit harus berupa angka!'), } | null;
notes: Yup.string().optional().nullable(), warehouse_id: number;
purchase_items: Yup.array() product?: {
.of( value: number;
Yup.object({ label: string;
warehouse: Yup.object({ } | null;
value: Yup.number().min(1).required(), product_id: number;
label: Yup.string().required(), product_warehouse?: {
}).nullable(), value: number;
warehouse_id: Yup.number() label: string;
.required('Warehouse wajib diisi!') } | null;
.min(1, 'Warehouse wajib diisi!') product_warehouse_id: number;
.typeError('Warehouse harus berupa angka!'), sub_qty: number | string;
product: Yup.object({ }[];
value: Yup.number().min(1).required(), };
label: Yup.string().required(),
}).nullable(), export type PurchaseItemSchema = {
product_id: Yup.number() warehouse?: {
.required('Produk wajib diisi!') value: number;
.min(1, 'Produk wajib diisi!') label: string;
.typeError('Produk harus berupa angka!'), } | null;
product_warehouse: Yup.object({ warehouse_id: number;
value: Yup.number().min(1).required(), product?: {
label: Yup.string().required(), value: number;
}).nullable(), label: string;
product_warehouse_id: Yup.number().optional().nullable(), } | null;
sub_qty: Yup.number() product_id: number;
.required('Sub Qty wajib diisi!') product_warehouse?: {
.min(0.001, 'Sub Qty tidak boleh negatif!') value: number;
.typeError('Sub Qty harus berupa angka!'), label: string;
price: Yup.number() } | null;
.required('Harga wajib diisi!') product_warehouse_id: number;
.min(0, 'Harga tidak boleh negatif!') sub_qty: number | string;
.typeError('Harga harus berupa angka!'), };
})
) const PurchaseItemObjectSchema: Yup.ObjectSchema<PurchaseItemSchema> =
.min(1, 'Minimal harus ada 1 item pembelian!') Yup.object({
.required('Item pembelian wajib diisi!') warehouse: Yup.object({
.typeError('Item pembelian wajib diisi!'), value: Yup.number().min(1).required(),
}); label: Yup.string().required(),
}).nullable(),
warehouse_id: Yup.number()
.required('Warehouse wajib diisi!')
.min(1, 'Warehouse wajib diisi!')
.typeError('Warehouse harus berupa angka!'),
product: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
product_id: Yup.number()
.required('Produk wajib diisi!')
.min(1, 'Produk wajib diisi!')
.typeError('Produk harus berupa angka!'),
product_warehouse: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
product_warehouse_id: Yup.number()
.required('Product Warehouse wajib diisi!')
.min(0.001, 'Product Warehouse tidak boleh negatif!')
.typeError('Product Warehouse harus berupa angka!'),
sub_qty: Yup.number()
.required('Sub Qty wajib diisi!')
.min(0.001, 'Sub Qty tidak boleh negatif!')
.typeError('Sub Qty harus berupa angka!'),
});
export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSchemaType> =
Yup.object({
supplier: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
supplier_id: Yup.number()
.required('Supplier wajib diisi!')
.min(1, 'Supplier wajib diisi!')
.typeError('Supplier wajib diisi!'),
credit_term: Yup.number()
.required('Termin kredit wajib diisi!')
.min(1, 'Termin kredit tidak boleh negatif!')
.typeError('Termin kredit harus berupa angka!'),
notes: Yup.string().nullable().default(null),
purchase_items: Yup.array()
.of(PurchaseItemObjectSchema)
.min(1, 'Minimal harus ada 1 item pembelian!')
.required('Item pembelian wajib diisi!')
.typeError('Item pembelian wajib diisi!'),
});
export const UpdatePurchaseRequestFormSchema = PurchaseRequestFormSchema; export const UpdatePurchaseRequestFormSchema = PurchaseRequestFormSchema;
@@ -64,20 +108,8 @@ export type PurchaseRequestFormValues = Yup.InferType<
typeof PurchaseRequestFormSchema typeof PurchaseRequestFormSchema
>; >;
type PurchaseRequestFormData = {
supplier?: Supplier;
supplier_id?: number;
credit_term?: number;
notes?: string | null;
purchase_items?: (CreatePurchaseRequestPayload['purchase_items'][0] & {
warehouse?: Warehouse;
product?: Product;
product_warehouse?: ProductWarehouse;
})[];
};
export const getPurchaseRequestFormInitialValues = ( export const getPurchaseRequestFormInitialValues = (
initialValues?: PurchaseRequestFormData initialValues?: Purchase
): PurchaseRequestFormValues => ({ ): PurchaseRequestFormValues => ({
supplier: initialValues?.supplier supplier: initialValues?.supplier
? { ? {
@@ -85,45 +117,8 @@ export const getPurchaseRequestFormInitialValues = (
label: initialValues.supplier.name, label: initialValues.supplier.name,
} }
: null, : null,
supplier_id: initialValues?.supplier_id ?? 0, supplier_id: initialValues?.supplier?.id ?? 0,
credit_term: initialValues?.credit_term ?? 1, credit_term: initialValues?.credit_term ?? '',
notes: initialValues?.notes ?? '', notes: initialValues?.notes ?? null,
purchase_items: initialValues?.purchase_items?.map( purchase_items: [],
(item: NonNullable<PurchaseRequestFormData['purchase_items']>[0]) => ({
warehouse: item.warehouse
? {
value: item.warehouse.id,
label: item.warehouse.name,
}
: null,
warehouse_id: item.warehouse_id,
product: item.product
? {
value: item.product.id,
label: item.product.name,
}
: null,
product_id: item.product_id,
product_warehouse: item.product_warehouse
? {
value: item.product_warehouse.id,
label: item.product_warehouse.product.name,
}
: null,
product_warehouse_id: item.product_warehouse_id || null,
sub_qty: item.sub_qty,
price: item.price,
})
) ?? [
{
warehouse: null,
warehouse_id: 0,
product: null,
product_id: 0,
product_warehouse: null,
product_warehouse_id: null,
sub_qty: 0,
price: 0,
},
],
}); });
@@ -142,19 +142,32 @@ const PurchaseRequestForm = ({
validateOnBlur: true, validateOnBlur: true,
onSubmit: async (values) => { onSubmit: async (values) => {
const payload: CreatePurchaseRequestPayload = { const payload: CreatePurchaseRequestPayload = {
supplier_id: values.supplier_id || 0, supplier_id:
credit_term: values.credit_term || 0, typeof values.supplier_id === 'string'
? parseInt(values.supplier_id) || 0
: values.supplier_id || 0,
credit_term:
typeof values.credit_term === 'string'
? parseInt(values.credit_term) || 0
: values.credit_term || 0,
notes: values.notes || '', notes: values.notes || '',
purchase_items: (values.purchase_items || []).map((item) => ({ purchase_items: (values.purchase_items || []).map((item) => ({
warehouse_id: item.warehouse_id || 0, warehouse_id:
product_id: item.product_id || 0, typeof item.warehouse_id === 'string'
product_warehouse_id: item.product_warehouse_id || undefined, ? parseInt(item.warehouse_id) || 0
sub_qty: item.sub_qty || 0, : item.warehouse_id || 0,
total_qty: item.total_qty || 0, product_id:
price: typeof item.product_id === 'string'
typeof item.price === 'number' ? parseInt(item.product_id) || 0
? item.price : item.product_id || 0,
: parseFloat(item.price) || 0, product_warehouse_id:
typeof item.product_warehouse_id === 'string'
? parseInt(item.product_warehouse_id) || 0
: item.product_warehouse_id || 0,
sub_qty:
typeof item.sub_qty === 'string'
? parseFloat(item.sub_qty) || 0
: item.sub_qty || 0,
})), })),
}; };
@@ -193,14 +206,12 @@ const PurchaseRequestForm = ({
...(formik.values.purchase_items || []), ...(formik.values.purchase_items || []),
{ {
warehouse: null, warehouse: null,
warehouse_id: 0, warehouse_id: '',
product: null, product: null,
product_id: 0, product_id: '',
product_warehouse: null, product_warehouse: null,
product_warehouse_id: null, product_warehouse_id: null,
sub_qty: 0, sub_qty: '',
total_qty: 0,
price: 0,
}, },
]; ];
formik.setFieldValue('purchase_items', newPurchaseItems); formik.setFieldValue('purchase_items', newPurchaseItems);
@@ -285,12 +296,11 @@ const PurchaseRequestForm = ({
type='number' type='number'
placeholder='Masukkan Supplier ID' placeholder='Masukkan Supplier ID'
/> />
<TextInput <TextInput
required required
label='Jatuh tempo (hari)' label='Jatuh tempo (hari)'
name='credit_term' name='credit_term'
value={formik.values.credit_term} value={formik.values.credit_term || ''}
onChange={formik.handleChange} onChange={formik.handleChange}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
isError={ isError={
@@ -303,6 +313,28 @@ const PurchaseRequestForm = ({
placeholder='Masukkan Credit Term' placeholder='Masukkan Credit Term'
/> />
<TextInput
required
label='Area'
name='area_id'
onChange={(e) => {}}
onBlur={formik.handleBlur}
readOnly={type === 'detail'}
type='number'
placeholder='Pilih Area'
/>
<TextInput
required
label='Lokasi'
name='location_id'
onChange={(e) => {}}
onBlur={formik.handleBlur}
readOnly={type === 'detail'}
type='number'
placeholder='Pilih Lokasi'
/>
<div className={type === 'detail' ? 'col-span-1' : 'col-span-2'}> <div className={type === 'detail' ? 'col-span-1' : 'col-span-2'}>
<TextInput <TextInput
label='Notes' label='Notes'
@@ -356,29 +388,19 @@ const PurchaseRequestForm = ({
</th> </th>
)} )}
<th> <th>
Warehouse ID Gudang
<span className='text-error'>*</span> <span className='text-error'>*</span>
</th> </th>
<th> <th>
Product ID Item
<span className='text-error'>*</span> <span className='text-error'>*</span>
</th> </th>
<th> <th>
Product Warehouse ID Jumlah
<span className='text-error'>*</span>
</th>
<th>
Sub Qty
<span className='text-error'>*</span>
</th>
<th>
Total Qty
<span className='text-error'>*</span>
</th>
<th>
Price
<span className='text-error'>*</span> <span className='text-error'>*</span>
</th> </th>
<th>Estimasi Harga</th>
<th>Satuan</th>
{type !== 'detail' && <th>Action</th>} {type !== 'detail' && <th>Action</th>}
</tr> </tr>
</thead> </thead>
@@ -410,7 +432,7 @@ const PurchaseRequestForm = ({
<TextInput <TextInput
required required
name={`purchase_items.${idx}.warehouse_id`} name={`purchase_items.${idx}.warehouse_id`}
value={item.warehouse_id} value={item.warehouse_id || ''}
onChange={(e) => onChange={(e) =>
handlePurchaseItemChange( handlePurchaseItemChange(
idx, idx,
@@ -420,28 +442,7 @@ const PurchaseRequestForm = ({
} }
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
type='number' type='number'
placeholder='Warehouse ID' placeholder='Masukkan Warehouse ID'
readOnly={type === 'detail'}
className={{
wrapper: 'min-w-24',
}}
/>
</td>
<td>
<TextInput
required
name={`purchase_items.${idx}.product_id`}
value={item.product_id}
onChange={(e) =>
handlePurchaseItemChange(
idx,
'product_id',
e.target.value
)
}
onBlur={formik.handleBlur}
type='number'
placeholder='Product ID'
readOnly={type === 'detail'} readOnly={type === 'detail'}
className={{ className={{
wrapper: 'min-w-24', wrapper: 'min-w-24',
@@ -472,7 +473,7 @@ const PurchaseRequestForm = ({
<TextInput <TextInput
required required
name={`purchase_items.${idx}.sub_qty`} name={`purchase_items.${idx}.sub_qty`}
value={item.sub_qty} value={item.sub_qty || ''}
onChange={(e) => onChange={(e) =>
handlePurchaseItemChange( handlePurchaseItemChange(
idx, idx,
@@ -482,28 +483,7 @@ const PurchaseRequestForm = ({
} }
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
type='number' type='number'
placeholder='Sub Qty' placeholder='Masukkan kuantitas'
readOnly={type === 'detail'}
className={{
wrapper: 'min-w-24',
}}
/>
</td>
<td>
<TextInput
required
name={`purchase_items.${idx}.total_qty`}
value={item.total_qty}
onChange={(e) =>
handlePurchaseItemChange(
idx,
'total_qty',
e.target.value
)
}
onBlur={formik.handleBlur}
type='number'
placeholder='Total Qty'
readOnly={type === 'detail'} readOnly={type === 'detail'}
className={{ className={{
wrapper: 'min-w-24', wrapper: 'min-w-24',
@@ -514,7 +494,6 @@ const PurchaseRequestForm = ({
<TextInput <TextInput
required required
name={`purchase_items.${idx}.price`} name={`purchase_items.${idx}.price`}
value={item.price}
onChange={(e) => onChange={(e) =>
handlePurchaseItemChange( handlePurchaseItemChange(
idx, idx,
@@ -524,8 +503,22 @@ const PurchaseRequestForm = ({
} }
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
type='number' type='number'
placeholder='Price' className={{
readOnly={type === 'detail'} wrapper: 'min-w-24',
}}
disabled={true}
readOnly={true}
inputPrefix={'Rp'}
/>
</td>
<td>
<TextInput
required
name={`purchase_items.${idx}.uom`}
onBlur={formik.handleBlur}
type='number'
readOnly={true}
disabled={true}
className={{ className={{
wrapper: 'min-w-24', wrapper: 'min-w-24',
}} }}
+1 -2
View File
@@ -24,9 +24,8 @@ export type CreatePurchaseRequestPayload = {
purchase_items: { purchase_items: {
warehouse_id: number; warehouse_id: number;
product_id: number; product_id: number;
product_warehouse_id?: number | null; product_warehouse_id: number;
sub_qty: number; sub_qty: number;
price: number;
}[]; }[];
}; };