mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'fix/finance-and-recording-adjustment' into 'development'
[FIX/FE] Adjust Biaya and Recording See merge request mbugroup/lti-web-client!155
This commit is contained in:
@@ -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)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user