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