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,26 +1,54 @@
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;
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({ Yup.object({
warehouse: Yup.object({ warehouse: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
@@ -42,17 +70,33 @@ export const PurchaseRequestFormSchema = Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
}).nullable(), }).nullable(),
product_warehouse_id: Yup.number().optional().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() sub_qty: Yup.number()
.required('Sub Qty wajib diisi!') .required('Sub Qty wajib diisi!')
.min(0.001, 'Sub Qty tidak boleh negatif!') .min(0.001, 'Sub Qty tidak boleh negatif!')
.typeError('Sub Qty harus berupa angka!'), .typeError('Sub Qty harus berupa angka!'),
price: Yup.number() });
.required('Harga wajib diisi!')
.min(0, 'Harga tidak boleh negatif!') export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSchemaType> =
.typeError('Harga harus berupa angka!'), 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!') .min(1, 'Minimal harus ada 1 item pembelian!')
.required('Item pembelian wajib diisi!') .required('Item pembelian wajib diisi!')
.typeError('Item pembelian wajib diisi!'), .typeError('Item pembelian wajib diisi!'),
@@ -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;
}[]; }[];
}; };