feat(FE-208,212): update purchase request form to use 'qty' instead of 'quantity' and add credit term field

This commit is contained in:
rstubryan
2025-11-17 14:38:09 +07:00
parent 6467af35bc
commit 71a41d3f37
3 changed files with 125 additions and 235 deletions
@@ -7,6 +7,7 @@ type PurchaseRequestFormSchemaType = {
label: string; label: string;
} | null; } | null;
supplier_id: number; supplier_id: number;
credit_term: number;
area?: { area?: {
value: number; value: number;
label: string; label: string;
@@ -34,7 +35,7 @@ type PurchaseRequestFormSchemaType = {
label: string; label: string;
} | null; } | null;
product_id: number; product_id: number;
quantity: number | string; qty: number | string;
}[]; }[];
}; };
@@ -49,7 +50,7 @@ export type PurchaseItemSchema = {
label: string; label: string;
} | null; } | null;
product_id: number; product_id: number;
quantity: number | string; qty: number | string;
}; };
const PurchaseItemObjectSchema: Yup.ObjectSchema<PurchaseItemSchema> = const PurchaseItemObjectSchema: Yup.ObjectSchema<PurchaseItemSchema> =
@@ -70,7 +71,7 @@ const PurchaseItemObjectSchema: Yup.ObjectSchema<PurchaseItemSchema> =
.required('Produk wajib dipilih!') .required('Produk wajib dipilih!')
.min(1, 'Produk wajib dipilih!') .min(1, 'Produk wajib dipilih!')
.typeError('Produk wajib dipilih!'), .typeError('Produk wajib dipilih!'),
quantity: Yup.mixed<string | number>() qty: Yup.mixed<string | number>()
.required('Kuantitas wajib diisi!') .required('Kuantitas wajib diisi!')
.test( .test(
'is-valid-qty', 'is-valid-qty',
@@ -91,6 +92,10 @@ export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSche
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
}).nullable(), }).nullable(),
credit_term: Yup.number()
.required('Jangka waktu kredit wajib diisi!')
.min(0, 'Jangka waktu kredit tidak boleh kurang dari 0!')
.typeError('Jangka waktu kredit wajib diisi!'),
supplier_id: Yup.number() supplier_id: Yup.number()
.required('Supplier wajib dipilih!') .required('Supplier wajib dipilih!')
.min(1, 'Supplier wajib dipilih!') .min(1, 'Supplier wajib dipilih!')
@@ -143,6 +148,7 @@ export const getPurchaseRequestFormInitialValues = (
} }
: null, : null,
supplier_id: initialValues?.supplier?.id ?? 0, supplier_id: initialValues?.supplier?.id ?? 0,
credit_term: initialValues?.credit_term ?? 0,
area: initialValues?.area area: initialValues?.area
? { ? {
value: initialValues.area.id, value: initialValues.area.id,
@@ -157,7 +163,6 @@ export const getPurchaseRequestFormInitialValues = (
} }
: null, : null,
location_id: initialValues?.location?.id ?? 0, location_id: initialValues?.location?.id ?? 0,
notes: initialValues?.notes ?? null,
warehouse: initialValues?.warehouse warehouse: initialValues?.warehouse
? { ? {
value: initialValues.warehouse.id, value: initialValues.warehouse.id,
@@ -165,5 +170,6 @@ export const getPurchaseRequestFormInitialValues = (
} }
: undefined, : undefined,
warehouse_id: initialValues?.warehouse?.id ?? 0, warehouse_id: initialValues?.warehouse?.id ?? 0,
notes: initialValues?.notes ?? null,
items: [], items: [],
}); });
@@ -68,13 +68,13 @@ const PurchaseRequestForm = ({
product_warehouse_id: number; product_warehouse_id: number;
warehouse_id: number; warehouse_id: number;
warehouse_name: string; warehouse_name: string;
quantity: number; qty: number;
} }
// ===== UTILITY FUNCTIONS ===== // ===== UTILITY FUNCTIONS =====
const getPurchaseItemError = ( const getPurchaseItemError = (
idx: number, idx: number,
field: 'warehouse_id' | 'product_warehouse_id' | 'product_id' | 'quantity' field: 'warehouse_id' | 'product_warehouse_id' | 'product_id' | 'qty'
): { isError: boolean; errorMessage: string } => { ): { isError: boolean; errorMessage: string } => {
const touchedItem = formik.touched.items?.[idx]; const touchedItem = formik.touched.items?.[idx];
const errorItem = formik.errors.items?.[idx] as const errorItem = formik.errors.items?.[idx] as
@@ -191,32 +191,21 @@ const PurchaseRequestForm = ({
typeof values.supplier_id === 'string' typeof values.supplier_id === 'string'
? parseInt(values.supplier_id) || 0 ? parseInt(values.supplier_id) || 0
: values.supplier_id || 0, : values.supplier_id || 0,
credit_term: values.credit_term || 0,
notes: values.notes || '', notes: values.notes || '',
area_id: items: (values.items || []).map((item) => ({
typeof values.area_id === 'string'
? parseInt(values.area_id) || 0
: values.area_id || 0,
location_id:
typeof values.location_id === 'string'
? parseInt(values.location_id) || 0
: values.location_id || 0,
warehouse_id: warehouse_id:
typeof values.warehouse_id === 'string' typeof values.warehouse_id === 'string'
? parseInt(values.warehouse_id) || 0 ? parseInt(values.warehouse_id) || 0
: values.warehouse_id || 0, : values.warehouse_id || 0,
items: (values.items || []).map((item) => ({
product_warehouse_id:
typeof item.product_warehouse_id === 'string'
? parseInt(item.product_warehouse_id) || 0
: item.product_warehouse_id || 0,
product_id: product_id:
typeof item.product_id === 'string' typeof item.product_id === 'string'
? parseInt(item.product_id) || 0 ? parseInt(item.product_id) || 0
: item.product_id || 0, : item.product_id || 0,
quantity: qty:
typeof item.quantity === 'string' typeof item.qty === 'string'
? parseFloat(item.quantity) || 0 ? parseFloat(item.qty) || 0
: item.quantity || 0, : item.qty || 0,
})), })),
}; };
@@ -261,7 +250,7 @@ const PurchaseRequestForm = ({
product_warehouse_id: pw.id, product_warehouse_id: pw.id,
warehouse_id: pw.warehouse.id, warehouse_id: pw.warehouse.id,
warehouse_name: pw.warehouse.name, warehouse_name: pw.warehouse.name,
quantity: pw.quantity, qty: pw.quantity,
})) || [] })) || []
); );
}, [productWarehouses]); }, [productWarehouses]);
@@ -295,6 +284,7 @@ const PurchaseRequestForm = ({
}); });
return data; return data;
}, [productsResponse]); }, [productsResponse]);
const locationsUrl = useMemo(() => { const locationsUrl = useMemo(() => {
const params = new URLSearchParams({ const params = new URLSearchParams({
search: locationSelectInputValue, search: locationSelectInputValue,
@@ -436,7 +426,7 @@ const PurchaseRequestForm = ({
product_warehouse_id: null, product_warehouse_id: null,
product: null, product: null,
product_id: '', product_id: '',
quantity: '', qty: '',
}, },
]; ];
formik.setFieldValue('items', newPurchaseItems); formik.setFieldValue('items', newPurchaseItems);
@@ -460,13 +450,13 @@ const PurchaseRequestForm = ({
// ===== PURCHASE ITEM OPERATIONS ===== // ===== PURCHASE ITEM OPERATIONS =====
const handlePurchaseItemChange = ( const handlePurchaseItemChange = (
idx: number, idx: number,
field: 'quantity', field: 'qty',
value: string | number value: string | number
) => { ) => {
if (field === 'quantity') { if (field === 'qty') {
const numValue = const numValue =
typeof value === 'string' ? parseFloat(value) || 0 : value; typeof value === 'string' ? parseFloat(value) || 0 : value;
formik.setFieldValue(`items.${idx}.quantity`, numValue); formik.setFieldValue(`items.${idx}.qty`, numValue);
} }
}; };
@@ -519,28 +509,28 @@ const PurchaseRequestForm = ({
isClearable isClearable
/> />
{/*<NumberInput*/} <NumberInput
{/* required={!!formik.values.supplier_id}*/} required={!!formik.values.supplier_id}
{/* 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={
{/* formik.touched.credit_term &&*/} formik.touched.credit_term &&
{/* Boolean(formik.errors.credit_term)*/} Boolean(formik.errors.credit_term)
{/* }*/} }
{/* errorMessage={formik.errors.credit_term as string}*/} errorMessage={formik.errors.credit_term as string}
{/* readOnly={type === 'detail' || !formik.values.supplier_id}*/} readOnly={type === 'detail' || !formik.values.supplier_id}
{/* disabled={type === 'detail' || !formik.values.supplier_id}*/} disabled={type === 'detail' || !formik.values.supplier_id}
{/* allowNegative={false}*/} allowNegative={false}
{/* decimalScale={0}*/} decimalScale={0}
{/* placeholder={*/} placeholder={
{/* !formik.values.supplier_id*/} !formik.values.supplier_id
{/* ? 'Pilih Vendor terlebih dahulu'*/} ? 'Pilih Vendor terlebih dahulu'
{/* : 'Masukkan jumlah hari jatuh tempo'*/} : 'Masukkan jumlah hari jatuh tempo'
{/* }*/} }
{/*/>*/} />
<SelectInput <SelectInput
required required
@@ -579,62 +569,6 @@ const PurchaseRequestForm = ({
key={`location-${formik.values.area_id}`} key={`location-${formik.values.area_id}`}
/> />
<SelectInput
required
label='Gudang'
placeholder={
!formik.values.area_id
? 'Pilih Area terlebih dahulu'
: formik.values.location_id
? 'Pilih Gudang...'
: 'Pilih Area dan Lokasi terlebih dahulu'
}
value={formik.values.warehouse}
onChange={(val) => {
const warehouse = val as OptionType | null;
formik.setFieldTouched('warehouse', true);
formik.setFieldValue('warehouse', warehouse);
formik.setFieldTouched('warehouse_id', true);
formik.setFieldValue(
'warehouse_id',
(warehouse as OptionType)?.value || 0
);
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(
`items.${idx}.product_warehouse`,
null
);
formik.setFieldValue(
`items.${idx}.product_warehouse_id`,
null
);
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, '');
});
}
}}
options={warehouseOptions}
onInputChange={setWarehouseSelectInputValue}
isLoading={isLoadingWarehouses}
isError={
formik.touched.warehouse &&
Boolean(formik.errors.warehouse_id)
}
errorMessage={formik.errors.warehouse_id as string}
isDisabled={
type === 'detail' ||
!formik.values.area_id ||
!formik.values.location_id
}
isClearable={
type !== 'detail' &&
!!formik.values.area_id &&
!!formik.values.location_id
}
key={`warehouse-${formik.values.area_id}-${formik.values.location_id}`}
/>
<div className={'col-span-2'}> <div className={'col-span-2'}>
<TextInput <TextInput
label='Notes' label='Notes'
@@ -691,6 +625,10 @@ const PurchaseRequestForm = ({
/> />
</th> </th>
)} )}
<th>
Gudang
<span className='text-error'>*</span>
</th>
<th> <th>
Item Item
<span className='text-error'>*</span> <span className='text-error'>*</span>
@@ -733,113 +671,68 @@ const PurchaseRequestForm = ({
/> />
</td> </td>
)} )}
{/*<td>*/} <td>
{/* <SelectInput*/} <SelectInput
{/* required*/} required
{/* value={item.warehouse}*/} placeholder={
{/* key={`warehouse-${idx}`}*/} !formik.values.area_id
{/* onChange={(val) => {*/} ? 'Pilih Area terlebih dahulu'
{/* const warehouse = val as OptionType | null;*/} : formik.values.location_id
{/* formik.setFieldValue(*/} ? 'Pilih Gudang...'
{/* `purchase_items.${idx}.warehouse`,*/} : 'Pilih Area dan Lokasi terlebih dahulu'
{/* warehouse*/} }
{/* );*/} value={formik.values.warehouse}
{/* formik.setFieldValue(*/} onChange={(val) => {
{/* `purchase_items.${idx}.warehouse_id`,*/} const warehouse = val as OptionType | null;
{/* (warehouse as OptionType)?.value || 0*/} formik.setFieldTouched('warehouse', true);
{/* );*/} formik.setFieldValue('warehouse', warehouse);
{/* formik.setFieldTouched(*/} formik.setFieldTouched('warehouse_id', true);
{/* `purchase_items.${idx}.product_warehouse`,*/} formik.setFieldValue(
{/* false*/} 'warehouse_id',
{/* );*/} (warehouse as OptionType)?.value || 0
{/* formik.setFieldValue(*/} );
{/* `purchase_items.${idx}.product_warehouse`,*/} if (formik.values.items) {
{/* null*/} formik.values.items.forEach((_, idx) => {
{/* );*/} formik.setFieldValue(
{/* formik.setFieldTouched(*/} `items.${idx}.product_warehouse`,
{/* `purchase_items.${idx}.product_warehouse_id`,*/} null
{/* false*/} );
{/* );*/} formik.setFieldValue(
{/* formik.setFieldValue(*/} `items.${idx}.product_warehouse_id`,
{/* `purchase_items.${idx}.product_warehouse_id`,*/} null
{/* 0*/} );
{/* );*/} formik.setFieldValue(
{/* formik.setFieldTouched(*/} `items.${idx}.product`,
{/* `purchase_items.${idx}.product_id`,*/} null
{/* false*/} );
{/* );*/} formik.setFieldValue(
{/* formik.setFieldValue(*/} `items.${idx}.product_id`,
{/* `purchase_items.${idx}.product_id`,*/} ''
{/* 0*/} );
{/* );*/} });
{/* }}*/} }
{/* options={warehouseOptions}*/} }}
{/* onInputChange={setWarehouseSelectInputValue}*/} options={warehouseOptions}
{/* isLoading={isLoadingWarehouses}*/} onInputChange={setWarehouseSelectInputValue}
{/* isError={*/} isLoading={isLoadingWarehouses}
{/* getPurchaseItemError(idx, 'warehouse_id').isError*/} isError={
{/* }*/} formik.touched.warehouse &&
{/* errorMessage={*/} Boolean(formik.errors.warehouse_id)
{/* getPurchaseItemError(idx, 'warehouse_id')*/} }
{/* .errorMessage*/} errorMessage={formik.errors.warehouse_id as string}
{/* }*/} isDisabled={
{/* isDisabled={type === 'detail'}*/} type === 'detail' ||
{/* isClearable*/} !formik.values.area_id ||
{/* placeholder='Pilih Gudang'*/} !formik.values.location_id
{/* className={{*/} }
{/* wrapper: 'min-w-32',*/} isClearable={
{/* }}*/} type !== 'detail' &&
{/* />*/} !!formik.values.area_id &&
{/*</td>*/} !!formik.values.location_id
{/*<td>*/} }
{/* <SelectInput*/} key={`warehouse-${formik.values.area_id}-${formik.values.location_id}`}
{/* required*/} />
{/* value={item.product_warehouse}*/} </td>
{/* key={`product-warehouse-${idx}-${item.warehouse_id}`}*/}
{/* onChange={(val) => {*/}
{/* const productWarehouse =*/}
{/* val as ProductWarehouseOptionType | null;*/}
{/* formik.setFieldValue(*/}
{/* `purchase_items.${idx}.product_warehouse`,*/}
{/* productWarehouse*/}
{/* );*/}
{/* formik.setFieldValue(*/}
{/* `purchase_items.${idx}.product_warehouse_id`,*/}
{/* (productWarehouse as ProductWarehouseOptionType)*/}
{/* ?.value || 0*/}
{/* );*/}
{/* const productId =*/}
{/* (productWarehouse as ProductWarehouseOptionType)*/}
{/* ?.product_id || 0;*/}
{/* formik.setFieldValue(*/}
{/* `purchase_items.${idx}.product_id`,*/}
{/* productId*/}
{/* );*/}
{/* }}*/}
{/* options={getProductWarehouseOptionsForItem(*/}
{/* item.warehouse_id*/}
{/* )}*/}
{/* isLoading={isLoadingProductWarehouses}*/}
{/* isError={*/}
{/* getPurchaseItemError(idx, 'product_warehouse_id')*/}
{/* .isError*/}
{/* }*/}
{/* errorMessage={*/}
{/* getPurchaseItemError(idx, 'product_warehouse_id')*/}
{/* .errorMessage*/}
{/* }*/}
{/* isDisabled={type === 'detail' || !item.warehouse_id}*/}
{/* isClearable={type !== 'detail' && !!item.warehouse_id}*/}
{/* placeholder={*/}
{/* !item.warehouse_id*/}
{/* ? 'Pilih Gudang terlebih dahulu'*/}
{/* : 'Pilih Produk'*/}
{/* }*/}
{/* className={{*/}
{/* wrapper: 'min-w-32',*/}
{/* }}*/}
{/* />*/}
{/*</td>*/}
<td> <td>
<SelectInput <SelectInput
required required
@@ -895,25 +788,19 @@ const PurchaseRequestForm = ({
<td> <td>
<NumberInput <NumberInput
required required
name={`items.${idx}.quantity`} name={`items.${idx}.qty`}
value={item.quantity || ''} value={item.qty || ''}
onChange={(e) => onChange={(e) =>
handlePurchaseItemChange( handlePurchaseItemChange(idx, 'qty', e.target.value)
idx,
'quantity',
e.target.value
)
} }
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
placeholder='Masukkan kuantitas' placeholder='Masukkan kuantitas'
readOnly={type === 'detail'} readOnly={type === 'detail'}
allowNegative={false} allowNegative={false}
decimalScale={0} decimalScale={0}
isError={ isError={getPurchaseItemError(idx, 'qty').isError}
getPurchaseItemError(idx, 'quantity').isError
}
errorMessage={ errorMessage={
getPurchaseItemError(idx, 'quantity').errorMessage getPurchaseItemError(idx, 'qty').errorMessage
} }
className={{ className={{
wrapper: 'min-w-24', wrapper: 'min-w-24',
@@ -928,9 +815,7 @@ const PurchaseRequestForm = ({
item.product_id && productData[item.product_id] item.product_id && productData[item.product_id]
? ( ? (
productData[item.product_id].product_price * productData[item.product_id].product_price *
(parseFloat( (parseFloat(item.qty?.toString() || '0') || 0)
item.quantity?.toString() || '0'
) || 0)
).toLocaleString('en-US') ).toLocaleString('en-US')
: '' : ''
} }
+4 -5
View File
@@ -13,6 +13,7 @@ export type PurchaseItem = {
product: Product; product: Product;
product_warehouse: ProductWarehouse; product_warehouse: ProductWarehouse;
quantity: number; quantity: number;
qty: number;
sub_qty: number; sub_qty: number;
total_qty: number; total_qty: number;
total_used: number; total_used: number;
@@ -47,14 +48,12 @@ export type Purchase = BaseMetadata & BasePurchase;
export type CreatePurchaseRequestPayload = { export type CreatePurchaseRequestPayload = {
supplier_id: number; supplier_id: number;
area_id: number; credit_term: number;
location_id: number;
warehouse_id: number;
notes?: string | null; notes?: string | null;
items: { items: {
product_warehouse_id: number; warehouse_id: number;
product_id: number; product_id: number;
quantity: number; qty: number;
}[]; }[];
}; };