feat(FE-137): integrate API for daily recording with enhanced data structure and validation

This commit is contained in:
rstubryan
2025-10-23 11:59:22 +07:00
parent 6687f4af98
commit 22f1a32e1b
3 changed files with 764 additions and 1500 deletions
@@ -1,212 +1,167 @@
import * as Yup from 'yup'; import * as Yup from 'yup';
import { RECORDING_FLAG_OPTIONS } from '@/config/constant'; import { RECORDING_FLAG_OPTIONS } from '@/config/constant';
import { Recording } from '@/types/api/production/recording'; import {
Recording,
CreateRecordingPayload,
} from '@/types/api/production/recording';
export const RecordingFormSchema = Yup.object({ export const RecordingFormSchema = Yup.object({
flock: Yup.object({ project_flock_kandang: Yup.object({
value: Yup.number().min(1).required(), value: Yup.number().min(1).required(),
label: Yup.string().required(), label: Yup.string().required(),
}).nullable(), }).nullable(),
flock_id: Yup.number() project_flock_kandang_id: Yup.number()
.default(0) .default(0)
.typeError('Flock wajib diisi!') .typeError('Project Flock Kandang wajib diisi!')
.test( .test(
'is-valid-flock', 'is-valid-project-flock-kandang',
'Flock wajib diisi!', 'Project Flock Kandang wajib diisi!',
(value) => value !== undefined && value !== null && value > 0 (value) => value !== undefined && value !== null && value > 0
) )
.required('Flock wajib diisi!'), .required('Project Flock Kandang wajib diisi!'),
location: Yup.object({ record_datetime: Yup.date()
value: Yup.number().min(1).required(), .required('Tanggal dan waktu recording wajib diisi')
label: Yup.string().required(), .typeError('Format tanggal dan waktu tidak valid'),
}).nullable(), status: Yup.number()
location_id: Yup.number() .optional()
.default(0) .oneOf([0, 1, 2, 3], 'Status tidak valid')
.typeError('Lokasi wajib diisi!') .typeError('Status harus berupa angka!'),
.test( ontime: Yup.boolean().optional(),
'is-valid-location', body_weights: Yup.array()
'Lokasi wajib diisi!',
(value) => value !== undefined && value !== null && value > 0
)
.required('Lokasi wajib diisi!'),
coop: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
coop_id: Yup.number()
.default(0)
.typeError('Kandang wajib diisi!')
.test(
'is-valid-coop',
'Kandang wajib diisi!',
(value) => value !== undefined && value !== null && value > 0
)
.required('Kandang wajib diisi!'),
recording_date: Yup.date()
.required('Tanggal recording wajib diisi')
.typeError('Format tanggal tidak valid'),
feed_data: Yup.array()
.of( .of(
Yup.object({ Yup.object({
feed_id: Yup.string().required('Nama pakan wajib diisi!'), weight: Yup.number()
feed_qty: Yup.mixed<number | ''>().notRequired(),
feed_stock: Yup.number()
.required('Jumlah pakan yang digunakan wajib diisi!')
.min(1, 'Jumlah pakan minimal 1!')
.typeError('Jumlah pakan yang digunakan harus berupa angka!')
.test(
'is-not-exceed-qty',
'Jumlah pakan yang digunakan tidak boleh melebihi stok tersedia!',
function (value) {
const { feed_qty } = this.parent;
if (value === undefined) return true;
if (
feed_qty === undefined ||
feed_qty === '' ||
typeof feed_qty !== 'number'
)
return true;
return value <= feed_qty;
}
),
})
)
.min(1, 'Minimal harus ada 1 data pakan!')
.required('Data pakan wajib diisi!'),
body_weight: Yup.array()
.of(
Yup.object({
chicken_weight: Yup.number()
.required('Berat ayam wajib diisi!') .required('Berat ayam wajib diisi!')
.min(1, 'Berat ayam minimal 1 gram!') .min(1, 'Berat ayam minimal 1 gram!')
.typeError('Berat ayam harus berupa angka!'), .typeError('Berat ayam harus berupa angka!'),
chicken_count: Yup.number() qty: Yup.number()
.required('Jumlah ayam wajib diisi!') .required('Jumlah ayam wajib diisi!')
.min(1, 'Jumlah ayam minimal 1 ekor!') .min(1, 'Jumlah ayam minimal 1 ekor!')
.typeError('Jumlah ayam harus berupa angka!'), .typeError('Jumlah ayam harus berupa angka!')
average_chicken_weight: Yup.number() .default(1),
.required('Rata-rata berat ayam wajib diisi!') notes: Yup.string().optional(),
.min(1, 'Rata-rata berat ayam minimal 1 gram!')
.typeError('Rata-rata berat ayam harus berupa angka!'),
}) })
) )
.min(1, 'Minimal harus ada 1 data bobot badan!') .min(1, 'Minimal harus ada 1 data bobot badan!')
.required('Data bobot badan wajib diisi!'), .required('Data bobot badan wajib diisi!'),
vaccination: Yup.array() stocks: Yup.array()
.of( .of(
Yup.object({ Yup.object({
vaccine_id: Yup.string().required('Nama vaksin wajib diisi!'), product_warehouse_id: Yup.number()
total_stock: Yup.mixed<number | ''>().notRequired(), .required('Produk wajib diisi!')
used_stock: Yup.number() .min(1, 'Produk wajib diisi!')
.required('Jumlah vaksin yang digunakan wajib diisi!') .typeError('Produk harus berupa angka!'),
.min(1, 'Jumlah vaksin minimal 1!') increase: Yup.number()
.typeError('Jumlah vaksin yang digunakan harus berupa angka!') .optional()
.test( .min(0, 'Penambahan tidak boleh negatif!')
'is-not-exceed-total', .typeError('Penambahan harus berupa angka!'),
'Jumlah vaksin yang digunakan tidak boleh melebihi stok tersedia!', decrease: Yup.number()
function (value) { .optional()
const { total_stock } = this.parent; .min(0, 'Pengurangan tidak boleh negatif!')
if (value === undefined) return true; .typeError('Pengurangan harus berupa angka!'),
if ( usage_amount: Yup.number()
total_stock === undefined || .optional()
total_stock === '' || .min(0, 'Jumlah penggunaan tidak boleh negatif!')
typeof total_stock !== 'number' .typeError('Jumlah penggunaan harus berupa angka!'),
) notes: Yup.string().optional(),
return true;
return value <= total_stock;
}
),
}) })
) )
.min(1, 'Minimal harus ada 1 data vaksinasi!') .min(1, 'Minimal harus ada 1 data stok!')
.required('Data vaksinasi wajib diisi!'), .required('Data stok wajib diisi!'),
mortality: Yup.array() depletions: Yup.array()
.of( .of(
Yup.object({ Yup.object({
condition: Yup.mixed<string>() product_warehouse_id: Yup.number()
.required('Produk wajib diisi!')
.min(1, 'Produk wajib diisi!')
.typeError('Produk harus berupa angka!'),
condition: Yup.string()
.required('Kondisi depletions wajib diisi!')
.oneOf( .oneOf(
RECORDING_FLAG_OPTIONS.map((opt) => opt.value), RECORDING_FLAG_OPTIONS.map((option) => option.value),
'Kondisi tidak valid!' 'Kondisi depletions tidak valid!'
) )
.required('Kondisi wajib diisi!'), .typeError('Kondisi depletions harus berupa teks!')
count: Yup.number() .min(1, 'Kondisi depletions wajib diisi!'),
.required('Jumlah mortalitas wajib diisi!') total: Yup.number()
.min(1, 'Jumlah mortalitas minimal 1 ekor!') .required('Jumlah depletions wajib diisi!')
.typeError('Jumlah mortalitas harus berupa angka!'), .min(1, 'Jumlah depletions minimal 1!')
.typeError('Jumlah depletions harus berupa angka!'),
notes: Yup.string().optional(),
}) })
) )
.min(1, 'Minimal harus ada 1 data mortalitas!') .min(1, 'Minimal harus ada 1 data depletions!')
.required('Data mortalitas wajib diisi!'), .required('Data depletions wajib diisi!'),
}); });
export const UpdateRecordingFormSchema = RecordingFormSchema; export const UpdateRecordingFormSchema = RecordingFormSchema;
export type RecordingFormValues = Yup.InferType<typeof RecordingFormSchema>; export type RecordingFormValues = Yup.InferType<typeof RecordingFormSchema>;
type RecordingFormData = Partial<Recording> & {
body_weights?: CreateRecordingPayload['body_weights'];
stocks?: CreateRecordingPayload['stocks'];
depletions?: CreateRecordingPayload['depletions'];
};
export const getRecordingFormInitialValues = ( export const getRecordingFormInitialValues = (
initialValues?: Recording initialValues?: RecordingFormData
): RecordingFormValues => ({ ): RecordingFormValues => ({
flock: initialValues?.flock project_flock_kandang: initialValues?.project_flock_kandang_id
? { ? {
value: initialValues.flock.id, value: initialValues.project_flock_kandang_id,
label: initialValues.flock.name, label: `Project Flock Kandang #${initialValues.project_flock_kandang_id}`,
} }
: null, : null,
flock_id: initialValues?.flock?.id ?? 0, project_flock_kandang_id: initialValues?.project_flock_kandang_id ?? 0,
location: initialValues?.location record_datetime: initialValues?.record_datetime
? { ? new Date(initialValues.record_datetime)
value: initialValues.location.id,
label: initialValues.location.name,
}
: null,
location_id: initialValues?.location?.id ?? 0,
coop: initialValues?.coop
? {
value: initialValues.coop.id,
label: initialValues.coop.name,
}
: null,
coop_id: initialValues?.coop?.id ?? 0,
recording_date: initialValues?.recording_date
? new Date(initialValues.recording_date)
: new Date(), : new Date(),
feed_data: initialValues?.feed_data status: initialValues?.status ?? 1,
? initialValues.feed_data.map((feed) => ({ ontime: initialValues?.ontime ?? true,
feed_id: feed.feed_name, body_weights: initialValues?.body_weights?.map(
feed_qty: feed.feed_qty, (bw: NonNullable<CreateRecordingPayload['body_weights']>[0]) => ({
feed_stock: feed.feed_stock, weight: bw.weight,
})) qty: bw.qty,
: [ notes: bw.notes || '',
{ })
feed_id: '', ) ?? [
feed_qty: '',
feed_stock: 0,
},
],
body_weight: initialValues?.body_weight ?? [
{ {
chicken_weight: 0, weight: 0,
chicken_count: 0, qty: 1,
average_chicken_weight: 0, notes: '',
}, },
], ],
vaccination: initialValues?.vaccination stocks: initialValues?.stocks?.map(
? initialValues.vaccination.map((vaccine) => ({ (stock: NonNullable<CreateRecordingPayload['stocks']>[0]) => ({
vaccine_id: vaccine.vaccine_name, product_warehouse_id: stock.product_warehouse_id,
total_stock: vaccine.total_stock, increase: stock.increase,
used_stock: vaccine.used_stock, decrease: stock.decrease,
})) usage_amount: stock.usage_amount,
: [ notes: stock.notes,
{ })
vaccine_id: '', ) ?? [
total_stock: '',
used_stock: 0,
},
],
mortality: initialValues?.mortality ?? [
{ {
product_warehouse_id: 0,
increase: 0,
decrease: 0,
usage_amount: 0,
notes: '',
},
],
depletions: initialValues?.depletions?.map(
(depletion: NonNullable<CreateRecordingPayload['depletions']>[0]) => ({
product_warehouse_id: depletion.product_warehouse_id,
condition: depletion.condition,
total: depletion.total,
notes: depletion.notes,
})
) ?? [
{
product_warehouse_id: 0,
condition: '', condition: '',
count: 0, total: 0,
notes: '',
}, },
], ],
}); });
File diff suppressed because it is too large Load Diff
+39 -47
View File
@@ -1,60 +1,52 @@
import { BaseMetadata } from '@/types/api/api-general'; import { BaseMetadata, User } from '@/types/api/api-general';
import { Location } from '@/types/api/master-data/location';
import { Kandang } from '@/types/api/master-data/kandang'; export type ProductionMetrics = {
import { Flock } from '@/types/api/master-data/flock'; total_depletion: number;
cum_depletion_rate: number;
daily_gain: number;
avg_daily_gain: number;
cum_intake: number;
fcr_value: number;
total_chick: number;
daily_depletion_rate: number;
cum_depletion: number;
};
export type BaseRecording = { export type BaseRecording = {
id: number; id: number;
flock: Flock; project_flock_kandang_id: number;
recording_date: string; record_datetime: string;
location: Location; record_date: string;
coop: Kandang; status: number;
feed_data: { ontime: boolean;
feed_name: string; day: number;
feed_qty: number; created_user: User;
feed_stock: number; } & ProductionMetrics;
}[];
body_weight: {
chicken_weight: number;
chicken_count: number;
average_chicken_weight: number;
}[];
vaccination: {
vaccine_name: string;
total_stock: number;
used_stock: number;
}[];
mortality: {
condition: string;
count: number;
}[];
};
export type Recording = BaseMetadata & BaseRecording; export type Recording = BaseMetadata & BaseRecording;
export type CreateRecordingPayload = { export type CreateRecordingPayload = {
flock_id: number; project_flock_kandang_id: number;
recording_date: string; record_datetime: string;
location_id: number; status?: number;
coop_id: number; ontime?: boolean;
feed_data: { body_weights?: {
feed_id: string; weight: number;
feed_qty: number; qty: number;
feed_stock: number; notes?: string;
}[]; }[];
body_weight: { stocks?: {
chicken_weight: number; product_warehouse_id: number;
chicken_count: number; increase?: number;
average_chicken_weight: number; decrease?: number;
usage_amount?: number;
notes?: string;
}[]; }[];
vaccination: { depletions?: {
vaccine_id: string; product_warehouse_id: number;
total_stock: number;
used_stock: number;
}[];
mortality: {
condition: string; condition: string;
count: number; total: number;
notes?: string;
}[]; }[];
}; };