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 { Supplier } from '@/types/api/master-data/supplier';
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';
import { Purchase } from '@/types/api/purchase/purchase';
export const PurchaseRequestFormSchema = 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().optional().nullable(),
purchase_items: Yup.array()
.of(
Yup.object({
warehouse: Yup.object({
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().optional().nullable(),
sub_qty: Yup.number()
.required('Sub Qty wajib diisi!')
.min(0.001, 'Sub Qty tidak boleh negatif!')
.typeError('Sub Qty harus berupa angka!'),
price: Yup.number()
.required('Harga wajib diisi!')
.min(0, 'Harga tidak boleh negatif!')
.typeError('Harga harus berupa angka!'),
})
)
.min(1, 'Minimal harus ada 1 item pembelian!')
.required('Item pembelian wajib diisi!')
.typeError('Item pembelian wajib diisi!'),
});
type PurchaseRequestFormSchemaType = {
supplier?: {
value: number;
label: string;
} | null;
supplier_id: number;
credit_term: number | string;
notes: string | null;
purchase_items: {
warehouse?: {
value: number;
label: string;
} | null;
warehouse_id: number;
product?: {
value: number;
label: string;
} | null;
product_id: number;
product_warehouse?: {
value: number;
label: string;
} | null;
product_warehouse_id: number;
sub_qty: number | string;
}[];
};
export type PurchaseItemSchema = {
warehouse?: {
value: number;
label: string;
} | null;
warehouse_id: number;
product?: {
value: number;
label: string;
} | null;
product_id: number;
product_warehouse?: {
value: number;
label: string;
} | null;
product_warehouse_id: number;
sub_qty: number | string;
};
const PurchaseItemObjectSchema: Yup.ObjectSchema<PurchaseItemSchema> =
Yup.object({
warehouse: Yup.object({
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;
@@ -64,20 +108,8 @@ export type PurchaseRequestFormValues = Yup.InferType<
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 = (
initialValues?: PurchaseRequestFormData
initialValues?: Purchase
): PurchaseRequestFormValues => ({
supplier: initialValues?.supplier
? {
@@ -85,45 +117,8 @@ export const getPurchaseRequestFormInitialValues = (
label: initialValues.supplier.name,
}
: null,
supplier_id: initialValues?.supplier_id ?? 0,
credit_term: initialValues?.credit_term ?? 1,
notes: initialValues?.notes ?? '',
purchase_items: initialValues?.purchase_items?.map(
(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,
},
],
supplier_id: initialValues?.supplier?.id ?? 0,
credit_term: initialValues?.credit_term ?? '',
notes: initialValues?.notes ?? null,
purchase_items: [],
});
@@ -142,19 +142,32 @@ const PurchaseRequestForm = ({
validateOnBlur: true,
onSubmit: async (values) => {
const payload: CreatePurchaseRequestPayload = {
supplier_id: values.supplier_id || 0,
credit_term: values.credit_term || 0,
supplier_id:
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 || '',
purchase_items: (values.purchase_items || []).map((item) => ({
warehouse_id: item.warehouse_id || 0,
product_id: item.product_id || 0,
product_warehouse_id: item.product_warehouse_id || undefined,
sub_qty: item.sub_qty || 0,
total_qty: item.total_qty || 0,
price:
typeof item.price === 'number'
? item.price
: parseFloat(item.price) || 0,
warehouse_id:
typeof item.warehouse_id === 'string'
? parseInt(item.warehouse_id) || 0
: item.warehouse_id || 0,
product_id:
typeof item.product_id === 'string'
? parseInt(item.product_id) || 0
: item.product_id || 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 || []),
{
warehouse: null,
warehouse_id: 0,
warehouse_id: '',
product: null,
product_id: 0,
product_id: '',
product_warehouse: null,
product_warehouse_id: null,
sub_qty: 0,
total_qty: 0,
price: 0,
sub_qty: '',
},
];
formik.setFieldValue('purchase_items', newPurchaseItems);
@@ -285,12 +296,11 @@ const PurchaseRequestForm = ({
type='number'
placeholder='Masukkan Supplier ID'
/>
<TextInput
required
label='Jatuh tempo (hari)'
name='credit_term'
value={formik.values.credit_term}
value={formik.values.credit_term || ''}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
isError={
@@ -303,6 +313,28 @@ const PurchaseRequestForm = ({
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'}>
<TextInput
label='Notes'
@@ -356,29 +388,19 @@ const PurchaseRequestForm = ({
</th>
)}
<th>
Warehouse ID
Gudang
<span className='text-error'>*</span>
</th>
<th>
Product ID
Item
<span className='text-error'>*</span>
</th>
<th>
Product Warehouse ID
<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
Jumlah
<span className='text-error'>*</span>
</th>
<th>Estimasi Harga</th>
<th>Satuan</th>
{type !== 'detail' && <th>Action</th>}
</tr>
</thead>
@@ -410,7 +432,7 @@ const PurchaseRequestForm = ({
<TextInput
required
name={`purchase_items.${idx}.warehouse_id`}
value={item.warehouse_id}
value={item.warehouse_id || ''}
onChange={(e) =>
handlePurchaseItemChange(
idx,
@@ -420,28 +442,7 @@ const PurchaseRequestForm = ({
}
onBlur={formik.handleBlur}
type='number'
placeholder='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'
placeholder='Masukkan Warehouse ID'
readOnly={type === 'detail'}
className={{
wrapper: 'min-w-24',
@@ -472,7 +473,7 @@ const PurchaseRequestForm = ({
<TextInput
required
name={`purchase_items.${idx}.sub_qty`}
value={item.sub_qty}
value={item.sub_qty || ''}
onChange={(e) =>
handlePurchaseItemChange(
idx,
@@ -482,28 +483,7 @@ const PurchaseRequestForm = ({
}
onBlur={formik.handleBlur}
type='number'
placeholder='Sub Qty'
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'
placeholder='Masukkan kuantitas'
readOnly={type === 'detail'}
className={{
wrapper: 'min-w-24',
@@ -514,7 +494,6 @@ const PurchaseRequestForm = ({
<TextInput
required
name={`purchase_items.${idx}.price`}
value={item.price}
onChange={(e) =>
handlePurchaseItemChange(
idx,
@@ -524,8 +503,22 @@ const PurchaseRequestForm = ({
}
onBlur={formik.handleBlur}
type='number'
placeholder='Price'
readOnly={type === 'detail'}
className={{
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={{
wrapper: 'min-w-24',
}}
+1 -2
View File
@@ -24,9 +24,8 @@ export type CreatePurchaseRequestPayload = {
purchase_items: {
warehouse_id: number;
product_id: number;
product_warehouse_id?: number | null;
product_warehouse_id: number;
sub_qty: number;
price: number;
}[];
};