Merge branch 'dev/hotfix/restu' into 'development'

[FIX/FE] Adjustment Recording, Biaya and HPP Harian Kandang (Report)

See merge request mbugroup/lti-web-client!211
This commit is contained in:
Rivaldi A N S
2026-01-20 02:09:43 +00:00
14 changed files with 85 additions and 128 deletions
@@ -261,7 +261,7 @@ const ExpenseRealizationContent = ({
<td>{pengajuanItem.qty}</td> <td>{pengajuanItem.qty}</td>
<td>{formatCurrency(pengajuanItem.price)}</td> <td>{formatCurrency(pengajuanItem.price)}</td>
<td className='w-xs'> <td className='w-xs'>
{pengajuanItem.note ?? '-'} {pengajuanItem.notes ?? '-'}
</td> </td>
</tr> </tr>
) )
@@ -329,7 +329,7 @@ const ExpenseRealizationContent = ({
<td>{realisasiItem.qty}</td> <td>{realisasiItem.qty}</td>
<td>{formatCurrency(realisasiItem.price)}</td> <td>{formatCurrency(realisasiItem.price)}</td>
<td className='w-xs'> <td className='w-xs'>
{realisasiItem.note ?? '-'} {realisasiItem.notes ?? '-'}
</td> </td>
</tr> </tr>
) )
@@ -654,7 +654,7 @@ const ExpenseRequestContent = ({
<td>{pengajuanItem.qty}</td> <td>{pengajuanItem.qty}</td>
<td>{formatCurrency(pengajuanItem.price)}</td> <td>{formatCurrency(pengajuanItem.price)}</td>
<td className='w-xs'> <td className='w-xs'>
{pengajuanItem.note ?? '-'} {pengajuanItem.notes ?? '-'}
</td> </td>
</tr> </tr>
) )
@@ -174,9 +174,16 @@ const ExpenseKandangsTable = ({
updateSortingFilter('picSort', picSortFilter); updateSortingFilter('picSort', picSortFilter);
}, [sorting, updateSortingFilter]); }, [sorting, updateSortingFilter]);
// Tampilkan tabel jika:
// 1. Mode request pertama kali (type='add' dan formType='request')
// 2. Atau sudah ada kandang yang dipilih
const shouldShowTable =
(type === 'add' && formType === 'request') ||
(selectedKandangs.length > 0 && selectedKandangs.some((k) => k.id));
return ( return (
<> <>
{selectedKandangs.length > 0 && selectedKandangs.some((k) => k.id) && ( {shouldShowTable && (
<Card <Card
className={{ className={{
wrapper: className?.wrapper, wrapper: className?.wrapper,
@@ -159,7 +159,7 @@ export const getExpenseRealizationFormInitialValues = (
}, },
quantity: realisasiItem.qty, quantity: realisasiItem.qty,
price: realisasiItem.price, price: realisasiItem.price,
notes: realisasiItem.note, notes: realisasiItem.notes,
}; };
}) })
: kandangExpense.pengajuans : kandangExpense.pengajuans
@@ -170,7 +170,7 @@ export const getExpenseRealizationFormInitialValues = (
}, },
quantity: expenseItem.qty, quantity: expenseItem.qty,
price: expenseItem.price, price: expenseItem.price,
notes: expenseItem.note, notes: expenseItem.notes,
})) }))
: []; : [];
@@ -208,7 +208,7 @@ export const getExpenseFormInitialValues = (
nonstock_id: expenseItem.nonstock.id, nonstock_id: expenseItem.nonstock.id,
quantity: expenseItem.qty, quantity: expenseItem.qty,
price: expenseItem.price, price: expenseItem.price,
notes: expenseItem.note, notes: expenseItem.notes,
})) }))
: [], : [],
})) }))
@@ -447,7 +447,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
]} ]}
> >
<Text style={ExpensePDFStyle.kandangExpenseLabelText}> <Text style={ExpensePDFStyle.kandangExpenseLabelText}>
{pengajuan.note} {pengajuan.notes}
</Text> </Text>
</View> </View>
</View> </View>
@@ -607,7 +607,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
]} ]}
> >
<Text style={ExpensePDFStyle.kandangExpenseLabelText}> <Text style={ExpensePDFStyle.kandangExpenseLabelText}>
{realisasi.note} {realisasi.notes}
</Text> </Text>
</View> </View>
</View> </View>
@@ -729,10 +729,6 @@ const RecordingTable = () => {
); );
}, },
}, },
{
header: 'Gudang',
cell: (props) => props.row.original.warehouse?.name,
},
{ {
header: 'Waktu Recording', header: 'Waktu Recording',
cell: (props) => cell: (props) =>
@@ -33,16 +33,16 @@ type RecordingGrowingFormSchemaType = {
qty: number | string; qty: number | string;
}[]; }[];
depletions: { depletions: {
product_warehouse_id: number; product_warehouse_id?: number;
qty: number | string; qty?: number | string;
}[]; }[];
}; };
type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & { type RecordingLayingFormSchemaType = RecordingGrowingFormSchemaType & {
eggs: { eggs: {
product_warehouse_id: number; product_warehouse_id?: number;
qty: number | string; qty?: number | string;
weight: number | string; weight?: number | string;
}[]; }[];
}; };
@@ -52,14 +52,14 @@ export type StockSchema = {
}; };
export type DepletionSchema = { export type DepletionSchema = {
product_warehouse_id: number; product_warehouse_id?: number;
qty: number | string; qty?: number | string;
}; };
export type EggSchema = { export type EggSchema = {
product_warehouse_id: number; product_warehouse_id?: number;
qty: number | string; qty?: number | string;
weight: number | string; weight?: number | string;
}; };
const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({ const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
@@ -75,28 +75,19 @@ const StockObjectSchema: Yup.ObjectSchema<StockSchema> = Yup.object({
const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({ const DepletionObjectSchema: Yup.ObjectSchema<DepletionSchema> = Yup.object({
product_warehouse_id: Yup.number() product_warehouse_id: Yup.number()
.required('Produk depletions wajib diisi!') .optional()
.min(1, 'Produk depletions wajib diisi!') .typeError('Depletions harus berupa angka!'),
.typeError('Produk depletions harus berupa angka!'),
qty: Yup.number() qty: Yup.number()
.required('Jumlah depletions wajib diisi!') .optional()
.min(1, 'Jumlah depletions minimal 1!')
.typeError('Jumlah depletions harus berupa angka!'), .typeError('Jumlah depletions harus berupa angka!'),
}); });
const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({ const EggObjectSchema: Yup.ObjectSchema<EggSchema> = Yup.object({
product_warehouse_id: Yup.number() product_warehouse_id: Yup.number()
.required('Kondisi telur wajib diisi!') .optional()
.min(1, 'Kondisi telur wajib diisi!')
.typeError('Kondisi telur harus berupa angka!'), .typeError('Kondisi telur harus berupa angka!'),
qty: Yup.number() qty: Yup.number().optional().typeError('Jumlah telur harus berupa angka!'),
.required('Jumlah telur wajib diisi!') weight: Yup.number().optional().typeError('Berat telur harus berupa angka!'),
.min(1, 'Jumlah telur tidak boleh 0!')
.typeError('Jumlah telur harus berupa angka!'),
weight: Yup.number()
.required('Berat telur wajib diisi!')
.min(1, 'Berat telur minimal 1 gram!')
.typeError('Berat telur harus berupa angka!'),
}); });
export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> = export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSchemaType> =
@@ -163,18 +154,12 @@ export const RecordingGrowingFormSchema: Yup.ObjectSchema<RecordingGrowingFormSc
.of(StockObjectSchema) .of(StockObjectSchema)
.min(1, 'Minimal harus ada 1 data stok!') .min(1, 'Minimal harus ada 1 data stok!')
.required('Data stok wajib diisi!'), .required('Data stok wajib diisi!'),
depletions: Yup.array() depletions: Yup.array().of(DepletionObjectSchema).default([]),
.of(DepletionObjectSchema)
.min(1, 'Minimal harus ada 1 data depletions!')
.required('Data depletions wajib diisi!'),
}); });
export const RecordingLayingFormSchema: Yup.ObjectSchema<RecordingLayingFormSchemaType> = export const RecordingLayingFormSchema: Yup.ObjectSchema<RecordingLayingFormSchemaType> =
RecordingGrowingFormSchema.shape({ RecordingGrowingFormSchema.shape({
eggs: Yup.array() eggs: Yup.array().of(EggObjectSchema).default([]),
.of(EggObjectSchema)
.min(1, 'Minimal harus ada 1 data telur!')
.required('Data telur wajib diisi!'),
}); });
export const UpdateRecordingGrowingFormSchema = export const UpdateRecordingGrowingFormSchema =
@@ -309,6 +309,13 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
// ===== PAYLOAD CREATION HELPERS ===== // ===== PAYLOAD CREATION HELPERS =====
const createGrowingPayload = useCallback( const createGrowingPayload = useCallback(
(values: RecordingGrowingFormValues) => { (values: RecordingGrowingFormValues) => {
const depletions = values.depletions
?.filter((d) => d.product_warehouse_id && d.qty)
.map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id!,
qty: Number(depletion.qty) || 0,
}));
return { return {
project_flock_kandang_id: values.project_flock_kandang_id, project_flock_kandang_id: values.project_flock_kandang_id,
record_date: values.record_date, record_date: values.record_date,
@@ -316,10 +323,7 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
product_warehouse_id: stock.product_warehouse_id, product_warehouse_id: stock.product_warehouse_id,
qty: Number(stock.qty) || 0, qty: Number(stock.qty) || 0,
})), })),
depletions: (values.depletions ?? []).map((depletion) => ({ ...(depletions && depletions.length > 0 && { depletions }),
product_warehouse_id: depletion.product_warehouse_id,
qty: Number(depletion.qty) || 0,
})),
}; };
}, },
[] []
@@ -327,25 +331,33 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
const createLayingPayload = useCallback( const createLayingPayload = useCallback(
(values: RecordingLayingFormValues) => { (values: RecordingLayingFormValues) => {
return { const depletions = values.depletions
project_flock_kandang_id: values.project_flock_kandang_id, ?.filter((d) => d.product_warehouse_id && d.qty)
record_date: values.record_date, .map((depletion) => ({
stocks: (values.stocks ?? []).map((stock) => ({ product_warehouse_id: depletion.product_warehouse_id!,
product_warehouse_id: stock.product_warehouse_id,
qty: Number(stock.qty) || 0,
})),
depletions: (values.depletions ?? []).map((depletion) => ({
product_warehouse_id: depletion.product_warehouse_id,
qty: Number(depletion.qty) || 0, qty: Number(depletion.qty) || 0,
})), }));
eggs: (values.eggs ?? []).map((egg) => ({
product_warehouse_id: egg.product_warehouse_id, const eggs = values.eggs
?.filter((e) => e.product_warehouse_id && e.qty && e.weight)
.map((egg) => ({
product_warehouse_id: egg.product_warehouse_id!,
qty: Number(egg.qty) || 0, qty: Number(egg.qty) || 0,
weight: weight:
typeof egg.weight === 'number' typeof egg.weight === 'number'
? egg.weight ? egg.weight
: parseFloat(String(egg.weight)) || 0, : parseFloat(String(egg.weight)) || 0,
}));
return {
project_flock_kandang_id: values.project_flock_kandang_id,
record_date: values.record_date,
stocks: values.stocks.map((stock) => ({
product_warehouse_id: stock.product_warehouse_id,
qty: Number(stock.qty) || 0,
})), })),
...(depletions && depletions.length > 0 && { depletions }),
...(eggs && eggs.length > 0 && { eggs }),
}; };
}, },
[] []
@@ -1692,12 +1704,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<AlertErrorList formErrorList={formErrorList} onClose={close} /> <AlertErrorList formErrorList={formErrorList} onClose={close} />
)} )}
<div className='text-xs text-error'>
{JSON.stringify(formik.errors)}
</div>
<div className='text-xs text-success'>
{JSON.stringify(formik.values)}
</div>
{/* Basic Info Card */} {/* Basic Info Card */}
{(type === 'add' || type === 'edit') && ( {(type === 'add' || type === 'edit') && (
<Card <Card
@@ -2557,24 +2563,8 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
/> />
</th> </th>
)} )}
<th> <th>Kondisi</th>
Kondisi <th>Jumlah</th>
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Jumlah
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th> <th>Action</th>
)} )}
@@ -2652,7 +2642,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td> </td>
<td> <td>
<NumberInput <NumberInput
required
name={`depletions.${idx}.qty`} name={`depletions.${idx}.qty`}
value={depletion.qty ?? ''} value={depletion.qty ?? ''}
onChange={handleDepletionQtyChangeWrapper(idx)} onChange={handleDepletionQtyChangeWrapper(idx)}
@@ -2768,33 +2757,9 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
/> />
</th> </th>
)} )}
<th> <th>Kondisi Telur</th>
Kondisi Telur <th>Jumlah</th>
<span <th>Berat (gram)</th>
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Jumlah
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
<th>
Berat (gram)
<span
className='tooltip tooltip-error tooltip-bottom '
data-tip='required'
>
<span className='text-error'>*</span>
</span>
</th>
{(type as 'add' | 'edit' | 'detail') !== 'detail' && ( {(type as 'add' | 'edit' | 'detail') !== 'detail' && (
<th>Action</th> <th>Action</th>
)} )}
@@ -2829,7 +2794,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
)} )}
<td> <td>
<SelectInput <SelectInput
required
value={ value={
eggProducts.find( eggProducts.find(
(product) => (product) =>
@@ -2872,7 +2836,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td> </td>
<td> <td>
<NumberInput <NumberInput
required
name={`eggs.${idx}.qty`} name={`eggs.${idx}.qty`}
value={egg.qty ?? ''} value={egg.qty ?? ''}
onChange={handleEggQtyChangeWrapper(idx)} onChange={handleEggQtyChangeWrapper(idx)}
@@ -2897,7 +2860,6 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
</td> </td>
<td> <td>
<NumberInput <NumberInput
required
name={`eggs.${idx}.weight`} name={`eggs.${idx}.weight`}
value={egg.weight ?? ''} value={egg.weight ?? ''}
onChange={handleEggWeightChangeWrapper(idx)} onChange={handleEggWeightChangeWrapper(idx)}
@@ -10,7 +10,7 @@ import DateInput from '@/components/input/DateInput';
import NumberInput from '@/components/input/NumberInput'; import NumberInput from '@/components/input/NumberInput';
import { AreaApi } from '@/services/api/master-data'; import { AreaApi } from '@/services/api/master-data';
import { LocationApi } from '@/services/api/master-data'; import { LocationApi } from '@/services/api/master-data';
import { KandangApi } from '@/services/api/master-data'; import { ProjectFlockKandangApi } from '@/services/api/production';
import { SaleReportApi } from '@/services/api/report/marketing-sale'; import { SaleReportApi } from '@/services/api/report/marketing-sale';
import Table from '@/components/Table'; import Table from '@/components/Table';
import { ColumnDef, Row, flexRender } from '@tanstack/react-table'; import { ColumnDef, Row, flexRender } from '@tanstack/react-table';
@@ -80,7 +80,12 @@ const HppPerKandangTab = () => {
options: kandangOptions, options: kandangOptions,
isLoadingOptions: isLoadingKandangs, isLoadingOptions: isLoadingKandangs,
loadMore: loadMoreKandangs, loadMore: loadMoreKandangs,
} = useSelect(KandangApi.basePath, 'id', 'name', 'search'); } = useSelect(
ProjectFlockKandangApi.basePath,
'id',
'name_with_period',
'search'
);
const showUnrecordedOptions: OptionType[] = [ const showUnrecordedOptions: OptionType[] = [
{ value: 'false', label: 'Sembunyikan' }, { value: 'false', label: 'Sembunyikan' },
@@ -557,8 +562,8 @@ const HppPerKandangTab = () => {
header: 'Kandang', header: 'Kandang',
accessorKey: 'kandang.name', accessorKey: 'kandang.name',
cell: (props) => { cell: (props) => {
const kandang = props.row.original.kandang; const row = props.row.original;
return kandang?.name || '-'; return row.name_with_periode || row.kandang?.name || '-';
}, },
footer: () => <div className='font-semibold text-gray-900'>ALL</div>, footer: () => <div className='font-semibold text-gray-900'>ALL</div>,
}, },
+2 -2
View File
@@ -34,7 +34,7 @@ export type BaseExpense = {
nonstock_id: number; nonstock_id: number;
qty: number; qty: number;
price: number; price: number;
note?: string; notes?: string;
nonstock: Pick<BaseNonstock, 'id' | 'name' | 'flags'>; nonstock: Pick<BaseNonstock, 'id' | 'name' | 'flags'>;
created_at: string; created_at: string;
}[]; }[];
@@ -43,7 +43,7 @@ export type BaseExpense = {
expense_nonstock_id: number; expense_nonstock_id: number;
qty: number; qty: number;
price: number; price: number;
note?: string; notes?: string;
nonstock: Pick<BaseNonstock, 'id' | 'name' | 'flags'>; nonstock: Pick<BaseNonstock, 'id' | 'name' | 'flags'>;
created_at: string; created_at: string;
}[]; }[];
+1
View File
@@ -10,6 +10,7 @@ export type BaseProjectFlockKandang = {
kandang_id: number; kandang_id: number;
kandang: Kandang; kandang: Kandang;
project_flock: ProjectFlock; project_flock: ProjectFlock;
name_with_period?: string;
approval: BaseApproval; approval: BaseApproval;
chickins?: Chickin[]; chickins?: Chickin[];
available_qtys?: AvailableQty[]; available_qtys?: AvailableQty[];
+5 -5
View File
@@ -111,15 +111,15 @@ export type CreateGrowingRecordingPayload = {
qty: number; qty: number;
}[]; }[];
depletions?: { depletions?: {
product_warehouse_id: number; product_warehouse_id?: number;
qty: number; qty?: number;
}[]; }[];
}; };
export type CreateEggPayload = { export type CreateEggPayload = {
product_warehouse_id: number; product_warehouse_id?: number;
qty: number; qty?: number;
weight: number; weight?: number;
}; };
export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & { export type CreateLayingRecordingPayload = CreateGrowingRecordingPayload & {
+1
View File
@@ -5,6 +5,7 @@ import { Kandang } from '@/types/api/master-data/kandang';
export type HppPerKandangRow = { export type HppPerKandangRow = {
id: number; id: number;
kandang: Kandang; kandang: Kandang;
name_with_periode?: string;
weight_range: { weight_range: {
weight_min: number; weight_min: number;
weight_max: number; weight_max: number;