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 { 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({
flock: Yup.object({
project_flock_kandang: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
flock_id: Yup.number()
project_flock_kandang_id: Yup.number()
.default(0)
.typeError('Flock wajib diisi!')
.typeError('Project Flock Kandang wajib diisi!')
.test(
'is-valid-flock',
'Flock wajib diisi!',
'is-valid-project-flock-kandang',
'Project Flock Kandang wajib diisi!',
(value) => value !== undefined && value !== null && value > 0
)
.required('Flock wajib diisi!'),
location: Yup.object({
value: Yup.number().min(1).required(),
label: Yup.string().required(),
}).nullable(),
location_id: Yup.number()
.default(0)
.typeError('Lokasi wajib diisi!')
.test(
'is-valid-location',
'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()
.required('Project Flock Kandang wajib diisi!'),
record_datetime: Yup.date()
.required('Tanggal dan waktu recording wajib diisi')
.typeError('Format tanggal dan waktu tidak valid'),
status: Yup.number()
.optional()
.oneOf([0, 1, 2, 3], 'Status tidak valid')
.typeError('Status harus berupa angka!'),
ontime: Yup.boolean().optional(),
body_weights: Yup.array()
.of(
Yup.object({
feed_id: Yup.string().required('Nama pakan wajib diisi!'),
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()
weight: Yup.number()
.required('Berat ayam wajib diisi!')
.min(1, 'Berat ayam minimal 1 gram!')
.typeError('Berat ayam harus berupa angka!'),
chicken_count: Yup.number()
qty: Yup.number()
.required('Jumlah ayam wajib diisi!')
.min(1, 'Jumlah ayam minimal 1 ekor!')
.typeError('Jumlah ayam harus berupa angka!'),
average_chicken_weight: Yup.number()
.required('Rata-rata berat ayam wajib diisi!')
.min(1, 'Rata-rata berat ayam minimal 1 gram!')
.typeError('Rata-rata berat ayam harus berupa angka!'),
.typeError('Jumlah ayam harus berupa angka!')
.default(1),
notes: Yup.string().optional(),
})
)
.min(1, 'Minimal harus ada 1 data bobot badan!')
.required('Data bobot badan wajib diisi!'),
vaccination: Yup.array()
stocks: Yup.array()
.of(
Yup.object({
vaccine_id: Yup.string().required('Nama vaksin wajib diisi!'),
total_stock: Yup.mixed<number | ''>().notRequired(),
used_stock: Yup.number()
.required('Jumlah vaksin yang digunakan wajib diisi!')
.min(1, 'Jumlah vaksin minimal 1!')
.typeError('Jumlah vaksin yang digunakan harus berupa angka!')
.test(
'is-not-exceed-total',
'Jumlah vaksin yang digunakan tidak boleh melebihi stok tersedia!',
function (value) {
const { total_stock } = this.parent;
if (value === undefined) return true;
if (
total_stock === undefined ||
total_stock === '' ||
typeof total_stock !== 'number'
)
return true;
return value <= total_stock;
}
),
product_warehouse_id: Yup.number()
.required('Produk wajib diisi!')
.min(1, 'Produk wajib diisi!')
.typeError('Produk harus berupa angka!'),
increase: Yup.number()
.optional()
.min(0, 'Penambahan tidak boleh negatif!')
.typeError('Penambahan harus berupa angka!'),
decrease: Yup.number()
.optional()
.min(0, 'Pengurangan tidak boleh negatif!')
.typeError('Pengurangan harus berupa angka!'),
usage_amount: Yup.number()
.optional()
.min(0, 'Jumlah penggunaan tidak boleh negatif!')
.typeError('Jumlah penggunaan harus berupa angka!'),
notes: Yup.string().optional(),
})
)
.min(1, 'Minimal harus ada 1 data vaksinasi!')
.required('Data vaksinasi wajib diisi!'),
mortality: Yup.array()
.min(1, 'Minimal harus ada 1 data stok!')
.required('Data stok wajib diisi!'),
depletions: Yup.array()
.of(
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(
RECORDING_FLAG_OPTIONS.map((opt) => opt.value),
'Kondisi tidak valid!'
RECORDING_FLAG_OPTIONS.map((option) => option.value),
'Kondisi depletions tidak valid!'
)
.required('Kondisi wajib diisi!'),
count: Yup.number()
.required('Jumlah mortalitas wajib diisi!')
.min(1, 'Jumlah mortalitas minimal 1 ekor!')
.typeError('Jumlah mortalitas harus berupa angka!'),
.typeError('Kondisi depletions harus berupa teks!')
.min(1, 'Kondisi depletions wajib diisi!'),
total: Yup.number()
.required('Jumlah depletions wajib diisi!')
.min(1, 'Jumlah depletions minimal 1!')
.typeError('Jumlah depletions harus berupa angka!'),
notes: Yup.string().optional(),
})
)
.min(1, 'Minimal harus ada 1 data mortalitas!')
.required('Data mortalitas wajib diisi!'),
.min(1, 'Minimal harus ada 1 data depletions!')
.required('Data depletions wajib diisi!'),
});
export const UpdateRecordingFormSchema = 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 = (
initialValues?: Recording
initialValues?: RecordingFormData
): RecordingFormValues => ({
flock: initialValues?.flock
project_flock_kandang: initialValues?.project_flock_kandang_id
? {
value: initialValues.flock.id,
label: initialValues.flock.name,
value: initialValues.project_flock_kandang_id,
label: `Project Flock Kandang #${initialValues.project_flock_kandang_id}`,
}
: null,
flock_id: initialValues?.flock?.id ?? 0,
location: initialValues?.location
? {
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)
project_flock_kandang_id: initialValues?.project_flock_kandang_id ?? 0,
record_datetime: initialValues?.record_datetime
? new Date(initialValues.record_datetime)
: new Date(),
feed_data: initialValues?.feed_data
? initialValues.feed_data.map((feed) => ({
feed_id: feed.feed_name,
feed_qty: feed.feed_qty,
feed_stock: feed.feed_stock,
}))
: [
{
feed_id: '',
feed_qty: '',
feed_stock: 0,
},
],
body_weight: initialValues?.body_weight ?? [
status: initialValues?.status ?? 1,
ontime: initialValues?.ontime ?? true,
body_weights: initialValues?.body_weights?.map(
(bw: NonNullable<CreateRecordingPayload['body_weights']>[0]) => ({
weight: bw.weight,
qty: bw.qty,
notes: bw.notes || '',
})
) ?? [
{
chicken_weight: 0,
chicken_count: 0,
average_chicken_weight: 0,
weight: 0,
qty: 1,
notes: '',
},
],
vaccination: initialValues?.vaccination
? initialValues.vaccination.map((vaccine) => ({
vaccine_id: vaccine.vaccine_name,
total_stock: vaccine.total_stock,
used_stock: vaccine.used_stock,
}))
: [
{
vaccine_id: '',
total_stock: '',
used_stock: 0,
},
],
mortality: initialValues?.mortality ?? [
stocks: initialValues?.stocks?.map(
(stock: NonNullable<CreateRecordingPayload['stocks']>[0]) => ({
product_warehouse_id: stock.product_warehouse_id,
increase: stock.increase,
decrease: stock.decrease,
usage_amount: stock.usage_amount,
notes: stock.notes,
})
) ?? [
{
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: '',
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 { Location } from '@/types/api/master-data/location';
import { Kandang } from '@/types/api/master-data/kandang';
import { Flock } from '@/types/api/master-data/flock';
import { BaseMetadata, User } from '@/types/api/api-general';
export type ProductionMetrics = {
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 = {
id: number;
flock: Flock;
recording_date: string;
location: Location;
coop: Kandang;
feed_data: {
feed_name: string;
feed_qty: number;
feed_stock: number;
}[];
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;
}[];
};
project_flock_kandang_id: number;
record_datetime: string;
record_date: string;
status: number;
ontime: boolean;
day: number;
created_user: User;
} & ProductionMetrics;
export type Recording = BaseMetadata & BaseRecording;
export type CreateRecordingPayload = {
flock_id: number;
recording_date: string;
location_id: number;
coop_id: number;
feed_data: {
feed_id: string;
feed_qty: number;
feed_stock: number;
project_flock_kandang_id: number;
record_datetime: string;
status?: number;
ontime?: boolean;
body_weights?: {
weight: number;
qty: number;
notes?: string;
}[];
body_weight: {
chicken_weight: number;
chicken_count: number;
average_chicken_weight: number;
stocks?: {
product_warehouse_id: number;
increase?: number;
decrease?: number;
usage_amount?: number;
notes?: string;
}[];
vaccination: {
vaccine_id: string;
total_stock: number;
used_stock: number;
}[];
mortality: {
depletions?: {
product_warehouse_id: number;
condition: string;
count: number;
total: number;
notes?: string;
}[];
};