Merge branch 'development' of https://gitlab.com/mbugroup/lti-web-client into dev/randy

This commit is contained in:
randy-ar
2026-01-12 14:01:35 +07:00
12 changed files with 344 additions and 93 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ const ExpenseRealization = () => {
const isExpenseCanBeRealized = const isExpenseCanBeRealized =
isResponseSuccess(expense) && isResponseSuccess(expense) &&
expense.data.latest_approval.action !== 'REJECTED' && expense.data.latest_approval.action !== 'REJECTED' &&
expense.data.latest_approval.step_number === 3; expense.data.latest_approval.step_number === 4;
if (isResponseSuccess(expense) && !isExpenseCanBeRealized) { if (isResponseSuccess(expense) && !isExpenseCanBeRealized) {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
@@ -28,7 +28,7 @@ const ExpenseDetail: React.FC<ExpenseDetailProps> = ({ initialValues }) => {
if ( if (
initialValues?.latest_approval && initialValues?.latest_approval &&
initialValues?.latest_approval.step_number >= 4 && initialValues?.latest_approval.step_number >= 5 &&
initialValues.latest_approval.action !== 'REJECTED' initialValues.latest_approval.action !== 'REJECTED'
) { ) {
validTabs.push({ validTabs.push({
@@ -59,34 +59,40 @@ const ExpenseRequestContent = ({
const isLatestApprovalRejectedOrDone = const isLatestApprovalRejectedOrDone =
isLatestApprovalRejected || isLatestApprovalRejected ||
initialValues?.latest_approval.step_number === 5; initialValues?.latest_approval.step_number === 6;
const isCurrentApprovalOnManager = const isCurrentApprovalOnHeadArea =
!isLatestApprovalRejected && !isLatestApprovalRejected &&
initialValues?.latest_approval.step_number === 1; initialValues?.latest_approval.step_number === 1;
const isCurrentApprovalOnFinance = const isCurrentApprovalOnUnitVicePresident =
!isLatestApprovalRejected && !isLatestApprovalRejected &&
initialValues?.latest_approval.step_number === 2; initialValues?.latest_approval.step_number === 2;
const isCurrentApprovalOnFinance =
!isLatestApprovalRejected &&
initialValues?.latest_approval.step_number === 3;
const isCurrentApprovalOnRealization = const isCurrentApprovalOnRealization =
!isLatestApprovalRejected && !isLatestApprovalRejected &&
initialValues?.latest_approval.step_number === 4; initialValues?.latest_approval.step_number === 5;
const showEditButton = const showEditButton =
initialValues?.latest_approval.step_number !== 5 && initialValues?.latest_approval.step_number !== 6 &&
(initialValues?.latest_approval.step_number === 1 || (initialValues?.latest_approval.step_number === 1 ||
initialValues?.latest_approval.step_number === 2 || initialValues?.latest_approval.step_number === 2 ||
initialValues?.latest_approval.step_number === 3); initialValues?.latest_approval.step_number === 3 ||
initialValues?.latest_approval.step_number === 4);
const showRejectButton = const showRejectButton =
!isLatestApprovalRejected && !isLatestApprovalRejected &&
(initialValues?.latest_approval.step_number === 1 || (initialValues?.latest_approval.step_number === 1 ||
initialValues?.latest_approval.step_number === 2); initialValues?.latest_approval.step_number === 2 ||
initialValues?.latest_approval.step_number === 3);
const isExpenseCanBeRealized = const isExpenseCanBeRealized =
!isLatestApprovalRejected && !isLatestApprovalRejected &&
initialValues?.latest_approval.step_number === 3; initialValues?.latest_approval.step_number === 4;
// Modal hooks // Modal hooks
const deleteModal = useModal(); const deleteModal = useModal();
@@ -174,8 +180,15 @@ const ExpenseRequestContent = ({
let approveResponse: BaseApiResponse<Expense> | undefined = undefined; let approveResponse: BaseApiResponse<Expense> | undefined = undefined;
if (isCurrentApprovalOnManager) { if (isCurrentApprovalOnHeadArea) {
approveResponse = await ExpenseApi.approveManager( approveResponse = await ExpenseApi.approveHeadArea(
initialValues.id,
notes
);
}
if (isCurrentApprovalOnUnitVicePresident) {
approveResponse = await ExpenseApi.approveUnitVicePresident(
initialValues.id, initialValues.id,
notes notes
); );
@@ -207,8 +220,15 @@ const ExpenseRequestContent = ({
let rejectResponse: BaseApiResponse<Expense> | undefined = undefined; let rejectResponse: BaseApiResponse<Expense> | undefined = undefined;
if (isCurrentApprovalOnManager) { if (isCurrentApprovalOnHeadArea) {
rejectResponse = await ExpenseApi.rejectManager(initialValues.id, notes); rejectResponse = await ExpenseApi.rejectHeadArea(initialValues.id, notes);
}
if (isCurrentApprovalOnUnitVicePresident) {
rejectResponse = await ExpenseApi.rejectUnitVicePresident(
initialValues.id,
notes
);
} }
if (isCurrentApprovalOnFinance) { if (isCurrentApprovalOnFinance) {
@@ -255,8 +275,8 @@ const ExpenseRequestContent = ({
{/* TODO: apply RBAC */} {/* TODO: apply RBAC */}
<div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'> <div className='w-full max-w-5xl mx-auto flex flex-col sm:flex-row justify-end gap-2'>
{isCurrentApprovalOnManager && ( {isCurrentApprovalOnHeadArea && (
<RequirePermission permissions='lti.expense.approve.manager'> <RequirePermission permissions='lti.expense.approve.head_area'>
<Button <Button
variant='outline' variant='outline'
color='info' color='info'
@@ -264,7 +284,21 @@ const ExpenseRequestContent = ({
className='w-full sm:w-fit' className='w-full sm:w-fit'
> >
<Icon icon='lucide-lab:farm' width={24} height={24} /> <Icon icon='lucide-lab:farm' width={24} height={24} />
Approve Manager Approve Head Area
</Button>
</RequirePermission>
)}
{isCurrentApprovalOnUnitVicePresident && (
<RequirePermission permissions='lti.expense.approve.unit_vice_president'>
<Button
variant='outline'
color='success'
onClick={approveClickHandler}
className='w-full sm:w-fit'
>
<Icon icon='tdesign:money' width={24} height={24} />
Approve Unit Vice President
</Button> </Button>
</RequirePermission> </RequirePermission>
)} )}
@@ -304,7 +338,8 @@ const ExpenseRequestContent = ({
{showRejectButton && ( {showRejectButton && (
<RequirePermission <RequirePermission
permissions={[ permissions={[
'lti.expense.approve.manager', 'lti.expense.approve.head_area',
'lti.expense.approve.unit_vice_president',
'lti.expense.approve.finance', 'lti.expense.approve.finance',
]} ]}
> >
@@ -454,8 +489,8 @@ const ExpenseRequestContent = ({
<th>:</th> <th>:</th>
<td> <td>
{formatCurrency( {formatCurrency(
initialValues?.latest_approval.step_number === 4 || initialValues?.latest_approval.step_number === 5 ||
initialValues?.latest_approval.step_number === 5 initialValues?.latest_approval.step_number === 6
? (initialValues?.total_realisasi ?? 0) ? (initialValues?.total_realisasi ?? 0)
: (initialValues?.total_pengajuan ?? 0) : (initialValues?.total_pengajuan ?? 0)
)} )}
+63 -18
View File
@@ -55,15 +55,16 @@ const RowOptionsMenu = ({
deleteClickHandler: () => void; deleteClickHandler: () => void;
}) => { }) => {
const showEditButton = const showEditButton =
props.row.original.latest_approval.step_number !== 5 && props.row.original.latest_approval.step_number !== 6 &&
(props.row.original.latest_approval.step_number === 1 || (props.row.original.latest_approval.step_number === 1 ||
props.row.original.latest_approval.step_number === 2 || props.row.original.latest_approval.step_number === 2 ||
props.row.original.latest_approval.step_number === 3); props.row.original.latest_approval.step_number === 3 ||
props.row.original.latest_approval.step_number === 4);
// TODO: apply RBAC // TODO: apply RBAC
const showRealizationButton = const showRealizationButton =
props.row.original.latest_approval.action !== 'REJECTED' && props.row.original.latest_approval.action !== 'REJECTED' &&
props.row.original.latest_approval.step_number === 3; props.row.original.latest_approval.step_number === 4;
return ( return (
<RowOptionsMenuWrapper type={type}> <RowOptionsMenuWrapper type={type}>
@@ -193,7 +194,7 @@ const ExpensesTable = () => {
parseInt(item) parseInt(item)
); );
const isAllSelectedRowLatestApprovalOnManager = useMemo(() => { const isAllSelectedRowLatestApprovalOnHeadArea = useMemo(() => {
return selectedRowIds.every((rowId) => { return selectedRowIds.every((rowId) => {
if (!isResponseSuccess(expenses)) return false; if (!isResponseSuccess(expenses)) return false;
@@ -202,11 +203,28 @@ const ExpensesTable = () => {
const isLatestApprovalRejected = const isLatestApprovalRejected =
expenseItem?.latest_approval.action === 'REJECTED'; expenseItem?.latest_approval.action === 'REJECTED';
const isCurrentApprovalOnManager = const isCurrentApprovalOnHeadArea =
!isLatestApprovalRejected && !isLatestApprovalRejected &&
expenseItem?.latest_approval.step_number === 1; expenseItem?.latest_approval.step_number === 1;
return isCurrentApprovalOnManager; return isCurrentApprovalOnHeadArea;
});
}, [expenses, selectedRowIds]);
const isAllSelectedRowLatestApprovalOnUnitVicePresident = useMemo(() => {
return selectedRowIds.every((rowId) => {
if (!isResponseSuccess(expenses)) return false;
const expenseItem = expenses.data.find((item) => item.id === rowId);
const isLatestApprovalRejected =
expenseItem?.latest_approval.action === 'REJECTED';
const isCurrentApprovalOnUnitVicePresident =
!isLatestApprovalRejected &&
expenseItem?.latest_approval.step_number === 2;
return isCurrentApprovalOnUnitVicePresident;
}); });
}, [expenses, selectedRowIds]); }, [expenses, selectedRowIds]);
@@ -221,7 +239,7 @@ const ExpensesTable = () => {
const isCurrentApprovalOnFinance = const isCurrentApprovalOnFinance =
!isLatestApprovalRejected && !isLatestApprovalRejected &&
expenseItem?.latest_approval.step_number === 2; expenseItem?.latest_approval.step_number === 3;
return isCurrentApprovalOnFinance; return isCurrentApprovalOnFinance;
}); });
@@ -238,7 +256,7 @@ const ExpensesTable = () => {
const isCurrentApprovalOnRealization = const isCurrentApprovalOnRealization =
!isLatestApprovalRejected && !isLatestApprovalRejected &&
expenseItem?.latest_approval.step_number === 4; expenseItem?.latest_approval.step_number === 5;
return isCurrentApprovalOnRealization; return isCurrentApprovalOnRealization;
}); });
@@ -397,7 +415,7 @@ const ExpensesTable = () => {
) => { ) => {
return ( return (
row.original.latest_approval.action !== 'REJECTED' && row.original.latest_approval.action !== 'REJECTED' &&
row.original.latest_approval.step_number !== 5 row.original.latest_approval.step_number !== 6
); );
}; };
@@ -441,8 +459,13 @@ const ExpensesTable = () => {
let bulkApproveResponse: BaseApiResponse<Expense> | undefined = undefined; let bulkApproveResponse: BaseApiResponse<Expense> | undefined = undefined;
if (isAllSelectedRowLatestApprovalOnManager) { if (isAllSelectedRowLatestApprovalOnHeadArea) {
bulkApproveResponse = await ExpenseApi.bulkApproveManager( bulkApproveResponse = await ExpenseApi.bulkApproveHeadArea(
selectedRowIds,
notes
);
} else if (isAllSelectedRowLatestApprovalOnUnitVicePresident) {
bulkApproveResponse = await ExpenseApi.bulkApproveUnitVicePresident(
selectedRowIds, selectedRowIds,
notes notes
); );
@@ -478,8 +501,13 @@ const ExpensesTable = () => {
let bulkRejectResponse: BaseApiResponse<Expense> | undefined = undefined; let bulkRejectResponse: BaseApiResponse<Expense> | undefined = undefined;
if (isAllSelectedRowLatestApprovalOnManager) { if (isAllSelectedRowLatestApprovalOnHeadArea) {
bulkRejectResponse = await ExpenseApi.bulkRejectManager( bulkRejectResponse = await ExpenseApi.bulkRejectHeadArea(
selectedRowIds,
notes
);
} else if (isAllSelectedRowLatestApprovalOnUnitVicePresident) {
bulkRejectResponse = await ExpenseApi.bulkRejectUnitVicePresident(
selectedRowIds, selectedRowIds,
notes notes
); );
@@ -594,16 +622,31 @@ const ExpensesTable = () => {
{selectedRowIds.length > 0 && ( {selectedRowIds.length > 0 && (
<> <>
<RequirePermission permissions='lti.expense.approve.manager'> <RequirePermission permissions='lti.expense.approve.head_area'>
<Button <Button
variant='outline' variant='outline'
color='info' color='info'
onClick={bulkApproveClickHandler} onClick={bulkApproveClickHandler}
disabled={!isAllSelectedRowLatestApprovalOnManager} disabled={!isAllSelectedRowLatestApprovalOnHeadArea}
className='w-full sm:w-fit' className='w-full sm:w-fit'
> >
<Icon icon='lucide-lab:farm' width={24} height={24} /> <Icon icon='lucide-lab:farm' width={24} height={24} />
Approve Manager Approve Head Area
</Button>
</RequirePermission>
<RequirePermission permissions='lti.expense.approve.unit_vice_president'>
<Button
variant='outline'
color='success'
onClick={bulkApproveClickHandler}
disabled={
!isAllSelectedRowLatestApprovalOnUnitVicePresident
}
className='w-full sm:w-fit'
>
<Icon icon='tdesign:money' width={24} height={24} />
Approve Unit Vice President
</Button> </Button>
</RequirePermission> </RequirePermission>
@@ -622,7 +665,8 @@ const ExpensesTable = () => {
<RequirePermission <RequirePermission
permissions={[ permissions={[
'lti.expense.approve.manager', 'lti.expense.approve.head_area',
'lti.expense.approve.unit_vice_president',
'lti.expense.approve.finance', 'lti.expense.approve.finance',
]} ]}
> >
@@ -631,7 +675,8 @@ const ExpensesTable = () => {
color='error' color='error'
onClick={bulkRejectClickHandler} onClick={bulkRejectClickHandler}
disabled={ disabled={
!isAllSelectedRowLatestApprovalOnManager && !isAllSelectedRowLatestApprovalOnHeadArea &&
!isAllSelectedRowLatestApprovalOnUnitVicePresident &&
!isAllSelectedRowLatestApprovalOnFinance !isAllSelectedRowLatestApprovalOnFinance
} }
className='w-full sm:w-fit' className='w-full sm:w-fit'
@@ -9,7 +9,7 @@ interface RealizationStatusBadgeProps {
const RealizationStatusBadge = ({ approval }: RealizationStatusBadgeProps) => { const RealizationStatusBadge = ({ approval }: RealizationStatusBadgeProps) => {
const isLatestApprovalRejected = approval?.action === 'REJECTED'; const isLatestApprovalRejected = approval?.action === 'REJECTED';
const isExpenseRealized = approval?.step_number && approval.step_number >= 4; const isExpenseRealized = approval?.step_number && approval.step_number >= 5;
const realizationStatus = isExpenseRealized const realizationStatus = isExpenseRealized
? 'Sudah Realisasi' ? 'Sudah Realisasi'
@@ -75,7 +75,16 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
deleted_documents: Yup.array().of(Yup.number().required()).optional(), deleted_documents: Yup.array().of(Yup.number().required()).optional(),
documents: Yup.array().of(Yup.mixed<File>().required()).optional(), documents: Yup.array()
.of(
Yup.mixed<File>()
.required()
.test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => {
if (!value || !(value instanceof File)) return true;
return value.size <= 5 * 1024 * 1024;
})
)
.optional(),
expense_nonstocks: Yup.array() expense_nonstocks: Yup.array()
.of( .of(
@@ -104,7 +113,16 @@ export const ExpenseRequestFormSchema: Yup.ObjectSchema<ExpenseFormSchemaType> =
export const UpdateExpenseRequestFormSchema = ExpenseRequestFormSchema; export const UpdateExpenseRequestFormSchema = ExpenseRequestFormSchema;
export const UploadRequestDocumentsFormSchema = Yup.object({ export const UploadRequestDocumentsFormSchema = Yup.object({
documents: Yup.array().of(Yup.mixed<File>().required()).required(), documents: Yup.array()
.of(
Yup.mixed<File>()
.required()
.test('fileSize', 'Ukuran dokumen maksimal 5 MB', (value) => {
if (!value || !(value instanceof File)) return true;
return value.size <= 5 * 1024 * 1024;
})
)
.required(),
}); });
export type ExpenseRequestFormValues = Yup.InferType< export type ExpenseRequestFormValues = Yup.InferType<
@@ -37,6 +37,8 @@ import { cn, sleep } from '@/lib/helper';
import { LocationApi, SupplierApi } from '@/services/api/master-data'; import { LocationApi, SupplierApi } from '@/services/api/master-data';
import { ACCEPTED_FILE_TYPE } from '@/config/constant'; import { ACCEPTED_FILE_TYPE } from '@/config/constant';
import { Supplier } from '@/types/api/master-data/supplier'; import { Supplier } from '@/types/api/master-data/supplier';
import { getUniqueFormikErrors } from '@/lib/formik-helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface ExpenseFormProps { interface ExpenseFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -55,6 +57,7 @@ const ExpenseRequestForm = ({
const rejectModal = useModal(); const rejectModal = useModal();
const [expenseFormErrorMessage, setExpenseFormErrorMessage] = useState(''); const [expenseFormErrorMessage, setExpenseFormErrorMessage] = useState('');
const [formErrorList, setFormErrorList] = useState<string[]>([]);
const createExpenseHandler = useCallback( const createExpenseHandler = useCallback(
async (payload: CreateExpensePayload) => { async (payload: CreateExpensePayload) => {
@@ -322,6 +325,22 @@ const ExpenseRequestForm = ({
router.push('/expense'); router.push('/expense');
}; };
const handleValidateForm = async () => {
const errors = await formik.validateForm();
if (Object.keys(errors).length > 0) {
const errorMessages = getUniqueFormikErrors(errors);
setFormErrorList(errorMessages);
return;
}
};
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
handleValidateForm();
formik.handleSubmit(e);
};
useEffect(() => { useEffect(() => {
formikSetValues(getExpenseFormInitialValues(initialValues)); formikSetValues(getExpenseFormInitialValues(initialValues));
}, [formikSetValues, getExpenseFormInitialValues, initialValues]); }, [formikSetValues, getExpenseFormInitialValues, initialValues]);
@@ -347,10 +366,27 @@ const ExpenseRequestForm = ({
</header> </header>
<form <form
onSubmit={formik.handleSubmit} onSubmit={handleFormSubmit}
onReset={formik.handleReset} onReset={formik.handleReset}
className='w-full mt-8 flex flex-col gap-6' className='w-full mt-8 flex flex-col gap-6'
> >
{expenseFormErrorMessage && (
<div role='alert' className='alert alert-error'>
<Icon
icon='material-symbols:error-outline'
width={24}
height={24}
/>
<span>{expenseFormErrorMessage}</span>
</div>
)}
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
)}
<div className='grid grid-cols-12 gap-4'> <div className='grid grid-cols-12 gap-4'>
<SelectInput <SelectInput
label='Kategori' label='Kategori'
@@ -535,17 +571,6 @@ const ExpenseRequestForm = ({
</div> </div>
)} )}
{expenseFormErrorMessage && (
<div role='alert' className='alert alert-error w-full'>
<Icon
icon='material-symbols:error-outline'
width={24}
height={24}
/>
<span>{expenseFormErrorMessage}</span>
</div>
)}
{type !== 'detail' && ( {type !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -198,7 +198,7 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
expense?.latest_approval?.action === 'REJECTED'; expense?.latest_approval?.action === 'REJECTED';
const isExpenseRealized = const isExpenseRealized =
expense?.latest_approval?.step_number && expense?.latest_approval?.step_number &&
expense?.latest_approval.step_number >= 4; expense?.latest_approval.step_number >= 5;
const realizationStatus = isExpenseRealized const realizationStatus = isExpenseRealized
? 'Sudah Realisasi' ? 'Sudah Realisasi'
@@ -242,8 +242,8 @@ const ExpensePDF = ({ expense }: ExpensePDFProps) => {
{ {
label: 'Nominal Biaya', label: 'Nominal Biaya',
value: formatCurrency( value: formatCurrency(
expense?.latest_approval.step_number === 4 || expense?.latest_approval.step_number === 5 ||
expense?.latest_approval.step_number === 5 expense?.latest_approval.step_number === 6
? (expense?.total_realisasi ?? 0) ? (expense?.total_realisasi ?? 0)
: (expense?.total_pengajuan ?? 0) : (expense?.total_pengajuan ?? 0)
), ),
@@ -1737,16 +1737,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<td className='py-3 font-medium'>Egg Mass</td> <td className='py-3 font-medium'>Egg Mass</td>
<td className='text-center py-3'> <td className='text-center py-3'>
<span className='font-semibold'> <span className='font-semibold'>
{initialValues.egg_mesh && {initialValues.egg_mass &&
initialValues.egg_mesh > 0 initialValues.egg_mass > 0
? formatNumber(initialValues.egg_mesh) ? formatNumber(initialValues.egg_mass)
: '-'} : '-'}
</span> </span>
</td> </td>
<td className='text-center py-3 text-gray-600'> <td className='text-center py-3 text-gray-600'>
{initialValues.egg_mesh_std && {initialValues.egg_mass_std &&
initialValues.egg_mesh_std > 0 initialValues.egg_mass_std > 0
? formatNumber(initialValues.egg_mesh_std) ? formatNumber(initialValues.egg_mass_std)
: '-'} : '-'}
</td> </td>
</tr> </tr>
@@ -1773,16 +1773,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<td className='py-3 font-medium'>Hen Day</td> <td className='py-3 font-medium'>Hen Day</td>
<td className='text-center py-3'> <td className='text-center py-3'>
<span className='font-semibold'> <span className='font-semibold'>
{initialValues.hand_day && {initialValues.hen_day &&
initialValues.hand_day > 0 initialValues.hen_day > 0
? formatNumber(initialValues.hand_day) ? formatNumber(initialValues.hen_day)
: '-'} : '-'}
</span> </span>
</td> </td>
<td className='text-center py-3 text-gray-600'> <td className='text-center py-3 text-gray-600'>
{initialValues.hand_day_std !== undefined && {initialValues.hen_day_std !== undefined &&
initialValues.hand_day_std > 0 initialValues.hen_day_std > 0
? `${initialValues.hand_day_std}%` ? `${initialValues.hen_day_std}%`
: '-'} : '-'}
</td> </td>
</tr> </tr>
@@ -1790,16 +1790,16 @@ const RecordingForm = ({ type = 'add', initialValues }: RecordingFormProps) => {
<td className='py-3 font-medium'>Hen House</td> <td className='py-3 font-medium'>Hen House</td>
<td className='text-center py-3'> <td className='text-center py-3'>
<span className='font-semibold'> <span className='font-semibold'>
{initialValues.hand_house && {initialValues.hen_house &&
initialValues.hand_house > 0 initialValues.hen_house > 0
? formatNumber(initialValues.hand_house) ? formatNumber(initialValues.hen_house)
: '-'} : '-'}
</span> </span>
</td> </td>
<td className='text-center py-3 text-gray-600'> <td className='text-center py-3 text-gray-600'>
{initialValues.hand_house_std !== undefined && {initialValues.hen_house_std !== undefined &&
initialValues.hand_house_std > 0 initialValues.hen_house_std > 0
? `${initialValues.hand_house_std}%` ? `${initialValues.hen_house_std}%`
: '-'} : '-'}
</td> </td>
</tr> </tr>
+24 -4
View File
@@ -74,7 +74,23 @@ export const RECORDING_APPROVAL_LINE: ApprovalLine = [
}, },
{ {
step_number: 2, step_number: 2,
step_name: 'Disetujui', step_name: 'Approval Head Area',
},
{
step_number: 3,
step_name: 'Approval Business Unit Vice President',
},
{
step_number: 4,
step_name: 'Approval Finance',
},
{
step_number: 5,
step_name: 'Realisasi',
},
{
step_number: 6,
step_name: 'Selesai',
}, },
] as const; ] as const;
@@ -130,18 +146,22 @@ export const EXPENSE_REQUEST_APPROVAL_LINE: ApprovalLine = [
}, },
{ {
step_number: 2, step_number: 2,
step_name: 'Approval Manager', step_name: 'Approval Head Area',
}, },
{ {
step_number: 3, step_number: 3,
step_name: 'Approval Finance', step_name: 'Approval Business Unit Vice President',
}, },
{ {
step_number: 4, step_number: 4,
step_name: 'Realisasi', step_name: 'Approval Finance',
}, },
{ {
step_number: 5, step_number: 5,
step_name: 'Realisasi',
},
{
step_number: 6,
step_name: 'Selesai', step_name: 'Selesai',
}, },
] as const; ] as const;
+116 -8
View File
@@ -169,13 +169,13 @@ export class ExpenseApiService extends BaseApiService<
} }
} }
async approveManager( async approveHeadArea(
id: number, id: number,
notes?: string notes?: string
): Promise<BaseApiResponse<Expense> | undefined> { ): Promise<BaseApiResponse<Expense> | undefined> {
try { try {
const approveRes = await httpClient<BaseApiResponse<Expense>>( const approveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/manager`, `${this.basePath}/approvals/head-area`,
{ {
method: 'POST', method: 'POST',
body: { body: {
@@ -196,13 +196,67 @@ export class ExpenseApiService extends BaseApiService<
} }
} }
async bulkApproveManager( async bulkApproveHeadArea(
ids: number[], ids: number[],
notes?: string notes?: string
): Promise<BaseApiResponse<Expense> | undefined> { ): Promise<BaseApiResponse<Expense> | undefined> {
try { try {
const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>( const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/manager`, `${this.basePath}/approvals/head-area`,
{
method: 'POST',
body: {
action: 'APPROVED',
approvable_ids: ids,
notes: notes,
},
}
);
return bulkApproveRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async approveUnitVicePresident(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const approveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/unit-vice-president`,
{
method: 'POST',
body: {
action: 'APPROVED',
approvable_ids: [id],
notes: notes,
},
}
);
return approveRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkApproveUnitVicePresident(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const bulkApproveRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/unit-vice-president`,
{ {
method: 'POST', method: 'POST',
body: { body: {
@@ -277,13 +331,13 @@ export class ExpenseApiService extends BaseApiService<
} }
} }
async rejectManager( async rejectHeadArea(
id: number, id: number,
notes?: string notes?: string
): Promise<BaseApiResponse<Expense> | undefined> { ): Promise<BaseApiResponse<Expense> | undefined> {
try { try {
const rejectRes = await httpClient<BaseApiResponse<Expense>>( const rejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/manager`, `${this.basePath}/approvals/head-area`,
{ {
method: 'POST', method: 'POST',
body: { body: {
@@ -304,13 +358,67 @@ export class ExpenseApiService extends BaseApiService<
} }
} }
async bulkRejectManager( async bulkRejectHeadArea(
ids: number[], ids: number[],
notes?: string notes?: string
): Promise<BaseApiResponse<Expense> | undefined> { ): Promise<BaseApiResponse<Expense> | undefined> {
try { try {
const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>( const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/manager`, `${this.basePath}/approvals/head-area`,
{
method: 'POST',
body: {
action: 'REJECTED',
approvable_ids: ids,
notes: notes,
},
}
);
return bulkRejectRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async rejectUnitVicePresident(
id: number,
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const rejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/unit-vice-president`,
{
method: 'POST',
body: {
action: 'REJECTED',
approvable_ids: [id],
notes: notes,
},
}
);
return rejectRes;
} catch (error) {
if (axios.isAxiosError<BaseApiResponse<Expense>>(error)) {
return error.response?.data;
}
return undefined;
}
}
async bulkRejectUnitVicePresident(
ids: number[],
notes?: string
): Promise<BaseApiResponse<Expense> | undefined> {
try {
const bulkRejectRes = await httpClient<BaseApiResponse<Expense>>(
`${this.basePath}/approvals/unit-vice-president`,
{ {
method: 'POST', method: 'POST',
body: { body: {
+7 -7
View File
@@ -8,15 +8,15 @@ export type ProductionMetrics = {
fcr_value: number; fcr_value: number;
fcr_std?: number; fcr_std?: number;
total_chick_qty: number; total_chick_qty: number;
hand_day?: number; hen_day?: number;
hand_house?: number; hen_house?: number;
feed_intake?: number; feed_intake?: number;
egg_mesh?: number;
egg_weight?: number;
hand_day_std?: number;
hand_house_std?: number;
feed_intake_std?: number; feed_intake_std?: number;
egg_mesh_std?: number; egg_mass?: number;
egg_weight?: number;
hen_day_std?: number;
hen_house_std?: number;
egg_mass_std?: number;
egg_weight_std?: number; egg_weight_std?: number;
daily_gain?: number; daily_gain?: number;
avg_daily_gain?: number; avg_daily_gain?: number;