mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 15:55:48 +00:00
refactor(FE-208,212): update PurchaseRequestForm schema and validation, enhance credit term handling and improve error messages for required fields
This commit is contained in:
@@ -17,7 +17,7 @@ type PurchaseRequestFormSchemaType = {
|
|||||||
label: string;
|
label: string;
|
||||||
} | null;
|
} | null;
|
||||||
location_id: number;
|
location_id: number;
|
||||||
credit_term: number | string;
|
credit_term: number | string | null;
|
||||||
notes: string | null;
|
notes: string | null;
|
||||||
purchase_items: {
|
purchase_items: {
|
||||||
warehouse?: {
|
warehouse?: {
|
||||||
@@ -65,29 +65,38 @@ const PurchaseItemObjectSchema: Yup.ObjectSchema<PurchaseItemSchema> =
|
|||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
warehouse_id: Yup.number()
|
warehouse_id: Yup.number()
|
||||||
.required('Warehouse wajib diisi!')
|
.required('Gudang wajib dipilih!')
|
||||||
.min(1, 'Warehouse wajib diisi!')
|
.min(1, 'Gudang wajib dipilih!')
|
||||||
.typeError('Warehouse harus berupa angka!'),
|
.typeError('Gudang wajib dipilih'),
|
||||||
product: Yup.object({
|
product: 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_id: Yup.number()
|
product_id: Yup.number()
|
||||||
.required('Produk wajib diisi!')
|
.required('Produk wajib dipilih!')
|
||||||
.min(1, 'Produk wajib diisi!')
|
.min(1, 'Produk wajib dipilih!')
|
||||||
.typeError('Produk harus berupa angka!'),
|
.typeError('Produk wajib dipilih!'),
|
||||||
product_warehouse: Yup.object({
|
product_warehouse: 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()
|
product_warehouse_id: Yup.number()
|
||||||
.required('Product Warehouse wajib diisi!')
|
.required('Produk wajib dipilih!')
|
||||||
.min(0.001, 'Product Warehouse tidak boleh negatif!')
|
.min(1, 'Produk wajib dipilih!')
|
||||||
.typeError('Product Warehouse harus berupa angka!'),
|
.typeError('Produk wajib dipilih!'),
|
||||||
sub_qty: Yup.number()
|
sub_qty: Yup.mixed<string | number>()
|
||||||
.required('Sub Qty wajib diisi!')
|
.required('Sub Qty wajib diisi!')
|
||||||
.min(0.001, 'Sub Qty tidak boleh negatif!')
|
.test(
|
||||||
.typeError('Sub Qty harus berupa angka!'),
|
'is-valid-sub-qty',
|
||||||
|
'Kuantitas harus berupa angka lebih dari 0!',
|
||||||
|
function (value) {
|
||||||
|
if (value === '' || value === null || value === undefined)
|
||||||
|
return false;
|
||||||
|
const numValue =
|
||||||
|
typeof value === 'string' ? parseFloat(value) : value;
|
||||||
|
return !isNaN(numValue) && numValue > 0;
|
||||||
|
}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSchemaType> =
|
export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSchemaType> =
|
||||||
@@ -97,35 +106,50 @@ export const PurchaseRequestFormSchema: Yup.ObjectSchema<PurchaseRequestFormSche
|
|||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
supplier_id: Yup.number()
|
supplier_id: Yup.number()
|
||||||
.required('Supplier wajib diisi!')
|
.required('Supplier wajib dipilih!')
|
||||||
.min(1, 'Supplier wajib diisi!')
|
.test('is-valid-supplier', 'Supplier wajib dipilih!', function (value) {
|
||||||
.typeError('Supplier wajib diisi!'),
|
if (!this.parent.supplier) return true;
|
||||||
|
return Boolean(value && value > 0);
|
||||||
|
})
|
||||||
|
.typeError('Supplier wajib dipilih!'),
|
||||||
area: Yup.object({
|
area: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
area_id: Yup.number()
|
area_id: Yup.number()
|
||||||
.required('Area wajib diisi!')
|
.required('Area wajib dipilih!')
|
||||||
.test('is-valid-area', 'Area wajib diisi!', function (value) {
|
.test('is-valid-area', 'Area wajib dipilih!', function (value) {
|
||||||
if (!this.parent.area) return true;
|
if (!this.parent.area) return true;
|
||||||
return Boolean(value && value > 0);
|
return Boolean(value && value > 0);
|
||||||
})
|
})
|
||||||
.typeError('Area wajib diisi!'),
|
.typeError('Area wajib dipilih!'),
|
||||||
location: Yup.object({
|
location: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number().min(1).required(),
|
||||||
label: Yup.string().required(),
|
label: Yup.string().required(),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
location_id: Yup.number()
|
location_id: Yup.number()
|
||||||
.required('Lokasi wajib diisi!')
|
.required('Lokasi wajib dipilih!')
|
||||||
.test('is-valid-location', 'Lokasi wajib diisi!', function (value) {
|
.test('is-valid-location', 'Lokasi wajib dipilih!', function (value) {
|
||||||
if (!this.parent.location) return true;
|
if (!this.parent.location) return true;
|
||||||
return Boolean(value && value > 0);
|
return Boolean(value && value > 0);
|
||||||
})
|
})
|
||||||
.typeError('Lokasi wajib diisi!'),
|
.typeError('Lokasi wajib dipilih!'),
|
||||||
credit_term: Yup.number()
|
credit_term: Yup.lazy((value, context) => {
|
||||||
.required('Termin kredit wajib diisi!')
|
const supplier_id = context.parent.supplier_id;
|
||||||
.min(1, 'Termin kredit tidak boleh negatif!')
|
const hasSupplier = supplier_id && supplier_id > 0;
|
||||||
.typeError('Termin kredit harus berupa angka!'),
|
|
||||||
|
if (!hasSupplier) {
|
||||||
|
return Yup.mixed<string | number>()
|
||||||
|
.nullable()
|
||||||
|
.default(null)
|
||||||
|
.transform(() => null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Yup.number()
|
||||||
|
.required('Jumlah hari jatuh tempo wajib diisi!')
|
||||||
|
.min(1, 'Jumlah hari jatuh tempo minimal 1 hari!')
|
||||||
|
.typeError('Jumlah hari jatuh tempo harus berupa angka!');
|
||||||
|
}),
|
||||||
notes: Yup.string().nullable().default(null),
|
notes: Yup.string().nullable().default(null),
|
||||||
purchase_items: Yup.array()
|
purchase_items: Yup.array()
|
||||||
.of(PurchaseItemObjectSchema)
|
.of(PurchaseItemObjectSchema)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@@ -8,6 +8,7 @@ import { Icon } from '@iconify/react';
|
|||||||
import { toast } from 'react-hot-toast';
|
import { toast } from 'react-hot-toast';
|
||||||
import Button from '@/components/Button';
|
import Button from '@/components/Button';
|
||||||
import TextInput from '@/components/input/TextInput';
|
import TextInput from '@/components/input/TextInput';
|
||||||
|
import NumberInput from '@/components/input/NumberInput';
|
||||||
import SelectInput, {
|
import SelectInput, {
|
||||||
OptionType,
|
OptionType,
|
||||||
useSelect,
|
useSelect,
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
LocationApi,
|
LocationApi,
|
||||||
WarehouseApi,
|
WarehouseApi,
|
||||||
} from '@/services/api/master-data';
|
} from '@/services/api/master-data';
|
||||||
|
import { Supplier } from '@/types/api/master-data/supplier';
|
||||||
import { ProductWarehouseApi } from '@/services/api/inventory';
|
import { ProductWarehouseApi } from '@/services/api/inventory';
|
||||||
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
import { isResponseSuccess, isResponseError } from '@/lib/api-helper';
|
||||||
import { PurchaseApi } from '@/services/api/purchasing';
|
import { PurchaseApi } from '@/services/api/purchasing';
|
||||||
@@ -96,6 +98,15 @@ const PurchaseRequestForm = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getSupplierById = (supplierId: number): Supplier | null => {
|
||||||
|
if (!isResponseSuccess(supplierRawData)) return null;
|
||||||
|
return (
|
||||||
|
supplierRawData?.data.find(
|
||||||
|
(supplier: Supplier) => supplier.id === supplierId
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// ===== FORM HANDLERS =====
|
// ===== FORM HANDLERS =====
|
||||||
const createPurchaseRequestHandler = useCallback(
|
const createPurchaseRequestHandler = useCallback(
|
||||||
async (payload: CreatePurchaseRequestPayload) => {
|
async (payload: CreatePurchaseRequestPayload) => {
|
||||||
@@ -155,7 +166,8 @@ const PurchaseRequestForm = ({
|
|||||||
setInputValue: setSupplierSelectInputValue,
|
setInputValue: setSupplierSelectInputValue,
|
||||||
options: supplierOptions,
|
options: supplierOptions,
|
||||||
isLoadingOptions: isLoadingSuppliers,
|
isLoadingOptions: isLoadingSuppliers,
|
||||||
} = useSelect(SupplierApi.basePath, 'id', 'name', 'search');
|
rawData: supplierRawData,
|
||||||
|
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name', 'search');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
inputValue: areaSelectInputValue,
|
inputValue: areaSelectInputValue,
|
||||||
@@ -353,6 +365,20 @@ const PurchaseRequestForm = ({
|
|||||||
formik.setFieldValue('supplier', supplier);
|
formik.setFieldValue('supplier', supplier);
|
||||||
formik.setFieldTouched('supplier_id', true);
|
formik.setFieldTouched('supplier_id', true);
|
||||||
formik.setFieldValue('supplier_id', (supplier as OptionType)?.value || 0);
|
formik.setFieldValue('supplier_id', (supplier as OptionType)?.value || 0);
|
||||||
|
|
||||||
|
if (supplier?.value) {
|
||||||
|
const supplierId =
|
||||||
|
typeof supplier.value === 'string'
|
||||||
|
? parseInt(supplier.value)
|
||||||
|
: supplier.value;
|
||||||
|
const supplierData = getSupplierById(supplierId);
|
||||||
|
if (supplierData?.due_date) {
|
||||||
|
formik.setFieldTouched('credit_term', true);
|
||||||
|
formik.setFieldValue('credit_term', supplierData.due_date.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formik.setFieldValue('credit_term', '');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
|
||||||
@@ -509,15 +535,15 @@ const PurchaseRequestForm = ({
|
|||||||
onInputChange={setSupplierSelectInputValue}
|
onInputChange={setSupplierSelectInputValue}
|
||||||
isLoading={isLoadingSuppliers}
|
isLoading={isLoadingSuppliers}
|
||||||
isError={
|
isError={
|
||||||
formik.touched.supplier_id &&
|
formik.touched.supplier && Boolean(formik.errors.supplier_id)
|
||||||
Boolean(formik.errors.supplier_id)
|
|
||||||
}
|
}
|
||||||
errorMessage={formik.errors.supplier_id as string}
|
errorMessage={formik.errors.supplier_id as string}
|
||||||
isDisabled={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
isClearable
|
isClearable
|
||||||
/>
|
/>
|
||||||
<TextInput
|
|
||||||
required
|
<NumberInput
|
||||||
|
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 || ''}
|
||||||
@@ -528,9 +554,15 @@ const PurchaseRequestForm = ({
|
|||||||
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'}
|
readOnly={type === 'detail' || !formik.values.supplier_id}
|
||||||
type='number'
|
disabled={type === 'detail' || !formik.values.supplier_id}
|
||||||
placeholder='Masukkan Credit Term'
|
allowNegative={false}
|
||||||
|
decimalScale={0}
|
||||||
|
placeholder={
|
||||||
|
!formik.values.supplier_id
|
||||||
|
? 'Pilih Vendor terlebih dahulu'
|
||||||
|
: 'Masukkan jumlah hari jatuh tempo'
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SelectInput
|
<SelectInput
|
||||||
@@ -542,9 +574,7 @@ const PurchaseRequestForm = ({
|
|||||||
options={areaOptions}
|
options={areaOptions}
|
||||||
onInputChange={setAreaSelectInputValue}
|
onInputChange={setAreaSelectInputValue}
|
||||||
isLoading={isLoadingAreas}
|
isLoading={isLoadingAreas}
|
||||||
isError={
|
isError={formik.touched.area && Boolean(formik.errors.area_id)}
|
||||||
formik.touched.area && Boolean(formik.errors.area_id)
|
|
||||||
}
|
|
||||||
errorMessage={formik.errors.area_id as string}
|
errorMessage={formik.errors.area_id as string}
|
||||||
isDisabled={type === 'detail'}
|
isDisabled={type === 'detail'}
|
||||||
isClearable
|
isClearable
|
||||||
|
|||||||
Reference in New Issue
Block a user