refactor(FE-170,174): restructure schema and validation for body weights, stocks, and depletions; improve handling of input values

This commit is contained in:
rstubryan
2025-11-02 23:04:54 +07:00
parent 4afeded080
commit e116311dc2
2 changed files with 207 additions and 144 deletions
@@ -7,107 +7,165 @@ import {
CreateGradingPayload,
} from '@/types/api/production/recording';
export const RecordingGrowingFormSchema = Yup.object({
project_flock_kandang: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
project_flock_kandangs_id: Yup.number()
.default(0)
.typeError('Project Flock Kandang wajib diisi!')
.test(
'is-valid-project-flock-kandang',
'Project Flock Kandang wajib diisi!',
(value) => value !== undefined && value !== null && value > 0
)
.required('Project Flock Kandang wajib diisi!')
.test(
'not-already-recorded',
'Project Flock ini sudah direcord hari ini!',
function (value) {
const recordedProjectFlockIds = this.options.context
?.recordedProjectFlockIds as Set<number>;
const formType = this.options.context?.type as
| 'add'
| 'edit'
| 'detail';
if (formType !== 'add') return true;
if (value && recordedProjectFlockIds?.has(value)) {
return false;
}
return true;
}
),
body_weights: Yup.array()
.of(
Yup.object({
weight: Yup.number()
.required('Berat ayam total wajib diisi!')
.min(1, 'Berat ayam total minimal 1 gram!')
.typeError('Berat ayam total harus berupa angka!'),
avg_weight: Yup.number()
.required('Berat ayam rata-rata wajib diisi!')
.min(1, 'Berat ayam rata-rata minimal 1 gram!')
.typeError('Berat ayam rata-rata harus berupa angka!'),
qty: Yup.number()
.required('Jumlah ayam wajib diisi!')
.min(1, 'Jumlah ayam minimal 1 ekor!')
.typeError('Jumlah ayam harus berupa angka!')
.default(1),
})
)
.min(1, 'Minimal harus ada 1 data bobot badan!')
.required('Data bobot badan wajib diisi!'),
stocks: Yup.array()
.of(
Yup.object({
product_warehouse_id: Yup.number()
.required('Produk wajib diisi!')
.min(1, 'Produk wajib diisi!')
.typeError('Produk harus berupa angka!'),
usage_qty: Yup.number()
.required('Jumlah penggunaan wajib diisi!')
.min(1, 'Jumlah penggunaan tidak boleh 0!')
.typeError('Jumlah penggunaan harus berupa angka!'),
})
)
.min(1, 'Minimal harus ada 1 data stok!')
.required('Data stok wajib diisi!'),
depletions: Yup.array()
.of(
Yup.object({
product_warehouse_id: Yup.number()
.required('Produk depletions wajib diisi!')
.min(1, 'Produk depletions wajib diisi!')
.typeError('Produk depletions harus berupa angka!'),
qty: Yup.number()
.required('Jumlah depletions wajib diisi!')
.min(1, 'Jumlah depletions minimal 1!')
.typeError('Jumlah depletions harus berupa angka!'),
})
)
.min(1, 'Minimal harus ada 1 data depletions!')
.required('Data depletions wajib diisi!'),
type RecordingGrowingFormSchemaType = {
project_flock_kandang: {
value: number;
label: string;
} | null;
project_flock_kandangs_id: number;
body_weights: {
weight: number | string;
avg_weight: number | string;
qty: number | string;
}[];
stocks: {
product_warehouse_id: number;
usage_qty: number | string;
}[];
depletions: {
product_warehouse_id: number;
qty: number | string;
}[];
};
type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & {
eggs: {
product_warehouse_id: number;
qty: number | string;
}[];
};
type RecordingGradingFormSchemaType = {
recording_egg_id: number;
eggs_grading: {
grade: string;
qty: number | string;
}[];
};
export type BodyWeightSchema = {
weight: number | string;
avg_weight: number | string;
qty: number | string;
};
export type StockSchema = {
product_warehouse_id: number;
usage_qty: number | string;
};
export type DepletionSchema = {
product_warehouse_id: number;
qty: number | string;
};
export type EggSchema = {
product_warehouse_id: number;
qty: number | string;
};
const BodyWeightObjectSchema: Yup.ObjectSchema<BodyWeightSchema> = Yup.object({
weight: Yup.number()
.required('Berat ayam total wajib diisi!')
.min(1, 'Berat ayam total minimal 1 gram!')
.typeError('Berat ayam total harus berupa angka!'),
avg_weight: Yup.number()
.required('Berat ayam rata-rata wajib diisi!')
.min(1, 'Berat ayam rata-rata minimal 1 gram!')
.typeError('Berat ayam rata-rata harus berupa angka!'),
qty: Yup.number()
.required('Jumlah ayam wajib diisi!')
.min(1, 'Jumlah ayam minimal 1 ekor!')
.typeError('Jumlah ayam harus berupa angka!'),
});
export const RecordingLayingFormSchema = RecordingGrowingFormSchema.shape({
eggs: Yup.array()
.of(
Yup.object({
product_warehouse_id: Yup.number()
.required('Produk telur wajib diisi!')
.min(1, 'Produk telur wajib diisi!')
.typeError('Produk telur harus berupa angka!'),
qty: Yup.number()
.required('Jumlah telur wajib diisi!')
.min(1, 'Jumlah telur tidak boleh 0!')
.typeError('Jumlah telur harus berupa angka!'),
})
)
.min(1, 'Minimal harus ada 1 data telur!')
.required('Data telur wajib diisi!'),
const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
product_warehouse_id: Yup.number()
.required('Produk wajib diisi!')
.min(1, 'Produk wajib diisi!')
.typeError('Produk harus berupa angka!'),
usage_qty: Yup.number()
.required('Jumlah penggunaan wajib diisi!')
.min(1, 'Jumlah penggunaan tidak boleh 0!')
.typeError('Jumlah penggunaan harus berupa angka!'),
});
const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
product_warehouse_id: Yup.number()
.required('Produk depletions wajib diisi!')
.min(1, 'Produk depletions wajib diisi!')
.typeError('Produk depletions harus berupa angka!'),
qty: Yup.number()
.required('Jumlah depletions wajib diisi!')
.min(1, 'Jumlah depletions minimal 1!')
.typeError('Jumlah depletions harus berupa angka!'),
});
const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({
product_warehouse_id: Yup.number()
.required('Produk telur wajib diisi!')
.min(1, 'Produk telur wajib diisi!')
.typeError('Produk telur harus berupa angka!'),
qty: Yup.number()
.required('Jumlah telur wajib diisi!')
.min(1, 'Jumlah telur tidak boleh 0!')
.typeError('Jumlah telur harus berupa angka!'),
});
export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> =
Yup.object({
project_flock_kandang: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
project_flock_kandangs_id: Yup.number()
.default(0)
.typeError('Project Flock Kandang wajib diisi!')
.test(
'is-valid-project-flock-kandang',
'Project Flock Kandang wajib diisi!',
(value) => value !== undefined && value !== null && value > 0
)
.required('Project Flock Kandang wajib diisi!')
.test(
'not-already-recorded',
'Project Flock ini sudah direcord hari ini!',
function (value) {
const recordedProjectFlockIds = this.options.context
?.recordedProjectFlockIds as Set<number>;
const formType = this.options.context?.type as
| 'add'
| 'edit'
| 'detail';
if (formType !== 'add') return true;
if (value && recordedProjectFlockIds?.has(value)) {
return false;
}
return true;
}
),
body_weights: Yup.array()
.of(BodyWeightObjectSchema)
.min(1, 'Minimal harus ada 1 data bobot badan!')
.required('Data bobot badan wajib diisi!'),
stocks: Yup.array()
.of(StockObjectSchema)
.min(1, 'Minimal harus ada 1 data stok!')
.required('Data stok wajib diisi!'),
depletions: Yup.array()
.of(DepletionObjectSchema)
.min(1, 'Minimal harus ada 1 data depletions!')
.required('Data depletions wajib diisi!'),
});
export const RecordingLayingFormSchema: Yup.ObjectSchema<RecordingLayingFormSchemaType> =
RecordingGrowingFormSchema.shape({
eggs: Yup.array()
.of(EggObjectSchema)
.min(1, 'Minimal harus ada 1 data telur!')
.required('Data telur wajib diisi!'),
});
export const UpdateRecordingGrowingFormSchema =
RecordingGrowingFormSchema.shape({
project_flock_kandangs_id: Yup.number()
@@ -133,26 +191,27 @@ export const UpdateRecordingLayingFormSchema = RecordingLayingFormSchema.shape({
.required('Project Flock Kandang wajib diisi!'),
});
export const RecordingGradingFormSchema = Yup.object({
recording_egg_id: Yup.number()
.required('Recording Egg ID wajib diisi!')
.min(1, 'Recording Egg ID minimal 1!')
.typeError('Recording Egg ID harus berupa angka!'),
eggs_grading: Yup.array()
.of(
Yup.object({
grade: Yup.string()
.required('Grade telur wajib diisi!')
.typeError('Grade telur harus berupa string!'),
qty: Yup.number()
.required('Jumlah telur wajib diisi!')
.min(1, 'Jumlah telur minimal 1!')
.typeError('Jumlah telur harus berupa angka!'),
})
)
.min(1, 'Minimal harus ada 1 data grading telur!')
.required('Data grading telur wajib diisi!'),
});
export const RecordingGradingFormSchema: Yup.ObjectSchema<RecordingGradingFormSchemaType> =
Yup.object({
recording_egg_id: Yup.number()
.required('Recording Egg ID wajib diisi!')
.min(1, 'Recording Egg ID minimal 1!')
.typeError('Recording Egg ID harus berupa angka!'),
eggs_grading: Yup.array()
.of(
Yup.object({
grade: Yup.string()
.required('Grade telur wajib diisi!')
.typeError('Grade telur harus berupa string!'),
qty: Yup.number()
.required('Jumlah telur wajib diisi!')
.min(1, 'Jumlah telur minimal 1!')
.typeError('Jumlah telur harus berupa angka!'),
})
)
.min(1, 'Minimal harus ada 1 data grading telur!')
.required('Data grading telur wajib diisi!'),
});
export const UpdateRecordingGradingFormSchema =
RecordingGradingFormSchema.shape({
@@ -199,9 +258,9 @@ export const getRecordingGrowingFormInitialValues = (
})
) ?? [
{
weight: 0,
avg_weight: 0,
qty: 0,
weight: '',
avg_weight: '',
qty: '',
},
],
stocks: initialValues?.stocks?.map(
@@ -212,7 +271,7 @@ export const getRecordingGrowingFormInitialValues = (
) ?? [
{
product_warehouse_id: 0,
usage_qty: 0,
usage_qty: '',
},
],
depletions: initialValues?.depletions?.map(
@@ -225,7 +284,7 @@ export const getRecordingGrowingFormInitialValues = (
) ?? [
{
product_warehouse_id: 0,
qty: 0,
qty: '',
},
],
});
@@ -241,7 +300,7 @@ export const getRecordingLayingFormInitialValues = (
})) ?? [
{
product_warehouse_id: 0,
qty: 0,
qty: '',
},
],
});
@@ -256,7 +315,7 @@ export const getRecordingGradingFormInitialValues = (
})) ?? [
{
grade: '',
qty: 0,
qty: '',
},
],
});
@@ -773,7 +773,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const currentWeight = formik.values.body_weights?.[idx];
if (currentWeight) {
const qty = currentWeight.qty;
const qty = Number(currentWeight.qty) || 0;
if (qty > 0 && value > 0) {
const avgWeight = parseFloat((value / qty).toFixed(2));
formik.setFieldValue(`body_weights.${idx}.avg_weight`, avgWeight);
@@ -794,7 +794,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const currentWeight = formik.values.body_weights?.[idx];
if (currentWeight) {
const qty = currentWeight.qty;
const qty = Number(currentWeight.qty) || 0;
if (qty > 0 && value > 0) {
const totalWeight = value * qty;
formik.setFieldValue(`body_weights.${idx}.weight`, totalWeight);
@@ -815,7 +815,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const currentWeight = formik.values.body_weights?.[idx];
if (currentWeight) {
const weight = currentWeight.weight;
const weight = Number(currentWeight.weight) || 0;
if (value > 0 && weight > 0) {
const avgWeight = parseFloat((weight / value).toFixed(2));
formik.setFieldValue(`body_weights.${idx}.avg_weight`, avgWeight);
@@ -864,7 +864,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
...(formik.values.stocks || []),
{
product_warehouse_id: 0,
usage_qty: 0,
usage_qty: '',
},
];
formik.setFieldValue('stocks', newStocks);
@@ -897,7 +897,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
...(formik.values.depletions || []),
{
product_warehouse_id: 0,
qty: 0,
qty: '',
},
];
formik.setFieldValue('depletions', newDepletions);
@@ -932,7 +932,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
...((formik.values as RecordingLayingFormValues).eggs || []),
{
product_warehouse_id: 0,
qty: 0,
qty: '',
},
];
formik.setFieldValue('eggs', newEggs);
@@ -966,7 +966,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
if (isLayingCategory && (type as 'add' | 'edit' | 'detail') !== 'detail') {
const layingValues = formik.values as RecordingLayingFormValues;
if (!layingValues.eggs || layingValues.eggs.length === 0) {
formik.setFieldValue('eggs', [{ product_warehouse_id: 0, qty: 0 }]);
formik.setFieldValue('eggs', [{ product_warehouse_id: 0, qty: '' }]);
}
}
}, [isLayingCategory, type, formik]);
@@ -998,11 +998,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
if (idx === editingAverageIndex || manuallyEditedRows.has(idx)) {
return weight;
}
const qty = Number(weight.qty) || 0;
const weightValue = Number(weight.weight) || 0;
return {
...weight,
avg_weight:
weight.qty > 0 && weight.weight > 0
? parseFloat((weight.weight / weight.qty).toFixed(2))
qty > 0 && weightValue > 0
? parseFloat((weightValue / qty).toFixed(2))
: 0,
};
}
@@ -1303,7 +1305,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<NumberInput
required
name={`body_weights.${idx}.weight`}
value={bw.weight}
value={bw.weight ?? ''}
onChange={handleWeightChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={2}
@@ -1311,6 +1313,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
thousandSeparator=','
decimalSeparator='.'
inputSuffix='gram'
placeholder='Masukkan berat total...'
isError={
isRepeaterInputError('body_weights', 'weight', idx)
.isError
@@ -1329,13 +1332,14 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<NumberInput
required
name={`body_weights.${idx}.qty`}
value={bw.qty}
value={bw.qty ?? ''}
onChange={handleQtyChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={0}
allowNegative={false}
thousandSeparator=','
decimalSeparator='.'
placeholder='Masukkan jumlah ayam...'
isError={
isRepeaterInputError('body_weights', 'qty', idx)
.isError
@@ -1354,7 +1358,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<NumberInput
required
name={`body_weights.${idx}.avg_weight`}
value={bw.avg_weight}
value={bw.avg_weight ?? ''}
onChange={handleAvgWeightChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={2}
@@ -1362,6 +1366,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
thousandSeparator=','
decimalSeparator='.'
inputSuffix='gram'
placeholder='Masukkan berat rata-rata...'
isError={
isRepeaterInputError(
'body_weights',
@@ -1574,7 +1579,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<NumberInput
required
name={`stocks.${idx}.usage_qty`}
value={stock.usage_qty}
value={stock.usage_qty ?? ''}
onChange={handleStockUsageQtyChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={0}
@@ -1595,7 +1600,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
className={{
wrapper: 'w-full min-w-24',
}}
placeholder='Jumlah Pakai'
placeholder='Masukkan jumlah pakai'
/>
{(type as 'add' | 'edit' | 'detail') !== 'detail' &&
getStockUsageAdornment(idx)}
@@ -1787,7 +1792,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<NumberInput
required
name={`depletions.${idx}.qty`}
value={depletion.qty}
value={depletion.qty ?? ''}
onChange={handleDepletionQtyChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={0}
@@ -1806,7 +1811,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
className={{
wrapper: 'w-full min-w-24',
}}
placeholder='Jumlah'
placeholder='Masukkan jumlah deplesi'
/>
</td>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && (
@@ -1998,7 +2003,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<NumberInput
required
name={`eggs.${idx}.qty`}
value={egg.qty}
value={egg.qty ?? ''}
onChange={handleEggQtyChangeWrapper(idx)}
onBlur={formik.handleBlur}
decimalScale={0}
@@ -2017,7 +2022,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
className={{
wrapper: 'w-full min-w-24',
}}
placeholder='Jumlah'
placeholder='Masukkan jumlah telur'
/>
</div>
</td>
@@ -2191,10 +2196,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
toast.success(
'Recording berhasil disimpan! Mengalihkan ke form Grading...'
);
// Redirect ke grading form setelah submit berhasil
setTimeout(() => {
router.push(
'/production/recording/grading/add?recording_id=new'
'/production/recording/grading/add?recording_id=1'
);
}, 1000);
}